ssrDirectives.spec.ts 14 KB

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