defineComponent.test-d.tsx 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  1. import { expectError, expectType } from 'tsd'
  2. import {
  3. describe,
  4. defineComponent,
  5. PropType,
  6. ref,
  7. reactive,
  8. createApp
  9. } from './index'
  10. describe('with object props', () => {
  11. interface ExpectedProps {
  12. a?: number | undefined
  13. b: string
  14. e?: Function
  15. bb: string
  16. cc?: string[] | undefined
  17. dd: { n: 1 }
  18. ee?: () => string
  19. ff?: (a: number, b: string) => { a: boolean }
  20. ccc?: string[] | undefined
  21. ddd: string[]
  22. eee: () => { a: string }
  23. fff: (a: number, b: string) => { a: boolean }
  24. }
  25. type GT = string & { __brand: unknown }
  26. const MyComponent = defineComponent({
  27. props: {
  28. a: Number,
  29. // required should make property non-void
  30. b: {
  31. type: String,
  32. required: true
  33. },
  34. e: Function,
  35. // default value should infer type and make it non-void
  36. bb: {
  37. default: 'hello'
  38. },
  39. // explicit type casting
  40. cc: Array as PropType<string[]>,
  41. // required + type casting
  42. dd: {
  43. type: Object as PropType<{ n: 1 }>,
  44. required: true
  45. },
  46. // return type
  47. ee: Function as PropType<() => string>,
  48. // arguments + object return
  49. ff: Function as PropType<(a: number, b: string) => { a: boolean }>,
  50. // explicit type casting with constructor
  51. ccc: Array as () => string[],
  52. // required + contructor type casting
  53. ddd: {
  54. type: Array as () => string[],
  55. required: true
  56. },
  57. // required + object return
  58. eee: {
  59. type: Function as PropType<() => { a: string }>,
  60. required: true
  61. },
  62. // required + arguments + object return
  63. fff: {
  64. type: Function as PropType<(a: number, b: string) => { a: boolean }>,
  65. required: true
  66. }
  67. },
  68. setup(props) {
  69. // type assertion. See https://github.com/SamVerschueren/tsd
  70. expectType<ExpectedProps['a']>(props.a)
  71. expectType<ExpectedProps['b']>(props.b)
  72. expectType<ExpectedProps['e']>(props.e)
  73. expectType<ExpectedProps['bb']>(props.bb)
  74. expectType<ExpectedProps['cc']>(props.cc)
  75. expectType<ExpectedProps['dd']>(props.dd)
  76. expectType<ExpectedProps['ee']>(props.ee)
  77. expectType<ExpectedProps['ff']>(props.ff)
  78. expectType<ExpectedProps['ccc']>(props.ccc)
  79. expectType<ExpectedProps['ddd']>(props.ddd)
  80. expectType<ExpectedProps['eee']>(props.eee)
  81. expectType<ExpectedProps['fff']>(props.fff)
  82. // props should be readonly
  83. expectError((props.a = 1))
  84. // setup context
  85. return {
  86. c: ref(1),
  87. d: {
  88. e: ref('hi')
  89. },
  90. f: reactive({
  91. g: ref('hello' as GT)
  92. })
  93. }
  94. },
  95. render() {
  96. const props = this.$props
  97. expectType<ExpectedProps['a']>(props.a)
  98. expectType<ExpectedProps['b']>(props.b)
  99. expectType<ExpectedProps['e']>(props.e)
  100. expectType<ExpectedProps['bb']>(props.bb)
  101. expectType<ExpectedProps['cc']>(props.cc)
  102. expectType<ExpectedProps['dd']>(props.dd)
  103. expectType<ExpectedProps['ee']>(props.ee)
  104. expectType<ExpectedProps['ff']>(props.ff)
  105. expectType<ExpectedProps['ccc']>(props.ccc)
  106. expectType<ExpectedProps['ddd']>(props.ddd)
  107. expectType<ExpectedProps['eee']>(props.eee)
  108. expectType<ExpectedProps['fff']>(props.fff)
  109. // props should be readonly
  110. expectError((props.a = 1))
  111. // should also expose declared props on `this`
  112. expectType<ExpectedProps['a']>(this.a)
  113. expectType<ExpectedProps['b']>(this.b)
  114. expectType<ExpectedProps['e']>(this.e)
  115. expectType<ExpectedProps['bb']>(this.bb)
  116. expectType<ExpectedProps['cc']>(this.cc)
  117. expectType<ExpectedProps['dd']>(this.dd)
  118. expectType<ExpectedProps['ee']>(this.ee)
  119. expectType<ExpectedProps['ff']>(this.ff)
  120. expectType<ExpectedProps['ccc']>(this.ccc)
  121. expectType<ExpectedProps['ddd']>(this.ddd)
  122. expectType<ExpectedProps['eee']>(this.eee)
  123. expectType<ExpectedProps['fff']>(this.fff)
  124. // props on `this` should be readonly
  125. expectError((this.a = 1))
  126. // assert setup context unwrapping
  127. expectType<number>(this.c)
  128. expectType<string>(this.d.e)
  129. expectType<GT>(this.f.g)
  130. // setup context properties should be mutable
  131. this.c = 2
  132. return null
  133. }
  134. })
  135. // Test TSX
  136. expectType<JSX.Element>(
  137. <MyComponent
  138. a={1}
  139. b="b"
  140. bb="bb"
  141. e={() => {}}
  142. cc={['cc']}
  143. dd={{ n: 1 }}
  144. ee={() => 'ee'}
  145. ccc={['ccc']}
  146. ddd={['ddd']}
  147. eee={() => ({ a: 'eee' })}
  148. fff={(a, b) => ({ a: a > +b })}
  149. // should allow extraneous as attrs
  150. class="bar"
  151. // should allow key
  152. key={'foo'}
  153. // should allow ref
  154. ref={'foo'}
  155. />
  156. )
  157. // missing required props
  158. expectError(<MyComponent />)
  159. // wrong prop types
  160. expectError(
  161. <MyComponent a={'wrong type'} b="foo" dd={{ n: 1 }} ddd={['foo']} />
  162. )
  163. expectError(<MyComponent b="foo" dd={{ n: 'string' }} ddd={['foo']} />)
  164. })
  165. // describe('type inference w/ optional props declaration', () => {
  166. // const MyComponent = defineComponent({
  167. // setup(_props: { msg: string }) {
  168. // return {
  169. // a: 1
  170. // }
  171. // },
  172. // render() {
  173. // expectType<string>(this.$props.msg)
  174. // // props should be readonly
  175. // expectError((this.$props.msg = 'foo'))
  176. // // should not expose on `this`
  177. // expectError(this.msg)
  178. // expectType<number>(this.a)
  179. // return null
  180. // }
  181. // })
  182. // expectType<JSX.Element>(<MyComponent msg="foo" />)
  183. // expectError(<MyComponent />)
  184. // expectError(<MyComponent msg={1} />)
  185. // })
  186. // describe('type inference w/ direct setup function', () => {
  187. // const MyComponent = defineComponent((_props: { msg: string }) => {})
  188. // expectType<JSX.Element>(<MyComponent msg="foo" />)
  189. // expectError(<MyComponent />)
  190. // expectError(<MyComponent msg={1} />)
  191. // })
  192. describe('type inference w/ array props declaration', () => {
  193. defineComponent({
  194. props: ['a', 'b'],
  195. setup(props) {
  196. // props should be readonly
  197. expectError((props.a = 1))
  198. expectType<any>(props.a)
  199. expectType<any>(props.b)
  200. return {
  201. c: 1
  202. }
  203. },
  204. render() {
  205. expectType<any>(this.$props.a)
  206. expectType<any>(this.$props.b)
  207. expectError((this.$props.a = 1))
  208. expectType<any>(this.a)
  209. expectType<any>(this.b)
  210. expectType<number>(this.c)
  211. }
  212. })
  213. })
  214. describe('type inference w/ options API', () => {
  215. defineComponent({
  216. props: { a: Number },
  217. setup() {
  218. return {
  219. b: 123
  220. }
  221. },
  222. data() {
  223. // Limitation: we cannot expose the return result of setup() on `this`
  224. // here in data() - somehow that would mess up the inference
  225. expectType<number | undefined>(this.a)
  226. return {
  227. c: this.a || 123
  228. }
  229. },
  230. computed: {
  231. d(): number {
  232. expectType<number>(this.b)
  233. return this.b + 1
  234. }
  235. },
  236. watch: {
  237. a() {
  238. expectType<number>(this.b)
  239. this.b + 1
  240. }
  241. },
  242. created() {
  243. // props
  244. expectType<number | undefined>(this.a)
  245. // returned from setup()
  246. expectType<number>(this.b)
  247. // returned from data()
  248. expectType<number>(this.c)
  249. // computed
  250. expectType<number>(this.d)
  251. },
  252. methods: {
  253. doSomething() {
  254. // props
  255. expectType<number | undefined>(this.a)
  256. // returned from setup()
  257. expectType<number>(this.b)
  258. // returned from data()
  259. expectType<number>(this.c)
  260. // computed
  261. expectType<number>(this.d)
  262. }
  263. },
  264. render() {
  265. // props
  266. expectType<number | undefined>(this.a)
  267. // returned from setup()
  268. expectType<number>(this.b)
  269. // returned from data()
  270. expectType<number>(this.c)
  271. // computed
  272. expectType<number>(this.d)
  273. }
  274. })
  275. })
  276. describe('compatibility w/ createApp', () => {
  277. const comp = defineComponent({})
  278. createApp(comp).mount('#hello')
  279. const comp2 = defineComponent({
  280. props: { foo: String }
  281. })
  282. createApp(comp2).mount('#hello')
  283. const comp3 = defineComponent({
  284. setup() {
  285. return {
  286. a: 1
  287. }
  288. }
  289. })
  290. createApp(comp3).mount('#hello')
  291. })
  292. describe('defineComponent', () => {
  293. test('should accept components defined with defineComponent', () => {
  294. const comp = defineComponent({})
  295. defineComponent({
  296. components: { comp }
  297. })
  298. })
  299. })
  300. describe('emits', () => {
  301. // Note: for TSX inference, ideally we want to map emits to onXXX props,
  302. // but that requires type-level string constant concatenation as suggested in
  303. // https://github.com/Microsoft/TypeScript/issues/12754
  304. // The workaround for TSX users is instead of using emits, declare onXXX props
  305. // and call them instead. Since `v-on:click` compiles to an `onClick` prop,
  306. // this would also support other users consuming the component in templates
  307. // with `v-on` listeners.
  308. // with object emits
  309. defineComponent({
  310. emits: {
  311. click: (n: number) => typeof n === 'number',
  312. input: (b: string) => null
  313. },
  314. setup(props, { emit }) {
  315. emit('click', 1)
  316. emit('input', 'foo')
  317. expectError(emit('nope'))
  318. expectError(emit('click'))
  319. expectError(emit('click', 'foo'))
  320. expectError(emit('input'))
  321. expectError(emit('input', 1))
  322. },
  323. created() {
  324. this.$emit('click', 1)
  325. this.$emit('input', 'foo')
  326. expectError(this.$emit('nope'))
  327. expectError(this.$emit('click'))
  328. expectError(this.$emit('click', 'foo'))
  329. expectError(this.$emit('input'))
  330. expectError(this.$emit('input', 1))
  331. }
  332. })
  333. // with array emits
  334. defineComponent({
  335. emits: ['foo', 'bar'],
  336. setup(props, { emit }) {
  337. emit('foo')
  338. emit('foo', 123)
  339. emit('bar')
  340. expectError(emit('nope'))
  341. },
  342. created() {
  343. this.$emit('foo')
  344. this.$emit('foo', 123)
  345. this.$emit('bar')
  346. expectError(this.$emit('nope'))
  347. }
  348. })
  349. })