modules.spec.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762
  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('root actions dispatched in namespaced modules', done => {
  517. const store = new Vuex.Store({
  518. modules: {
  519. a: {
  520. namespaced: true,
  521. actions: {
  522. [TEST]: {
  523. root: true,
  524. handler () {
  525. return 1
  526. }
  527. }
  528. }
  529. },
  530. b: {
  531. namespaced: true,
  532. actions: {
  533. [TEST]: {
  534. root: true,
  535. handler () {
  536. return new Promise(r => r(2))
  537. }
  538. }
  539. }
  540. },
  541. c: {
  542. namespaced: true,
  543. actions: {
  544. [TEST]: {
  545. handler () {
  546. // Should not be called
  547. return 3
  548. }
  549. }
  550. }
  551. },
  552. d: {
  553. namespaced: true,
  554. actions: {
  555. [TEST] () {
  556. // Should not be called
  557. return 4
  558. }
  559. }
  560. }
  561. }
  562. })
  563. store.dispatch(TEST).then(res => {
  564. expect(res.length).toBe(2)
  565. expect(res[0]).toBe(1)
  566. expect(res[1]).toBe(2)
  567. done()
  568. })
  569. })
  570. it('plugins', function () {
  571. let initState
  572. const actionSpy = jasmine.createSpy()
  573. const mutations = []
  574. const subscribeActionSpy = jasmine.createSpy()
  575. const store = new Vuex.Store({
  576. state: {
  577. a: 1
  578. },
  579. mutations: {
  580. [TEST] (state, n) {
  581. state.a += n
  582. }
  583. },
  584. actions: {
  585. [TEST]: actionSpy
  586. },
  587. plugins: [
  588. store => {
  589. initState = store.state
  590. store.subscribe((mut, state) => {
  591. expect(state).toBe(state)
  592. mutations.push(mut)
  593. })
  594. store.subscribeAction(subscribeActionSpy)
  595. }
  596. ]
  597. })
  598. expect(initState).toBe(store.state)
  599. store.commit(TEST, 2)
  600. store.dispatch(TEST, 2)
  601. expect(mutations.length).toBe(1)
  602. expect(mutations[0].type).toBe(TEST)
  603. expect(mutations[0].payload).toBe(2)
  604. expect(actionSpy).toHaveBeenCalled()
  605. expect(subscribeActionSpy).toHaveBeenCalledWith(
  606. { type: TEST, payload: 2 },
  607. store.state
  608. )
  609. })
  610. })
  611. it('asserts a mutation should be a function', () => {
  612. expect(() => {
  613. new Vuex.Store({
  614. mutations: {
  615. test: null
  616. }
  617. })
  618. }).toThrowError(
  619. /mutations should be function but "mutations\.test" is null/
  620. )
  621. expect(() => {
  622. new Vuex.Store({
  623. modules: {
  624. foo: {
  625. modules: {
  626. bar: {
  627. mutations: {
  628. test: 123
  629. }
  630. }
  631. }
  632. }
  633. }
  634. })
  635. }).toThrowError(
  636. /mutations should be function but "mutations\.test" in module "foo\.bar" is 123/
  637. )
  638. })
  639. it('asserts an action should be a function', () => {
  640. expect(() => {
  641. new Vuex.Store({
  642. actions: {
  643. test: 'test'
  644. }
  645. })
  646. }).toThrowError(
  647. /actions should be function or object with "handler" function but "actions\.test" is "test"/
  648. )
  649. expect(() => {
  650. new Vuex.Store({
  651. modules: {
  652. foo: {
  653. modules: {
  654. bar: {
  655. actions: {
  656. test: 'error'
  657. }
  658. }
  659. }
  660. }
  661. }
  662. })
  663. }).toThrowError(
  664. /actions should be function or object with "handler" function but "actions\.test" in module "foo\.bar" is "error"/
  665. )
  666. })
  667. it('asserts a getter should be a function', () => {
  668. expect(() => {
  669. new Vuex.Store({
  670. getters: {
  671. test: undefined
  672. }
  673. })
  674. }).toThrowError(
  675. /getters should be function but "getters\.test" is undefined/
  676. )
  677. expect(() => {
  678. new Vuex.Store({
  679. modules: {
  680. foo: {
  681. modules: {
  682. bar: {
  683. getters: {
  684. test: true
  685. }
  686. }
  687. }
  688. }
  689. }
  690. })
  691. }).toThrowError(
  692. /getters should be function but "getters\.test" in module "foo\.bar" is true/
  693. )
  694. })
  695. })