| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364 |
- import { type Ref, nextTick, ref } from '@vue/runtime-dom'
- import {
- createComponent,
- defineVaporComponent,
- renderEffect,
- setClass,
- setDynamicProps,
- setProp,
- setStyle,
- template,
- } from '../src'
- import { makeRender } from './_utils'
- import { stringifyStyle } from '@vue/shared'
- import { setElementText } from '../src/dom/prop'
- const define = makeRender<any>()
- // TODO: port more tests from rendererAttrsFallthrough.spec.ts
- describe('attribute fallthrough', () => {
- it('should allow attrs to fallthrough', async () => {
- const t0 = template('<div>', true)
- const { component: Child } = define({
- props: ['foo'],
- setup(props: any) {
- const n0 = t0() as Element
- renderEffect(() => setElementText(n0, props.foo))
- return n0
- },
- })
- const foo = ref(1)
- const id = ref('a')
- const { host } = define({
- setup() {
- return createComponent(
- Child,
- {
- foo: () => foo.value,
- id: () => id.value,
- },
- null,
- true,
- )
- },
- }).render()
- expect(host.innerHTML).toBe('<div id="a">1</div>')
- foo.value++
- await nextTick()
- expect(host.innerHTML).toBe('<div id="a">2</div>')
- id.value = 'b'
- await nextTick()
- expect(host.innerHTML).toBe('<div id="b">2</div>')
- })
- it('should not fallthrough if explicitly pass inheritAttrs: false', async () => {
- const t0 = template('<div>', true)
- const { component: Child } = define({
- props: ['foo'],
- inheritAttrs: false,
- setup(props: any) {
- const n0 = t0() as Element
- renderEffect(() => setElementText(n0, props.foo))
- return n0
- },
- })
- const foo = ref(1)
- const id = ref('a')
- const { host } = define({
- setup() {
- return createComponent(
- Child,
- {
- foo: () => foo.value,
- id: () => id.value,
- },
- null,
- true,
- )
- },
- }).render()
- expect(host.innerHTML).toBe('<div>1</div>')
- foo.value++
- await nextTick()
- expect(host.innerHTML).toBe('<div>2</div>')
- id.value = 'b'
- await nextTick()
- expect(host.innerHTML).toBe('<div>2</div>')
- })
- it('should pass through attrs in nested single root components', async () => {
- const t0 = template('<div>', true)
- const { component: Grandson } = define({
- props: ['custom-attr'],
- setup(_: any, { attrs }: any) {
- const n0 = t0() as Element
- renderEffect(() => setElementText(n0, attrs.foo))
- return n0
- },
- })
- const { component: Child } = define({
- setup() {
- const n0 = createComponent(
- Grandson,
- {
- 'custom-attr': () => 'custom-attr',
- },
- null,
- true,
- )
- return n0
- },
- })
- const foo = ref(1)
- const id = ref('a')
- const { host } = define({
- setup() {
- return createComponent(
- Child,
- {
- foo: () => foo.value,
- id: () => id.value,
- },
- null,
- true,
- )
- },
- }).render()
- expect(host.innerHTML).toBe('<div foo="1" id="a">1</div>')
- foo.value++
- await nextTick()
- expect(host.innerHTML).toBe('<div foo="2" id="a">2</div>')
- id.value = 'b'
- await nextTick()
- expect(host.innerHTML).toBe('<div foo="2" id="b">2</div>')
- })
- it('should merge classes', async () => {
- const rootClass = ref('root')
- const parentClass = ref('parent')
- const childClass = ref('child')
- const t0 = template('<div>', true /* root */)
- const Child = defineVaporComponent({
- setup() {
- const n = t0() as Element
- renderEffect(() => {
- // binding on template root generates incremental class setter
- setClass(n, childClass.value)
- })
- return n
- },
- })
- const Parent = defineVaporComponent({
- setup() {
- return createComponent(
- Child,
- {
- class: () => parentClass.value,
- },
- null,
- true, // pass single root flag
- )
- },
- })
- const { host } = define({
- setup() {
- return createComponent(Parent, {
- class: () => rootClass.value,
- })
- },
- }).render()
- const list = host.children[0].classList
- // assert classes without being order-sensitive
- function assertClasses(cls: string[]) {
- expect(list.length).toBe(cls.length)
- for (const c of cls) {
- expect(list.contains(c)).toBe(true)
- }
- }
- assertClasses(['root', 'parent', 'child'])
- rootClass.value = 'root1'
- await nextTick()
- assertClasses(['root1', 'parent', 'child'])
- parentClass.value = 'parent1'
- await nextTick()
- assertClasses(['root1', 'parent1', 'child'])
- childClass.value = 'child1'
- await nextTick()
- assertClasses(['root1', 'parent1', 'child1'])
- })
- it('should merge styles', async () => {
- const rootStyle: Ref<string | Record<string, string>> = ref('color:red')
- const parentStyle: Ref<string | null> = ref('font-size:12px')
- const childStyle = ref('font-weight:bold')
- const t0 = template('<div>', true /* root */)
- const Child = defineVaporComponent({
- setup() {
- const n = t0() as Element
- renderEffect(() => {
- // binding on template root generates incremental class setter
- setStyle(n, childStyle.value)
- })
- return n
- },
- })
- const Parent = defineVaporComponent({
- setup() {
- return createComponent(
- Child,
- {
- style: () => parentStyle.value,
- },
- null,
- true, // pass single root flag
- )
- },
- })
- const { host } = define({
- setup() {
- return createComponent(Parent, {
- style: () => rootStyle.value,
- })
- },
- }).render()
- const el = host.children[0] as HTMLElement
- function getCSS() {
- return el.style.cssText.replace(/\s+/g, '')
- }
- function assertStyles() {
- const css = getCSS()
- expect(css).toContain(stringifyStyle(rootStyle.value))
- if (parentStyle.value) {
- expect(css).toContain(stringifyStyle(parentStyle.value))
- }
- expect(css).toContain(stringifyStyle(childStyle.value))
- }
- assertStyles()
- rootStyle.value = { color: 'green' }
- await nextTick()
- assertStyles()
- expect(getCSS()).not.toContain('color:red')
- parentStyle.value = null
- await nextTick()
- assertStyles()
- expect(getCSS()).not.toContain('font-size:12px')
- childStyle.value = 'font-weight:500'
- await nextTick()
- assertStyles()
- expect(getCSS()).not.toContain('font-size:bold')
- })
- test('parent value should take priority', async () => {
- const parentVal = ref('parent')
- const childVal = ref('child')
- const t0 = template('<div>', true /* root */)
- const Child = defineVaporComponent({
- setup() {
- const n = t0()
- renderEffect(() => {
- // prop bindings on template root generates extra `root: true` flag
- setProp(n, 'id', childVal.value)
- setProp(n, 'aria-x', childVal.value)
- setDynamicProps(n, [{ 'aria-y': childVal.value }])
- })
- return n
- },
- })
- const { host } = define({
- setup() {
- return createComponent(Child, {
- id: () => parentVal.value,
- 'aria-x': () => parentVal.value,
- 'aria-y': () => parentVal.value,
- })
- },
- }).render()
- const el = host.children[0]
- expect(el.id).toBe(parentVal.value)
- expect(el.getAttribute('aria-x')).toBe(parentVal.value)
- expect(el.getAttribute('aria-y')).toBe(parentVal.value)
- childVal.value = 'child1'
- await nextTick()
- expect(el.id).toBe(parentVal.value)
- expect(el.getAttribute('aria-x')).toBe(parentVal.value)
- expect(el.getAttribute('aria-y')).toBe(parentVal.value)
- parentVal.value = 'parent1'
- await nextTick()
- expect(el.id).toBe(parentVal.value)
- expect(el.getAttribute('aria-x')).toBe(parentVal.value)
- expect(el.getAttribute('aria-y')).toBe(parentVal.value)
- })
- it('empty string should not be passed to classList.add', async () => {
- const t0 = template('<div>', true /* root */)
- const Child = defineVaporComponent({
- setup() {
- const n = t0() as Element
- renderEffect(() => {
- setClass(n, {
- foo: false,
- })
- })
- return n
- },
- })
- const Parent = defineVaporComponent({
- setup() {
- return createComponent(
- Child,
- {
- class: () => ({
- bar: false,
- }),
- },
- null,
- true,
- )
- },
- })
- const { host } = define({
- setup() {
- return createComponent(Parent)
- },
- }).render()
- const el = host.children[0]
- expect(el.classList.length).toBe(0)
- })
- })
|