ssrDirectives.spec.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525
  1. /**
  2. * @jest-environment node
  3. */
  4. import { renderToString } from '../src/renderToString'
  5. import {
  6. createApp,
  7. h,
  8. withDirectives,
  9. vShow,
  10. vModelText,
  11. vModelRadio,
  12. vModelCheckbox,
  13. vModelDynamic,
  14. resolveDirective
  15. } from 'vue'
  16. import { ssrGetDirectiveProps, ssrRenderAttrs } from '../src'
  17. describe('ssr: directives', () => {
  18. describe('template v-show', () => {
  19. test('basic', async () => {
  20. expect(
  21. await renderToString(
  22. createApp({
  23. template: `<div v-show="true"/>`
  24. })
  25. )
  26. ).toBe(`<div style=""></div>`)
  27. expect(
  28. await renderToString(
  29. createApp({
  30. template: `<div v-show="false"/>`
  31. })
  32. )
  33. ).toBe(`<div style="display:none;"></div>`)
  34. })
  35. test('with static style', async () => {
  36. expect(
  37. await renderToString(
  38. createApp({
  39. template: `<div style="color:red" v-show="false"/>`
  40. })
  41. )
  42. ).toBe(`<div style="color:red;display:none;"></div>`)
  43. })
  44. test('with dynamic style', async () => {
  45. expect(
  46. await renderToString(
  47. createApp({
  48. data: () => ({ style: { color: 'red' } }),
  49. template: `<div :style="style" v-show="false"/>`
  50. })
  51. )
  52. ).toBe(`<div style="color:red;display:none;"></div>`)
  53. })
  54. test('with static + dynamic style', async () => {
  55. expect(
  56. await renderToString(
  57. createApp({
  58. data: () => ({ style: { color: 'red' } }),
  59. template: `<div :style="style" style="font-size:12;" v-show="false"/>`
  60. })
  61. )
  62. ).toBe(`<div style="color:red;font-size:12;display:none;"></div>`)
  63. })
  64. })
  65. describe('template v-model', () => {
  66. test('text', async () => {
  67. expect(
  68. await renderToString(
  69. createApp({
  70. data: () => ({ text: 'hello' }),
  71. template: `<input v-model="text">`
  72. })
  73. )
  74. ).toBe(`<input value="hello">`)
  75. })
  76. test('radio', async () => {
  77. expect(
  78. await renderToString(
  79. createApp({
  80. data: () => ({ selected: 'foo' }),
  81. template: `<input type="radio" value="foo" v-model="selected">`
  82. })
  83. )
  84. ).toBe(`<input type="radio" value="foo" checked>`)
  85. expect(
  86. await renderToString(
  87. createApp({
  88. data: () => ({ selected: 'foo' }),
  89. template: `<input type="radio" value="bar" v-model="selected">`
  90. })
  91. )
  92. ).toBe(`<input type="radio" value="bar">`)
  93. // non-string values
  94. expect(
  95. await renderToString(
  96. createApp({
  97. data: () => ({ selected: 'foo' }),
  98. template: `<input type="radio" :value="{}" v-model="selected">`
  99. })
  100. )
  101. ).toBe(`<input type="radio">`)
  102. })
  103. test('checkbox', async () => {
  104. expect(
  105. await renderToString(
  106. createApp({
  107. data: () => ({ checked: true }),
  108. template: `<input type="checkbox" v-model="checked">`
  109. })
  110. )
  111. ).toBe(`<input type="checkbox" checked>`)
  112. expect(
  113. await renderToString(
  114. createApp({
  115. data: () => ({ checked: false }),
  116. template: `<input type="checkbox" v-model="checked">`
  117. })
  118. )
  119. ).toBe(`<input type="checkbox">`)
  120. expect(
  121. await renderToString(
  122. createApp({
  123. data: () => ({ checked: ['foo'] }),
  124. template: `<input type="checkbox" value="foo" v-model="checked">`
  125. })
  126. )
  127. ).toBe(`<input type="checkbox" value="foo" checked>`)
  128. expect(
  129. await renderToString(
  130. createApp({
  131. data: () => ({ checked: [] }),
  132. template: `<input type="checkbox" value="foo" v-model="checked">`
  133. })
  134. )
  135. ).toBe(`<input type="checkbox" value="foo">`)
  136. })
  137. test('textarea', async () => {
  138. expect(
  139. await renderToString(
  140. createApp({
  141. data: () => ({ foo: 'hello' }),
  142. template: `<textarea v-model="foo"/>`
  143. })
  144. )
  145. ).toBe(`<textarea>hello</textarea>`)
  146. })
  147. test('dynamic type', async () => {
  148. expect(
  149. await renderToString(
  150. createApp({
  151. data: () => ({ type: 'text', model: 'hello' }),
  152. template: `<input :type="type" v-model="model">`
  153. })
  154. )
  155. ).toBe(`<input type="text" value="hello">`)
  156. expect(
  157. await renderToString(
  158. createApp({
  159. data: () => ({ type: 'checkbox', model: true }),
  160. template: `<input :type="type" v-model="model">`
  161. })
  162. )
  163. ).toBe(`<input type="checkbox" checked>`)
  164. expect(
  165. await renderToString(
  166. createApp({
  167. data: () => ({ type: 'checkbox', model: false }),
  168. template: `<input :type="type" v-model="model">`
  169. })
  170. )
  171. ).toBe(`<input type="checkbox">`)
  172. expect(
  173. await renderToString(
  174. createApp({
  175. data: () => ({ type: 'checkbox', model: ['hello'] }),
  176. template: `<input :type="type" value="hello" v-model="model">`
  177. })
  178. )
  179. ).toBe(`<input type="checkbox" value="hello" checked>`)
  180. expect(
  181. await renderToString(
  182. createApp({
  183. data: () => ({ type: 'checkbox', model: [] }),
  184. template: `<input :type="type" value="hello" v-model="model">`
  185. })
  186. )
  187. ).toBe(`<input type="checkbox" value="hello">`)
  188. expect(
  189. await renderToString(
  190. createApp({
  191. data: () => ({ type: 'radio', model: 'hello' }),
  192. template: `<input :type="type" value="hello" v-model="model">`
  193. })
  194. )
  195. ).toBe(`<input type="radio" value="hello" checked>`)
  196. expect(
  197. await renderToString(
  198. createApp({
  199. data: () => ({ type: 'radio', model: 'hello' }),
  200. template: `<input :type="type" value="bar" v-model="model">`
  201. })
  202. )
  203. ).toBe(`<input type="radio" value="bar">`)
  204. })
  205. test('with v-bind', async () => {
  206. expect(
  207. await renderToString(
  208. createApp({
  209. data: () => ({
  210. obj: { type: 'radio', value: 'hello' },
  211. model: 'hello'
  212. }),
  213. template: `<input v-bind="obj" v-model="model">`
  214. })
  215. )
  216. ).toBe(`<input type="radio" value="hello" checked>`)
  217. })
  218. })
  219. describe('vnode v-show', () => {
  220. test('basic', async () => {
  221. expect(
  222. await renderToString(
  223. createApp({
  224. render() {
  225. return withDirectives(h('div'), [[vShow, true]])
  226. }
  227. })
  228. )
  229. ).toBe(`<div></div>`)
  230. expect(
  231. await renderToString(
  232. createApp({
  233. render() {
  234. return withDirectives(h('div'), [[vShow, false]])
  235. }
  236. })
  237. )
  238. ).toBe(`<div style="display:none;"></div>`)
  239. })
  240. test('with merge', async () => {
  241. expect(
  242. await renderToString(
  243. createApp({
  244. render() {
  245. return withDirectives(
  246. h('div', {
  247. style: {
  248. color: 'red'
  249. }
  250. }),
  251. [[vShow, false]]
  252. )
  253. }
  254. })
  255. )
  256. ).toBe(`<div style="color:red;display:none;"></div>`)
  257. })
  258. })
  259. describe('vnode v-model', () => {
  260. test('text', async () => {
  261. expect(
  262. await renderToString(
  263. createApp({
  264. render() {
  265. return withDirectives(h('input'), [[vModelText, 'hello']])
  266. }
  267. })
  268. )
  269. ).toBe(`<input value="hello">`)
  270. })
  271. test('radio', async () => {
  272. expect(
  273. await renderToString(
  274. createApp({
  275. render() {
  276. return withDirectives(
  277. h('input', { type: 'radio', value: 'hello' }),
  278. [[vModelRadio, 'hello']]
  279. )
  280. }
  281. })
  282. )
  283. ).toBe(`<input type="radio" value="hello" checked>`)
  284. expect(
  285. await renderToString(
  286. createApp({
  287. render() {
  288. return withDirectives(
  289. h('input', { type: 'radio', value: 'hello' }),
  290. [[vModelRadio, 'foo']]
  291. )
  292. }
  293. })
  294. )
  295. ).toBe(`<input type="radio" value="hello">`)
  296. })
  297. test('checkbox', async () => {
  298. expect(
  299. await renderToString(
  300. createApp({
  301. render() {
  302. return withDirectives(h('input', { type: 'checkbox' }), [
  303. [vModelCheckbox, true]
  304. ])
  305. }
  306. })
  307. )
  308. ).toBe(`<input type="checkbox" checked>`)
  309. expect(
  310. await renderToString(
  311. createApp({
  312. render() {
  313. return withDirectives(h('input', { type: 'checkbox' }), [
  314. [vModelCheckbox, false]
  315. ])
  316. }
  317. })
  318. )
  319. ).toBe(`<input type="checkbox">`)
  320. expect(
  321. await renderToString(
  322. createApp({
  323. render() {
  324. return withDirectives(
  325. h('input', { type: 'checkbox', value: 'foo' }),
  326. [[vModelCheckbox, ['foo']]]
  327. )
  328. }
  329. })
  330. )
  331. ).toBe(`<input type="checkbox" value="foo" checked>`)
  332. expect(
  333. await renderToString(
  334. createApp({
  335. render() {
  336. return withDirectives(
  337. h('input', { type: 'checkbox', value: 'foo' }),
  338. [[vModelCheckbox, []]]
  339. )
  340. }
  341. })
  342. )
  343. ).toBe(`<input type="checkbox" value="foo">`)
  344. })
  345. })
  346. describe('vnode v-model dynamic', () => {
  347. test('text', async () => {
  348. expect(
  349. await renderToString(
  350. createApp({
  351. render() {
  352. return withDirectives(h('input'), [[vModelDynamic, 'hello']])
  353. }
  354. })
  355. )
  356. ).toBe(`<input value="hello">`)
  357. })
  358. test('radio', async () => {
  359. expect(
  360. await renderToString(
  361. createApp({
  362. render() {
  363. return withDirectives(
  364. h('input', { type: 'radio', value: 'hello' }),
  365. [[vModelDynamic, 'hello']]
  366. )
  367. }
  368. })
  369. )
  370. ).toBe(`<input type="radio" value="hello" checked>`)
  371. expect(
  372. await renderToString(
  373. createApp({
  374. render() {
  375. return withDirectives(
  376. h('input', { type: 'radio', value: 'hello' }),
  377. [[vModelDynamic, 'foo']]
  378. )
  379. }
  380. })
  381. )
  382. ).toBe(`<input type="radio" value="hello">`)
  383. })
  384. test('checkbox', async () => {
  385. expect(
  386. await renderToString(
  387. createApp({
  388. render() {
  389. return withDirectives(h('input', { type: 'checkbox' }), [
  390. [vModelDynamic, true]
  391. ])
  392. }
  393. })
  394. )
  395. ).toBe(`<input type="checkbox" checked>`)
  396. expect(
  397. await renderToString(
  398. createApp({
  399. render() {
  400. return withDirectives(h('input', { type: 'checkbox' }), [
  401. [vModelDynamic, false]
  402. ])
  403. }
  404. })
  405. )
  406. ).toBe(`<input type="checkbox">`)
  407. expect(
  408. await renderToString(
  409. createApp({
  410. render() {
  411. return withDirectives(
  412. h('input', { type: 'checkbox', value: 'foo' }),
  413. [[vModelDynamic, ['foo']]]
  414. )
  415. }
  416. })
  417. )
  418. ).toBe(`<input type="checkbox" value="foo" checked>`)
  419. expect(
  420. await renderToString(
  421. createApp({
  422. render() {
  423. return withDirectives(
  424. h('input', { type: 'checkbox', value: 'foo' }),
  425. [[vModelDynamic, []]]
  426. )
  427. }
  428. })
  429. )
  430. ).toBe(`<input type="checkbox" value="foo">`)
  431. })
  432. })
  433. test('custom directive w/ getSSRProps (vdom)', async () => {
  434. expect(
  435. await renderToString(
  436. createApp({
  437. render() {
  438. return withDirectives(h('div'), [
  439. [
  440. {
  441. getSSRProps({ value }) {
  442. return { id: value }
  443. }
  444. },
  445. 'foo'
  446. ]
  447. ])
  448. }
  449. })
  450. )
  451. ).toBe(`<div id="foo"></div>`)
  452. })
  453. test('custom directive w/ getSSRProps (optimized)', async () => {
  454. expect(
  455. await renderToString(
  456. createApp({
  457. data() {
  458. return {
  459. x: 'foo'
  460. }
  461. },
  462. directives: {
  463. xxx: {
  464. getSSRProps({ value, arg, modifiers }) {
  465. return { id: [value, arg, modifiers.ok].join('-') }
  466. }
  467. }
  468. },
  469. ssrRender(_ctx, _push, _parent, _attrs) {
  470. const _directive_xxx = resolveDirective('xxx')!
  471. _push(
  472. `<div${ssrRenderAttrs(
  473. ssrGetDirectiveProps(_ctx, _directive_xxx, _ctx.x, 'arg', {
  474. ok: true
  475. })
  476. )}></div>`
  477. )
  478. }
  479. })
  480. )
  481. ).toBe(`<div id="foo-arg-true"></div>`)
  482. })
  483. })