apiOptions.spec.ts 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508
  1. import {
  2. h,
  3. nodeOps,
  4. render,
  5. serializeInner,
  6. triggerEvent,
  7. TestElement,
  8. nextTick,
  9. renderToString,
  10. ref,
  11. createComponent
  12. } from '@vue/runtime-test'
  13. describe('api: options', () => {
  14. test('data', async () => {
  15. const Comp = createComponent({
  16. data() {
  17. return {
  18. foo: 1
  19. }
  20. },
  21. render() {
  22. return h(
  23. 'div',
  24. {
  25. onClick: () => {
  26. this.foo++
  27. }
  28. },
  29. this.foo
  30. )
  31. }
  32. })
  33. const root = nodeOps.createElement('div')
  34. render(h(Comp), root)
  35. expect(serializeInner(root)).toBe(`<div>1</div>`)
  36. triggerEvent(root.children[0] as TestElement, 'click')
  37. await nextTick()
  38. expect(serializeInner(root)).toBe(`<div>2</div>`)
  39. })
  40. test('computed', async () => {
  41. const Comp = createComponent({
  42. data() {
  43. return {
  44. foo: 1
  45. }
  46. },
  47. computed: {
  48. bar(): number {
  49. return this.foo + 1
  50. },
  51. baz(): number {
  52. return this.bar + 1
  53. }
  54. },
  55. render() {
  56. return h(
  57. 'div',
  58. {
  59. onClick: () => {
  60. this.foo++
  61. }
  62. },
  63. this.bar + this.baz
  64. )
  65. }
  66. })
  67. const root = nodeOps.createElement('div')
  68. render(h(Comp), root)
  69. expect(serializeInner(root)).toBe(`<div>5</div>`)
  70. triggerEvent(root.children[0] as TestElement, 'click')
  71. await nextTick()
  72. expect(serializeInner(root)).toBe(`<div>7</div>`)
  73. })
  74. test('methods', async () => {
  75. const Comp = createComponent({
  76. data() {
  77. return {
  78. foo: 1
  79. }
  80. },
  81. methods: {
  82. inc() {
  83. this.foo++
  84. }
  85. },
  86. render() {
  87. return h(
  88. 'div',
  89. {
  90. onClick: this.inc
  91. },
  92. this.foo
  93. )
  94. }
  95. })
  96. const root = nodeOps.createElement('div')
  97. render(h(Comp), root)
  98. expect(serializeInner(root)).toBe(`<div>1</div>`)
  99. triggerEvent(root.children[0] as TestElement, 'click')
  100. await nextTick()
  101. expect(serializeInner(root)).toBe(`<div>2</div>`)
  102. })
  103. test('watch', async () => {
  104. function returnThis(this: any) {
  105. return this
  106. }
  107. const spyA = jest.fn(returnThis)
  108. const spyB = jest.fn(returnThis)
  109. const spyC = jest.fn(returnThis)
  110. let ctx: any
  111. const Comp = {
  112. data() {
  113. return {
  114. foo: 1,
  115. bar: 2,
  116. baz: {
  117. qux: 3
  118. }
  119. }
  120. },
  121. watch: {
  122. // string method name
  123. foo: 'onFooChange',
  124. // direct function
  125. bar: spyB,
  126. baz: {
  127. handler: spyC,
  128. deep: true
  129. }
  130. },
  131. methods: {
  132. onFooChange: spyA
  133. },
  134. render() {
  135. ctx = this
  136. }
  137. }
  138. const root = nodeOps.createElement('div')
  139. render(h(Comp), root)
  140. function assertCall(spy: jest.Mock, callIndex: number, args: any[]) {
  141. expect(spy.mock.calls[callIndex].slice(0, 2)).toMatchObject(args)
  142. }
  143. assertCall(spyA, 0, [1, undefined])
  144. assertCall(spyB, 0, [2, undefined])
  145. assertCall(spyC, 0, [{ qux: 3 }, undefined])
  146. expect(spyA).toHaveReturnedWith(ctx)
  147. expect(spyB).toHaveReturnedWith(ctx)
  148. expect(spyC).toHaveReturnedWith(ctx)
  149. ctx.foo++
  150. await nextTick()
  151. expect(spyA).toHaveBeenCalledTimes(2)
  152. assertCall(spyA, 1, [2, 1])
  153. ctx.bar++
  154. await nextTick()
  155. expect(spyB).toHaveBeenCalledTimes(2)
  156. assertCall(spyB, 1, [3, 2])
  157. ctx.baz.qux++
  158. await nextTick()
  159. expect(spyC).toHaveBeenCalledTimes(2)
  160. // new and old objects have same identity
  161. assertCall(spyC, 1, [{ qux: 4 }, { qux: 4 }])
  162. })
  163. test('provide/inject', () => {
  164. const Root = {
  165. data() {
  166. return {
  167. a: 1
  168. }
  169. },
  170. provide() {
  171. return {
  172. a: this.a
  173. }
  174. },
  175. render() {
  176. return [h(ChildA), h(ChildB), h(ChildC), h(ChildD)]
  177. }
  178. } as any
  179. const ChildA = {
  180. inject: ['a'],
  181. render() {
  182. return this.a
  183. }
  184. } as any
  185. const ChildB = {
  186. // object alias
  187. inject: { b: 'a' },
  188. render() {
  189. return this.b
  190. }
  191. } as any
  192. const ChildC = {
  193. inject: {
  194. b: {
  195. from: 'a'
  196. }
  197. },
  198. render() {
  199. return this.b
  200. }
  201. } as any
  202. const ChildD = {
  203. inject: {
  204. b: {
  205. from: 'c',
  206. default: 2
  207. }
  208. },
  209. render() {
  210. return this.b
  211. }
  212. } as any
  213. expect(renderToString(h(Root))).toBe(`<!---->1112<!---->`)
  214. })
  215. test('lifecycle', async () => {
  216. const count = ref(0)
  217. const root = nodeOps.createElement('div')
  218. const calls: string[] = []
  219. const Root = {
  220. beforeCreate() {
  221. calls.push('root beforeCreate')
  222. },
  223. created() {
  224. calls.push('root created')
  225. },
  226. beforeMount() {
  227. calls.push('root onBeforeMount')
  228. },
  229. mounted() {
  230. calls.push('root onMounted')
  231. },
  232. beforeUpdate() {
  233. calls.push('root onBeforeUpdate')
  234. },
  235. updated() {
  236. calls.push('root onUpdated')
  237. },
  238. beforeUnmount() {
  239. calls.push('root onBeforeUnmount')
  240. },
  241. unmounted() {
  242. calls.push('root onUnmounted')
  243. },
  244. render() {
  245. return h(Mid, { count: count.value })
  246. }
  247. }
  248. const Mid = {
  249. beforeCreate() {
  250. calls.push('mid beforeCreate')
  251. },
  252. created() {
  253. calls.push('mid created')
  254. },
  255. beforeMount() {
  256. calls.push('mid onBeforeMount')
  257. },
  258. mounted() {
  259. calls.push('mid onMounted')
  260. },
  261. beforeUpdate() {
  262. calls.push('mid onBeforeUpdate')
  263. },
  264. updated() {
  265. calls.push('mid onUpdated')
  266. },
  267. beforeUnmount() {
  268. calls.push('mid onBeforeUnmount')
  269. },
  270. unmounted() {
  271. calls.push('mid onUnmounted')
  272. },
  273. render(this: any) {
  274. return h(Child, { count: this.$props.count })
  275. }
  276. }
  277. const Child = {
  278. beforeCreate() {
  279. calls.push('child beforeCreate')
  280. },
  281. created() {
  282. calls.push('child created')
  283. },
  284. beforeMount() {
  285. calls.push('child onBeforeMount')
  286. },
  287. mounted() {
  288. calls.push('child onMounted')
  289. },
  290. beforeUpdate() {
  291. calls.push('child onBeforeUpdate')
  292. },
  293. updated() {
  294. calls.push('child onUpdated')
  295. },
  296. beforeUnmount() {
  297. calls.push('child onBeforeUnmount')
  298. },
  299. unmounted() {
  300. calls.push('child onUnmounted')
  301. },
  302. render(this: any) {
  303. return h('div', this.$props.count)
  304. }
  305. }
  306. // mount
  307. render(h(Root), root)
  308. expect(calls).toEqual([
  309. 'root beforeCreate',
  310. 'root created',
  311. 'root onBeforeMount',
  312. 'mid beforeCreate',
  313. 'mid created',
  314. 'mid onBeforeMount',
  315. 'child beforeCreate',
  316. 'child created',
  317. 'child onBeforeMount',
  318. 'child onMounted',
  319. 'mid onMounted',
  320. 'root onMounted'
  321. ])
  322. calls.length = 0
  323. // update
  324. count.value++
  325. await nextTick()
  326. expect(calls).toEqual([
  327. 'root onBeforeUpdate',
  328. 'mid onBeforeUpdate',
  329. 'child onBeforeUpdate',
  330. 'child onUpdated',
  331. 'mid onUpdated',
  332. 'root onUpdated'
  333. ])
  334. calls.length = 0
  335. // unmount
  336. render(null, root)
  337. expect(calls).toEqual([
  338. 'root onBeforeUnmount',
  339. 'mid onBeforeUnmount',
  340. 'child onBeforeUnmount',
  341. 'child onUnmounted',
  342. 'mid onUnmounted',
  343. 'root onUnmounted'
  344. ])
  345. })
  346. test('mixins', () => {
  347. const calls: string[] = []
  348. const mixinA = {
  349. data() {
  350. return {
  351. a: 1
  352. }
  353. },
  354. created(this: any) {
  355. calls.push('mixinA created')
  356. expect(this.a).toBe(1)
  357. expect(this.b).toBe(2)
  358. expect(this.c).toBe(3)
  359. },
  360. mounted() {
  361. calls.push('mixinA mounted')
  362. }
  363. }
  364. const mixinB = {
  365. data() {
  366. return {
  367. b: 2
  368. }
  369. },
  370. created(this: any) {
  371. calls.push('mixinB created')
  372. expect(this.a).toBe(1)
  373. expect(this.b).toBe(2)
  374. expect(this.c).toBe(3)
  375. },
  376. mounted() {
  377. calls.push('mixinB mounted')
  378. }
  379. }
  380. const Comp = {
  381. mixins: [mixinA, mixinB],
  382. data() {
  383. return {
  384. c: 3
  385. }
  386. },
  387. created(this: any) {
  388. calls.push('comp created')
  389. expect(this.a).toBe(1)
  390. expect(this.b).toBe(2)
  391. expect(this.c).toBe(3)
  392. },
  393. mounted() {
  394. calls.push('comp mounted')
  395. },
  396. render(this: any) {
  397. return `${this.a}${this.b}${this.c}`
  398. }
  399. }
  400. expect(renderToString(h(Comp))).toBe(`123`)
  401. expect(calls).toEqual([
  402. 'mixinA created',
  403. 'mixinB created',
  404. 'comp created',
  405. 'mixinA mounted',
  406. 'mixinB mounted',
  407. 'comp mounted'
  408. ])
  409. })
  410. test('extends', () => {
  411. const calls: string[] = []
  412. const Base = {
  413. data() {
  414. return {
  415. a: 1
  416. }
  417. },
  418. mounted() {
  419. calls.push('base')
  420. }
  421. }
  422. const Comp = {
  423. extends: Base,
  424. data() {
  425. return {
  426. b: 2
  427. }
  428. },
  429. mounted() {
  430. calls.push('comp')
  431. },
  432. render(this: any) {
  433. return `${this.a}${this.b}`
  434. }
  435. }
  436. expect(renderToString(h(Comp))).toBe(`12`)
  437. expect(calls).toEqual(['base', 'comp'])
  438. })
  439. test('accessing setup() state from options', async () => {
  440. const Comp = createComponent({
  441. setup() {
  442. return {
  443. count: ref(0)
  444. }
  445. },
  446. data() {
  447. return {
  448. plusOne: (this as any).count + 1
  449. }
  450. },
  451. computed: {
  452. plusTwo(): number {
  453. return this.count + 2
  454. }
  455. },
  456. methods: {
  457. inc() {
  458. this.count++
  459. }
  460. },
  461. render() {
  462. return h(
  463. 'div',
  464. {
  465. onClick: this.inc
  466. },
  467. `${this.count},${this.plusOne},${this.plusTwo}`
  468. )
  469. }
  470. })
  471. const root = nodeOps.createElement('div')
  472. render(h(Comp), root)
  473. expect(serializeInner(root)).toBe(`<div>0,1,2</div>`)
  474. triggerEvent(root.children[0] as TestElement, 'click')
  475. await nextTick()
  476. expect(serializeInner(root)).toBe(`<div>1,1,3</div>`)
  477. })
  478. })