inject.spec.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576
  1. import Vue from 'vue'
  2. import { Observer } from 'core/observer/index'
  3. import { isNative, isObject, hasOwn } from 'core/util/index'
  4. describe('Options provide/inject', () => {
  5. let injected
  6. const injectedComp = {
  7. inject: ['foo', 'bar'],
  8. render () {},
  9. created () {
  10. injected = [this.foo, this.bar]
  11. }
  12. }
  13. beforeEach(() => {
  14. injected = null
  15. })
  16. it('should work', () => {
  17. new Vue({
  18. template: `<child/>`,
  19. provide: {
  20. foo: 1,
  21. bar: false
  22. },
  23. components: {
  24. child: {
  25. template: `<injected-comp/>`,
  26. components: {
  27. injectedComp
  28. }
  29. }
  30. }
  31. }).$mount()
  32. expect(injected).toEqual([1, false])
  33. })
  34. it('should use closest parent', () => {
  35. new Vue({
  36. template: `<child/>`,
  37. provide: {
  38. foo: 1,
  39. bar: null
  40. },
  41. components: {
  42. child: {
  43. provide: {
  44. foo: 3
  45. },
  46. template: `<injected-comp/>`,
  47. components: {
  48. injectedComp
  49. }
  50. }
  51. }
  52. }).$mount()
  53. expect(injected).toEqual([3, null])
  54. })
  55. it('provide function', () => {
  56. new Vue({
  57. template: `<child/>`,
  58. data: {
  59. a: 1,
  60. b: false
  61. },
  62. provide () {
  63. return {
  64. foo: this.a,
  65. bar: this.b
  66. }
  67. },
  68. components: {
  69. child: {
  70. template: `<injected-comp/>`,
  71. components: {
  72. injectedComp
  73. }
  74. }
  75. }
  76. }).$mount()
  77. expect(injected).toEqual([1, false])
  78. })
  79. it('inject with alias', () => {
  80. const injectAlias = {
  81. inject: {
  82. baz: 'foo',
  83. qux: 'bar'
  84. },
  85. render () {},
  86. created () {
  87. injected = [this.baz, this.qux]
  88. }
  89. }
  90. new Vue({
  91. template: `<child/>`,
  92. provide: {
  93. foo: false,
  94. bar: 2
  95. },
  96. components: {
  97. child: {
  98. template: `<inject-alias/>`,
  99. components: {
  100. injectAlias
  101. }
  102. }
  103. }
  104. }).$mount()
  105. expect(injected).toEqual([false, 2])
  106. })
  107. it('inject before resolving data/props', () => {
  108. const vm = new Vue({
  109. provide: {
  110. foo: 1
  111. }
  112. })
  113. const child = new Vue({
  114. parent: vm,
  115. inject: ['foo'],
  116. data () {
  117. return {
  118. bar: this.foo + 1
  119. }
  120. },
  121. props: {
  122. baz: {
  123. default () {
  124. return this.foo + 2
  125. }
  126. }
  127. }
  128. })
  129. expect(child.foo).toBe(1)
  130. expect(child.bar).toBe(2)
  131. expect(child.baz).toBe(3)
  132. })
  133. // GitHub issue #5194
  134. it('should work with functional', () => {
  135. new Vue({
  136. template: `<child/>`,
  137. provide: {
  138. foo: 1,
  139. bar: false
  140. },
  141. components: {
  142. child: {
  143. functional: true,
  144. inject: ['foo', 'bar'],
  145. render (h, context) {
  146. const { injections } = context
  147. injected = [injections.foo, injections.bar]
  148. }
  149. }
  150. }
  151. }).$mount()
  152. expect(injected).toEqual([1, false])
  153. })
  154. if (typeof Reflect !== 'undefined' && isNative(Reflect.ownKeys)) {
  155. it('with Symbol keys', () => {
  156. const s = Symbol()
  157. const vm = new Vue({
  158. template: `<child/>`,
  159. provide: {
  160. [s]: 123
  161. },
  162. components: {
  163. child: {
  164. inject: { s },
  165. template: `<div>{{ s }}</div>`
  166. }
  167. }
  168. }).$mount()
  169. expect(vm.$el.textContent).toBe('123')
  170. })
  171. }
  172. // GitHub issue #5223
  173. it('should work with reactive array', done => {
  174. const vm = new Vue({
  175. template: `<div><child></child></div>`,
  176. data () {
  177. return {
  178. foo: []
  179. }
  180. },
  181. provide () {
  182. return {
  183. foo: this.foo
  184. }
  185. },
  186. components: {
  187. child: {
  188. inject: ['foo'],
  189. template: `<span>{{foo.length}}</span>`
  190. }
  191. }
  192. }).$mount()
  193. expect(vm.$el.innerHTML).toEqual(`<span>0</span>`)
  194. vm.foo.push(vm.foo.length)
  195. vm.$nextTick(() => {
  196. expect(vm.$el.innerHTML).toEqual(`<span>1</span>`)
  197. vm.foo.pop()
  198. vm.$nextTick(() => {
  199. expect(vm.$el.innerHTML).toEqual(`<span>0</span>`)
  200. done()
  201. })
  202. })
  203. })
  204. it('should extend properly', () => {
  205. const parent = Vue.extend({
  206. template: `<span/>`,
  207. inject: ['foo']
  208. })
  209. const child = parent.extend({
  210. template: `<span/>`,
  211. inject: ['bar'],
  212. created () {
  213. injected = [this.foo, this.bar]
  214. }
  215. })
  216. new Vue({
  217. template: `<div><parent/><child/></div>`,
  218. provide: {
  219. foo: 1,
  220. bar: false
  221. },
  222. components: {
  223. parent,
  224. child
  225. }
  226. }).$mount()
  227. expect(injected).toEqual([1, false])
  228. })
  229. it('should merge from mixins properly (objects)', () => {
  230. const mixinA = { inject: { foo: 'foo' }}
  231. const mixinB = { inject: { bar: 'bar' }}
  232. const child = {
  233. mixins: [mixinA, mixinB],
  234. template: `<span/>`,
  235. created () {
  236. injected = [this.foo, this.bar]
  237. }
  238. }
  239. new Vue({
  240. provide: { foo: 'foo', bar: 'bar', baz: 'baz' },
  241. render (h) {
  242. return h(child)
  243. }
  244. }).$mount()
  245. expect(injected).toEqual(['foo', 'bar'])
  246. })
  247. it('should merge from mixins properly (arrays)', () => {
  248. const mixinA = { inject: ['foo'] }
  249. const mixinB = { inject: ['bar'] }
  250. const child = {
  251. mixins: [mixinA, mixinB],
  252. inject: ['baz'],
  253. template: `<span/>`,
  254. created () {
  255. injected = [this.foo, this.bar, this.baz]
  256. }
  257. }
  258. new Vue({
  259. provide: { foo: 'foo', bar: 'bar', baz: 'baz' },
  260. render (h) {
  261. return h(child)
  262. }
  263. }).$mount()
  264. expect(injected).toEqual(['foo', 'bar', 'baz'])
  265. })
  266. it('should merge from mixins properly (mix of objects and arrays)', () => {
  267. const mixinA = { inject: { foo: 'foo' }}
  268. const mixinB = { inject: ['bar'] }
  269. const child = {
  270. mixins: [mixinA, mixinB],
  271. inject: { qux: 'baz' },
  272. template: `<span/>`,
  273. created () {
  274. injected = [this.foo, this.bar, this.qux]
  275. }
  276. }
  277. new Vue({
  278. provide: { foo: 'foo', bar: 'bar', baz: 'baz' },
  279. render (h) {
  280. return h(child)
  281. }
  282. }).$mount()
  283. expect(injected).toEqual(['foo', 'bar', 'baz'])
  284. })
  285. it('should warn when injections has been modified', () => {
  286. const key = 'foo'
  287. const vm = new Vue({
  288. provide: {
  289. foo: 1
  290. }
  291. })
  292. const child = new Vue({
  293. parent: vm,
  294. inject: ['foo']
  295. })
  296. expect(child.foo).toBe(1)
  297. child.foo = 2
  298. expect(
  299. `Avoid mutating an injected value directly since the changes will be ` +
  300. `overwritten whenever the provided component re-renders. ` +
  301. `injection being mutated: "${key}"`).toHaveBeenWarned()
  302. })
  303. it('should warn when injections cannot be found', () => {
  304. const vm = new Vue({})
  305. new Vue({
  306. parent: vm,
  307. inject: ['foo', 'bar'],
  308. created () {}
  309. })
  310. expect(`Injection "foo" not found`).toHaveBeenWarned()
  311. expect(`Injection "bar" not found`).toHaveBeenWarned()
  312. })
  313. it('should not warn when injections can be found', () => {
  314. const vm = new Vue({
  315. provide: {
  316. foo: 1,
  317. bar: false,
  318. baz: undefined
  319. }
  320. })
  321. new Vue({
  322. parent: vm,
  323. inject: ['foo', 'bar', 'baz'],
  324. created () {}
  325. })
  326. expect(`Injection "foo" not found`).not.toHaveBeenWarned()
  327. expect(`Injection "bar" not found`).not.toHaveBeenWarned()
  328. expect(`Injection "baz" not found`).not.toHaveBeenWarned()
  329. })
  330. it('should not warn when injection key which is not provided is not enumerable', () => {
  331. const parent = new Vue({ provide: { foo: 1 }})
  332. const inject = { foo: 'foo' }
  333. Object.defineProperty(inject, '__ob__', { enumerable: false, value: '__ob__' })
  334. new Vue({ parent, inject })
  335. expect(`Injection "__ob__" not found`).not.toHaveBeenWarned()
  336. })
  337. // Github issue #6008
  338. it('should merge provide from mixins (objects)', () => {
  339. const mixinA = { provide: { foo: 'foo' }}
  340. const mixinB = { provide: { bar: 'bar' }}
  341. const child = {
  342. inject: ['foo', 'bar'],
  343. template: `<span/>`,
  344. created () {
  345. injected = [this.foo, this.bar]
  346. }
  347. }
  348. new Vue({
  349. mixins: [mixinA, mixinB],
  350. render (h) {
  351. return h(child)
  352. }
  353. }).$mount()
  354. expect(injected).toEqual(['foo', 'bar'])
  355. })
  356. it('should merge provide from mixins (functions)', () => {
  357. const mixinA = { provide: () => ({ foo: 'foo' }) }
  358. const mixinB = { provide: () => ({ bar: 'bar' }) }
  359. const child = {
  360. inject: ['foo', 'bar'],
  361. template: `<span/>`,
  362. created () {
  363. injected = [this.foo, this.bar]
  364. }
  365. }
  366. new Vue({
  367. mixins: [mixinA, mixinB],
  368. render (h) {
  369. return h(child)
  370. }
  371. }).$mount()
  372. expect(injected).toEqual(['foo', 'bar'])
  373. })
  374. it('should merge provide from mixins (mix of objects and functions)', () => {
  375. const mixinA = { provide: { foo: 'foo' }}
  376. const mixinB = { provide: () => ({ bar: 'bar' }) }
  377. const mixinC = { provide: { baz: 'baz' }}
  378. const mixinD = { provide: () => ({ bam: 'bam' }) }
  379. const child = {
  380. inject: ['foo', 'bar', 'baz', 'bam'],
  381. template: `<span/>`,
  382. created () {
  383. injected = [this.foo, this.bar, this.baz, this.bam]
  384. }
  385. }
  386. new Vue({
  387. mixins: [mixinA, mixinB, mixinC, mixinD],
  388. render (h) {
  389. return h(child)
  390. }
  391. }).$mount()
  392. expect(injected).toEqual(['foo', 'bar', 'baz', 'bam'])
  393. })
  394. it('should merge provide from mixins and override existing keys', () => {
  395. const mixinA = { provide: { foo: 'foo' }}
  396. const mixinB = { provide: { foo: 'bar' }}
  397. const child = {
  398. inject: ['foo'],
  399. template: `<span/>`,
  400. created () {
  401. injected = [this.foo]
  402. }
  403. }
  404. new Vue({
  405. mixins: [mixinA, mixinB],
  406. render (h) {
  407. return h(child)
  408. }
  409. }).$mount()
  410. expect(injected).toEqual(['bar'])
  411. })
  412. it('should merge provide when Vue.extend', () => {
  413. const mixinA = { provide: () => ({ foo: 'foo' }) }
  414. const child = {
  415. inject: ['foo', 'bar'],
  416. template: `<span/>`,
  417. created () {
  418. injected = [this.foo, this.bar]
  419. }
  420. }
  421. const Ctor = Vue.extend({
  422. mixins: [mixinA],
  423. provide: { bar: 'bar' },
  424. render (h) {
  425. return h(child)
  426. }
  427. })
  428. new Ctor().$mount()
  429. expect(injected).toEqual(['foo', 'bar'])
  430. })
  431. // #5913
  432. it('should keep the reactive with provide', () => {
  433. function isObserver (obj) {
  434. if (isObject(obj)) {
  435. return hasOwn(obj, '__ob__') && obj.__ob__ instanceof Observer
  436. }
  437. return false
  438. }
  439. const vm = new Vue({
  440. template: `<div><child ref='child'></child></div>`,
  441. data () {
  442. return {
  443. foo: {},
  444. $foo: {},
  445. foo1: []
  446. }
  447. },
  448. provide () {
  449. return {
  450. foo: this.foo,
  451. $foo: this.$foo,
  452. foo1: this.foo1,
  453. bar: {},
  454. baz: []
  455. }
  456. },
  457. components: {
  458. child: {
  459. inject: ['foo', '$foo', 'foo1', 'bar', 'baz'],
  460. template: `<span/>`
  461. }
  462. }
  463. }).$mount()
  464. const child = vm.$refs.child
  465. expect(isObserver(child.foo)).toBe(true)
  466. expect(isObserver(child.$foo)).toBe(false)
  467. expect(isObserver(child.foo1)).toBe(true)
  468. expect(isObserver(child.bar)).toBe(false)
  469. expect(isObserver(child.baz)).toBe(false)
  470. })
  471. // #6175
  472. it('merge provide properly from mixins', () => {
  473. const ProvideFooMixin = {
  474. provide: {
  475. foo: 'foo injected'
  476. }
  477. }
  478. const ProvideBarMixin = {
  479. provide: {
  480. bar: 'bar injected'
  481. }
  482. }
  483. const Child = {
  484. inject: ['foo', 'bar'],
  485. render (h) {
  486. return h('div', [`foo: ${this.foo}, `, `bar: ${this.bar}`])
  487. }
  488. }
  489. const Parent = {
  490. mixins: [ProvideFooMixin, ProvideBarMixin],
  491. render (h) {
  492. return h(Child)
  493. }
  494. }
  495. const vm = new Vue({
  496. render (h) {
  497. return h(Parent)
  498. }
  499. }).$mount()
  500. expect(vm.$el.textContent).toBe(`foo: foo injected, bar: bar injected`)
  501. })
  502. it('merge provide with object syntax when using Vue.extend', () => {
  503. const child = {
  504. inject: ['foo'],
  505. template: `<span/>`,
  506. created () {
  507. injected = this.foo
  508. }
  509. }
  510. const Ctor = Vue.extend({
  511. provide: { foo: 'foo' },
  512. render (h) {
  513. return h(child)
  514. }
  515. })
  516. new Ctor().$mount()
  517. expect(injected).toEqual('foo')
  518. })
  519. })