ssrDirectives.spec.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545
  1. import { renderToString } from '../src/renderToString'
  2. import {
  3. createApp,
  4. h,
  5. resolveDirective,
  6. vModelCheckbox,
  7. vModelDynamic,
  8. vModelRadio,
  9. vModelText,
  10. vShow,
  11. withDirectives,
  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. })