apiCreateApp.spec.ts 11 KB

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