defineComponent.test-d.tsx 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. import { expectError, expectType } from 'tsd'
  2. import { describe, defineComponent, PropType, ref, createApp } from './index'
  3. describe('with object props', () => {
  4. interface ExpectedProps {
  5. a?: number | undefined
  6. b: string
  7. bb: string
  8. cc?: string[] | undefined
  9. dd: string[]
  10. ccc?: string[] | undefined
  11. ddd: string[]
  12. }
  13. type GT = string & { __brand: unknown }
  14. const MyComponent = defineComponent({
  15. props: {
  16. a: Number,
  17. // required should make property non-void
  18. b: {
  19. type: String,
  20. required: true
  21. },
  22. // default value should infer type and make it non-void
  23. bb: {
  24. default: 'hello'
  25. },
  26. // explicit type casting
  27. cc: Array as PropType<string[]>,
  28. // required + type casting
  29. dd: {
  30. type: Array as PropType<string[]>,
  31. required: true
  32. },
  33. // explicit type casting with constructor
  34. ccc: Array as () => string[],
  35. // required + contructor type casting
  36. ddd: {
  37. type: Array as () => string[],
  38. required: true
  39. }
  40. },
  41. setup(props) {
  42. // type assertion. See https://github.com/SamVerschueren/tsd
  43. expectType<ExpectedProps['a']>(props.a)
  44. expectType<ExpectedProps['b']>(props.b)
  45. expectType<ExpectedProps['bb']>(props.bb)
  46. expectType<ExpectedProps['cc']>(props.cc)
  47. expectType<ExpectedProps['dd']>(props.dd)
  48. expectType<ExpectedProps['ccc']>(props.ccc)
  49. expectType<ExpectedProps['ddd']>(props.ddd)
  50. // props should be readonly
  51. expectError((props.a = 1))
  52. // setup context
  53. return {
  54. c: ref(1),
  55. d: {
  56. e: ref('hi')
  57. },
  58. f: {
  59. g: ref('hello' as GT)
  60. }
  61. }
  62. },
  63. render() {
  64. const props = this.$props
  65. expectType<ExpectedProps['a']>(props.a)
  66. expectType<ExpectedProps['b']>(props.b)
  67. expectType<ExpectedProps['bb']>(props.bb)
  68. expectType<ExpectedProps['cc']>(props.cc)
  69. expectType<ExpectedProps['dd']>(props.dd)
  70. expectType<ExpectedProps['ccc']>(props.ccc)
  71. expectType<ExpectedProps['ddd']>(props.ddd)
  72. // props should be readonly
  73. expectError((props.a = 1))
  74. // should also expose declared props on `this`
  75. expectType<ExpectedProps['a']>(this.a)
  76. expectType<ExpectedProps['b']>(this.b)
  77. expectType<ExpectedProps['bb']>(this.bb)
  78. expectType<ExpectedProps['cc']>(this.cc)
  79. expectType<ExpectedProps['dd']>(this.dd)
  80. expectType<ExpectedProps['ccc']>(this.ccc)
  81. expectType<ExpectedProps['ddd']>(this.ddd)
  82. // props on `this` should be readonly
  83. expectError((this.a = 1))
  84. // assert setup context unwrapping
  85. expectType<number>(this.c)
  86. expectType<string>(this.d.e)
  87. expectType<GT>(this.f.g)
  88. // setup context properties should be mutable
  89. this.c = 2
  90. return null
  91. }
  92. })
  93. // Test TSX
  94. expectType<JSX.Element>(
  95. <MyComponent
  96. a={1}
  97. b="b"
  98. bb="bb"
  99. cc={['cc']}
  100. dd={['dd']}
  101. ccc={['ccc']}
  102. ddd={['ddd']}
  103. // should allow extraneous as attrs
  104. class="bar"
  105. // should allow key
  106. key={'foo'}
  107. // should allow ref
  108. ref={'foo'}
  109. />
  110. )
  111. // missing required props
  112. expectError(<MyComponent />)
  113. // wrong prop types
  114. expectError(
  115. <MyComponent a={'wrong type'} b="foo" dd={['foo']} ddd={['foo']} />
  116. )
  117. expectError(<MyComponent b="foo" dd={[123]} ddd={['foo']} />)
  118. })
  119. describe('type inference w/ optional props declaration', () => {
  120. const MyComponent = defineComponent({
  121. setup(_props: { msg: string }) {
  122. return {
  123. a: 1
  124. }
  125. },
  126. render() {
  127. expectType<string>(this.$props.msg)
  128. // props should be readonly
  129. expectError((this.$props.msg = 'foo'))
  130. // should not expose on `this`
  131. expectError(this.msg)
  132. expectType<number>(this.a)
  133. return null
  134. }
  135. })
  136. expectType<JSX.Element>(<MyComponent msg="foo" />)
  137. expectError(<MyComponent />)
  138. expectError(<MyComponent msg={1} />)
  139. })
  140. describe('type inference w/ direct setup function', () => {
  141. const MyComponent = defineComponent((_props: { msg: string }) => {})
  142. expectType<JSX.Element>(<MyComponent msg="foo" />)
  143. expectError(<MyComponent />)
  144. expectError(<MyComponent msg={1} />)
  145. })
  146. describe('type inference w/ array props declaration', () => {
  147. defineComponent({
  148. props: ['a', 'b'],
  149. setup(props) {
  150. // props should be readonly
  151. expectError((props.a = 1))
  152. expectType<any>(props.a)
  153. expectType<any>(props.b)
  154. return {
  155. c: 1
  156. }
  157. },
  158. render() {
  159. expectType<any>(this.$props.a)
  160. expectType<any>(this.$props.b)
  161. expectError((this.$props.a = 1))
  162. expectType<any>(this.a)
  163. expectType<any>(this.b)
  164. expectType<number>(this.c)
  165. }
  166. })
  167. })
  168. describe('type inference w/ options API', () => {
  169. defineComponent({
  170. props: { a: Number },
  171. setup() {
  172. return {
  173. b: 123
  174. }
  175. },
  176. data() {
  177. // Limitation: we cannot expose the return result of setup() on `this`
  178. // here in data() - somehow that would mess up the inference
  179. expectType<number | undefined>(this.a)
  180. return {
  181. c: this.a || 123
  182. }
  183. },
  184. computed: {
  185. d(): number {
  186. expectType<number>(this.b)
  187. return this.b + 1
  188. }
  189. },
  190. watch: {
  191. a() {
  192. expectType<number>(this.b)
  193. this.b + 1
  194. }
  195. },
  196. created() {
  197. // props
  198. expectType<number | undefined>(this.a)
  199. // returned from setup()
  200. expectType<number>(this.b)
  201. // returned from data()
  202. expectType<number>(this.c)
  203. // computed
  204. expectType<number>(this.d)
  205. },
  206. methods: {
  207. doSomething() {
  208. // props
  209. expectType<number | undefined>(this.a)
  210. // returned from setup()
  211. expectType<number>(this.b)
  212. // returned from data()
  213. expectType<number>(this.c)
  214. // computed
  215. expectType<number>(this.d)
  216. }
  217. },
  218. render() {
  219. // props
  220. expectType<number | undefined>(this.a)
  221. // returned from setup()
  222. expectType<number>(this.b)
  223. // returned from data()
  224. expectType<number>(this.c)
  225. // computed
  226. expectType<number>(this.d)
  227. }
  228. })
  229. })
  230. describe('compatibility w/ createApp', () => {
  231. const comp = defineComponent({})
  232. createApp(comp).mount('#hello')
  233. const comp2 = defineComponent({
  234. props: { foo: String }
  235. })
  236. createApp(comp2).mount('#hello')
  237. const comp3 = defineComponent({
  238. setup() {
  239. return {
  240. a: 1
  241. }
  242. }
  243. })
  244. createApp(comp3).mount('#hello')
  245. })
  246. describe('defineComponent', () => {
  247. test('should accept components defined with defineComponent', () => {
  248. const comp = defineComponent({})
  249. defineComponent({
  250. components: { comp }
  251. })
  252. })
  253. })