@dimailn/vuetify 2.7.2-alpha52 → 2.7.2-alpha54

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,12 +1,16 @@
1
1
  // Components
2
2
  import VListItem from '../VListItem'
3
3
 
4
+ // Libraries
5
+ import { defineComponent, h, nextTick } from 'vue'
6
+
4
7
  // Utilities
5
8
  import {
6
9
  mount,
7
10
  VueWrapper,
8
11
  enableAutoUnmount
9
12
  } from '@vue/test-utils'
13
+ import { createRouter, createWebHistory } from 'vue-router'
10
14
  import { Vue3RouterLinkStub } from '../../../../test/util/stubs'
11
15
 
12
16
  describe('VListItem.ts', () => {
@@ -325,4 +329,117 @@ describe('VListItem.ts', () => {
325
329
 
326
330
  expect(wrapper.vm.isClickable).toBe(true)
327
331
  })
332
+
333
+ it('passes active and toggle in default slot scope', async () => {
334
+ const wrapper = mountFunction({
335
+ props: { modelValue: true },
336
+ slots: {
337
+ default: ({ active, toggle }: { active: boolean, toggle: Function }) => h('div', [
338
+ h('span', { class: { 'link--text': active } }, String(active)),
339
+ h('button', { onClick: toggle }, 'toggle')
340
+ ])
341
+ }
342
+ })
343
+
344
+ expect(wrapper.find('.link--text').exists()).toBe(true)
345
+ expect(wrapper.find('span').text()).toBe('true')
346
+ })
347
+
348
+ it('does not pass undefined to activeClass when to is used without activeClass', () => {
349
+ const RouterLinkCapture = defineComponent({
350
+ name: 'RouterLinkCapture',
351
+ props: {
352
+ to: { type: [String, Object], required: true },
353
+ activeClass: String,
354
+ exactActiveClass: String
355
+ },
356
+ setup (props, { slots }) {
357
+ return () => h('a', { class: props.activeClass }, slots.default?.())
358
+ }
359
+ })
360
+
361
+ const wrapper = mountFunction({
362
+ props: { to: '/foo' },
363
+ global: {
364
+ stubs: {
365
+ 'router-link': RouterLinkCapture
366
+ }
367
+ }
368
+ })
369
+
370
+ const routerLink = wrapper.findComponent({ name: 'RouterLinkCapture' })
371
+
372
+ expect(routerLink.props('activeClass')).toBe('v-list-item--active')
373
+ expect(routerLink.props('activeClass')).not.toContain('undefined')
374
+ })
375
+
376
+ it('syncs slot active with router-link when route matches', async () => {
377
+ const router = createRouter({
378
+ history: createWebHistory(),
379
+ routes: [
380
+ { path: '/products/:id', component: { template: '<div />' } }
381
+ ]
382
+ })
383
+
384
+ await router.push('/products/131830946')
385
+ await router.isReady()
386
+
387
+ const Host = defineComponent({
388
+ components: { VListItem },
389
+ props: { to: String },
390
+ template: `
391
+ <v-list-item :to="to">
392
+ <template #default="{ active }">
393
+ <span :class="{ 'link--text': active }">{{ active }}</span>
394
+ </template>
395
+ </v-list-item>
396
+ `
397
+ })
398
+
399
+ const wrapper = mount(Host, {
400
+ props: { to: '/products/131830946' },
401
+ global: {
402
+ plugins: [router],
403
+ provide: { isInGroup: true },
404
+ stubs: {
405
+ 'router-link': Vue3RouterLinkStub
406
+ }
407
+ }
408
+ })
409
+
410
+ const listItem = wrapper.findComponent(VListItem)
411
+
412
+ const linkRef = listItem.vm.$refs.link as any
413
+ const linkEl = linkRef.$el || linkRef
414
+ linkEl.classList.add('v-list-item--active')
415
+
416
+ listItem.vm.onRouteChange()
417
+ await nextTick()
418
+ await nextTick()
419
+
420
+ expect(listItem.vm.isActive).toBe(true)
421
+ expect(wrapper.find('.link--text').exists()).toBe(true)
422
+ expect(listItem.element.getAttribute('aria-selected')).toBe('true')
423
+ })
424
+
425
+ it('syncs aria-selected with isActive in list item group', async () => {
426
+ const wrapper = mountFunction({
427
+ props: { modelValue: 'item-1' },
428
+ global: {
429
+ provide: {
430
+ isInGroup: true,
431
+ listItemGroup: {
432
+ activeClass: 'v-item--active',
433
+ register: () => {},
434
+ unregister: () => {}
435
+ }
436
+ }
437
+ }
438
+ })
439
+
440
+ wrapper.vm.isActive = true
441
+ await wrapper.vm.$nextTick()
442
+
443
+ expect(wrapper.element.getAttribute('aria-selected')).toBe('true')
444
+ })
328
445
  })
@@ -80,11 +80,9 @@ describe('VTab.ts', () => {
80
80
  // explicitly mock class added
81
81
  // by vue router
82
82
  if (wrapper.vm.$refs.link) {
83
- (wrapper.vm.$refs.link as any)._vnode = {
84
- data: {
85
- class: { 'bar v-tab--active': true }
86
- }
87
- }
83
+ const linkRef = wrapper.vm.$refs.link as any
84
+ const linkEl = linkRef.$el || linkRef
85
+ linkEl.classList.add('bar', 'v-tab--active')
88
86
  }
89
87
  (wrapper.vm as any).$route.path = '/foo'
90
88
 
@@ -1,5 +1,5 @@
1
1
  import type { PropType, VNode } from 'vue'
2
- import { h } from 'vue'
2
+ import { h, markRaw } from 'vue'
3
3
  import type { VNodeChildrenArrayContents } from '../../types/vue-internal'
4
4
  // Styles
5
5
  import './VTreeview.sass'
@@ -320,7 +320,7 @@ export default mixins(
320
320
  register (node: VTreeviewNodeInstance) {
321
321
  const key = getObjectValueByPath(node.item, this.itemKey)
322
322
 
323
- this.nodes[key].vnode = node
323
+ this.nodes[key].vnode = markRaw(node)
324
324
 
325
325
  this.updateVnodeState(key)
326
326
  },
@@ -1,7 +1,7 @@
1
1
  import Routable from '../'
2
2
  import { mount, Wrapper } from '@vue/test-utils'
3
3
  import { createRouter, createWebHistory } from 'vue-router'
4
- import { nextTick } from 'vue'
4
+ import { defineComponent, h, nextTick } from 'vue'
5
5
 
6
6
  describe('routable.ts', () => {
7
7
  let mountFunction: (options?: object) => Wrapper<any>
@@ -102,4 +102,78 @@ describe('routable.ts', () => {
102
102
  expect(wrapper.vm.isLink).toBe(true)
103
103
  expect(wrapper.vm.isClickable).toBe(true)
104
104
  })
105
+
106
+ it('should not include undefined in activeClass passed to router-link', async () => {
107
+ const RouterLinkCapture = defineComponent({
108
+ name: 'RouterLinkCapture',
109
+ props: {
110
+ to: { type: [String, Object], required: true },
111
+ activeClass: String,
112
+ exactActiveClass: String
113
+ },
114
+ template: '<a><slot /></a>'
115
+ })
116
+
117
+ const wrapper = mount({
118
+ mixins: [Routable],
119
+ data: () => ({
120
+ proxyClass: 'v-list-item--active'
121
+ }),
122
+ render () {
123
+ const { tag, data } = this.generateRouteLink()
124
+ return h(tag, data, { default: () => 'link' })
125
+ }
126
+ }, {
127
+ global: {
128
+ plugins: [router],
129
+ stubs: {
130
+ 'router-link': RouterLinkCapture
131
+ }
132
+ },
133
+ props: {
134
+ to: '/'
135
+ }
136
+ })
137
+
138
+ const routerLink = wrapper.findComponent({ name: 'RouterLinkCapture' })
139
+
140
+ expect(routerLink.props('activeClass')).toBe('v-list-item--active')
141
+ expect(routerLink.props('activeClass')).not.toContain('undefined')
142
+ expect(routerLink.props('exactActiveClass')).toBe('v-list-item--active')
143
+ })
144
+
145
+ it('should sync isActive with router-link on route change', async () => {
146
+ const toggle = jest.fn()
147
+ const wrapper = mount({
148
+ mixins: [Routable],
149
+ data: () => ({
150
+ proxyClass: 'v-tab--active'
151
+ }),
152
+ methods: {
153
+ toggle
154
+ },
155
+ template: '<div ref="link" />'
156
+ }, {
157
+ global: {
158
+ plugins: [router]
159
+ },
160
+ props: {
161
+ to: '/foo',
162
+ activeClass: 'bar'
163
+ }
164
+ })
165
+
166
+ wrapper.vm.onRouteChange()
167
+ await nextTick()
168
+
169
+ expect(toggle).not.toHaveBeenCalled()
170
+
171
+ const linkEl = wrapper.vm.$refs.link as HTMLElement
172
+ linkEl.classList.add('bar', 'v-tab--active')
173
+
174
+ wrapper.vm.onRouteChange()
175
+ await nextTick()
176
+
177
+ expect(toggle).toHaveBeenCalledTimes(1)
178
+ })
105
179
  })
@@ -6,7 +6,6 @@ import { defineComponent, resolveComponent } from 'vue'
6
6
  import Ripple, { RippleOptions } from '../../directives/ripple'
7
7
 
8
8
  // Utilities
9
- import { getObjectValueByPath } from '../../util/helpers'
10
9
 
11
10
  export default defineComponent({
12
11
  name: 'routable',
@@ -119,8 +118,8 @@ export default defineComponent({
119
118
  let exactActiveClass = this.exactActiveClass || activeClass
120
119
 
121
120
  if (this.proxyClass) {
122
- activeClass = `${activeClass} ${this.proxyClass}`.trim()
123
- exactActiveClass = `${exactActiveClass} ${this.proxyClass}`.trim()
121
+ activeClass = [activeClass, this.proxyClass].filter(Boolean).join(' ')
122
+ exactActiveClass = [exactActiveClass, this.proxyClass].filter(Boolean).join(' ')
124
123
  }
125
124
 
126
125
  tag = resolveComponent(this.nuxt ? 'nuxt-link' : 'router-link')
@@ -148,11 +147,14 @@ export default defineComponent({
148
147
  const activeClass = `${this.activeClass || ''} ${this.proxyClass || ''}`.trim()
149
148
  const exactActiveClass = `${this.exactActiveClass || ''} ${this.proxyClass || ''}`.trim() || activeClass
150
149
 
151
- const path = '_vnode.data.class.' + (this.exact ? exactActiveClass : activeClass)
150
+ const activeClasses = (this.exact ? exactActiveClass : activeClass).split(' ')
152
151
 
153
152
  this.$nextTick(() => {
154
153
  /* istanbul ignore else */
155
- if (!getObjectValueByPath(this.$refs.link, path) === this.isActive) {
154
+ const el = (this.$refs.link as any).$el || this.$refs.link
155
+ const isLinkActive = activeClasses.every(c => el?.classList?.contains(c))
156
+
157
+ if (isLinkActive !== this.isActive) {
156
158
  this.toggle()
157
159
  }
158
160
  })