class.spec.ts 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. // https://github.com/vuejs/vue/blob/dev/test/unit/features/directives/class.spec.js
  2. import { h, render, defineComponent } from '../../src'
  3. type ClassItem = {
  4. value: string | object | string[]
  5. }
  6. function assertClass(assertions: Array<Array<any>>) {
  7. const root = document.createElement('div')
  8. const dynamic = { value: '' }
  9. const wrapper = () => h('div', { class: ['foo', dynamic.value] })
  10. for (const [input, expected] of assertions) {
  11. if (typeof input === 'function') {
  12. input(dynamic.value)
  13. } else {
  14. dynamic.value = input
  15. }
  16. render(wrapper(), root)
  17. expect(root.children[0].className).toBe(expected)
  18. }
  19. }
  20. describe('class', () => {
  21. test('plain string', () => {
  22. assertClass([
  23. ['bar', 'foo bar'],
  24. ['baz qux', 'foo baz qux'],
  25. ['qux', 'foo qux'],
  26. [undefined, 'foo']
  27. ])
  28. })
  29. test('object value', () => {
  30. assertClass([
  31. [{ bar: true, baz: false }, 'foo bar'],
  32. [{ baz: true }, 'foo baz'],
  33. [null, 'foo'],
  34. [{ 'bar baz': true, qux: false }, 'foo bar baz'],
  35. [{ qux: true }, 'foo qux']
  36. ])
  37. })
  38. test('array value', () => {
  39. assertClass([
  40. [['bar', 'baz'], 'foo bar baz'],
  41. [['qux', 'baz'], 'foo qux baz'],
  42. [['w', 'x y z'], 'foo w x y z'],
  43. [undefined, 'foo'],
  44. [['bar'], 'foo bar'],
  45. [(val: Array<any>) => val.push('baz'), 'foo bar baz']
  46. ])
  47. })
  48. test('array of mixed values', () => {
  49. assertClass([
  50. [['x', { y: true, z: true }], 'foo x y z'],
  51. [['x', { y: true, z: false }], 'foo x y'],
  52. [['f', { z: true }], 'foo f z'],
  53. [['l', 'f', { n: true, z: true }], 'foo l f n z'],
  54. [['x', {}], 'foo x'],
  55. [undefined, 'foo']
  56. ])
  57. })
  58. test('class merge between parent and child', () => {
  59. const root = document.createElement('div')
  60. const childClass: ClassItem = { value: 'd' }
  61. const child = {
  62. props: {},
  63. render: () => h('div', { class: ['c', childClass.value] })
  64. }
  65. const parentClass: ClassItem = { value: 'b' }
  66. const parent = {
  67. props: {},
  68. render: () => h(child, { class: ['a', parentClass.value] })
  69. }
  70. render(h(parent), root)
  71. expect(root.children[0].className).toBe('c d a b')
  72. parentClass.value = 'e'
  73. // the `foo` here is just for forcing parent to be updated
  74. // (otherwise it's skipped since its props never change)
  75. render(h(parent, { foo: 1 }), root)
  76. expect(root.children[0].className).toBe('c d a e')
  77. parentClass.value = 'f'
  78. render(h(parent, { foo: 2 }), root)
  79. expect(root.children[0].className).toBe('c d a f')
  80. parentClass.value = { foo: true }
  81. childClass.value = ['bar', 'baz']
  82. render(h(parent, { foo: 3 }), root)
  83. expect(root.children[0].className).toBe('c bar baz a foo')
  84. })
  85. test('class merge between multiple nested components sharing same element', () => {
  86. const component1 = defineComponent({
  87. props: {},
  88. render() {
  89. return this.$slots.default()[0]
  90. }
  91. })
  92. const component2 = defineComponent({
  93. props: {},
  94. render() {
  95. return this.$slots.default()[0]
  96. }
  97. })
  98. const component3 = defineComponent({
  99. props: {},
  100. render() {
  101. return h(
  102. 'div',
  103. {
  104. class: 'staticClass'
  105. },
  106. [this.$slots.default()]
  107. )
  108. }
  109. })
  110. const root = document.createElement('div')
  111. const componentClass1 = { value: 'componentClass1' }
  112. const componentClass2 = { value: 'componentClass2' }
  113. const componentClass3 = { value: 'componentClass3' }
  114. const wrapper = () =>
  115. h(component1, { class: componentClass1.value }, () => [
  116. h(component2, { class: componentClass2.value }, () => [
  117. h(component3, { class: componentClass3.value }, () => ['some text'])
  118. ])
  119. ])
  120. render(wrapper(), root)
  121. expect(root.children[0].className).toBe(
  122. 'staticClass componentClass3 componentClass2 componentClass1'
  123. )
  124. componentClass1.value = 'c1'
  125. render(wrapper(), root)
  126. expect(root.children[0].className).toBe(
  127. 'staticClass componentClass3 componentClass2 c1'
  128. )
  129. componentClass2.value = 'c2'
  130. render(wrapper(), root)
  131. expect(root.children[0].className).toBe('staticClass componentClass3 c2 c1')
  132. componentClass3.value = 'c3'
  133. render(wrapper(), root)
  134. expect(root.children[0].className).toBe('staticClass c3 c2 c1')
  135. })
  136. test('deep update', () => {
  137. const root = document.createElement('div')
  138. const test = {
  139. a: true,
  140. b: false
  141. }
  142. const wrapper = () => h('div', { class: test })
  143. render(wrapper(), root)
  144. expect(root.children[0].className).toBe('a')
  145. test.b = true
  146. render(wrapper(), root)
  147. expect(root.children[0].className).toBe('a b')
  148. })
  149. // a vdom patch edge case where the user has several un-keyed elements of the
  150. // same tag next to each other, and toggling them.
  151. test('properly remove staticClass for toggling un-keyed children', () => {
  152. const root = document.createElement('div')
  153. const ok = { value: true }
  154. const wrapper = () =>
  155. h('div', [ok.value ? h('div', { class: 'a' }) : h('div')])
  156. render(wrapper(), root)
  157. expect(root.children[0].children[0].className).toBe('a')
  158. ok.value = false
  159. render(wrapper(), root)
  160. expect(root.children[0].children[0].className).toBe('')
  161. })
  162. })