apiCreateApp.spec.ts 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443
  1. import {
  2. createApp,
  3. h,
  4. nodeOps,
  5. serializeInner,
  6. provide,
  7. inject,
  8. resolveComponent,
  9. resolveDirective,
  10. withDirectives,
  11. Plugin,
  12. ref,
  13. getCurrentInstance,
  14. defineComponent
  15. } from '@vue/runtime-test'
  16. import { mockWarn } from '@vue/shared'
  17. describe('api: createApp', () => {
  18. mockWarn()
  19. test('mount', () => {
  20. const Comp = defineComponent({
  21. props: {
  22. count: {
  23. default: 0
  24. }
  25. },
  26. setup(props) {
  27. return () => props.count
  28. }
  29. })
  30. const root1 = nodeOps.createElement('div')
  31. createApp(Comp).mount(root1)
  32. expect(serializeInner(root1)).toBe(`0`)
  33. // mount with props
  34. const root2 = nodeOps.createElement('div')
  35. const app2 = createApp(Comp, { count: 1 })
  36. app2.mount(root2)
  37. expect(serializeInner(root2)).toBe(`1`)
  38. // remount warning
  39. const root3 = nodeOps.createElement('div')
  40. app2.mount(root3)
  41. expect(serializeInner(root3)).toBe(``)
  42. expect(`already been mounted`).toHaveBeenWarned()
  43. })
  44. test('unmount', () => {
  45. const Comp = defineComponent({
  46. props: {
  47. count: {
  48. default: 0
  49. }
  50. },
  51. setup(props) {
  52. return () => props.count
  53. }
  54. })
  55. const root = nodeOps.createElement('div')
  56. const app = createApp(Comp)
  57. app.mount(root)
  58. app.unmount(root)
  59. expect(serializeInner(root)).toBe(``)
  60. })
  61. test('provide', () => {
  62. const Root = {
  63. setup() {
  64. // test override
  65. provide('foo', 3)
  66. return () => h(Child)
  67. }
  68. }
  69. const Child = {
  70. setup() {
  71. const foo = inject('foo')
  72. const bar = inject('bar')
  73. try {
  74. inject('__proto__')
  75. } catch (e) {}
  76. return () => `${foo},${bar}`
  77. }
  78. }
  79. const app = createApp(Root)
  80. app.provide('foo', 1)
  81. app.provide('bar', 2)
  82. const root = nodeOps.createElement('div')
  83. app.mount(root)
  84. expect(serializeInner(root)).toBe(`3,2`)
  85. expect('[Vue warn]: injection "__proto__" not found.').toHaveBeenWarned()
  86. })
  87. test('component', () => {
  88. const Root = {
  89. // local override
  90. components: {
  91. BarBaz: () => 'barbaz-local!'
  92. },
  93. setup() {
  94. // resolve in setup
  95. const FooBar = resolveComponent('foo-bar') as any
  96. return () => {
  97. // resolve in render
  98. const BarBaz = resolveComponent('bar-baz') as any
  99. return h('div', [h(FooBar), h(BarBaz)])
  100. }
  101. }
  102. }
  103. const app = createApp(Root)
  104. const FooBar = () => 'foobar!'
  105. app.component('FooBar', FooBar)
  106. expect(app.component('FooBar')).toBe(FooBar)
  107. app.component('BarBaz', () => 'barbaz!')
  108. app.component('BarBaz', () => 'barbaz!')
  109. expect(
  110. 'Component "BarBaz" has already been registered in target app.'
  111. ).toHaveBeenWarnedTimes(1)
  112. const root = nodeOps.createElement('div')
  113. app.mount(root)
  114. expect(serializeInner(root)).toBe(`<div>foobar!barbaz-local!</div>`)
  115. })
  116. test('directive', () => {
  117. const spy1 = jest.fn()
  118. const spy2 = jest.fn()
  119. const spy3 = jest.fn()
  120. const Root = {
  121. // local override
  122. directives: {
  123. BarBaz: { mounted: spy3 }
  124. },
  125. setup() {
  126. // resolve in setup
  127. const FooBar = resolveDirective('foo-bar')!
  128. return () => {
  129. // resolve in render
  130. const BarBaz = resolveDirective('bar-baz')!
  131. return withDirectives(h('div'), [[FooBar], [BarBaz]])
  132. }
  133. }
  134. }
  135. const app = createApp(Root)
  136. const FooBar = { mounted: spy1 }
  137. app.directive('FooBar', FooBar)
  138. expect(app.directive('FooBar')).toBe(FooBar)
  139. app.directive('BarBaz', {
  140. mounted: spy2
  141. })
  142. app.directive('BarBaz', {
  143. mounted: spy2
  144. })
  145. expect(
  146. 'Directive "BarBaz" has already been registered in target app.'
  147. ).toHaveBeenWarnedTimes(1)
  148. const root = nodeOps.createElement('div')
  149. app.mount(root)
  150. expect(spy1).toHaveBeenCalled()
  151. expect(spy2).not.toHaveBeenCalled()
  152. expect(spy3).toHaveBeenCalled()
  153. app.directive('bind', FooBar)
  154. expect(
  155. `Do not use built-in directive ids as custom directive id: bind`
  156. ).toHaveBeenWarned()
  157. })
  158. test('mixin', () => {
  159. const calls: string[] = []
  160. const mixinA = {
  161. data() {
  162. return {
  163. a: 1
  164. }
  165. },
  166. created(this: any) {
  167. calls.push('mixinA created')
  168. expect(this.a).toBe(1)
  169. expect(this.b).toBe(2)
  170. expect(this.c).toBe(3)
  171. },
  172. mounted() {
  173. calls.push('mixinA mounted')
  174. }
  175. }
  176. const mixinB = {
  177. name: 'mixinB',
  178. data() {
  179. return {
  180. b: 2
  181. }
  182. },
  183. created(this: any) {
  184. calls.push('mixinB created')
  185. expect(this.a).toBe(1)
  186. expect(this.b).toBe(2)
  187. expect(this.c).toBe(3)
  188. },
  189. mounted() {
  190. calls.push('mixinB mounted')
  191. }
  192. }
  193. const Comp = {
  194. data() {
  195. return {
  196. c: 3
  197. }
  198. },
  199. created(this: any) {
  200. calls.push('comp created')
  201. expect(this.a).toBe(1)
  202. expect(this.b).toBe(2)
  203. expect(this.c).toBe(3)
  204. },
  205. mounted() {
  206. calls.push('comp mounted')
  207. },
  208. render(this: any) {
  209. return `${this.a}${this.b}${this.c}`
  210. }
  211. }
  212. const app = createApp(Comp)
  213. app.mixin(mixinA)
  214. app.mixin(mixinB)
  215. app.mixin(mixinA)
  216. app.mixin(mixinB)
  217. expect(
  218. 'Mixin has already been applied to target app'
  219. ).toHaveBeenWarnedTimes(2)
  220. expect(
  221. 'Mixin has already been applied to target app: mixinB'
  222. ).toHaveBeenWarnedTimes(1)
  223. const root = nodeOps.createElement('div')
  224. app.mount(root)
  225. expect(serializeInner(root)).toBe(`123`)
  226. expect(calls).toEqual([
  227. 'mixinA created',
  228. 'mixinB created',
  229. 'comp created',
  230. 'mixinA mounted',
  231. 'mixinB mounted',
  232. 'comp mounted'
  233. ])
  234. })
  235. test('use', () => {
  236. const PluginA: Plugin = app => app.provide('foo', 1)
  237. const PluginB: Plugin = {
  238. install: (app, arg1, arg2) => app.provide('bar', arg1 + arg2)
  239. }
  240. class PluginC {
  241. someProperty = {}
  242. static install() {
  243. app.provide('baz', 2)
  244. }
  245. }
  246. const PluginD: any = undefined
  247. const Root = {
  248. setup() {
  249. const foo = inject('foo')
  250. const bar = inject('bar')
  251. return () => `${foo},${bar}`
  252. }
  253. }
  254. const app = createApp(Root)
  255. app.use(PluginA)
  256. app.use(PluginB, 1, 1)
  257. app.use(PluginC)
  258. const root = nodeOps.createElement('div')
  259. app.mount(root)
  260. expect(serializeInner(root)).toBe(`1,2`)
  261. app.use(PluginA)
  262. expect(
  263. `Plugin has already been applied to target app`
  264. ).toHaveBeenWarnedTimes(1)
  265. app.use(PluginD)
  266. expect(
  267. `A plugin must either be a function or an object with an "install" ` +
  268. `function.`
  269. ).toHaveBeenWarnedTimes(1)
  270. })
  271. test('config.errorHandler', () => {
  272. const error = new Error()
  273. const count = ref(0)
  274. const handler = jest.fn((err, instance, info) => {
  275. expect(err).toBe(error)
  276. expect((instance as any).count).toBe(count.value)
  277. expect(info).toBe(`render function`)
  278. })
  279. const Root = {
  280. setup() {
  281. const count = ref(0)
  282. return {
  283. count
  284. }
  285. },
  286. render() {
  287. throw error
  288. }
  289. }
  290. const app = createApp(Root)
  291. app.config.errorHandler = handler
  292. app.mount(nodeOps.createElement('div'))
  293. expect(handler).toHaveBeenCalled()
  294. })
  295. test('config.warnHandler', () => {
  296. let ctx: any
  297. const handler = jest.fn((msg, instance, trace) => {
  298. expect(msg).toMatch(`Component is missing template or render function`)
  299. expect(instance).toBe(ctx.proxy)
  300. expect(trace).toMatch(`Hello`)
  301. })
  302. const Root = {
  303. name: 'Hello',
  304. setup() {
  305. ctx = getCurrentInstance()
  306. }
  307. }
  308. const app = createApp(Root)
  309. app.config.warnHandler = handler
  310. app.mount(nodeOps.createElement('div'))
  311. expect(handler).toHaveBeenCalledTimes(1)
  312. })
  313. describe('config.isNativeTag', () => {
  314. const isNativeTag = jest.fn(tag => tag === 'div')
  315. test('Component.name', () => {
  316. const Root = {
  317. name: 'div',
  318. render() {
  319. return null
  320. }
  321. }
  322. const app = createApp(Root)
  323. Object.defineProperty(app.config, 'isNativeTag', {
  324. value: isNativeTag,
  325. writable: false
  326. })
  327. app.mount(nodeOps.createElement('div'))
  328. expect(
  329. `Do not use built-in or reserved HTML elements as component id: div`
  330. ).toHaveBeenWarned()
  331. })
  332. test('Component.components', () => {
  333. const Root = {
  334. components: {
  335. div: () => 'div'
  336. },
  337. render() {
  338. return null
  339. }
  340. }
  341. const app = createApp(Root)
  342. Object.defineProperty(app.config, 'isNativeTag', {
  343. value: isNativeTag,
  344. writable: false
  345. })
  346. app.mount(nodeOps.createElement('div'))
  347. expect(
  348. `Do not use built-in or reserved HTML elements as component id: div`
  349. ).toHaveBeenWarned()
  350. })
  351. test('Component.directives', () => {
  352. const Root = {
  353. directives: {
  354. bind: () => {}
  355. },
  356. render() {
  357. return null
  358. }
  359. }
  360. const app = createApp(Root)
  361. Object.defineProperty(app.config, 'isNativeTag', {
  362. value: isNativeTag,
  363. writable: false
  364. })
  365. app.mount(nodeOps.createElement('div'))
  366. expect(
  367. `Do not use built-in directive ids as custom directive id: bind`
  368. ).toHaveBeenWarned()
  369. })
  370. test('register using app.component', () => {
  371. const app = createApp({
  372. render() {}
  373. })
  374. Object.defineProperty(app.config, 'isNativeTag', {
  375. value: isNativeTag,
  376. writable: false
  377. })
  378. app.component('div', () => 'div')
  379. app.mount(nodeOps.createElement('div'))
  380. expect(
  381. `Do not use built-in or reserved HTML elements as component id: div`
  382. ).toHaveBeenWarned()
  383. })
  384. })
  385. })