'<div><span>1 is number</span><span>true is boolean</span></div>',
)
})
+
+ test('should patch all props together', async () => {
+ let prop1Calls = 0
+ let prop2Calls = 0
+ const E = defineCustomElement({
+ props: {
+ prop1: {
+ type: String,
+ default: 'default1',
+ },
+ prop2: {
+ type: String,
+ default: 'default2',
+ },
+ },
+ data() {
+ return {
+ data1: 'defaultData1',
+ data2: 'defaultData2',
+ }
+ },
+ watch: {
+ prop1(_) {
+ prop1Calls++
+ this.data2 = this.prop2
+ },
+ prop2(_) {
+ prop2Calls++
+ this.data1 = this.prop1
+ },
+ },
+ render() {
+ return h('div', [
+ h('h1', this.prop1),
+ h('h1', this.prop2),
+ h('h2', this.data1),
+ h('h2', this.data2),
+ ])
+ },
+ })
+ customElements.define('my-watch-element', E)
+
+ render(h('my-watch-element'), container)
+ const e = container.childNodes[0] as VueElement
+ expect(e).toBeInstanceOf(E)
+ expect(e._instance).toBeTruthy()
+ expect(e.shadowRoot!.innerHTML).toBe(
+ `<div><h1>default1</h1><h1>default2</h1><h2>defaultData1</h2><h2>defaultData2</h2></div>`,
+ )
+ expect(prop1Calls).toBe(0)
+ expect(prop2Calls).toBe(0)
+
+ // patch props
+ render(
+ h('my-watch-element', { prop1: 'newValue1', prop2: 'newValue2' }),
+ container,
+ )
+ await nextTick()
+ expect(e.shadowRoot!.innerHTML).toBe(
+ `<div><h1>newValue1</h1><h1>newValue2</h1><h2>newValue1</h2><h2>newValue2</h2></div>`,
+ )
+ expect(prop1Calls).toBe(1)
+ expect(prop2Calls).toBe(1)
+
+ // same prop values
+ render(
+ h('my-watch-element', { prop1: 'newValue1', prop2: 'newValue2' }),
+ container,
+ )
+ await nextTick()
+ expect(e.shadowRoot!.innerHTML).toBe(
+ `<div><h1>newValue1</h1><h1>newValue2</h1><h2>newValue1</h2><h2>newValue2</h2></div>`,
+ )
+ expect(prop1Calls).toBe(1)
+ expect(prop2Calls).toBe(1)
+
+ // update only prop1
+ render(
+ h('my-watch-element', { prop1: 'newValue3', prop2: 'newValue2' }),
+ container,
+ )
+ await nextTick()
+ expect(e.shadowRoot!.innerHTML).toBe(
+ `<div><h1>newValue3</h1><h1>newValue2</h1><h2>newValue1</h2><h2>newValue2</h2></div>`,
+ )
+ expect(prop1Calls).toBe(2)
+ expect(prop2Calls).toBe(1)
+ })
+
+ test('should patch all props together (async)', async () => {
+ let prop1Calls = 0
+ let prop2Calls = 0
+ const E = defineCustomElement(
+ defineAsyncComponent(() =>
+ Promise.resolve(
+ defineComponent({
+ props: {
+ prop1: {
+ type: String,
+ default: 'default1',
+ },
+ prop2: {
+ type: String,
+ default: 'default2',
+ },
+ },
+ data() {
+ return {
+ data1: 'defaultData1',
+ data2: 'defaultData2',
+ }
+ },
+ watch: {
+ prop1(_) {
+ prop1Calls++
+ this.data2 = this.prop2
+ },
+ prop2(_) {
+ prop2Calls++
+ this.data1 = this.prop1
+ },
+ },
+ render() {
+ return h('div', [
+ h('h1', this.prop1),
+ h('h1', this.prop2),
+ h('h2', this.data1),
+ h('h2', this.data2),
+ ])
+ },
+ }),
+ ),
+ ),
+ )
+ customElements.define('my-async-watch-element', E)
+
+ render(h('my-async-watch-element'), container)
+
+ await new Promise(r => setTimeout(r))
+ const e = container.childNodes[0] as VueElement
+ expect(e).toBeInstanceOf(E)
+ expect(e._instance).toBeTruthy()
+ expect(e.shadowRoot!.innerHTML).toBe(
+ `<div><h1>default1</h1><h1>default2</h1><h2>defaultData1</h2><h2>defaultData2</h2></div>`,
+ )
+ expect(prop1Calls).toBe(0)
+ expect(prop2Calls).toBe(0)
+
+ // patch props
+ render(
+ h('my-async-watch-element', { prop1: 'newValue1', prop2: 'newValue2' }),
+ container,
+ )
+ await nextTick()
+ expect(e.shadowRoot!.innerHTML).toBe(
+ `<div><h1>newValue1</h1><h1>newValue2</h1><h2>newValue1</h2><h2>newValue2</h2></div>`,
+ )
+ expect(prop1Calls).toBe(1)
+ expect(prop2Calls).toBe(1)
+
+ // same prop values
+ render(
+ h('my-async-watch-element', { prop1: 'newValue1', prop2: 'newValue2' }),
+ container,
+ )
+ await nextTick()
+ expect(e.shadowRoot!.innerHTML).toBe(
+ `<div><h1>newValue1</h1><h1>newValue2</h1><h2>newValue1</h2><h2>newValue2</h2></div>`,
+ )
+ expect(prop1Calls).toBe(1)
+ expect(prop2Calls).toBe(1)
+
+ // update only prop1
+ render(
+ h('my-async-watch-element', { prop1: 'newValue3', prop2: 'newValue2' }),
+ container,
+ )
+ await nextTick()
+ expect(e.shadowRoot!.innerHTML).toBe(
+ `<div><h1>newValue3</h1><h1>newValue2</h1><h2>newValue1</h2><h2>newValue2</h2></div>`,
+ )
+ expect(prop1Calls).toBe(2)
+ expect(prop2Calls).toBe(1)
+ })
})
describe('attrs', () => {
private _connected = false
private _resolved = false
+ private _patching = false
+ private _dirty = false
private _numberProps: Record<string, true> | null = null
private _styleChildren = new WeakSet()
private _pendingResolve: Promise<void> | undefined
// defining getter/setters on prototype
for (const key of declaredPropKeys.map(camelize)) {
Object.defineProperty(this, key, {
- get() {
+ get(this: VueElement) {
return this._getProp(key)
},
- set(val) {
- this._setProp(key, val, true, true)
+ set(this: VueElement, val) {
+ this._setProp(key, val, true, !this._patching)
},
})
}
shouldUpdate = false,
): void {
if (val !== this._props[key]) {
+ this._dirty = true
if (val === REMOVAL) {
delete this._props[key]
} else {
this._applyStyles(comp.styles, comp)
}
+ /**
+ * @internal
+ */
+ _beginPatch(): void {
+ this._patching = true
+ this._dirty = false
+ }
+
+ /**
+ * @internal
+ */
+ _endPatch(): void {
+ this._patching = false
+ if (this._dirty && this._instance) {
+ this._update()
+ }
+ }
+
/**
* @internal
*/