scopeId.spec.ts 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679
  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. test('nested components with slots', async () => {
  287. const Child = defineVaporComponent({
  288. setup() {
  289. const n0 = template('<div>')() as any
  290. setInsertionState(n0, null, 0, true)
  291. createSlot('default')
  292. return n0
  293. },
  294. })
  295. const Parent = defineVaporComponent({
  296. __scopeId: 'data-v-parent',
  297. setup() {
  298. const n3 = createComponent(
  299. Child,
  300. null,
  301. {
  302. default: withVaporCtx(() => {
  303. const n2 = createComponent(
  304. Child,
  305. null,
  306. {
  307. default: withVaporCtx(() => {
  308. const n1 = createComponent(
  309. Child,
  310. null,
  311. {
  312. default: () => {
  313. const t0 = template('test')() as any
  314. return t0
  315. },
  316. },
  317. true,
  318. )
  319. return n1
  320. }),
  321. },
  322. true,
  323. )
  324. return n2
  325. }),
  326. },
  327. true,
  328. )
  329. return n3
  330. },
  331. })
  332. const { host } = define({
  333. __scopeId: 'app',
  334. setup() {
  335. return createComponent(Parent)
  336. },
  337. }).render()
  338. expect(host.innerHTML).toBe(
  339. `<div data-v-parent="" app="">` +
  340. `<div data-v-parent="">` +
  341. `<div data-v-parent="">test<!--slot-->` +
  342. `</div><!--slot-->` +
  343. `</div><!--slot-->` +
  344. `</div>`,
  345. )
  346. })
  347. })
  348. describe('vdom interop', () => {
  349. test('vdom parent > vapor child', () => {
  350. const VaporChild = defineVaporComponent({
  351. __scopeId: 'vapor-child',
  352. setup() {
  353. return template('<button vapor-child></button>', true)()
  354. },
  355. })
  356. const VdomParent = {
  357. __scopeId: 'vdom-parent',
  358. setup() {
  359. return () => h(VaporChild as any)
  360. },
  361. }
  362. const App = {
  363. setup() {
  364. return () => h(VdomParent)
  365. },
  366. }
  367. const root = document.createElement('div')
  368. createApp(App).use(vaporInteropPlugin).mount(root)
  369. expect(root.innerHTML).toBe(
  370. `<button vapor-child="" vdom-parent=""></button>`,
  371. )
  372. })
  373. test('vdom parent > vapor child > vdom child', () => {
  374. const VdomChild = {
  375. __scopeId: 'vdom-child',
  376. setup() {
  377. return () => h('button')
  378. },
  379. }
  380. const VaporChild = defineVaporComponent({
  381. __scopeId: 'vapor-child',
  382. setup() {
  383. return createComponent(VdomChild as any, null, null, true)
  384. },
  385. })
  386. const VdomParent = {
  387. __scopeId: 'vdom-parent',
  388. setup() {
  389. return () => h(VaporChild as any)
  390. },
  391. }
  392. const App = {
  393. setup() {
  394. return () => h(VdomParent)
  395. },
  396. }
  397. const root = document.createElement('div')
  398. createApp(App).use(vaporInteropPlugin).mount(root)
  399. expect(root.innerHTML).toBe(
  400. `<button vdom-child="" vapor-child="" vdom-parent=""></button>`,
  401. )
  402. })
  403. test('vdom parent > vapor child > vapor child > vdom child', () => {
  404. const VdomChild = {
  405. __scopeId: 'vdom-child',
  406. setup() {
  407. return () => h('button')
  408. },
  409. }
  410. const NestedVaporChild = defineVaporComponent({
  411. __scopeId: 'nested-vapor-child',
  412. setup() {
  413. return createComponent(VdomChild as any, null, null, true)
  414. },
  415. })
  416. const VaporChild = defineVaporComponent({
  417. __scopeId: 'vapor-child',
  418. setup() {
  419. return createComponent(NestedVaporChild as any, null, null, true)
  420. },
  421. })
  422. const VdomParent = {
  423. __scopeId: 'vdom-parent',
  424. setup() {
  425. return () => h(VaporChild as any)
  426. },
  427. }
  428. const App = {
  429. setup() {
  430. return () => h(VdomParent)
  431. },
  432. }
  433. const root = document.createElement('div')
  434. createApp(App).use(vaporInteropPlugin).mount(root)
  435. expect(root.innerHTML).toBe(
  436. `<button vdom-child="" nested-vapor-child="" vapor-child="" vdom-parent=""></button>`,
  437. )
  438. })
  439. test('vdom parent > vapor dynamic child', () => {
  440. const VaporChild = defineVaporComponent({
  441. __scopeId: 'vapor-child',
  442. setup() {
  443. return createDynamicComponent(() => 'button', null, null, true)
  444. },
  445. })
  446. const VdomParent = {
  447. __scopeId: 'vdom-parent',
  448. setup() {
  449. return () => h(VaporChild as any)
  450. },
  451. }
  452. const App = {
  453. setup() {
  454. return () => h(VdomParent)
  455. },
  456. }
  457. const root = document.createElement('div')
  458. createApp(App).use(vaporInteropPlugin).mount(root)
  459. expect(root.innerHTML).toBe(
  460. `<button vapor-child="" vdom-parent=""></button><!--dynamic-component-->`,
  461. )
  462. })
  463. test('vapor parent > vdom child', () => {
  464. const VdomChild = {
  465. __scopeId: 'vdom-child',
  466. setup() {
  467. return () => h('button')
  468. },
  469. }
  470. const VaporParent = defineVaporComponent({
  471. __scopeId: 'vapor-parent',
  472. setup() {
  473. return createComponent(VdomChild as any, null, null, true)
  474. },
  475. })
  476. const App = {
  477. setup() {
  478. return () => h(VaporParent as any)
  479. },
  480. }
  481. const root = document.createElement('div')
  482. createApp(App).use(vaporInteropPlugin).mount(root)
  483. expect(root.innerHTML).toBe(
  484. `<button vdom-child="" vapor-parent=""></button>`,
  485. )
  486. })
  487. test('vapor parent > vdom child > vapor child', () => {
  488. const VaporChild = defineVaporComponent({
  489. __scopeId: 'vapor-child',
  490. setup() {
  491. return template('<button vapor-child></button>', true)()
  492. },
  493. })
  494. const VdomChild = {
  495. __scopeId: 'vdom-child',
  496. setup() {
  497. return () => h(VaporChild as any)
  498. },
  499. }
  500. const VaporParent = defineVaporComponent({
  501. __scopeId: 'vapor-parent',
  502. setup() {
  503. return createComponent(VdomChild as any, null, null, true)
  504. },
  505. })
  506. const App = {
  507. setup() {
  508. return () => h(VaporParent as any)
  509. },
  510. }
  511. const root = document.createElement('div')
  512. createApp(App).use(vaporInteropPlugin).mount(root)
  513. expect(root.innerHTML).toBe(
  514. `<button vapor-child="" vdom-child="" vapor-parent=""></button>`,
  515. )
  516. })
  517. test('vapor parent > vdom child > vdom child > vapor child', () => {
  518. const VaporChild = defineVaporComponent({
  519. __scopeId: 'vapor-child',
  520. setup() {
  521. return template('<button vapor-child></button>', true)()
  522. },
  523. })
  524. const VdomChild = {
  525. __scopeId: 'vdom-child',
  526. setup() {
  527. return () => h(VaporChild as any)
  528. },
  529. }
  530. const VdomParent = {
  531. __scopeId: 'vdom-parent',
  532. setup() {
  533. return () => h(VdomChild as any)
  534. },
  535. }
  536. const VaporParent = defineVaporComponent({
  537. __scopeId: 'vapor-parent',
  538. setup() {
  539. return createComponent(VdomParent as any, null, null, true)
  540. },
  541. })
  542. const App = {
  543. setup() {
  544. return () => h(VaporParent as any)
  545. },
  546. }
  547. const root = document.createElement('div')
  548. createApp(App).use(vaporInteropPlugin).mount(root)
  549. expect(root.innerHTML).toBe(
  550. `<button vapor-child="" vdom-child="" vdom-parent="" vapor-parent=""></button>`,
  551. )
  552. })
  553. test('vapor parent > vapor slot > vdom child', () => {
  554. const VaporSlot = defineVaporComponent({
  555. __scopeId: 'vapor-slot',
  556. setup() {
  557. const n1 = template('<div vapor-slot></div>', true)() as any
  558. setInsertionState(n1)
  559. createSlot('default', null)
  560. return n1
  561. },
  562. })
  563. const VdomChild = {
  564. __scopeId: 'vdom-child',
  565. setup() {
  566. return () => h('span')
  567. },
  568. }
  569. const VaporParent = defineVaporComponent({
  570. __scopeId: 'vapor-parent',
  571. setup() {
  572. const n2 = createComponent(
  573. VaporSlot,
  574. null,
  575. {
  576. default: withVaporCtx(() => {
  577. const n0 = template('<div vapor-parent></div>')()
  578. const n1 = createComponent(VdomChild)
  579. return [n0, n1]
  580. }),
  581. },
  582. true,
  583. )
  584. return n2
  585. },
  586. })
  587. const App = {
  588. setup() {
  589. return () => h(VaporParent as any)
  590. },
  591. }
  592. const root = document.createElement('div')
  593. createApp(App).use(vaporInteropPlugin).mount(root)
  594. expect(root.innerHTML).toBe(
  595. `<div vapor-slot="" vapor-parent="">` +
  596. `<div vapor-parent="" vapor-slot-s=""></div>` +
  597. `<span vdom-child="" vapor-parent="" vapor-slot-s=""></span>` +
  598. `<!--slot-->` +
  599. `</div>`,
  600. )
  601. })
  602. })