scopeId.spec.ts 18 KB

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