ssrDirectives.spec.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521
  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('checkbox', async () => {
  101. expect(
  102. await renderToString(
  103. createApp({
  104. data: () => ({ checked: true }),
  105. template: `<input type="checkbox" v-model="checked">`
  106. })
  107. )
  108. ).toBe(`<input type="checkbox" checked>`)
  109. expect(
  110. await renderToString(
  111. createApp({
  112. data: () => ({ checked: false }),
  113. template: `<input type="checkbox" v-model="checked">`
  114. })
  115. )
  116. ).toBe(`<input type="checkbox">`)
  117. expect(
  118. await renderToString(
  119. createApp({
  120. data: () => ({ checked: ['foo'] }),
  121. template: `<input type="checkbox" value="foo" v-model="checked">`
  122. })
  123. )
  124. ).toBe(`<input type="checkbox" value="foo" checked>`)
  125. expect(
  126. await renderToString(
  127. createApp({
  128. data: () => ({ checked: [] }),
  129. template: `<input type="checkbox" value="foo" v-model="checked">`
  130. })
  131. )
  132. ).toBe(`<input type="checkbox" value="foo">`)
  133. })
  134. test('textarea', async () => {
  135. expect(
  136. await renderToString(
  137. createApp({
  138. data: () => ({ foo: 'hello' }),
  139. template: `<textarea v-model="foo"/>`
  140. })
  141. )
  142. ).toBe(`<textarea>hello</textarea>`)
  143. })
  144. test('dynamic type', async () => {
  145. expect(
  146. await renderToString(
  147. createApp({
  148. data: () => ({ type: 'text', model: 'hello' }),
  149. template: `<input :type="type" v-model="model">`
  150. })
  151. )
  152. ).toBe(`<input type="text" value="hello">`)
  153. expect(
  154. await renderToString(
  155. createApp({
  156. data: () => ({ type: 'checkbox', model: true }),
  157. template: `<input :type="type" v-model="model">`
  158. })
  159. )
  160. ).toBe(`<input type="checkbox" checked>`)
  161. expect(
  162. await renderToString(
  163. createApp({
  164. data: () => ({ type: 'checkbox', model: false }),
  165. template: `<input :type="type" v-model="model">`
  166. })
  167. )
  168. ).toBe(`<input type="checkbox">`)
  169. expect(
  170. await renderToString(
  171. createApp({
  172. data: () => ({ type: 'checkbox', model: ['hello'] }),
  173. template: `<input :type="type" value="hello" v-model="model">`
  174. })
  175. )
  176. ).toBe(`<input type="checkbox" value="hello" checked>`)
  177. expect(
  178. await renderToString(
  179. createApp({
  180. data: () => ({ type: 'checkbox', model: [] }),
  181. template: `<input :type="type" value="hello" v-model="model">`
  182. })
  183. )
  184. ).toBe(`<input type="checkbox" value="hello">`)
  185. expect(
  186. await renderToString(
  187. createApp({
  188. data: () => ({ type: 'radio', model: 'hello' }),
  189. template: `<input :type="type" value="hello" v-model="model">`
  190. })
  191. )
  192. ).toBe(`<input type="radio" value="hello" checked>`)
  193. expect(
  194. await renderToString(
  195. createApp({
  196. data: () => ({ type: 'radio', model: 'hello' }),
  197. template: `<input :type="type" value="bar" v-model="model">`
  198. })
  199. )
  200. ).toBe(`<input type="radio" value="bar">`)
  201. })
  202. test('with v-bind', async () => {
  203. expect(
  204. await renderToString(
  205. createApp({
  206. data: () => ({
  207. obj: { type: 'radio', value: 'hello' },
  208. model: 'hello'
  209. }),
  210. template: `<input v-bind="obj" v-model="model">`
  211. })
  212. )
  213. ).toBe(`<input type="radio" value="hello" checked>`)
  214. })
  215. })
  216. describe('vnode v-show', () => {
  217. test('basic', async () => {
  218. expect(
  219. await renderToString(
  220. createApp({
  221. render() {
  222. return withDirectives(h('div'), [[vShow, true]])
  223. }
  224. })
  225. )
  226. ).toBe(`<div></div>`)
  227. expect(
  228. await renderToString(
  229. createApp({
  230. render() {
  231. return withDirectives(h('div'), [[vShow, false]])
  232. }
  233. })
  234. )
  235. ).toBe(`<div style="display:none;"></div>`)
  236. })
  237. test('with merge', async () => {
  238. expect(
  239. await renderToString(
  240. createApp({
  241. render() {
  242. return withDirectives(
  243. h('div', {
  244. style: {
  245. color: 'red'
  246. }
  247. }),
  248. [[vShow, false]]
  249. )
  250. }
  251. })
  252. )
  253. ).toBe(`<div style="color:red;display:none;"></div>`)
  254. })
  255. })
  256. describe('vnode v-model', () => {
  257. test('text', async () => {
  258. expect(
  259. await renderToString(
  260. createApp({
  261. render() {
  262. return withDirectives(h('input'), [[vModelText, 'hello']])
  263. }
  264. })
  265. )
  266. ).toBe(`<input value="hello">`)
  267. })
  268. test('radio', async () => {
  269. expect(
  270. await renderToString(
  271. createApp({
  272. render() {
  273. return withDirectives(
  274. h('input', { type: 'radio', value: 'hello' }),
  275. [[vModelRadio, 'hello']]
  276. )
  277. }
  278. })
  279. )
  280. ).toBe(`<input type="radio" value="hello" checked>`)
  281. expect(
  282. await renderToString(
  283. createApp({
  284. render() {
  285. return withDirectives(
  286. h('input', { type: 'radio', value: 'hello' }),
  287. [[vModelRadio, 'foo']]
  288. )
  289. }
  290. })
  291. )
  292. ).toBe(`<input type="radio" value="hello">`)
  293. })
  294. test('checkbox', async () => {
  295. expect(
  296. await renderToString(
  297. createApp({
  298. render() {
  299. return withDirectives(h('input', { type: 'checkbox' }), [
  300. [vModelCheckbox, true]
  301. ])
  302. }
  303. })
  304. )
  305. ).toBe(`<input type="checkbox" checked>`)
  306. expect(
  307. await renderToString(
  308. createApp({
  309. render() {
  310. return withDirectives(h('input', { type: 'checkbox' }), [
  311. [vModelCheckbox, false]
  312. ])
  313. }
  314. })
  315. )
  316. ).toBe(`<input type="checkbox">`)
  317. expect(
  318. await renderToString(
  319. createApp({
  320. render() {
  321. return withDirectives(
  322. h('input', { type: 'checkbox', value: 'foo' }),
  323. [[vModelCheckbox, ['foo']]]
  324. )
  325. }
  326. })
  327. )
  328. ).toBe(`<input type="checkbox" value="foo" checked>`)
  329. expect(
  330. await renderToString(
  331. createApp({
  332. render() {
  333. return withDirectives(
  334. h('input', { type: 'checkbox', value: 'foo' }),
  335. [[vModelCheckbox, []]]
  336. )
  337. }
  338. })
  339. )
  340. ).toBe(`<input type="checkbox" value="foo">`)
  341. })
  342. })
  343. describe('vnode v-model dynamic', () => {
  344. test('text', async () => {
  345. expect(
  346. await renderToString(
  347. createApp({
  348. render() {
  349. return withDirectives(h('input'), [[vModelDynamic, 'hello']])
  350. }
  351. })
  352. )
  353. ).toBe(`<input value="hello">`)
  354. })
  355. test('radio', async () => {
  356. expect(
  357. await renderToString(
  358. createApp({
  359. render() {
  360. return withDirectives(
  361. h('input', { type: 'radio', value: 'hello' }),
  362. [[vModelDynamic, 'hello']]
  363. )
  364. }
  365. })
  366. )
  367. ).toBe(`<input type="radio" value="hello" checked>`)
  368. expect(
  369. await renderToString(
  370. createApp({
  371. render() {
  372. return withDirectives(
  373. h('input', { type: 'radio', value: 'hello' }),
  374. [[vModelDynamic, 'foo']]
  375. )
  376. }
  377. })
  378. )
  379. ).toBe(`<input type="radio" value="hello">`)
  380. })
  381. test('checkbox', async () => {
  382. expect(
  383. await renderToString(
  384. createApp({
  385. render() {
  386. return withDirectives(h('input', { type: 'checkbox' }), [
  387. [vModelDynamic, true]
  388. ])
  389. }
  390. })
  391. )
  392. ).toBe(`<input type="checkbox" checked>`)
  393. expect(
  394. await renderToString(
  395. createApp({
  396. render() {
  397. return withDirectives(h('input', { type: 'checkbox' }), [
  398. [vModelDynamic, false]
  399. ])
  400. }
  401. })
  402. )
  403. ).toBe(`<input type="checkbox">`)
  404. expect(
  405. await renderToString(
  406. createApp({
  407. render() {
  408. return withDirectives(
  409. h('input', { type: 'checkbox', value: 'foo' }),
  410. [[vModelDynamic, ['foo']]]
  411. )
  412. }
  413. })
  414. )
  415. ).toBe(`<input type="checkbox" value="foo" checked>`)
  416. expect(
  417. await renderToString(
  418. createApp({
  419. render() {
  420. return withDirectives(
  421. h('input', { type: 'checkbox', value: 'foo' }),
  422. [[vModelDynamic, []]]
  423. )
  424. }
  425. })
  426. )
  427. ).toBe(`<input type="checkbox" value="foo">`)
  428. })
  429. })
  430. test('custom directive w/ getSSRProps (vdom)', async () => {
  431. expect(
  432. await renderToString(
  433. createApp({
  434. render() {
  435. return withDirectives(h('div'), [
  436. [
  437. {
  438. getSSRProps({ value }) {
  439. return { id: value }
  440. }
  441. },
  442. 'foo'
  443. ]
  444. ])
  445. }
  446. })
  447. )
  448. ).toBe(`<div id="foo"></div>`)
  449. })
  450. test('custom directive w/ getSSRProps (optimized)', async () => {
  451. expect(
  452. await renderToString(
  453. createApp({
  454. data() {
  455. return {
  456. x: 'foo'
  457. }
  458. },
  459. directives: {
  460. xxx: {
  461. getSSRProps({ value, arg, modifiers }) {
  462. return { id: [value, arg, modifiers.ok].join('-') }
  463. }
  464. }
  465. },
  466. ssrRender(_ctx, _push, _parent, _attrs) {
  467. const _directive_xxx = resolveDirective('xxx')!
  468. _push(
  469. `<div${ssrRenderAttrs(
  470. ssrGetDirectiveProps(_ctx, _directive_xxx, _ctx.x, 'arg', {
  471. ok: true
  472. })
  473. )}></div>`
  474. )
  475. }
  476. })
  477. )
  478. ).toBe(`<div id="foo-arg-true"></div>`)
  479. })
  480. })