inject.spec.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507
  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. // Github issue #6008
  331. it('should merge provide from mixins (objects)', () => {
  332. const mixinA = { provide: { foo: 'foo' }}
  333. const mixinB = { provide: { bar: 'bar' }}
  334. const child = {
  335. inject: ['foo', 'bar'],
  336. template: `<span/>`,
  337. created () {
  338. injected = [this.foo, this.bar]
  339. }
  340. }
  341. new Vue({
  342. mixins: [mixinA, mixinB],
  343. render (h) {
  344. return h(child)
  345. }
  346. }).$mount()
  347. expect(injected).toEqual(['foo', 'bar'])
  348. })
  349. it('should merge provide from mixins (functions)', () => {
  350. const mixinA = { provide: () => ({ foo: 'foo' }) }
  351. const mixinB = { provide: () => ({ bar: 'bar' }) }
  352. const child = {
  353. inject: ['foo', 'bar'],
  354. template: `<span/>`,
  355. created () {
  356. injected = [this.foo, this.bar]
  357. }
  358. }
  359. new Vue({
  360. mixins: [mixinA, mixinB],
  361. render (h) {
  362. return h(child)
  363. }
  364. }).$mount()
  365. expect(injected).toEqual(['foo', 'bar'])
  366. })
  367. it('should merge provide from mixins (mix of objects and functions)', () => {
  368. const mixinA = { provide: { foo: 'foo' }}
  369. const mixinB = { provide: () => ({ bar: 'bar' }) }
  370. const mixinC = { provide: { baz: 'baz' }}
  371. const mixinD = { provide: () => ({ bam: 'bam' }) }
  372. const child = {
  373. inject: ['foo', 'bar', 'baz', 'bam'],
  374. template: `<span/>`,
  375. created () {
  376. injected = [this.foo, this.bar, this.baz, this.bam]
  377. }
  378. }
  379. new Vue({
  380. mixins: [mixinA, mixinB, mixinC, mixinD],
  381. render (h) {
  382. return h(child)
  383. }
  384. }).$mount()
  385. expect(injected).toEqual(['foo', 'bar', 'baz', 'bam'])
  386. })
  387. it('should merge provide from mixins and override existing keys', () => {
  388. const mixinA = { provide: { foo: 'foo' }}
  389. const mixinB = { provide: { foo: 'bar' }}
  390. const child = {
  391. inject: ['foo'],
  392. template: `<span/>`,
  393. created () {
  394. injected = [this.foo]
  395. }
  396. }
  397. new Vue({
  398. mixins: [mixinA, mixinB],
  399. render (h) {
  400. return h(child)
  401. }
  402. }).$mount()
  403. expect(injected).toEqual(['bar'])
  404. })
  405. it('should merge provide when Vue.extend', () => {
  406. const mixinA = { provide: () => ({ foo: 'foo' }) }
  407. const child = {
  408. inject: ['foo', 'bar'],
  409. template: `<span/>`,
  410. created () {
  411. injected = [this.foo, this.bar]
  412. }
  413. }
  414. const Ctor = Vue.extend({
  415. mixins: [mixinA],
  416. provide: { bar: 'bar' },
  417. render (h) {
  418. return h(child)
  419. }
  420. })
  421. new Ctor().$mount()
  422. expect(injected).toEqual(['foo', 'bar'])
  423. })
  424. // #5913
  425. it('should keep the reactive with provide', () => {
  426. function isObserver (obj) {
  427. if (isObject(obj)) {
  428. return hasOwn(obj, '__ob__') && obj.__ob__ instanceof Observer
  429. }
  430. return false
  431. }
  432. const vm = new Vue({
  433. template: `<div><child ref='child'></child></div>`,
  434. data () {
  435. return {
  436. foo: {},
  437. $foo: {},
  438. foo1: []
  439. }
  440. },
  441. provide () {
  442. return {
  443. foo: this.foo,
  444. $foo: this.$foo,
  445. foo1: this.foo1,
  446. bar: {},
  447. baz: []
  448. }
  449. },
  450. components: {
  451. child: {
  452. inject: ['foo', '$foo', 'foo1', 'bar', 'baz'],
  453. template: `<span/>`
  454. }
  455. }
  456. }).$mount()
  457. const child = vm.$refs.child
  458. expect(isObserver(child.foo)).toBe(true)
  459. expect(isObserver(child.$foo)).toBe(false)
  460. expect(isObserver(child.foo1)).toBe(true)
  461. expect(isObserver(child.bar)).toBe(false)
  462. expect(isObserver(child.baz)).toBe(false)
  463. })
  464. })