scopeId.spec.ts 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627
  1. import { createApp, h } from '@vue/runtime-dom'
  2. import {
  3. createComponent,
  4. createDynamicComponent,
  5. createSlot,
  6. defineVaporComponent,
  7. forwardedSlotCreator,
  8. setInsertionState,
  9. template,
  10. vaporInteropPlugin,
  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: () => {
  208. const n0 = template('<div parent></div>')()
  209. const n1 = createComponent(
  210. Child2,
  211. null,
  212. null,
  213. undefined,
  214. undefined,
  215. 'parent',
  216. )
  217. return [n0, n1]
  218. },
  219. },
  220. true,
  221. )
  222. return n2
  223. },
  224. }).render()
  225. expect(html()).toBe(
  226. `<div child="" parent="">` +
  227. `<div parent="" child-s=""></div>` +
  228. // component inside slot should have:
  229. // - scopeId from template context
  230. // - slotted scopeId from slot owner
  231. // - its own scopeId
  232. `<span child2="" parent="" child-s="" child=""></span>` +
  233. `<!--slot-->` +
  234. `</div>`,
  235. )
  236. })
  237. test(':slotted on forwarded slots', async () => {
  238. const Wrapper = defineVaporComponent({
  239. __scopeId: 'wrapper',
  240. setup() {
  241. // <div><slot/></div>
  242. const n1 = template('<div wrapper></div>', true)() as any
  243. setInsertionState(n1)
  244. createSlot('default', null)
  245. return n1
  246. },
  247. })
  248. const Slotted = defineVaporComponent({
  249. __scopeId: 'slotted',
  250. setup() {
  251. // <Wrapper><slot/></Wrapper>
  252. const _createForwardedSlot = forwardedSlotCreator()
  253. const n1 = createComponent(
  254. Wrapper,
  255. null,
  256. {
  257. default: () => {
  258. const n0 = _createForwardedSlot('default', null)
  259. return n0
  260. },
  261. },
  262. true,
  263. )
  264. return n1
  265. },
  266. })
  267. const { html } = define({
  268. __scopeId: 'root',
  269. setup() {
  270. // <Slotted><div></div></Slotted>
  271. const n2 = createComponent(
  272. Slotted,
  273. null,
  274. {
  275. default: () => {
  276. return template('<div root></div>')()
  277. },
  278. },
  279. true,
  280. )
  281. return n2
  282. },
  283. }).render()
  284. expect(html()).toBe(
  285. `<div wrapper="" slotted="" root="">` +
  286. `<div root="" slotted-s=""></div>` +
  287. `<!--slot--><!--slot-->` +
  288. `</div>`,
  289. )
  290. })
  291. })
  292. describe('vdom interop', () => {
  293. test('vdom parent > vapor child', () => {
  294. const VaporChild = defineVaporComponent({
  295. __scopeId: 'vapor-child',
  296. setup() {
  297. return template('<button vapor-child></button>', true)()
  298. },
  299. })
  300. const VdomParent = {
  301. __scopeId: 'vdom-parent',
  302. setup() {
  303. return () => h(VaporChild as any)
  304. },
  305. }
  306. const App = {
  307. setup() {
  308. return () => h(VdomParent)
  309. },
  310. }
  311. const root = document.createElement('div')
  312. createApp(App).use(vaporInteropPlugin).mount(root)
  313. expect(root.innerHTML).toBe(
  314. `<button vapor-child="" vdom-parent=""></button>`,
  315. )
  316. })
  317. test('vdom parent > vapor > vdom child', () => {
  318. const VdomChild = {
  319. __scopeId: 'vdom-child',
  320. setup() {
  321. return () => h('button')
  322. },
  323. }
  324. const VaporChild = defineVaporComponent({
  325. __scopeId: 'vapor-child',
  326. setup() {
  327. return createComponent(VdomChild as any, null, null, true)
  328. },
  329. })
  330. const VdomParent = {
  331. __scopeId: 'vdom-parent',
  332. setup() {
  333. return () => h(VaporChild as any)
  334. },
  335. }
  336. const App = {
  337. setup() {
  338. return () => h(VdomParent)
  339. },
  340. }
  341. const root = document.createElement('div')
  342. createApp(App).use(vaporInteropPlugin).mount(root)
  343. expect(root.innerHTML).toBe(
  344. `<button vdom-child="" vapor-child="" vdom-parent=""></button>`,
  345. )
  346. })
  347. test('vdom parent > vapor > vapor > vdom child', () => {
  348. const VdomChild = {
  349. __scopeId: 'vdom-child',
  350. setup() {
  351. return () => h('button')
  352. },
  353. }
  354. const NestedVaporChild = defineVaporComponent({
  355. __scopeId: 'nested-vapor-child',
  356. setup() {
  357. return createComponent(VdomChild as any, null, null, true)
  358. },
  359. })
  360. const VaporChild = defineVaporComponent({
  361. __scopeId: 'vapor-child',
  362. setup() {
  363. return createComponent(NestedVaporChild as any, null, null, true)
  364. },
  365. })
  366. const VdomParent = {
  367. __scopeId: 'vdom-parent',
  368. setup() {
  369. return () => h(VaporChild as any)
  370. },
  371. }
  372. const App = {
  373. setup() {
  374. return () => h(VdomParent)
  375. },
  376. }
  377. const root = document.createElement('div')
  378. createApp(App).use(vaporInteropPlugin).mount(root)
  379. expect(root.innerHTML).toBe(
  380. `<button vdom-child="" nested-vapor-child="" vapor-child="" vdom-parent=""></button>`,
  381. )
  382. })
  383. test('vdom parent > vapor dynamic child', () => {
  384. const VaporChild = defineVaporComponent({
  385. __scopeId: 'vapor-child',
  386. setup() {
  387. return createDynamicComponent(() => 'button', null, null, true)
  388. },
  389. })
  390. const VdomParent = {
  391. __scopeId: 'vdom-parent',
  392. setup() {
  393. return () => h(VaporChild as any)
  394. },
  395. }
  396. const App = {
  397. setup() {
  398. return () => h(VdomParent)
  399. },
  400. }
  401. const root = document.createElement('div')
  402. createApp(App).use(vaporInteropPlugin).mount(root)
  403. expect(root.innerHTML).toBe(
  404. `<button vapor-child="" vdom-parent=""></button><!--dynamic-component-->`,
  405. )
  406. })
  407. test('vapor parent > vdom child', () => {
  408. const VdomChild = {
  409. __scopeId: 'vdom-child',
  410. setup() {
  411. return () => h('button')
  412. },
  413. }
  414. const VaporParent = defineVaporComponent({
  415. __scopeId: 'vapor-parent',
  416. setup() {
  417. return createComponent(VdomChild as any, null, null, true)
  418. },
  419. })
  420. const App = {
  421. setup() {
  422. return () => h(VaporParent as any)
  423. },
  424. }
  425. const root = document.createElement('div')
  426. createApp(App).use(vaporInteropPlugin).mount(root)
  427. expect(root.innerHTML).toBe(
  428. `<button vdom-child="" vapor-parent=""></button>`,
  429. )
  430. })
  431. test('vapor parent > vdom > vapor child', () => {
  432. const VaporChild = defineVaporComponent({
  433. __scopeId: 'vapor-child',
  434. setup() {
  435. return template('<button vapor-child></button>', true)()
  436. },
  437. })
  438. const VdomChild = {
  439. __scopeId: 'vdom-child',
  440. setup() {
  441. return () => h(VaporChild as any)
  442. },
  443. }
  444. const VaporParent = defineVaporComponent({
  445. __scopeId: 'vapor-parent',
  446. setup() {
  447. return createComponent(VdomChild as any, null, null, true)
  448. },
  449. })
  450. const App = {
  451. setup() {
  452. return () => h(VaporParent as any)
  453. },
  454. }
  455. const root = document.createElement('div')
  456. createApp(App).use(vaporInteropPlugin).mount(root)
  457. expect(root.innerHTML).toBe(
  458. `<button vapor-child="" vdom-child="" vapor-parent=""></button>`,
  459. )
  460. })
  461. test('vapor parent > vdom > vdom > vapor child', () => {
  462. const VaporChild = defineVaporComponent({
  463. __scopeId: 'vapor-child',
  464. setup() {
  465. return template('<button vapor-child></button>', true)()
  466. },
  467. })
  468. const VdomChild = {
  469. __scopeId: 'vdom-child',
  470. setup() {
  471. return () => h(VaporChild as any)
  472. },
  473. }
  474. const VdomParent = {
  475. __scopeId: 'vdom-parent',
  476. setup() {
  477. return () => h(VdomChild as any)
  478. },
  479. }
  480. const VaporParent = defineVaporComponent({
  481. __scopeId: 'vapor-parent',
  482. setup() {
  483. return createComponent(VdomParent as any, null, null, true)
  484. },
  485. })
  486. const App = {
  487. setup() {
  488. return () => h(VaporParent as any)
  489. },
  490. }
  491. const root = document.createElement('div')
  492. createApp(App).use(vaporInteropPlugin).mount(root)
  493. expect(root.innerHTML).toBe(
  494. `<button vapor-child="" vdom-child="" vdom-parent="" vapor-parent=""></button>`,
  495. )
  496. })
  497. test.todo('vapor parent > vapor slot > vdom child', () => {
  498. const VaporSlot = defineVaporComponent({
  499. __scopeId: 'vapor-slot',
  500. setup() {
  501. const n1 = template('<div vapor-slot></div>', true)() as any
  502. setInsertionState(n1)
  503. createSlot('default', null)
  504. return n1
  505. },
  506. })
  507. const VdomChild = {
  508. __scopeId: 'vdom-child',
  509. setup() {
  510. return () => h('span')
  511. },
  512. }
  513. const VaporParent = defineVaporComponent({
  514. __scopeId: 'vapor-parent',
  515. setup() {
  516. const n2 = createComponent(
  517. VaporSlot,
  518. null,
  519. {
  520. default: () => {
  521. const n0 = template('<div vapor-parent></div>')()
  522. const n1 = createComponent(
  523. VdomChild,
  524. undefined,
  525. undefined,
  526. undefined,
  527. undefined,
  528. 'vapor-parent',
  529. )
  530. return [n0, n1]
  531. },
  532. },
  533. true,
  534. )
  535. return n2
  536. },
  537. })
  538. const App = {
  539. setup() {
  540. return () => h(VaporParent as any)
  541. },
  542. }
  543. const root = document.createElement('div')
  544. createApp(App).use(vaporInteropPlugin).mount(root)
  545. expect(root.innerHTML).toBe(
  546. `<div vapor-slot="" vapor-parent="">` +
  547. `<div vapor-parent="" vapor-slot-s=""></div>` +
  548. `<span vdom-child="" vapor-parent="" vapor-slot-s=""></span>` +
  549. `<!--slot-->` +
  550. `</div>`,
  551. )
  552. })
  553. })