ssrRenderAttrs.spec.ts 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. import {
  2. ssrRenderAttr,
  3. ssrRenderAttrs,
  4. ssrRenderClass,
  5. ssrRenderStyle,
  6. } from '../src/helpers/ssrRenderAttrs'
  7. import { escapeHtml } from '@vue/shared'
  8. describe('ssr: renderAttrs', () => {
  9. test('ignore reserved props', () => {
  10. expect(
  11. ssrRenderAttrs({
  12. key: 1,
  13. ref_key: 'foo',
  14. ref_for: 'bar',
  15. ref: () => {},
  16. onClick: () => {},
  17. }),
  18. ).toBe('')
  19. })
  20. test('normal attrs', () => {
  21. expect(
  22. ssrRenderAttrs({
  23. id: 'foo',
  24. title: 'bar',
  25. }),
  26. ).toBe(` id="foo" title="bar"`)
  27. })
  28. test('empty value attrs', () => {
  29. expect(
  30. ssrRenderAttrs({
  31. 'data-v-abc': '',
  32. }),
  33. ).toBe(` data-v-abc`)
  34. })
  35. test('escape attrs', () => {
  36. expect(
  37. ssrRenderAttrs({
  38. id: '"><script',
  39. }),
  40. ).toBe(` id="&quot;&gt;&lt;script"`)
  41. })
  42. test('boolean attrs', () => {
  43. expect(
  44. ssrRenderAttrs({
  45. checked: true,
  46. multiple: false,
  47. readonly: 0,
  48. disabled: '',
  49. }),
  50. ).toBe(` checked disabled`) // boolean attr w/ false should be ignored
  51. })
  52. test('ignore falsy values', () => {
  53. expect(
  54. ssrRenderAttrs({
  55. foo: false,
  56. title: null,
  57. baz: undefined,
  58. }),
  59. ).toBe(` foo="false"`) // non boolean should render `false` as is
  60. })
  61. test('ignore non-renderable values', () => {
  62. expect(
  63. ssrRenderAttrs({
  64. foo: {},
  65. bar: [],
  66. baz: () => {},
  67. }),
  68. ).toBe(``)
  69. })
  70. test('props to attrs', () => {
  71. expect(
  72. ssrRenderAttrs({
  73. readOnly: true, // simple lower case conversion
  74. htmlFor: 'foobar', // special cases
  75. }),
  76. ).toBe(` readonly for="foobar"`)
  77. })
  78. test('preserve name on custom element', () => {
  79. expect(
  80. ssrRenderAttrs(
  81. {
  82. fooBar: 'ok',
  83. },
  84. 'my-el',
  85. ),
  86. ).toBe(` fooBar="ok"`)
  87. })
  88. test('preserve name on svg elements', () => {
  89. expect(
  90. ssrRenderAttrs(
  91. {
  92. viewBox: 'foo',
  93. },
  94. 'svg',
  95. ),
  96. ).toBe(` viewBox="foo"`)
  97. })
  98. })
  99. describe('ssr: renderAttr', () => {
  100. test('basic', () => {
  101. expect(ssrRenderAttr('foo', 'bar')).toBe(` foo="bar"`)
  102. })
  103. test('null and undefined', () => {
  104. expect(ssrRenderAttr('foo', null)).toBe(``)
  105. expect(ssrRenderAttr('foo', undefined)).toBe(``)
  106. })
  107. test('escape', () => {
  108. expect(ssrRenderAttr('foo', '<script>')).toBe(
  109. ` foo="${escapeHtml(`<script>`)}"`,
  110. )
  111. })
  112. })
  113. describe('ssr: renderClass', () => {
  114. test('via renderProps', () => {
  115. expect(
  116. ssrRenderAttrs({
  117. class: ['foo', 'bar'],
  118. }),
  119. ).toBe(` class="foo bar"`)
  120. })
  121. test('standalone', () => {
  122. expect(ssrRenderClass(`foo`)).toBe(`foo`)
  123. expect(ssrRenderClass([`foo`, `bar`])).toBe(`foo bar`)
  124. expect(ssrRenderClass({ foo: true, bar: false })).toBe(`foo`)
  125. expect(ssrRenderClass([{ foo: true, bar: false }, `baz`])).toBe(`foo baz`)
  126. })
  127. test('escape class values', () => {
  128. expect(ssrRenderClass(`"><script`)).toBe(`&quot;&gt;&lt;script`)
  129. })
  130. test('className', () => {
  131. expect(
  132. ssrRenderAttrs({
  133. className: 'foo',
  134. }),
  135. ).toBe(` class="foo"`)
  136. expect(
  137. ssrRenderAttrs({
  138. className: ['foo', 'bar'],
  139. }),
  140. ).toBe(` class="foo,bar"`)
  141. })
  142. })
  143. describe('ssr: renderStyle', () => {
  144. test('via renderProps', () => {
  145. expect(
  146. ssrRenderAttrs({
  147. style: {
  148. color: 'red',
  149. '--a': 2,
  150. '-webkit-line-clamp': 1,
  151. },
  152. }),
  153. ).toBe(` style="color:red;--a:2;-webkit-line-clamp:1;"`)
  154. })
  155. test('standalone', () => {
  156. expect(ssrRenderStyle(`color:red`)).toBe(`color:red`)
  157. expect(
  158. ssrRenderStyle({
  159. color: `red`,
  160. }),
  161. ).toBe(`color:red;`)
  162. expect(
  163. ssrRenderStyle([
  164. { color: `red` },
  165. { fontSize: `15px` }, // case conversion
  166. ]),
  167. ).toBe(`color:red;font-size:15px;`)
  168. })
  169. test('number handling', () => {
  170. expect(
  171. ssrRenderStyle({
  172. fontSize: null, // invalid value should be ignored
  173. opacity: 0.5,
  174. }),
  175. ).toBe(`opacity:0.5;`)
  176. })
  177. test('escape inline CSS', () => {
  178. expect(ssrRenderStyle(`"><script`)).toBe(`&quot;&gt;&lt;script`)
  179. expect(
  180. ssrRenderStyle({
  181. color: `"><script`,
  182. }),
  183. ).toBe(`color:&quot;&gt;&lt;script;`)
  184. })
  185. test('useCssVars handling', () => {
  186. expect(
  187. ssrRenderStyle({
  188. fontSize: null,
  189. ':--v1': undefined,
  190. ':--v2': null,
  191. ':--v3': '',
  192. ':--v4': ' ',
  193. ':--v5': 'foo',
  194. ':--v6': 0,
  195. '--foo': 1,
  196. }),
  197. ).toBe(`--v1:initial;--v2:initial;--v3: ;--v4: ;--v5:foo;--v6:0;--foo:1;`)
  198. })
  199. })