From 0a32df5706fe7eb227918efc4375eef726b9f7a9 Mon Sep 17 00:00:00 2001 From: Yuchao Date: Thu, 20 Jun 2024 16:02:17 +1000 Subject: [PATCH] refactor(VTreeview): avoid re-render by opened & respect openAll (#20032) --- .../vuetify/src/labs/VTreeview/VTreeview.tsx | 17 +- .../VTreeview/__tests__/VTreeview.spec.cy.tsx | 309 ++++++++++++++++++ 2 files changed, 314 insertions(+), 12 deletions(-) create mode 100644 packages/vuetify/src/labs/VTreeview/__tests__/VTreeview.spec.cy.tsx diff --git a/packages/vuetify/src/labs/VTreeview/VTreeview.tsx b/packages/vuetify/src/labs/VTreeview/VTreeview.tsx index 7778c69df26..23541031c0c 100644 --- a/packages/vuetify/src/labs/VTreeview/VTreeview.tsx +++ b/packages/vuetify/src/labs/VTreeview/VTreeview.tsx @@ -8,11 +8,10 @@ import { makeFilterProps, useFilter } from '@/composables/filter' import { useProxiedModel } from '@/composables/proxiedModel' // Utilities -import { computed, provide, ref, toRef, watch } from 'vue' -import { genericComponent, getCurrentInstance, omit, propsFactory, useRender } from '@/util' +import { computed, provide, ref, toRef } from 'vue' +import { genericComponent, omit, propsFactory, useRender } from '@/util' // Types -import type { ExtractPublicPropTypes } from 'vue' import { VTreeviewSymbol } from './shared' import type { VListChildrenSlots } from '@/components/VList/VListChildren' import type { ListItem } from '@/composables/list-items' @@ -35,8 +34,6 @@ export const makeVTreeviewProps = propsFactory({ ...omit(makeVListProps({ collapseIcon: '$treeviewCollapse', expandIcon: '$treeviewExpand', - selectStrategy: 'classic' as const, - openStrategy: 'multiple' as const, slim: true, }), ['nav']), }, 'VTreeview') @@ -60,17 +57,16 @@ export const VTreeview = genericComponent( }, setup (props, { slots }) { - const vm = getCurrentInstance('VTreeview') const { items } = useListItems(props) const activeColor = toRef(props, 'activeColor') const baseColor = toRef(props, 'baseColor') const color = toRef(props, 'color') - const opened = useProxiedModel(props, 'opened') const activated = useProxiedModel(props, 'activated') const selected = useProxiedModel(props, 'selected') const vListRef = ref() + const opened = computed(() => props.openAll ? openAll(items.value) : props.opened) const flatItems = computed(() => flatten(items.value)) const search = toRef(props, 'search') const { filteredItems } = useFilter(props, flatItems, search) @@ -105,10 +101,6 @@ export const VTreeview = genericComponent( return arr } - watch(() => props.openAll, val => { - opened.value = val ? openAll(items.value) : [] - }, { immediate: true }) - function openAll (item: any) { let ids: number[] = [] @@ -148,7 +140,7 @@ export const VTreeview = genericComponent( }) useRender(() => { - const listProps = VList.filterProps(vm.vnode.props! as ExtractPublicPropTypes) + const listProps = VList.filterProps(props) const treeviewChildrenProps = VTreeviewChildren.filterProps(props) @@ -161,6 +153,7 @@ export const VTreeview = genericComponent( props.class, ]} style={ props.style } + opened={ opened.value } v-model:activated={ activated.value } v-model:selected={ selected.value } > diff --git a/packages/vuetify/src/labs/VTreeview/__tests__/VTreeview.spec.cy.tsx b/packages/vuetify/src/labs/VTreeview/__tests__/VTreeview.spec.cy.tsx new file mode 100644 index 00000000000..c6deb7de622 --- /dev/null +++ b/packages/vuetify/src/labs/VTreeview/__tests__/VTreeview.spec.cy.tsx @@ -0,0 +1,309 @@ +/// + +// Components +import { VTreeview } from '../VTreeview' + +// Utilities +import { ref } from 'vue' + +describe('VTreeview', () => { + const items = ref([ + { + id: 1, + title: 'Videos :', + children: [ + { + id: 2, + title: 'Tutorials :', + children: [ + { id: 3, title: 'Basic layouts : mp4' }, + { id: 4, title: 'Advanced techniques : mp4' }, + { id: 5, title: 'All about app : dir' }, + ], + }, + { id: 6, title: 'Intro : mov' }, + { id: 7, title: 'Conference introduction : avi' }, + ], + }, + ]) + describe('activate', () => { + it('single-leaf strategy', () => { + const activated = ref([]) + cy.mount(() => ( + <> + + + )) + + cy.get('.v-treeview-item').should('have.length', 7) + .get('.v-treeview-item').eq(0).click() + .then(_ => { + expect(activated.value).to.deep.equal([]) + }) + .get('.v-treeview-item').eq(2).click() + .get('.v-treeview-item').eq(3).click() + .get('.v-treeview-item').eq(4).click() + .then(_ => { + expect(activated.value).to.deep.equal([5]) + }) + }) + it('leaf strategy', () => { + const activated = ref([]) + cy.mount(() => ( + <> + + + )) + + cy.get('.v-treeview-item').should('have.length', 7) + .get('.v-treeview-item').eq(0).click() + .then(_ => { + expect(activated.value).to.deep.equal([]) + }) + .get('.v-treeview-item').eq(2).click() + .get('.v-treeview-item').eq(3).click() + .get('.v-treeview-item').eq(4).click() + .then(_ => { + expect(activated.value.sort()).to.deep.equal([3, 4, 5].sort()) + }) + }) + it('independent strategy', () => { + const activated = ref([]) + cy.mount(() => ( + <> + + + )) + + cy.get('.v-treeview-item').should('have.length', 7) + .get('.v-treeview-item').eq(0).click() + .then(_ => { + expect(activated.value).to.deep.equal([1]) + }) + .get('.v-treeview-item').eq(1).click() + .then(_ => { + expect(activated.value).to.deep.equal([1, 2]) + }) + .get('.v-treeview-item').eq(2).click() + .get('.v-treeview-item').eq(3).click() + .get('.v-treeview-item').eq(4).click() + .then(_ => { + expect(activated.value.sort()).to.deep.equal([1, 2, 3, 4, 5].sort()) + }) + }) + it('single-independent strategy', () => { + const activated = ref([]) + cy.mount(() => ( + <> + + + )) + + cy.get('.v-treeview-item').should('have.length', 7) + .get('.v-treeview-item').eq(0).click() + .then(_ => { + expect(activated.value).to.deep.equal([1]) + }) + .get('.v-treeview-item').eq(1).click() + .then(_ => { + expect(activated.value).to.deep.equal([2]) + }) + .get('.v-treeview-item').eq(2).click() + .get('.v-treeview-item').eq(3).click() + .get('.v-treeview-item').eq(4).click() + .then(_ => { + expect(activated.value).to.deep.equal([5]) + }) + }) + }) + describe('select', () => { + it('single-leaf strategy', () => { + const selected = ref([]) + cy.mount(() => ( + <> + + + )) + + cy.get('.v-checkbox-btn').should('have.length', 5) + .get('.v-checkbox-btn').eq(0).click(20, 20) + .get('.v-checkbox-btn').eq(1).click(20, 20) + .then(_ => { + expect(selected.value).to.deep.equal([4]) + }) + .get('.v-checkbox-btn').eq(3).click(20, 20) + .then(_ => { + expect(selected.value).to.deep.equal([6]) + }) + }) + it('leaf strategy', () => { + const selected = ref([]) + cy.mount(() => ( + <> + + + )) + + cy.get('.v-checkbox-btn').should('have.length', 5) + .get('.v-checkbox-btn').eq(0).click(20, 20) + .get('.v-checkbox-btn').eq(1).click(20, 20) + .then(_ => { + expect(selected.value.sort()).to.deep.equal([3, 4].sort()) + }) + .get('.v-checkbox-btn').eq(3).click(20, 20) + .then(_ => { + expect(selected.value.sort()).to.deep.equal([3, 4, 6].sort()) + }) + }) + it.only('independent strategy', () => { + const selected = ref([]) + cy.mount(() => ( + <> + + + )) + + cy.get('.v-checkbox-btn').should('have.length', 7) + .get('.v-checkbox-btn').eq(2).click(20, 20) + .get('.v-checkbox-btn').eq(3).click(20, 20) + .then(_ => { + expect(selected.value.sort()).to.deep.equal([3, 4].sort()) + }) + .get('.v-checkbox-btn').eq(1).click(20, 20) + .get('.v-checkbox-btn').eq(0).click(20, 20) + .then(_ => { + expect(selected.value.sort()).to.deep.equal([1, 2, 3, 4].sort()) + }) + }) + it('single-independent strategy', () => { + const selected = ref([]) + cy.mount(() => ( + <> + + + )) + + cy.get('.v-checkbox-btn').should('have.length', 7) + .get('.v-checkbox-btn').eq(2).click(20, 20) + .get('.v-checkbox-btn').eq(3).click(20, 20) + .then(_ => { + expect(selected.value.sort()).to.deep.equal([4].sort()) + }) + .get('.v-checkbox-btn').eq(1).click(20, 20) + .get('.v-checkbox-btn').eq(0).click(20, 20) + .then(_ => { + expect(selected.value.sort()).to.deep.equal([1].sort()) + }) + }) + it('classic strategy', () => { + const selected = ref([]) + cy.mount(() => ( + <> + + + )) + + cy.get('.v-checkbox-btn').eq(2).click(20, 20) + .get('.v-checkbox-btn').eq(3).click(20, 20) + .get('.v-checkbox-btn').eq(4).click(20, 20) + .then(_ => { + expect(selected.value).to.deep.equal([3, 4, 5]) + }) + .get('.v-checkbox-btn').eq(1).click(20, 20) + .then(_ => { + expect(selected.value).to.deep.equal([]) + }) + .get('.v-checkbox-btn').eq(0).click(20, 20) + .then(_ => { + expect(selected.value.sort()).to.deep.equal([3, 4, 5, 6, 7].sort()) + }) + }) + }) + it('should have all items visible when open-all is applied', () => { + cy.mount(() => ( + <> + + + )) + .get('.v-treeview-item') + .filter(':visible') + .should('have.length', 7) + }) +})