2020-04-11 03:37:30 +08:00
|
|
|
import { patchProp } from '../src/patchProp'
|
|
|
|
import { render, h } from '../src'
|
|
|
|
|
|
|
|
describe('runtime-dom: props patching', () => {
|
|
|
|
test('basic', () => {
|
|
|
|
const el = document.createElement('div')
|
|
|
|
patchProp(el, 'id', null, 'foo')
|
|
|
|
expect(el.id).toBe('foo')
|
2020-05-01 23:06:24 +08:00
|
|
|
// prop with string value should be set to empty string on null values
|
2020-04-11 03:37:30 +08:00
|
|
|
patchProp(el, 'id', null, null)
|
|
|
|
expect(el.id).toBe('')
|
2021-05-29 03:48:22 +08:00
|
|
|
expect(el.getAttribute('id')).toBe(null)
|
2020-04-11 03:37:30 +08:00
|
|
|
})
|
|
|
|
|
|
|
|
test('value', () => {
|
|
|
|
const el = document.createElement('input')
|
|
|
|
patchProp(el, 'value', null, 'foo')
|
|
|
|
expect(el.value).toBe('foo')
|
|
|
|
patchProp(el, 'value', null, null)
|
|
|
|
expect(el.value).toBe('')
|
2021-05-29 03:48:22 +08:00
|
|
|
expect(el.getAttribute('value')).toBe(null)
|
2020-04-11 03:37:30 +08:00
|
|
|
const obj = {}
|
|
|
|
patchProp(el, 'value', null, obj)
|
|
|
|
expect(el.value).toBe(obj.toString())
|
|
|
|
expect((el as any)._value).toBe(obj)
|
|
|
|
})
|
|
|
|
|
2021-11-25 18:30:32 +08:00
|
|
|
test('value for custom elements', () => {
|
|
|
|
class TestElement extends HTMLElement {
|
|
|
|
constructor() {
|
|
|
|
super()
|
|
|
|
}
|
|
|
|
|
|
|
|
// intentionally uses _value because this is used in "normal" HTMLElement for storing the object of the set property value
|
|
|
|
private _value: any
|
|
|
|
get value() {
|
|
|
|
return this._value
|
|
|
|
}
|
|
|
|
|
|
|
|
set value(val) {
|
|
|
|
this._value = val
|
|
|
|
this.setterCalled++
|
|
|
|
}
|
|
|
|
|
|
|
|
public setterCalled: number = 0
|
|
|
|
}
|
|
|
|
window.customElements.define('test-element', TestElement)
|
|
|
|
const el = document.createElement('test-element') as TestElement
|
|
|
|
patchProp(el, 'value', null, 'foo')
|
|
|
|
expect(el.value).toBe('foo')
|
|
|
|
expect(el.setterCalled).toBe(1)
|
|
|
|
patchProp(el, 'value', null, null)
|
|
|
|
expect(el.value).toBe('')
|
|
|
|
expect(el.setterCalled).toBe(2)
|
|
|
|
expect(el.getAttribute('value')).toBe(null)
|
|
|
|
const obj = {}
|
|
|
|
patchProp(el, 'value', null, obj)
|
|
|
|
expect(el.value).toBe(obj)
|
|
|
|
expect(el.setterCalled).toBe(3)
|
|
|
|
})
|
|
|
|
|
2021-05-29 03:48:22 +08:00
|
|
|
// For <input type="text">, setting el.value won't create a `value` attribute
|
|
|
|
// so we need to add tests for other elements
|
|
|
|
test('value for non-text input', () => {
|
|
|
|
const el = document.createElement('option')
|
2021-11-25 18:05:02 +08:00
|
|
|
el.textContent = 'foo' // #4956
|
2021-05-29 03:48:22 +08:00
|
|
|
patchProp(el, 'value', null, 'foo')
|
2021-11-25 18:05:02 +08:00
|
|
|
expect(el.getAttribute('value')).toBe('foo')
|
2021-05-29 03:48:22 +08:00
|
|
|
expect(el.value).toBe('foo')
|
|
|
|
patchProp(el, 'value', null, null)
|
2021-11-25 18:05:02 +08:00
|
|
|
el.textContent = ''
|
2021-05-29 03:48:22 +08:00
|
|
|
expect(el.value).toBe('')
|
|
|
|
// #3475
|
|
|
|
expect(el.getAttribute('value')).toBe(null)
|
|
|
|
})
|
|
|
|
|
2020-04-11 03:37:30 +08:00
|
|
|
test('boolean prop', () => {
|
|
|
|
const el = document.createElement('select')
|
|
|
|
patchProp(el, 'multiple', null, '')
|
|
|
|
expect(el.multiple).toBe(true)
|
|
|
|
patchProp(el, 'multiple', null, null)
|
|
|
|
expect(el.multiple).toBe(false)
|
2021-08-17 06:18:36 +08:00
|
|
|
patchProp(el, 'multiple', null, true)
|
|
|
|
expect(el.multiple).toBe(true)
|
|
|
|
patchProp(el, 'multiple', null, 0)
|
|
|
|
expect(el.multiple).toBe(false)
|
|
|
|
patchProp(el, 'multiple', null, '0')
|
|
|
|
expect(el.multiple).toBe(true)
|
|
|
|
patchProp(el, 'multiple', null, false)
|
|
|
|
expect(el.multiple).toBe(false)
|
|
|
|
patchProp(el, 'multiple', null, 1)
|
|
|
|
expect(el.multiple).toBe(true)
|
|
|
|
patchProp(el, 'multiple', null, undefined)
|
|
|
|
expect(el.multiple).toBe(false)
|
2020-04-11 03:37:30 +08:00
|
|
|
})
|
|
|
|
|
|
|
|
test('innerHTML unmount prev children', () => {
|
|
|
|
const fn = jest.fn()
|
|
|
|
const comp = {
|
|
|
|
render: () => 'foo',
|
|
|
|
unmounted: fn
|
|
|
|
}
|
|
|
|
const root = document.createElement('div')
|
|
|
|
render(h('div', null, [h(comp)]), root)
|
|
|
|
expect(root.innerHTML).toBe(`<div>foo</div>`)
|
|
|
|
|
|
|
|
render(h('div', { innerHTML: 'bar' }), root)
|
|
|
|
expect(root.innerHTML).toBe(`<div>bar</div>`)
|
|
|
|
expect(fn).toHaveBeenCalled()
|
|
|
|
})
|
|
|
|
|
2020-04-14 00:13:37 +08:00
|
|
|
// #954
|
|
|
|
test('(svg) innerHTML unmount prev children', () => {
|
|
|
|
const fn = jest.fn()
|
|
|
|
const comp = {
|
|
|
|
render: () => 'foo',
|
|
|
|
unmounted: fn
|
|
|
|
}
|
|
|
|
const root = document.createElement('div')
|
|
|
|
render(h('div', null, [h(comp)]), root)
|
|
|
|
expect(root.innerHTML).toBe(`<div>foo</div>`)
|
|
|
|
|
|
|
|
render(h('svg', { innerHTML: '<g></g>' }), root)
|
|
|
|
expect(root.innerHTML).toBe(`<svg><g></g></svg>`)
|
|
|
|
expect(fn).toHaveBeenCalled()
|
|
|
|
})
|
|
|
|
|
2020-04-11 03:37:30 +08:00
|
|
|
test('textContent unmount prev children', () => {
|
|
|
|
const fn = jest.fn()
|
|
|
|
const comp = {
|
|
|
|
render: () => 'foo',
|
|
|
|
unmounted: fn
|
|
|
|
}
|
|
|
|
const root = document.createElement('div')
|
|
|
|
render(h('div', null, [h(comp)]), root)
|
|
|
|
expect(root.innerHTML).toBe(`<div>foo</div>`)
|
|
|
|
|
|
|
|
render(h('div', { textContent: 'bar' }), root)
|
|
|
|
expect(root.innerHTML).toBe(`<div>bar</div>`)
|
|
|
|
expect(fn).toHaveBeenCalled()
|
|
|
|
})
|
2020-05-01 23:06:24 +08:00
|
|
|
|
|
|
|
// #1049
|
|
|
|
test('set value as-is for non string-value props', () => {
|
|
|
|
const el = document.createElement('video')
|
|
|
|
// jsdom doesn't really support video playback. srcObject in a real browser
|
|
|
|
// should default to `null`, but in jsdom it's `undefined`.
|
|
|
|
// anyway, here we just want to make sure Vue doesn't set non-string props
|
|
|
|
// to an empty string on nullish values - it should reset to its default
|
|
|
|
// value.
|
2020-07-08 18:32:42 +08:00
|
|
|
const initialValue = el.srcObject
|
2020-05-01 23:06:24 +08:00
|
|
|
const fakeObject = {}
|
|
|
|
patchProp(el, 'srcObject', null, fakeObject)
|
|
|
|
expect(el.srcObject).not.toBe(fakeObject)
|
|
|
|
patchProp(el, 'srcObject', null, null)
|
2020-07-08 18:32:42 +08:00
|
|
|
expect(el.srcObject).toBe(initialValue)
|
2020-05-01 23:06:24 +08:00
|
|
|
})
|
2020-05-02 06:47:27 +08:00
|
|
|
|
|
|
|
test('catch and warn prop set TypeError', () => {
|
|
|
|
const el = document.createElement('div')
|
|
|
|
Object.defineProperty(el, 'someProp', {
|
|
|
|
set() {
|
|
|
|
throw new TypeError('Invalid type')
|
|
|
|
}
|
|
|
|
})
|
|
|
|
patchProp(el, 'someProp', null, 'foo')
|
|
|
|
|
|
|
|
expect(`Failed setting prop "someProp" on <div>`).toHaveBeenWarnedLast()
|
|
|
|
})
|
2020-07-15 04:25:21 +08:00
|
|
|
|
|
|
|
// #1576
|
|
|
|
test('remove attribute when value is falsy', () => {
|
|
|
|
const el = document.createElement('div')
|
|
|
|
patchProp(el, 'id', null, '')
|
|
|
|
expect(el.hasAttribute('id')).toBe(true)
|
|
|
|
patchProp(el, 'id', null, null)
|
|
|
|
expect(el.hasAttribute('id')).toBe(false)
|
|
|
|
|
|
|
|
patchProp(el, 'id', null, '')
|
|
|
|
expect(el.hasAttribute('id')).toBe(true)
|
|
|
|
patchProp(el, 'id', null, undefined)
|
|
|
|
expect(el.hasAttribute('id')).toBe(false)
|
|
|
|
|
|
|
|
patchProp(el, 'id', null, '')
|
|
|
|
expect(el.hasAttribute('id')).toBe(true)
|
2020-12-01 08:28:03 +08:00
|
|
|
|
|
|
|
// #2677
|
|
|
|
const img = document.createElement('img')
|
|
|
|
patchProp(img, 'width', null, '')
|
|
|
|
expect(el.hasAttribute('width')).toBe(false)
|
|
|
|
patchProp(img, 'width', null, 0)
|
|
|
|
expect(img.hasAttribute('width')).toBe(true)
|
|
|
|
|
|
|
|
patchProp(img, 'width', null, null)
|
|
|
|
expect(img.hasAttribute('width')).toBe(false)
|
|
|
|
patchProp(img, 'width', null, 0)
|
|
|
|
expect(img.hasAttribute('width')).toBe(true)
|
|
|
|
|
|
|
|
patchProp(img, 'width', null, undefined)
|
|
|
|
expect(img.hasAttribute('width')).toBe(false)
|
|
|
|
patchProp(img, 'width', null, 0)
|
|
|
|
expect(img.hasAttribute('width')).toBe(true)
|
2020-07-15 04:25:21 +08:00
|
|
|
})
|
2020-08-06 21:32:28 +08:00
|
|
|
|
|
|
|
test('form attribute', () => {
|
|
|
|
const el = document.createElement('input')
|
|
|
|
patchProp(el, 'form', null, 'foo')
|
|
|
|
// non existant element
|
|
|
|
expect(el.form).toBe(null)
|
|
|
|
expect(el.getAttribute('form')).toBe('foo')
|
2021-02-04 02:11:09 +08:00
|
|
|
// remove attribute
|
|
|
|
patchProp(el, 'form', 'foo', null)
|
|
|
|
expect(el.getAttribute('form')).toBe(null)
|
2020-08-06 21:32:28 +08:00
|
|
|
})
|
2021-02-25 03:51:19 +08:00
|
|
|
|
|
|
|
test('readonly type prop on textarea', () => {
|
|
|
|
const el = document.createElement('textarea')
|
|
|
|
// just to verify that it doesn't throw when i.e. switching a dynamic :is from an 'input' to a 'textarea'
|
2022-01-18 16:43:59 +08:00
|
|
|
// see https://github.com/vuejs/core/issues/2766
|
2021-02-25 03:51:19 +08:00
|
|
|
patchProp(el, 'type', 'text', null)
|
|
|
|
})
|
2021-07-16 04:32:25 +08:00
|
|
|
|
2021-07-14 03:58:18 +08:00
|
|
|
test('force patch as prop', () => {
|
|
|
|
const el = document.createElement('div') as any
|
|
|
|
patchProp(el, '.x', null, 1)
|
|
|
|
expect(el.x).toBe(1)
|
|
|
|
})
|
|
|
|
|
|
|
|
test('force patch as attribute', () => {
|
|
|
|
const el = document.createElement('div') as any
|
|
|
|
el.x = 1
|
|
|
|
patchProp(el, '^x', null, 2)
|
|
|
|
expect(el.x).toBe(1)
|
|
|
|
expect(el.getAttribute('x')).toBe('2')
|
|
|
|
})
|
|
|
|
|
2022-04-13 17:25:11 +08:00
|
|
|
test('input with size (number property)', () => {
|
2021-07-16 04:32:25 +08:00
|
|
|
const el = document.createElement('input')
|
|
|
|
patchProp(el, 'size', null, 100)
|
|
|
|
expect(el.size).toBe(100)
|
|
|
|
patchProp(el, 'size', 100, null)
|
|
|
|
expect(el.getAttribute('size')).toBe(null)
|
2022-04-13 17:25:11 +08:00
|
|
|
expect('Failed setting prop "size" on <input>').toHaveBeenWarnedLast()
|
|
|
|
})
|
|
|
|
|
|
|
|
test('select with type (string property)', () => {
|
|
|
|
const el = document.createElement('select')
|
|
|
|
patchProp(el, 'type', null, 'test')
|
|
|
|
expect(el.type).toBe('select-one')
|
|
|
|
expect('Failed setting prop "type" on <select>').toHaveBeenWarnedLast()
|
|
|
|
})
|
|
|
|
|
|
|
|
test('select with willValidate (boolean property)', () => {
|
|
|
|
const el = document.createElement('select')
|
|
|
|
patchProp(el, 'willValidate', true, null)
|
|
|
|
expect(el.willValidate).toBe(true)
|
|
|
|
expect(
|
|
|
|
'Failed setting prop "willValidate" on <select>'
|
|
|
|
).toHaveBeenWarnedLast()
|
2021-07-16 04:32:25 +08:00
|
|
|
})
|
2021-08-17 03:46:27 +08:00
|
|
|
|
|
|
|
test('patch value for select', () => {
|
|
|
|
const root = document.createElement('div')
|
|
|
|
render(
|
|
|
|
h('select', { value: 'foo' }, [
|
|
|
|
h('option', { value: 'foo' }, 'foo'),
|
|
|
|
h('option', { value: 'bar' }, 'bar')
|
|
|
|
]),
|
|
|
|
root
|
|
|
|
)
|
|
|
|
const el = root.children[0] as HTMLSelectElement
|
|
|
|
expect(el.value).toBe('foo')
|
|
|
|
|
|
|
|
render(
|
|
|
|
h('select', { value: 'baz' }, [
|
|
|
|
h('option', { value: 'foo' }, 'foo'),
|
|
|
|
h('option', { value: 'baz' }, 'baz')
|
|
|
|
]),
|
|
|
|
root
|
|
|
|
)
|
|
|
|
expect(el.value).toBe('baz')
|
|
|
|
})
|
2022-04-13 17:16:33 +08:00
|
|
|
|
|
|
|
test('translate attribute', () => {
|
|
|
|
const el = document.createElement('div')
|
|
|
|
patchProp(el, 'translate', null, 'no')
|
|
|
|
expect(el.translate).toBeFalsy()
|
|
|
|
expect(el.getAttribute('translate')).toBe('no')
|
|
|
|
})
|
2020-04-11 03:37:30 +08:00
|
|
|
})
|