scopeId.spec.ts 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615
  1. import { createApp, h } from '@vue/runtime-dom'
  2. import {
  3. createComponent,
  4. createDynamicComponent,
  5. createSlot,
  6. defineVaporComponent,
  7. setInsertionState,
  8. template,
  9. vaporInteropPlugin,
  10. withVaporCtx,
  11. } from '../src'
  12. import { makeRender } from './_utils'
  13. const define = makeRender()
  14. describe('scopeId', () => {
  15. test('should attach scopeId to child component', () => {
  16. const Child = defineVaporComponent({
  17. __scopeId: 'child',
  18. setup() {
  19. return template('<div child></div>', true)()
  20. },
  21. })
  22. const { html } = define({
  23. __scopeId: 'parent',
  24. setup() {
  25. return createComponent(Child)
  26. },
  27. }).render()
  28. expect(html()).toBe(`<div child="" parent=""></div>`)
  29. })
  30. test('should attach scopeId to child component with insertion state', () => {
  31. const Child = defineVaporComponent({
  32. __scopeId: 'child',
  33. setup() {
  34. return template('<div child></div>', true)()
  35. },
  36. })
  37. const { html } = define({
  38. __scopeId: 'parent',
  39. setup() {
  40. const t0 = template('<div parent></div>', true)
  41. const n1 = t0() as any
  42. setInsertionState(n1)
  43. createComponent(Child)
  44. return n1
  45. },
  46. }).render()
  47. expect(html()).toBe(`<div parent=""><div child="" parent=""></div></div>`)
  48. })
  49. test('should attach scopeId to nested child component', () => {
  50. const Child = defineVaporComponent({
  51. __scopeId: 'child',
  52. setup() {
  53. return template('<div child></div>', true)()
  54. },
  55. })
  56. const Parent = defineVaporComponent({
  57. __scopeId: 'parent',
  58. setup() {
  59. return createComponent(Child)
  60. },
  61. })
  62. const { html } = define({
  63. __scopeId: 'app',
  64. setup() {
  65. return createComponent(Parent)
  66. },
  67. }).render()
  68. expect(html()).toBe(`<div child="" parent="" app=""></div>`)
  69. })
  70. test('should not attach scopeId to nested multiple root components', () => {
  71. const Child = defineVaporComponent({
  72. __scopeId: 'child',
  73. setup() {
  74. return template('<div child></div>', true)()
  75. },
  76. })
  77. const Parent = defineVaporComponent({
  78. __scopeId: 'parent',
  79. setup() {
  80. const n0 = template('<div parent></div>')()
  81. const n1 = createComponent(Child)
  82. return [n0, n1]
  83. },
  84. })
  85. const { html } = define({
  86. __scopeId: 'app',
  87. setup() {
  88. return createComponent(Parent)
  89. },
  90. }).render()
  91. expect(html()).toBe(`<div parent=""></div><div child="" parent=""></div>`)
  92. })
  93. test('should attach scopeId to nested child component with insertion state', () => {
  94. const Child = defineVaporComponent({
  95. __scopeId: 'child',
  96. setup() {
  97. return template('<div child></div>', true)()
  98. },
  99. })
  100. const Parent = defineVaporComponent({
  101. __scopeId: 'parent',
  102. setup() {
  103. return createComponent(Child)
  104. },
  105. })
  106. const { html } = define({
  107. __scopeId: 'app',
  108. setup() {
  109. const t0 = template('<div app></div>', true)
  110. const n1 = t0() as any
  111. setInsertionState(n1)
  112. createComponent(Parent)
  113. return n1
  114. },
  115. }).render()
  116. expect(html()).toBe(
  117. `<div app=""><div child="" parent="" app=""></div></div>`,
  118. )
  119. })
  120. test('should attach scopeId to dynamic component', () => {
  121. const { html } = define({
  122. __scopeId: 'parent',
  123. setup() {
  124. return createDynamicComponent(() => 'button')
  125. },
  126. }).render()
  127. expect(html()).toBe(`<button parent=""></button><!--dynamic-component-->`)
  128. })
  129. test('should attach scopeId to dynamic component with insertion state', () => {
  130. const { html } = define({
  131. __scopeId: 'parent',
  132. setup() {
  133. const t0 = template('<div parent></div>', true)
  134. const n1 = t0() as any
  135. setInsertionState(n1)
  136. createDynamicComponent(() => 'button')
  137. return n1
  138. },
  139. }).render()
  140. expect(html()).toBe(
  141. `<div parent=""><button parent=""></button><!--dynamic-component--></div>`,
  142. )
  143. })
  144. test('should attach scopeId to nested dynamic component', () => {
  145. const Comp = defineVaporComponent({
  146. __scopeId: 'child',
  147. setup() {
  148. return createDynamicComponent(() => 'button', null, null, true)
  149. },
  150. })
  151. const { html } = define({
  152. __scopeId: 'parent',
  153. setup() {
  154. return createComponent(Comp, null, null, true)
  155. },
  156. }).render()
  157. expect(html()).toBe(
  158. `<button child="" parent=""></button><!--dynamic-component-->`,
  159. )
  160. })
  161. test('should attach scopeId to nested dynamic component with insertion state', () => {
  162. const Comp = defineVaporComponent({
  163. __scopeId: 'child',
  164. setup() {
  165. return createDynamicComponent(() => 'button', null, null, true)
  166. },
  167. })
  168. const { html } = define({
  169. __scopeId: 'parent',
  170. setup() {
  171. const t0 = template('<div parent></div>', true)
  172. const n1 = t0() as any
  173. setInsertionState(n1)
  174. createComponent(Comp, null, null, true)
  175. return n1
  176. },
  177. }).render()
  178. expect(html()).toBe(
  179. `<div parent=""><button child="" parent=""></button><!--dynamic-component--></div>`,
  180. )
  181. })
  182. test.todo('should attach scopeId to suspense content', async () => {})
  183. // :slotted basic
  184. test('should work on slots', () => {
  185. const Child = defineVaporComponent({
  186. __scopeId: 'child',
  187. setup() {
  188. const n1 = template('<div child></div>', true)() as any
  189. setInsertionState(n1)
  190. createSlot('default', null)
  191. return n1
  192. },
  193. })
  194. const Child2 = defineVaporComponent({
  195. __scopeId: 'child2',
  196. setup() {
  197. return template('<span child2></span>', true)()
  198. },
  199. })
  200. const { html } = define({
  201. __scopeId: 'parent',
  202. setup() {
  203. const n2 = createComponent(
  204. Child,
  205. null,
  206. {
  207. default: withVaporCtx(() => {
  208. const n0 = template('<div parent></div>')()
  209. const n1 = createComponent(Child2)
  210. return [n0, n1]
  211. }),
  212. },
  213. true,
  214. )
  215. return n2
  216. },
  217. }).render()
  218. // slot content should have:
  219. // - scopeId from parent
  220. // - slotted scopeId (with `-s` postfix) from child (the tree owner)
  221. expect(html()).toBe(
  222. `<div child="" parent="">` +
  223. `<div parent="" child-s=""></div>` +
  224. // component inside slot should have:
  225. // - scopeId from template context
  226. // - slotted scopeId from slot owner
  227. // - its own scopeId
  228. `<span child2="" child-s="" parent=""></span>` +
  229. `<!--slot-->` +
  230. `</div>`,
  231. )
  232. })
  233. test(':slotted on forwarded slots', async () => {
  234. const Wrapper = defineVaporComponent({
  235. __scopeId: 'wrapper',
  236. setup() {
  237. // <div><slot/></div>
  238. const n1 = template('<div wrapper></div>', true)() as any
  239. setInsertionState(n1)
  240. createSlot('default', null, undefined, true /* noSlotted */)
  241. return n1
  242. },
  243. })
  244. const Slotted = defineVaporComponent({
  245. __scopeId: 'slotted',
  246. setup() {
  247. // <Wrapper><slot/></Wrapper>
  248. const n1 = createComponent(
  249. Wrapper,
  250. null,
  251. {
  252. default: withVaporCtx(() => {
  253. const n0 = createSlot('default', null)
  254. return n0
  255. }),
  256. },
  257. true,
  258. )
  259. return n1
  260. },
  261. })
  262. const { html } = define({
  263. __scopeId: 'root',
  264. setup() {
  265. // <Slotted><div></div></Slotted>
  266. const n2 = createComponent(
  267. Slotted,
  268. null,
  269. {
  270. default: () => {
  271. return template('<div root></div>')()
  272. },
  273. },
  274. true,
  275. )
  276. return n2
  277. },
  278. }).render()
  279. expect(html()).toBe(
  280. `<div wrapper="" slotted="" root="">` +
  281. `<div root="" slotted-s=""></div>` +
  282. `<!--slot--><!--slot-->` +
  283. `</div>`,
  284. )
  285. })
  286. })
  287. describe('vdom interop', () => {
  288. test('vdom parent > vapor child', () => {
  289. const VaporChild = defineVaporComponent({
  290. __scopeId: 'vapor-child',
  291. setup() {
  292. return template('<button vapor-child></button>', true)()
  293. },
  294. })
  295. const VdomParent = {
  296. __scopeId: 'vdom-parent',
  297. setup() {
  298. return () => h(VaporChild as any)
  299. },
  300. }
  301. const App = {
  302. setup() {
  303. return () => h(VdomParent)
  304. },
  305. }
  306. const root = document.createElement('div')
  307. createApp(App).use(vaporInteropPlugin).mount(root)
  308. expect(root.innerHTML).toBe(
  309. `<button vapor-child="" vdom-parent=""></button>`,
  310. )
  311. })
  312. test('vdom parent > vapor child > vdom child', () => {
  313. const VdomChild = {
  314. __scopeId: 'vdom-child',
  315. setup() {
  316. return () => h('button')
  317. },
  318. }
  319. const VaporChild = defineVaporComponent({
  320. __scopeId: 'vapor-child',
  321. setup() {
  322. return createComponent(VdomChild as any, null, null, true)
  323. },
  324. })
  325. const VdomParent = {
  326. __scopeId: 'vdom-parent',
  327. setup() {
  328. return () => h(VaporChild as any)
  329. },
  330. }
  331. const App = {
  332. setup() {
  333. return () => h(VdomParent)
  334. },
  335. }
  336. const root = document.createElement('div')
  337. createApp(App).use(vaporInteropPlugin).mount(root)
  338. expect(root.innerHTML).toBe(
  339. `<button vdom-child="" vapor-child="" vdom-parent=""></button>`,
  340. )
  341. })
  342. test('vdom parent > vapor child > vapor child > vdom child', () => {
  343. const VdomChild = {
  344. __scopeId: 'vdom-child',
  345. setup() {
  346. return () => h('button')
  347. },
  348. }
  349. const NestedVaporChild = defineVaporComponent({
  350. __scopeId: 'nested-vapor-child',
  351. setup() {
  352. return createComponent(VdomChild as any, null, null, true)
  353. },
  354. })
  355. const VaporChild = defineVaporComponent({
  356. __scopeId: 'vapor-child',
  357. setup() {
  358. return createComponent(NestedVaporChild as any, null, null, true)
  359. },
  360. })
  361. const VdomParent = {
  362. __scopeId: 'vdom-parent',
  363. setup() {
  364. return () => h(VaporChild as any)
  365. },
  366. }
  367. const App = {
  368. setup() {
  369. return () => h(VdomParent)
  370. },
  371. }
  372. const root = document.createElement('div')
  373. createApp(App).use(vaporInteropPlugin).mount(root)
  374. expect(root.innerHTML).toBe(
  375. `<button vdom-child="" nested-vapor-child="" vapor-child="" vdom-parent=""></button>`,
  376. )
  377. })
  378. test('vdom parent > vapor dynamic child', () => {
  379. const VaporChild = defineVaporComponent({
  380. __scopeId: 'vapor-child',
  381. setup() {
  382. return createDynamicComponent(() => 'button', null, null, true)
  383. },
  384. })
  385. const VdomParent = {
  386. __scopeId: 'vdom-parent',
  387. setup() {
  388. return () => h(VaporChild as any)
  389. },
  390. }
  391. const App = {
  392. setup() {
  393. return () => h(VdomParent)
  394. },
  395. }
  396. const root = document.createElement('div')
  397. createApp(App).use(vaporInteropPlugin).mount(root)
  398. expect(root.innerHTML).toBe(
  399. `<button vapor-child="" vdom-parent=""></button><!--dynamic-component-->`,
  400. )
  401. })
  402. test('vapor parent > vdom child', () => {
  403. const VdomChild = {
  404. __scopeId: 'vdom-child',
  405. setup() {
  406. return () => h('button')
  407. },
  408. }
  409. const VaporParent = defineVaporComponent({
  410. __scopeId: 'vapor-parent',
  411. setup() {
  412. return createComponent(VdomChild as any, null, null, true)
  413. },
  414. })
  415. const App = {
  416. setup() {
  417. return () => h(VaporParent as any)
  418. },
  419. }
  420. const root = document.createElement('div')
  421. createApp(App).use(vaporInteropPlugin).mount(root)
  422. expect(root.innerHTML).toBe(
  423. `<button vdom-child="" vapor-parent=""></button>`,
  424. )
  425. })
  426. test('vapor parent > vdom child > vapor child', () => {
  427. const VaporChild = defineVaporComponent({
  428. __scopeId: 'vapor-child',
  429. setup() {
  430. return template('<button vapor-child></button>', true)()
  431. },
  432. })
  433. const VdomChild = {
  434. __scopeId: 'vdom-child',
  435. setup() {
  436. return () => h(VaporChild as any)
  437. },
  438. }
  439. const VaporParent = defineVaporComponent({
  440. __scopeId: 'vapor-parent',
  441. setup() {
  442. return createComponent(VdomChild as any, null, null, true)
  443. },
  444. })
  445. const App = {
  446. setup() {
  447. return () => h(VaporParent as any)
  448. },
  449. }
  450. const root = document.createElement('div')
  451. createApp(App).use(vaporInteropPlugin).mount(root)
  452. expect(root.innerHTML).toBe(
  453. `<button vapor-child="" vdom-child="" vapor-parent=""></button>`,
  454. )
  455. })
  456. test('vapor parent > vdom child > vdom child > vapor child', () => {
  457. const VaporChild = defineVaporComponent({
  458. __scopeId: 'vapor-child',
  459. setup() {
  460. return template('<button vapor-child></button>', true)()
  461. },
  462. })
  463. const VdomChild = {
  464. __scopeId: 'vdom-child',
  465. setup() {
  466. return () => h(VaporChild as any)
  467. },
  468. }
  469. const VdomParent = {
  470. __scopeId: 'vdom-parent',
  471. setup() {
  472. return () => h(VdomChild as any)
  473. },
  474. }
  475. const VaporParent = defineVaporComponent({
  476. __scopeId: 'vapor-parent',
  477. setup() {
  478. return createComponent(VdomParent as any, null, null, true)
  479. },
  480. })
  481. const App = {
  482. setup() {
  483. return () => h(VaporParent as any)
  484. },
  485. }
  486. const root = document.createElement('div')
  487. createApp(App).use(vaporInteropPlugin).mount(root)
  488. expect(root.innerHTML).toBe(
  489. `<button vapor-child="" vdom-child="" vdom-parent="" vapor-parent=""></button>`,
  490. )
  491. })
  492. test('vapor parent > vapor slot > vdom child', () => {
  493. const VaporSlot = defineVaporComponent({
  494. __scopeId: 'vapor-slot',
  495. setup() {
  496. const n1 = template('<div vapor-slot></div>', true)() as any
  497. setInsertionState(n1)
  498. createSlot('default', null)
  499. return n1
  500. },
  501. })
  502. const VdomChild = {
  503. __scopeId: 'vdom-child',
  504. setup() {
  505. return () => h('span')
  506. },
  507. }
  508. const VaporParent = defineVaporComponent({
  509. __scopeId: 'vapor-parent',
  510. setup() {
  511. const n2 = createComponent(
  512. VaporSlot,
  513. null,
  514. {
  515. default: withVaporCtx(() => {
  516. const n0 = template('<div vapor-parent></div>')()
  517. const n1 = createComponent(VdomChild)
  518. return [n0, n1]
  519. }),
  520. },
  521. true,
  522. )
  523. return n2
  524. },
  525. })
  526. const App = {
  527. setup() {
  528. return () => h(VaporParent as any)
  529. },
  530. }
  531. const root = document.createElement('div')
  532. createApp(App).use(vaporInteropPlugin).mount(root)
  533. expect(root.innerHTML).toBe(
  534. `<div vapor-slot="" vapor-parent="">` +
  535. `<div vapor-parent="" vapor-slot-s=""></div>` +
  536. `<span vdom-child="" vapor-parent="" vapor-slot-s=""></span>` +
  537. `<!--slot-->` +
  538. `</div>`,
  539. )
  540. })
  541. })