// using DOM renderer because this case is mostly DOM-specific import { h, render, nextTick, mergeProps, ref, onUpdated, createComponent } from '@vue/runtime-dom' describe('attribute fallthrough', () => { it('everything should be in props when component has no declared props', async () => { const click = jest.fn() const childUpdated = jest.fn() const Hello = { setup() { const count = ref(0) function inc() { count.value++ click() } return () => h(Child, { foo: 1, id: 'test', class: 'c' + count.value, style: { color: count.value ? 'red' : 'green' }, onClick: inc }) } } const Child = { setup(props: any) { onUpdated(childUpdated) return () => h( 'div', mergeProps( { class: 'c2', style: { fontWeight: 'bold' } }, props ), props.foo ) } } const root = document.createElement('div') document.body.appendChild(root) render(h(Hello), root) const node = root.children[0] as HTMLElement expect(node.getAttribute('id')).toBe('test') expect(node.getAttribute('foo')).toBe('1') expect(node.getAttribute('class')).toBe('c2 c0') expect(node.style.color).toBe('green') expect(node.style.fontWeight).toBe('bold') node.dispatchEvent(new CustomEvent('click')) expect(click).toHaveBeenCalled() await nextTick() expect(childUpdated).toHaveBeenCalled() expect(node.getAttribute('id')).toBe('test') expect(node.getAttribute('foo')).toBe('1') expect(node.getAttribute('class')).toBe('c2 c1') expect(node.style.color).toBe('red') expect(node.style.fontWeight).toBe('bold') }) it('should separate in attrs when component has declared props', async () => { const click = jest.fn() const childUpdated = jest.fn() const Hello = { setup() { const count = ref(0) function inc() { count.value++ click() } return () => h(Child, { foo: 1, id: 'test', class: 'c' + count.value, style: { color: count.value ? 'red' : 'green' }, onClick: inc }) } } const Child = createComponent({ props: { foo: Number }, setup(props, { attrs }) { onUpdated(childUpdated) return () => h( 'div', mergeProps( { class: 'c2', style: { fontWeight: 'bold' } }, attrs ), props.foo ) } }) const root = document.createElement('div') document.body.appendChild(root) render(h(Hello), root) const node = root.children[0] as HTMLElement // with declared props, any parent attr that isn't a prop falls through expect(node.getAttribute('id')).toBe('test') expect(node.getAttribute('class')).toBe('c2 c0') expect(node.style.color).toBe('green') expect(node.style.fontWeight).toBe('bold') node.dispatchEvent(new CustomEvent('click')) expect(click).toHaveBeenCalled() // ...while declared ones remain props expect(node.hasAttribute('foo')).toBe(false) await nextTick() expect(childUpdated).toHaveBeenCalled() expect(node.getAttribute('id')).toBe('test') expect(node.getAttribute('class')).toBe('c2 c1') expect(node.style.color).toBe('red') expect(node.style.fontWeight).toBe('bold') expect(node.hasAttribute('foo')).toBe(false) }) it('should fallthrough on multi-nested components', async () => { const click = jest.fn() const childUpdated = jest.fn() const grandChildUpdated = jest.fn() const Hello = { setup() { const count = ref(0) function inc() { count.value++ click() } return () => h(Child, { foo: 1, id: 'test', class: 'c' + count.value, style: { color: count.value ? 'red' : 'green' }, onClick: inc }) } } const Child = { setup(props: any) { onUpdated(childUpdated) return () => h(GrandChild, props) } } const GrandChild = createComponent({ props: { foo: Number }, setup(props, { attrs }) { onUpdated(grandChildUpdated) return () => h( 'div', mergeProps( { class: 'c2', style: { fontWeight: 'bold' } }, attrs ), props.foo ) } }) const root = document.createElement('div') document.body.appendChild(root) render(h(Hello), root) const node = root.children[0] as HTMLElement // with declared props, any parent attr that isn't a prop falls through expect(node.getAttribute('id')).toBe('test') expect(node.getAttribute('class')).toBe('c2 c0') expect(node.style.color).toBe('green') expect(node.style.fontWeight).toBe('bold') node.dispatchEvent(new CustomEvent('click')) expect(click).toHaveBeenCalled() // ...while declared ones remain props expect(node.hasAttribute('foo')).toBe(false) await nextTick() expect(childUpdated).toHaveBeenCalled() expect(grandChildUpdated).toHaveBeenCalled() expect(node.getAttribute('id')).toBe('test') expect(node.getAttribute('class')).toBe('c2 c1') expect(node.style.color).toBe('red') expect(node.style.fontWeight).toBe('bold') expect(node.hasAttribute('foo')).toBe(false) }) })