modules.spec.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695
  1. import Vue from 'vue'
  2. import Vuex from '../../dist/vuex.common.js'
  3. const TEST = 'TEST'
  4. describe('Modules', () => {
  5. describe('module registration', () => {
  6. it('dynamic module registration', () => {
  7. const store = new Vuex.Store({
  8. strict: true,
  9. modules: {
  10. foo: {
  11. state: { bar: 1 },
  12. mutations: { inc: state => state.bar++ },
  13. actions: { incFoo: ({ commit }) => commit('inc') },
  14. getters: { bar: state => state.bar }
  15. }
  16. }
  17. })
  18. expect(() => {
  19. store.registerModule('hi', {
  20. state: { a: 1 },
  21. mutations: { inc: state => state.a++ },
  22. actions: { inc: ({ commit }) => commit('inc') },
  23. getters: { a: state => state.a }
  24. })
  25. }).not.toThrow()
  26. expect(store._mutations.inc.length).toBe(2)
  27. expect(store.state.hi.a).toBe(1)
  28. expect(store.getters.a).toBe(1)
  29. // assert initial modules work as expected after dynamic registration
  30. expect(store.state.foo.bar).toBe(1)
  31. expect(store.getters.bar).toBe(1)
  32. // test dispatching actions defined in dynamic module
  33. store.dispatch('inc')
  34. expect(store.state.hi.a).toBe(2)
  35. expect(store.getters.a).toBe(2)
  36. expect(store.state.foo.bar).toBe(2)
  37. expect(store.getters.bar).toBe(2)
  38. // unregister
  39. store.unregisterModule('hi')
  40. expect(store.state.hi).toBeUndefined()
  41. expect(store.getters.a).toBeUndefined()
  42. expect(store._mutations.inc.length).toBe(1)
  43. expect(store._actions.inc).toBeUndefined()
  44. // assert initial modules still work as expected after unregister
  45. store.dispatch('incFoo')
  46. expect(store.state.foo.bar).toBe(3)
  47. expect(store.getters.bar).toBe(3)
  48. })
  49. it('dynamic module registration with namespace inheritance', () => {
  50. const store = new Vuex.Store({
  51. modules: {
  52. a: {
  53. namespaced: true
  54. }
  55. }
  56. })
  57. const actionSpy = jasmine.createSpy()
  58. const mutationSpy = jasmine.createSpy()
  59. store.registerModule(['a', 'b'], {
  60. state: { value: 1 },
  61. getters: { foo: state => state.value },
  62. actions: { foo: actionSpy },
  63. mutations: { foo: mutationSpy }
  64. })
  65. expect(store.state.a.b.value).toBe(1)
  66. expect(store.getters['a/foo']).toBe(1)
  67. store.dispatch('a/foo')
  68. expect(actionSpy).toHaveBeenCalled()
  69. store.commit('a/foo')
  70. expect(mutationSpy).toHaveBeenCalled()
  71. })
  72. it('dynamic module registration preserving hydration', () => {
  73. const store = new Vuex.Store({})
  74. store.replaceState({ a: { foo: 'state' }})
  75. const actionSpy = jasmine.createSpy()
  76. const mutationSpy = jasmine.createSpy()
  77. store.registerModule('a', {
  78. namespaced: true,
  79. getters: { foo: state => state.foo },
  80. actions: { foo: actionSpy },
  81. mutations: { foo: mutationSpy }
  82. }, { preserveState: true })
  83. expect(store.state.a.foo).toBe('state')
  84. expect(store.getters['a/foo']).toBe('state')
  85. store.dispatch('a/foo')
  86. expect(actionSpy).toHaveBeenCalled()
  87. store.commit('a/foo')
  88. expect(mutationSpy).toHaveBeenCalled()
  89. })
  90. })
  91. // #524
  92. it('should not fire an unrelated watcher', done => {
  93. const spy = jasmine.createSpy()
  94. const store = new Vuex.Store({
  95. modules: {
  96. a: {
  97. state: { value: 1 }
  98. },
  99. b: {}
  100. }
  101. })
  102. store.watch(state => state.a, spy)
  103. store.registerModule(['b', 'c'], {
  104. state: { value: 2 }
  105. })
  106. Vue.nextTick(() => {
  107. expect(spy).not.toHaveBeenCalled()
  108. done()
  109. })
  110. })
  111. describe('modules usage', () => {
  112. it('state as function (multiple module in same store)', () => {
  113. const module = {
  114. state () {
  115. return { a: 0 }
  116. },
  117. mutations: {
  118. [TEST] (state, n) {
  119. state.a += n
  120. }
  121. }
  122. }
  123. const store = new Vuex.Store({
  124. modules: {
  125. one: module,
  126. two: module
  127. }
  128. })
  129. expect(store.state.one.a).toBe(0)
  130. expect(store.state.two.a).toBe(0)
  131. store.commit(TEST, 1)
  132. expect(store.state.one.a).toBe(1)
  133. expect(store.state.two.a).toBe(1)
  134. })
  135. it('state as function (same module in multiple stores)', () => {
  136. const module = {
  137. state () {
  138. return { a: 0 }
  139. },
  140. mutations: {
  141. [TEST] (state, n) {
  142. state.a += n
  143. }
  144. }
  145. }
  146. const storeA = new Vuex.Store({
  147. modules: {
  148. foo: module
  149. }
  150. })
  151. const storeB = new Vuex.Store({
  152. modules: {
  153. bar: module
  154. }
  155. })
  156. expect(storeA.state.foo.a).toBe(0)
  157. expect(storeB.state.bar.a).toBe(0)
  158. storeA.commit(TEST, 1)
  159. expect(storeA.state.foo.a).toBe(1)
  160. expect(storeB.state.bar.a).toBe(0)
  161. storeB.commit(TEST, 2)
  162. expect(storeA.state.foo.a).toBe(1)
  163. expect(storeB.state.bar.a).toBe(2)
  164. })
  165. it('module: mutation', function () {
  166. const mutations = {
  167. [TEST] (state, n) {
  168. state.a += n
  169. }
  170. }
  171. const store = new Vuex.Store({
  172. state: {
  173. a: 1
  174. },
  175. mutations,
  176. modules: {
  177. nested: {
  178. state: { a: 2 },
  179. mutations,
  180. modules: {
  181. one: {
  182. state: { a: 3 },
  183. mutations
  184. },
  185. nested: {
  186. modules: {
  187. two: {
  188. state: { a: 4 },
  189. mutations
  190. },
  191. three: {
  192. state: { a: 5 },
  193. mutations
  194. }
  195. }
  196. }
  197. }
  198. },
  199. four: {
  200. state: { a: 6 },
  201. mutations
  202. }
  203. }
  204. })
  205. store.commit(TEST, 1)
  206. expect(store.state.a).toBe(2)
  207. expect(store.state.nested.a).toBe(3)
  208. expect(store.state.nested.one.a).toBe(4)
  209. expect(store.state.nested.nested.two.a).toBe(5)
  210. expect(store.state.nested.nested.three.a).toBe(6)
  211. expect(store.state.four.a).toBe(7)
  212. })
  213. it('module: action', function () {
  214. let calls = 0
  215. const makeAction = n => {
  216. return {
  217. [TEST] ({ state, rootState }) {
  218. calls++
  219. expect(state.a).toBe(n)
  220. expect(rootState).toBe(store.state)
  221. }
  222. }
  223. }
  224. const store = new Vuex.Store({
  225. state: {
  226. a: 1
  227. },
  228. actions: makeAction(1),
  229. modules: {
  230. nested: {
  231. state: { a: 2 },
  232. actions: makeAction(2),
  233. modules: {
  234. one: {
  235. state: { a: 3 },
  236. actions: makeAction(3)
  237. },
  238. nested: {
  239. modules: {
  240. two: {
  241. state: { a: 4 },
  242. actions: makeAction(4)
  243. },
  244. three: {
  245. state: { a: 5 },
  246. actions: makeAction(5)
  247. }
  248. }
  249. }
  250. }
  251. },
  252. four: {
  253. state: { a: 6 },
  254. actions: makeAction(6)
  255. }
  256. }
  257. })
  258. store.dispatch(TEST)
  259. expect(calls).toBe(6)
  260. })
  261. it('module: getters', function () {
  262. const makeGetter = n => ({
  263. [`getter${n}`]: (state, getters, rootState) => {
  264. expect(getters.constant).toBe(0)
  265. expect(rootState).toBe(store.state)
  266. return state.a
  267. }
  268. })
  269. const store = new Vuex.Store({
  270. state: {
  271. a: 1
  272. },
  273. getters: {
  274. constant: () => 0,
  275. ...makeGetter(1)
  276. },
  277. modules: {
  278. nested: {
  279. state: { a: 2 },
  280. getters: makeGetter(2),
  281. modules: {
  282. one: {
  283. state: { a: 3 },
  284. getters: makeGetter(3)
  285. },
  286. nested: {
  287. modules: {
  288. two: {
  289. state: { a: 4 },
  290. getters: makeGetter(4)
  291. },
  292. three: {
  293. state: { a: 5 },
  294. getters: makeGetter(5)
  295. }
  296. }
  297. }
  298. }
  299. },
  300. four: {
  301. state: { a: 6 },
  302. getters: makeGetter(6)
  303. }
  304. }
  305. })
  306. ;[1, 2, 3, 4, 5, 6].forEach(n => {
  307. expect(store.getters[`getter${n}`]).toBe(n)
  308. })
  309. })
  310. it('module: namespace', () => {
  311. const actionSpy = jasmine.createSpy()
  312. const mutationSpy = jasmine.createSpy()
  313. const store = new Vuex.Store({
  314. modules: {
  315. a: {
  316. namespaced: true,
  317. state: {
  318. a: 1
  319. },
  320. getters: {
  321. b: () => 2
  322. },
  323. actions: {
  324. [TEST]: actionSpy
  325. },
  326. mutations: {
  327. [TEST]: mutationSpy
  328. }
  329. }
  330. }
  331. })
  332. expect(store.state.a.a).toBe(1)
  333. expect(store.getters['a/b']).toBe(2)
  334. store.dispatch('a/' + TEST)
  335. expect(actionSpy).toHaveBeenCalled()
  336. store.commit('a/' + TEST)
  337. expect(mutationSpy).toHaveBeenCalled()
  338. })
  339. it('module: nested namespace', () => {
  340. // mock module generator
  341. const actionSpys = []
  342. const mutationSpys = []
  343. const createModule = (name, namespaced, children) => {
  344. const actionSpy = jasmine.createSpy()
  345. const mutationSpy = jasmine.createSpy()
  346. actionSpys.push(actionSpy)
  347. mutationSpys.push(mutationSpy)
  348. return {
  349. namespaced,
  350. state: {
  351. [name]: true
  352. },
  353. getters: {
  354. [name]: state => state[name]
  355. },
  356. actions: {
  357. [name]: actionSpy
  358. },
  359. mutations: {
  360. [name]: mutationSpy
  361. },
  362. modules: children
  363. }
  364. }
  365. // mock module
  366. const modules = {
  367. a: createModule('a', true, { // a/a
  368. b: createModule('b', false, { // a/b - does not add namespace
  369. c: createModule('c', true) // a/c/c
  370. }),
  371. d: createModule('d', true) // a/d/d
  372. })
  373. }
  374. const store = new Vuex.Store({ modules })
  375. const expectedTypes = [
  376. 'a/a', 'a/b', 'a/c/c', 'a/d/d'
  377. ]
  378. // getters
  379. expectedTypes.forEach(type => {
  380. expect(store.getters[type]).toBe(true)
  381. })
  382. // actions
  383. expectedTypes.forEach(type => {
  384. store.dispatch(type)
  385. })
  386. actionSpys.forEach(spy => {
  387. expect(spy.calls.count()).toBe(1)
  388. })
  389. // mutations
  390. expectedTypes.forEach(type => {
  391. store.commit(type)
  392. })
  393. mutationSpys.forEach(spy => {
  394. expect(spy.calls.count()).toBe(1)
  395. })
  396. })
  397. it('module: getters are namespaced in namespaced module', () => {
  398. const store = new Vuex.Store({
  399. state: { value: 'root' },
  400. getters: {
  401. foo: state => state.value
  402. },
  403. modules: {
  404. a: {
  405. namespaced: true,
  406. state: { value: 'module' },
  407. getters: {
  408. foo: state => state.value,
  409. bar: (state, getters) => getters.foo,
  410. baz: (state, getters, rootState, rootGetters) => rootGetters.foo
  411. }
  412. }
  413. }
  414. })
  415. expect(store.getters['a/foo']).toBe('module')
  416. expect(store.getters['a/bar']).toBe('module')
  417. expect(store.getters['a/baz']).toBe('root')
  418. })
  419. it('module: action context is namespaced in namespaced module', done => {
  420. const rootActionSpy = jasmine.createSpy()
  421. const rootMutationSpy = jasmine.createSpy()
  422. const moduleActionSpy = jasmine.createSpy()
  423. const moduleMutationSpy = jasmine.createSpy()
  424. const store = new Vuex.Store({
  425. state: { value: 'root' },
  426. getters: { foo: state => state.value },
  427. actions: { foo: rootActionSpy },
  428. mutations: { foo: rootMutationSpy },
  429. modules: {
  430. a: {
  431. namespaced: true,
  432. state: { value: 'module' },
  433. getters: { foo: state => state.value },
  434. actions: {
  435. foo: moduleActionSpy,
  436. test ({ dispatch, commit, getters, rootGetters }) {
  437. expect(getters.foo).toBe('module')
  438. expect(rootGetters.foo).toBe('root')
  439. dispatch('foo')
  440. expect(moduleActionSpy.calls.count()).toBe(1)
  441. dispatch('foo', null, { root: true })
  442. expect(rootActionSpy.calls.count()).toBe(1)
  443. commit('foo')
  444. expect(moduleMutationSpy.calls.count()).toBe(1)
  445. commit('foo', null, { root: true })
  446. expect(rootMutationSpy.calls.count()).toBe(1)
  447. done()
  448. }
  449. },
  450. mutations: { foo: moduleMutationSpy }
  451. }
  452. }
  453. })
  454. store.dispatch('a/test')
  455. })
  456. it('module: use other module that has same namespace', done => {
  457. const actionSpy = jasmine.createSpy()
  458. const mutationSpy = jasmine.createSpy()
  459. const store = new Vuex.Store({
  460. modules: {
  461. parent: {
  462. namespaced: true,
  463. modules: {
  464. a: {
  465. state: { value: 'a' },
  466. getters: { foo: state => state.value },
  467. actions: { foo: actionSpy },
  468. mutations: { foo: mutationSpy }
  469. },
  470. b: {
  471. state: { value: 'b' },
  472. getters: { bar: (state, getters) => getters.foo },
  473. actions: {
  474. test ({ dispatch, commit, getters }) {
  475. expect(getters.foo).toBe('a')
  476. expect(getters.bar).toBe('a')
  477. dispatch('foo')
  478. expect(actionSpy).toHaveBeenCalled()
  479. commit('foo')
  480. expect(mutationSpy).toHaveBeenCalled()
  481. done()
  482. }
  483. }
  484. }
  485. }
  486. }
  487. }
  488. })
  489. store.dispatch('parent/test')
  490. })
  491. it('dispatching multiple actions in different modules', done => {
  492. const store = new Vuex.Store({
  493. modules: {
  494. a: {
  495. actions: {
  496. [TEST] () {
  497. return 1
  498. }
  499. }
  500. },
  501. b: {
  502. actions: {
  503. [TEST] () {
  504. return new Promise(r => r(2))
  505. }
  506. }
  507. }
  508. }
  509. })
  510. store.dispatch(TEST).then(res => {
  511. expect(res[0]).toBe(1)
  512. expect(res[1]).toBe(2)
  513. done()
  514. })
  515. })
  516. it('plugins', function () {
  517. let initState
  518. const mutations = []
  519. const store = new Vuex.Store({
  520. state: {
  521. a: 1
  522. },
  523. mutations: {
  524. [TEST] (state, n) {
  525. state.a += n
  526. }
  527. },
  528. plugins: [
  529. store => {
  530. initState = store.state
  531. store.subscribe((mut, state) => {
  532. expect(state).toBe(store.state)
  533. mutations.push(mut)
  534. })
  535. }
  536. ]
  537. })
  538. expect(initState).toBe(store.state)
  539. store.commit(TEST, 2)
  540. expect(mutations.length).toBe(1)
  541. expect(mutations[0].type).toBe(TEST)
  542. expect(mutations[0].payload).toBe(2)
  543. })
  544. })
  545. it('asserts a mutation should be a function', () => {
  546. expect(() => {
  547. new Vuex.Store({
  548. mutations: {
  549. test: null
  550. }
  551. })
  552. }).toThrowError(
  553. /mutations should be function but "mutations\.test" is null/
  554. )
  555. expect(() => {
  556. new Vuex.Store({
  557. modules: {
  558. foo: {
  559. modules: {
  560. bar: {
  561. mutations: {
  562. test: 123
  563. }
  564. }
  565. }
  566. }
  567. }
  568. })
  569. }).toThrowError(
  570. /mutations should be function but "mutations\.test" in module "foo\.bar" is 123/
  571. )
  572. })
  573. it('asserts an action should be a function', () => {
  574. expect(() => {
  575. new Vuex.Store({
  576. actions: {
  577. test: 'test'
  578. }
  579. })
  580. }).toThrowError(
  581. /actions should be function but "actions\.test" is "test"/
  582. )
  583. expect(() => {
  584. new Vuex.Store({
  585. modules: {
  586. foo: {
  587. modules: {
  588. bar: {
  589. actions: {
  590. test: 'error'
  591. }
  592. }
  593. }
  594. }
  595. }
  596. })
  597. }).toThrowError(
  598. /actions should be function but "actions\.test" in module "foo\.bar" is "error"/
  599. )
  600. })
  601. it('asserts a getter should be a function', () => {
  602. expect(() => {
  603. new Vuex.Store({
  604. getters: {
  605. test: undefined
  606. }
  607. })
  608. }).toThrowError(
  609. /getters should be function but "getters\.test" is undefined/
  610. )
  611. expect(() => {
  612. new Vuex.Store({
  613. modules: {
  614. foo: {
  615. modules: {
  616. bar: {
  617. getters: {
  618. test: true
  619. }
  620. }
  621. }
  622. }
  623. }
  624. })
  625. }).toThrowError(
  626. /getters should be function but "getters\.test" in module "foo\.bar" is true/
  627. )
  628. })
  629. })