scopeId.spec.ts 9.0 KB

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