defineComponent.test-d.tsx 10 KB

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