scopeId.spec.ts 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  1. import {
  2. h,
  3. render,
  4. nodeOps,
  5. serializeInner,
  6. renderSlot,
  7. withScopeId,
  8. pushScopeId,
  9. popScopeId
  10. } from '@vue/runtime-test'
  11. import { withCtx } from '../src/componentRenderContext'
  12. describe('scopeId runtime support', () => {
  13. test('should attach scopeId', () => {
  14. const App = {
  15. __scopeId: 'parent',
  16. render: () => h('div', [h('div')])
  17. }
  18. const root = nodeOps.createElement('div')
  19. render(h(App), root)
  20. expect(serializeInner(root)).toBe(`<div parent><div parent></div></div>`)
  21. })
  22. test('should attach scopeId to components in parent component', () => {
  23. const Child = {
  24. __scopeId: 'child',
  25. render: () => h('div')
  26. }
  27. const App = {
  28. __scopeId: 'parent',
  29. render: () => h('div', [h(Child)])
  30. }
  31. const root = nodeOps.createElement('div')
  32. render(h(App), root)
  33. expect(serializeInner(root)).toBe(
  34. `<div parent><div child parent></div></div>`
  35. )
  36. })
  37. // :slotted basic
  38. test('should work on slots', () => {
  39. const Child = {
  40. __scopeId: 'child',
  41. render(this: any) {
  42. return h('div', renderSlot(this.$slots, 'default'))
  43. }
  44. }
  45. const Child2 = {
  46. __scopeId: 'child2',
  47. render: () => h('span')
  48. }
  49. const App = {
  50. __scopeId: 'parent',
  51. render: () => {
  52. return h(
  53. Child,
  54. withCtx(() => {
  55. return [h('div'), h(Child2)]
  56. })
  57. )
  58. }
  59. }
  60. const root = nodeOps.createElement('div')
  61. render(h(App), root)
  62. // slot content should have:
  63. // - scopeId from parent
  64. // - slotted scopeId (with `-s` postfix) from child (the tree owner)
  65. expect(serializeInner(root)).toBe(
  66. `<div child parent>` +
  67. `<div parent child-s></div>` +
  68. // component inside slot should have:
  69. // - scopeId from template context
  70. // - slotted scopeId from slot owner
  71. // - its own scopeId
  72. `<span child2 parent child-s></span>` +
  73. `</div>`
  74. )
  75. })
  76. // #2892
  77. test(':slotted on forwarded slots', async () => {
  78. const Wrapper = {
  79. __scopeId: 'wrapper',
  80. render(this: any) {
  81. // <div class="wrapper"><slot/></div>
  82. return h('div', { class: 'wrapper' }, [
  83. renderSlot(
  84. this.$slots,
  85. 'default',
  86. {},
  87. undefined,
  88. true /* noSlotted */
  89. )
  90. ])
  91. }
  92. }
  93. const Slotted = {
  94. __scopeId: 'slotted',
  95. render(this: any) {
  96. // <Wrapper><slot/></Wrapper>
  97. return h(Wrapper, null, {
  98. default: withCtx(() => [renderSlot(this.$slots, 'default')])
  99. })
  100. }
  101. }
  102. // simulate hoisted node
  103. pushScopeId('root')
  104. const hoisted = h('div', 'hoisted')
  105. popScopeId()
  106. const Root = {
  107. __scopeId: 'root',
  108. render(this: any) {
  109. // <Slotted><div>hoisted</div><div>{{ dynamic }}</div></Slotted>
  110. return h(Slotted, null, {
  111. default: withCtx(() => {
  112. return [hoisted, h('div', 'dynamic')]
  113. })
  114. })
  115. }
  116. }
  117. const root = nodeOps.createElement('div')
  118. render(h(Root), root)
  119. expect(serializeInner(root)).toBe(
  120. `<div class="wrapper" wrapper slotted root>` +
  121. `<div root slotted-s>hoisted</div>` +
  122. `<div root slotted-s>dynamic</div>` +
  123. `</div>`
  124. )
  125. const Root2 = {
  126. __scopeId: 'root',
  127. render(this: any) {
  128. // <Slotted>
  129. // <Wrapper>
  130. // <div>hoisted</div><div>{{ dynamic }}</div>
  131. // </Wrapper>
  132. // </Slotted>
  133. return h(Slotted, null, {
  134. default: withCtx(() => [
  135. h(Wrapper, null, {
  136. default: withCtx(() => [hoisted, h('div', 'dynamic')])
  137. })
  138. ])
  139. })
  140. }
  141. }
  142. const root2 = nodeOps.createElement('div')
  143. render(h(Root2), root2)
  144. expect(serializeInner(root2)).toBe(
  145. `<div class="wrapper" wrapper slotted root>` +
  146. `<div class="wrapper" wrapper root slotted-s>` +
  147. `<div root>hoisted</div>` +
  148. `<div root>dynamic</div>` +
  149. `</div>` +
  150. `</div>`
  151. )
  152. })
  153. // #1988
  154. test('should inherit scopeId through nested HOCs with inheritAttrs: false', () => {
  155. const App = {
  156. __scopeId: 'parent',
  157. render: () => {
  158. return h(Child)
  159. }
  160. }
  161. function Child() {
  162. return h(Child2, { class: 'foo' })
  163. }
  164. function Child2() {
  165. return h('div')
  166. }
  167. Child2.inheritAttrs = false
  168. const root = nodeOps.createElement('div')
  169. render(h(App), root)
  170. expect(serializeInner(root)).toBe(`<div parent></div>`)
  171. })
  172. })
  173. describe('backwards compat with <=3.0.7', () => {
  174. const withParentId = withScopeId('parent')
  175. const withChildId = withScopeId('child')
  176. test('should attach scopeId', () => {
  177. const App = {
  178. __scopeId: 'parent',
  179. render: withParentId(() => {
  180. return h('div', [h('div')])
  181. })
  182. }
  183. const root = nodeOps.createElement('div')
  184. render(h(App), root)
  185. expect(serializeInner(root)).toBe(`<div parent><div parent></div></div>`)
  186. })
  187. test('should attach scopeId to components in parent component', () => {
  188. const Child = {
  189. __scopeId: 'child',
  190. render: withChildId(() => {
  191. return h('div')
  192. })
  193. }
  194. const App = {
  195. __scopeId: 'parent',
  196. render: withParentId(() => {
  197. return h('div', [h(Child)])
  198. })
  199. }
  200. const root = nodeOps.createElement('div')
  201. render(h(App), root)
  202. expect(serializeInner(root)).toBe(
  203. `<div parent><div child parent></div></div>`
  204. )
  205. })
  206. test('should work on slots', () => {
  207. const Child = {
  208. __scopeId: 'child',
  209. render: withChildId(function (this: any) {
  210. return h('div', renderSlot(this.$slots, 'default'))
  211. })
  212. }
  213. const withChild2Id = withScopeId('child2')
  214. const Child2 = {
  215. __scopeId: 'child2',
  216. render: withChild2Id(() => h('span'))
  217. }
  218. const App = {
  219. __scopeId: 'parent',
  220. render: withParentId(() => {
  221. return h(
  222. Child,
  223. withParentId(() => {
  224. return [h('div'), h(Child2)]
  225. })
  226. )
  227. })
  228. }
  229. const root = nodeOps.createElement('div')
  230. render(h(App), root)
  231. // slot content should have:
  232. // - scopeId from parent
  233. // - slotted scopeId (with `-s` postfix) from child (the tree owner)
  234. expect(serializeInner(root)).toBe(
  235. `<div child parent>` +
  236. `<div parent child-s></div>` +
  237. // component inside slot should have:
  238. // - scopeId from template context
  239. // - slotted scopeId from slot owner
  240. // - its own scopeId
  241. `<span child2 parent child-s></span>` +
  242. `</div>`
  243. )
  244. })
  245. // #1988
  246. test('should inherit scopeId through nested HOCs with inheritAttrs: false', () => {
  247. const withParentId = withScopeId('parent')
  248. const App = {
  249. __scopeId: 'parent',
  250. render: withParentId(() => {
  251. return h(Child)
  252. })
  253. }
  254. function Child() {
  255. return h(Child2, { class: 'foo' })
  256. }
  257. function Child2() {
  258. return h('div')
  259. }
  260. Child2.inheritAttrs = false
  261. const root = nodeOps.createElement('div')
  262. render(h(App), root)
  263. expect(serializeInner(root)).toBe(`<div parent></div>`)
  264. })
  265. test('hoisted nodes', async () => {
  266. pushScopeId('foobar')
  267. const hoisted = h('div', 'hello')
  268. popScopeId()
  269. const App = {
  270. __scopeId: 'foobar',
  271. render: () => h('div', [hoisted])
  272. }
  273. const root = nodeOps.createElement('div')
  274. render(h(App), root)
  275. expect(serializeInner(root)).toBe(
  276. `<div foobar><div foobar>hello</div></div>`
  277. )
  278. })
  279. })