Skip to content

Commit 0a32df5

Browse files
authored
refactor(VTreeview): avoid re-render by opened & respect openAll (#20032)
1 parent d7fbcc5 commit 0a32df5

File tree

2 files changed

+314
-12
lines changed

2 files changed

+314
-12
lines changed

packages/vuetify/src/labs/VTreeview/VTreeview.tsx

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,10 @@ import { makeFilterProps, useFilter } from '@/composables/filter'
88
import { useProxiedModel } from '@/composables/proxiedModel'
99

1010
// Utilities
11-
import { computed, provide, ref, toRef, watch } from 'vue'
12-
import { genericComponent, getCurrentInstance, omit, propsFactory, useRender } from '@/util'
11+
import { computed, provide, ref, toRef } from 'vue'
12+
import { genericComponent, omit, propsFactory, useRender } from '@/util'
1313

1414
// Types
15-
import type { ExtractPublicPropTypes } from 'vue'
1615
import { VTreeviewSymbol } from './shared'
1716
import type { VListChildrenSlots } from '@/components/VList/VListChildren'
1817
import type { ListItem } from '@/composables/list-items'
@@ -35,8 +34,6 @@ export const makeVTreeviewProps = propsFactory({
3534
...omit(makeVListProps({
3635
collapseIcon: '$treeviewCollapse',
3736
expandIcon: '$treeviewExpand',
38-
selectStrategy: 'classic' as const,
39-
openStrategy: 'multiple' as const,
4037
slim: true,
4138
}), ['nav']),
4239
}, 'VTreeview')
@@ -60,17 +57,16 @@ export const VTreeview = genericComponent<new <T>(
6057
},
6158

6259
setup (props, { slots }) {
63-
const vm = getCurrentInstance('VTreeview')
6460
const { items } = useListItems(props)
6561
const activeColor = toRef(props, 'activeColor')
6662
const baseColor = toRef(props, 'baseColor')
6763
const color = toRef(props, 'color')
68-
const opened = useProxiedModel(props, 'opened')
6964
const activated = useProxiedModel(props, 'activated')
7065
const selected = useProxiedModel(props, 'selected')
7166

7267
const vListRef = ref<VList>()
7368

69+
const opened = computed(() => props.openAll ? openAll(items.value) : props.opened)
7470
const flatItems = computed(() => flatten(items.value))
7571
const search = toRef(props, 'search')
7672
const { filteredItems } = useFilter(props, flatItems, search)
@@ -105,10 +101,6 @@ export const VTreeview = genericComponent<new <T>(
105101
return arr
106102
}
107103

108-
watch(() => props.openAll, val => {
109-
opened.value = val ? openAll(items.value) : []
110-
}, { immediate: true })
111-
112104
function openAll (item: any) {
113105
let ids: number[] = []
114106

@@ -148,7 +140,7 @@ export const VTreeview = genericComponent<new <T>(
148140
})
149141

150142
useRender(() => {
151-
const listProps = VList.filterProps(vm.vnode.props! as ExtractPublicPropTypes<typeof makeVTreeviewProps>)
143+
const listProps = VList.filterProps(props)
152144

153145
const treeviewChildrenProps = VTreeviewChildren.filterProps(props)
154146

@@ -161,6 +153,7 @@ export const VTreeview = genericComponent<new <T>(
161153
props.class,
162154
]}
163155
style={ props.style }
156+
opened={ opened.value }
164157
v-model:activated={ activated.value }
165158
v-model:selected={ selected.value }
166159
>
Lines changed: 309 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,309 @@
1+
/// <reference types="../../../../types/cypress" />
2+
3+
// Components
4+
import { VTreeview } from '../VTreeview'
5+
6+
// Utilities
7+
import { ref } from 'vue'
8+
9+
describe('VTreeview', () => {
10+
const items = ref([
11+
{
12+
id: 1,
13+
title: 'Videos :',
14+
children: [
15+
{
16+
id: 2,
17+
title: 'Tutorials :',
18+
children: [
19+
{ id: 3, title: 'Basic layouts : mp4' },
20+
{ id: 4, title: 'Advanced techniques : mp4' },
21+
{ id: 5, title: 'All about app : dir' },
22+
],
23+
},
24+
{ id: 6, title: 'Intro : mov' },
25+
{ id: 7, title: 'Conference introduction : avi' },
26+
],
27+
},
28+
])
29+
describe('activate', () => {
30+
it('single-leaf strategy', () => {
31+
const activated = ref([])
32+
cy.mount(() => (
33+
<>
34+
<VTreeview
35+
v-model:activated={ activated.value }
36+
open-all
37+
items={ items.value }
38+
item-title="title"
39+
item-value="id"
40+
activatable
41+
active-strategy="single-leaf"
42+
/>
43+
</>
44+
))
45+
46+
cy.get('.v-treeview-item').should('have.length', 7)
47+
.get('.v-treeview-item').eq(0).click()
48+
.then(_ => {
49+
expect(activated.value).to.deep.equal([])
50+
})
51+
.get('.v-treeview-item').eq(2).click()
52+
.get('.v-treeview-item').eq(3).click()
53+
.get('.v-treeview-item').eq(4).click()
54+
.then(_ => {
55+
expect(activated.value).to.deep.equal([5])
56+
})
57+
})
58+
it('leaf strategy', () => {
59+
const activated = ref([])
60+
cy.mount(() => (
61+
<>
62+
<VTreeview
63+
v-model:activated={ activated.value }
64+
open-all
65+
items={ items.value }
66+
item-title="title"
67+
item-value="id"
68+
activatable
69+
active-strategy="leaf"
70+
/>
71+
</>
72+
))
73+
74+
cy.get('.v-treeview-item').should('have.length', 7)
75+
.get('.v-treeview-item').eq(0).click()
76+
.then(_ => {
77+
expect(activated.value).to.deep.equal([])
78+
})
79+
.get('.v-treeview-item').eq(2).click()
80+
.get('.v-treeview-item').eq(3).click()
81+
.get('.v-treeview-item').eq(4).click()
82+
.then(_ => {
83+
expect(activated.value.sort()).to.deep.equal([3, 4, 5].sort())
84+
})
85+
})
86+
it('independent strategy', () => {
87+
const activated = ref([])
88+
cy.mount(() => (
89+
<>
90+
<VTreeview
91+
v-model:activated={ activated.value }
92+
open-all
93+
items={ items.value }
94+
item-title="title"
95+
item-value="id"
96+
activatable
97+
active-strategy="independent"
98+
/>
99+
</>
100+
))
101+
102+
cy.get('.v-treeview-item').should('have.length', 7)
103+
.get('.v-treeview-item').eq(0).click()
104+
.then(_ => {
105+
expect(activated.value).to.deep.equal([1])
106+
})
107+
.get('.v-treeview-item').eq(1).click()
108+
.then(_ => {
109+
expect(activated.value).to.deep.equal([1, 2])
110+
})
111+
.get('.v-treeview-item').eq(2).click()
112+
.get('.v-treeview-item').eq(3).click()
113+
.get('.v-treeview-item').eq(4).click()
114+
.then(_ => {
115+
expect(activated.value.sort()).to.deep.equal([1, 2, 3, 4, 5].sort())
116+
})
117+
})
118+
it('single-independent strategy', () => {
119+
const activated = ref([])
120+
cy.mount(() => (
121+
<>
122+
<VTreeview
123+
v-model:activated={ activated.value }
124+
open-all
125+
items={ items.value }
126+
item-title="title"
127+
item-value="id"
128+
activatable
129+
active-strategy="single-independent"
130+
/>
131+
</>
132+
))
133+
134+
cy.get('.v-treeview-item').should('have.length', 7)
135+
.get('.v-treeview-item').eq(0).click()
136+
.then(_ => {
137+
expect(activated.value).to.deep.equal([1])
138+
})
139+
.get('.v-treeview-item').eq(1).click()
140+
.then(_ => {
141+
expect(activated.value).to.deep.equal([2])
142+
})
143+
.get('.v-treeview-item').eq(2).click()
144+
.get('.v-treeview-item').eq(3).click()
145+
.get('.v-treeview-item').eq(4).click()
146+
.then(_ => {
147+
expect(activated.value).to.deep.equal([5])
148+
})
149+
})
150+
})
151+
describe('select', () => {
152+
it('single-leaf strategy', () => {
153+
const selected = ref([])
154+
cy.mount(() => (
155+
<>
156+
<VTreeview
157+
v-model:selected={ selected.value }
158+
open-all
159+
items={ items.value }
160+
item-title="title"
161+
item-value="id"
162+
selectable
163+
select-strategy="single-leaf"
164+
/>
165+
</>
166+
))
167+
168+
cy.get('.v-checkbox-btn').should('have.length', 5)
169+
.get('.v-checkbox-btn').eq(0).click(20, 20)
170+
.get('.v-checkbox-btn').eq(1).click(20, 20)
171+
.then(_ => {
172+
expect(selected.value).to.deep.equal([4])
173+
})
174+
.get('.v-checkbox-btn').eq(3).click(20, 20)
175+
.then(_ => {
176+
expect(selected.value).to.deep.equal([6])
177+
})
178+
})
179+
it('leaf strategy', () => {
180+
const selected = ref([])
181+
cy.mount(() => (
182+
<>
183+
<VTreeview
184+
v-model:selected={ selected.value }
185+
open-all
186+
items={ items.value }
187+
item-title="title"
188+
item-value="id"
189+
selectable
190+
select-strategy="leaf"
191+
/>
192+
</>
193+
))
194+
195+
cy.get('.v-checkbox-btn').should('have.length', 5)
196+
.get('.v-checkbox-btn').eq(0).click(20, 20)
197+
.get('.v-checkbox-btn').eq(1).click(20, 20)
198+
.then(_ => {
199+
expect(selected.value.sort()).to.deep.equal([3, 4].sort())
200+
})
201+
.get('.v-checkbox-btn').eq(3).click(20, 20)
202+
.then(_ => {
203+
expect(selected.value.sort()).to.deep.equal([3, 4, 6].sort())
204+
})
205+
})
206+
it.only('independent strategy', () => {
207+
const selected = ref([])
208+
cy.mount(() => (
209+
<>
210+
<VTreeview
211+
v-model:selected={ selected.value }
212+
open-all
213+
items={ items.value }
214+
item-title="title"
215+
item-value="id"
216+
selectable
217+
select-strategy="independent"
218+
/>
219+
</>
220+
))
221+
222+
cy.get('.v-checkbox-btn').should('have.length', 7)
223+
.get('.v-checkbox-btn').eq(2).click(20, 20)
224+
.get('.v-checkbox-btn').eq(3).click(20, 20)
225+
.then(_ => {
226+
expect(selected.value.sort()).to.deep.equal([3, 4].sort())
227+
})
228+
.get('.v-checkbox-btn').eq(1).click(20, 20)
229+
.get('.v-checkbox-btn').eq(0).click(20, 20)
230+
.then(_ => {
231+
expect(selected.value.sort()).to.deep.equal([1, 2, 3, 4].sort())
232+
})
233+
})
234+
it('single-independent strategy', () => {
235+
const selected = ref([])
236+
cy.mount(() => (
237+
<>
238+
<VTreeview
239+
v-model:selected={ selected.value }
240+
open-all
241+
items={ items.value }
242+
item-title="title"
243+
item-value="id"
244+
selectable
245+
select-strategy="single-independent"
246+
/>
247+
</>
248+
))
249+
250+
cy.get('.v-checkbox-btn').should('have.length', 7)
251+
.get('.v-checkbox-btn').eq(2).click(20, 20)
252+
.get('.v-checkbox-btn').eq(3).click(20, 20)
253+
.then(_ => {
254+
expect(selected.value.sort()).to.deep.equal([4].sort())
255+
})
256+
.get('.v-checkbox-btn').eq(1).click(20, 20)
257+
.get('.v-checkbox-btn').eq(0).click(20, 20)
258+
.then(_ => {
259+
expect(selected.value.sort()).to.deep.equal([1].sort())
260+
})
261+
})
262+
it('classic strategy', () => {
263+
const selected = ref([])
264+
cy.mount(() => (
265+
<>
266+
<VTreeview
267+
v-model:selected={ selected.value }
268+
open-all
269+
items={ items.value }
270+
item-title="title"
271+
item-value="id"
272+
selectable
273+
select-strategy="classic"
274+
/>
275+
</>
276+
))
277+
278+
cy.get('.v-checkbox-btn').eq(2).click(20, 20)
279+
.get('.v-checkbox-btn').eq(3).click(20, 20)
280+
.get('.v-checkbox-btn').eq(4).click(20, 20)
281+
.then(_ => {
282+
expect(selected.value).to.deep.equal([3, 4, 5])
283+
})
284+
.get('.v-checkbox-btn').eq(1).click(20, 20)
285+
.then(_ => {
286+
expect(selected.value).to.deep.equal([])
287+
})
288+
.get('.v-checkbox-btn').eq(0).click(20, 20)
289+
.then(_ => {
290+
expect(selected.value.sort()).to.deep.equal([3, 4, 5, 6, 7].sort())
291+
})
292+
})
293+
})
294+
it('should have all items visible when open-all is applied', () => {
295+
cy.mount(() => (
296+
<>
297+
<VTreeview
298+
open-all
299+
items={ items.value }
300+
item-title="title"
301+
item-value="id"
302+
/>
303+
</>
304+
))
305+
.get('.v-treeview-item')
306+
.filter(':visible')
307+
.should('have.length', 7)
308+
})
309+
})

0 commit comments

Comments
 (0)