@dimailn/vuetify 2.7.2-alpha51 → 2.7.2-alpha53
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.
- package/dist/vuetify.js +12 -5
- package/dist/vuetify.js.map +1 -1
- package/dist/vuetify.min.css +1 -1
- package/dist/vuetify.min.js +2 -2
- package/es5/components/VDatePicker/VDatePicker.js +3 -1
- package/es5/components/VDatePicker/VDatePicker.js.map +1 -1
- package/es5/framework.js +1 -1
- package/es5/mixins/routable/index.js +5 -3
- package/es5/mixins/routable/index.js.map +1 -1
- package/es5/util/helpers.js +2 -0
- package/es5/util/helpers.js.map +1 -1
- package/lib/components/VDatePicker/VDatePicker.js +3 -1
- package/lib/components/VDatePicker/VDatePicker.js.map +1 -1
- package/lib/framework.js +1 -1
- package/lib/mixins/routable/index.js +5 -3
- package/lib/mixins/routable/index.js.map +1 -1
- package/lib/util/helpers.js +3 -0
- package/lib/util/helpers.js.map +1 -1
- package/package.json +1 -1
- package/src/components/VDatePicker/VDatePicker.ts +5 -1
- package/src/components/VDatePicker/__tests__/VDatePicker.date.spec.ts +28 -0
- package/src/components/VList/__tests__/VListItem.spec.ts +119 -0
- package/src/mixins/routable/__tests__/routable.spec.ts +78 -1
- package/src/mixins/routable/index.ts +4 -3
- package/src/services/presets/__tests__/presets.spec.ts +13 -0
- package/src/util/helpers.ts +4 -0
|
@@ -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,119 @@ 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
|
+
;(listItem.vm.$refs.link as any)._vnode = {
|
|
413
|
+
data: {
|
|
414
|
+
class: { 'v-list-item--active': true }
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
listItem.vm.onRouteChange()
|
|
419
|
+
await nextTick()
|
|
420
|
+
await nextTick()
|
|
421
|
+
|
|
422
|
+
expect(listItem.vm.isActive).toBe(true)
|
|
423
|
+
expect(wrapper.find('.link--text').exists()).toBe(true)
|
|
424
|
+
expect(listItem.element.getAttribute('aria-selected')).toBe('true')
|
|
425
|
+
})
|
|
426
|
+
|
|
427
|
+
it('syncs aria-selected with isActive in list item group', async () => {
|
|
428
|
+
const wrapper = mountFunction({
|
|
429
|
+
props: { modelValue: 'item-1' },
|
|
430
|
+
global: {
|
|
431
|
+
provide: {
|
|
432
|
+
isInGroup: true,
|
|
433
|
+
listItemGroup: {
|
|
434
|
+
activeClass: 'v-item--active',
|
|
435
|
+
register: () => {},
|
|
436
|
+
unregister: () => {}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
})
|
|
441
|
+
|
|
442
|
+
wrapper.vm.isActive = true
|
|
443
|
+
await wrapper.vm.$nextTick()
|
|
444
|
+
|
|
445
|
+
expect(wrapper.element.getAttribute('aria-selected')).toBe('true')
|
|
446
|
+
})
|
|
328
447
|
})
|
|
@@ -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,81 @@ 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
|
+
;(wrapper.vm.$refs.link as any)._vnode = {
|
|
172
|
+
data: {
|
|
173
|
+
class: { 'bar v-tab--active': true }
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
wrapper.vm.onRouteChange()
|
|
178
|
+
await nextTick()
|
|
179
|
+
|
|
180
|
+
expect(toggle).toHaveBeenCalledTimes(1)
|
|
181
|
+
})
|
|
105
182
|
})
|
|
@@ -119,8 +119,8 @@ export default defineComponent({
|
|
|
119
119
|
let exactActiveClass = this.exactActiveClass || activeClass
|
|
120
120
|
|
|
121
121
|
if (this.proxyClass) {
|
|
122
|
-
activeClass =
|
|
123
|
-
exactActiveClass =
|
|
122
|
+
activeClass = [activeClass, this.proxyClass].filter(Boolean).join(' ')
|
|
123
|
+
exactActiveClass = [exactActiveClass, this.proxyClass].filter(Boolean).join(' ')
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
tag = resolveComponent(this.nuxt ? 'nuxt-link' : 'router-link')
|
|
@@ -152,7 +152,8 @@ export default defineComponent({
|
|
|
152
152
|
|
|
153
153
|
this.$nextTick(() => {
|
|
154
154
|
/* istanbul ignore else */
|
|
155
|
-
|
|
155
|
+
const isLinkActive = Boolean(getObjectValueByPath(this.$refs.link, path))
|
|
156
|
+
if (isLinkActive !== this.isActive) {
|
|
156
157
|
this.toggle()
|
|
157
158
|
}
|
|
158
159
|
})
|
|
@@ -48,4 +48,17 @@ describe('$vuetify.presets', () => {
|
|
|
48
48
|
expect(JSON.stringify(itheme.themes)).toMatchSnapshot()
|
|
49
49
|
expect(JSON.stringify(breakpoints.thresholds)).toMatchSnapshot()
|
|
50
50
|
})
|
|
51
|
+
|
|
52
|
+
it('should prevent prototype pollution from malicious presets', () => {
|
|
53
|
+
const polluted = {}
|
|
54
|
+
|
|
55
|
+
expect((polluted as any).isPolluted).toBeUndefined()
|
|
56
|
+
|
|
57
|
+
const vuetify = new Framework({
|
|
58
|
+
preset: JSON.parse('{"__proto__":{"isPolluted":"yes"}}')
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
expect(({} as any).isPolluted).toBeUndefined()
|
|
62
|
+
expect((vuetify.preset as any).isPolluted).toBeUndefined()
|
|
63
|
+
})
|
|
51
64
|
})
|
package/src/util/helpers.ts
CHANGED
|
@@ -503,7 +503,11 @@ export function mergeDeep (
|
|
|
503
503
|
source: Dictionary<any> = {},
|
|
504
504
|
target: Dictionary<any> = {}
|
|
505
505
|
) {
|
|
506
|
+
const blockedKeys = ['__proto__', 'constructor', 'prototype']
|
|
507
|
+
|
|
506
508
|
for (const key in target) {
|
|
509
|
+
if (blockedKeys.includes(key)) continue
|
|
510
|
+
|
|
507
511
|
const sourceProperty = source[key]
|
|
508
512
|
const targetProperty = target[key]
|
|
509
513
|
|