modules.spec.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826
  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 existance test', () => {
  73. const store = new Vuex.Store({
  74. })
  75. store.registerModule('bonjour', {
  76. })
  77. expect(store.hasModule('bonjour')).toBe(true)
  78. store.unregisterModule('bonjour')
  79. expect(store.hasModule('bonjour')).toBe(false)
  80. })
  81. it('dynamic module registration preserving hydration', () => {
  82. const store = new Vuex.Store({})
  83. store.replaceState({ a: { foo: 'state' }})
  84. const actionSpy = jasmine.createSpy()
  85. const mutationSpy = jasmine.createSpy()
  86. store.registerModule('a', {
  87. namespaced: true,
  88. getters: { foo: state => state.foo },
  89. actions: { foo: actionSpy },
  90. mutations: { foo: mutationSpy }
  91. }, { preserveState: true })
  92. expect(store.state.a.foo).toBe('state')
  93. expect(store.getters['a/foo']).toBe('state')
  94. store.dispatch('a/foo')
  95. expect(actionSpy).toHaveBeenCalled()
  96. store.commit('a/foo')
  97. expect(mutationSpy).toHaveBeenCalled()
  98. })
  99. })
  100. // #524
  101. it('should not fire an unrelated watcher', done => {
  102. const spy = jasmine.createSpy()
  103. const store = new Vuex.Store({
  104. modules: {
  105. a: {
  106. state: { value: 1 }
  107. },
  108. b: {}
  109. }
  110. })
  111. store.watch(state => state.a, spy)
  112. store.registerModule(['b', 'c'], {
  113. state: { value: 2 }
  114. })
  115. Vue.nextTick(() => {
  116. expect(spy).not.toHaveBeenCalled()
  117. done()
  118. })
  119. })
  120. describe('modules usage', () => {
  121. it('state as function (multiple module in same store)', () => {
  122. const module = {
  123. state () {
  124. return { a: 0 }
  125. },
  126. mutations: {
  127. [TEST] (state, n) {
  128. state.a += n
  129. }
  130. }
  131. }
  132. const store = new Vuex.Store({
  133. modules: {
  134. one: module,
  135. two: module
  136. }
  137. })
  138. expect(store.state.one.a).toBe(0)
  139. expect(store.state.two.a).toBe(0)
  140. store.commit(TEST, 1)
  141. expect(store.state.one.a).toBe(1)
  142. expect(store.state.two.a).toBe(1)
  143. })
  144. it('state as function (same module in multiple stores)', () => {
  145. const module = {
  146. state () {
  147. return { a: 0 }
  148. },
  149. mutations: {
  150. [TEST] (state, n) {
  151. state.a += n
  152. }
  153. }
  154. }
  155. const storeA = new Vuex.Store({
  156. modules: {
  157. foo: module
  158. }
  159. })
  160. const storeB = new Vuex.Store({
  161. modules: {
  162. bar: module
  163. }
  164. })
  165. expect(storeA.state.foo.a).toBe(0)
  166. expect(storeB.state.bar.a).toBe(0)
  167. storeA.commit(TEST, 1)
  168. expect(storeA.state.foo.a).toBe(1)
  169. expect(storeB.state.bar.a).toBe(0)
  170. storeB.commit(TEST, 2)
  171. expect(storeA.state.foo.a).toBe(1)
  172. expect(storeB.state.bar.a).toBe(2)
  173. })
  174. it('module: mutation', function () {
  175. const mutations = {
  176. [TEST] (state, n) {
  177. state.a += n
  178. }
  179. }
  180. const store = new Vuex.Store({
  181. state: {
  182. a: 1
  183. },
  184. mutations,
  185. modules: {
  186. nested: {
  187. state: { a: 2 },
  188. mutations,
  189. modules: {
  190. one: {
  191. state: { a: 3 },
  192. mutations
  193. },
  194. nested: {
  195. modules: {
  196. two: {
  197. state: { a: 4 },
  198. mutations
  199. },
  200. three: {
  201. state: { a: 5 },
  202. mutations
  203. }
  204. }
  205. }
  206. }
  207. },
  208. four: {
  209. state: { a: 6 },
  210. mutations
  211. }
  212. }
  213. })
  214. store.commit(TEST, 1)
  215. expect(store.state.a).toBe(2)
  216. expect(store.state.nested.a).toBe(3)
  217. expect(store.state.nested.one.a).toBe(4)
  218. expect(store.state.nested.nested.two.a).toBe(5)
  219. expect(store.state.nested.nested.three.a).toBe(6)
  220. expect(store.state.four.a).toBe(7)
  221. })
  222. it('module: action', function () {
  223. let calls = 0
  224. const makeAction = n => {
  225. return {
  226. [TEST] ({ state, rootState }) {
  227. calls++
  228. expect(state.a).toBe(n)
  229. expect(rootState).toBe(store.state)
  230. }
  231. }
  232. }
  233. const store = new Vuex.Store({
  234. state: {
  235. a: 1
  236. },
  237. actions: makeAction(1),
  238. modules: {
  239. nested: {
  240. state: { a: 2 },
  241. actions: makeAction(2),
  242. modules: {
  243. one: {
  244. state: { a: 3 },
  245. actions: makeAction(3)
  246. },
  247. nested: {
  248. modules: {
  249. two: {
  250. state: { a: 4 },
  251. actions: makeAction(4)
  252. },
  253. three: {
  254. state: { a: 5 },
  255. actions: makeAction(5)
  256. }
  257. }
  258. }
  259. }
  260. },
  261. four: {
  262. state: { a: 6 },
  263. actions: makeAction(6)
  264. }
  265. }
  266. })
  267. store.dispatch(TEST)
  268. expect(calls).toBe(6)
  269. })
  270. it('module: getters', function () {
  271. const makeGetter = n => ({
  272. [`getter${n}`]: (state, getters, rootState) => {
  273. expect(getters.constant).toBe(0)
  274. expect(rootState).toBe(store.state)
  275. return state.a
  276. }
  277. })
  278. const store = new Vuex.Store({
  279. state: {
  280. a: 1
  281. },
  282. getters: {
  283. constant: () => 0,
  284. ...makeGetter(1)
  285. },
  286. modules: {
  287. nested: {
  288. state: { a: 2 },
  289. getters: makeGetter(2),
  290. modules: {
  291. one: {
  292. state: { a: 3 },
  293. getters: makeGetter(3)
  294. },
  295. nested: {
  296. modules: {
  297. two: {
  298. state: { a: 4 },
  299. getters: makeGetter(4)
  300. },
  301. three: {
  302. state: { a: 5 },
  303. getters: makeGetter(5)
  304. }
  305. }
  306. }
  307. }
  308. },
  309. four: {
  310. state: { a: 6 },
  311. getters: makeGetter(6)
  312. }
  313. }
  314. })
  315. ;[1, 2, 3, 4, 5, 6].forEach(n => {
  316. expect(store.getters[`getter${n}`]).toBe(n)
  317. })
  318. })
  319. it('module: namespace', () => {
  320. const actionSpy = jasmine.createSpy()
  321. const mutationSpy = jasmine.createSpy()
  322. const store = new Vuex.Store({
  323. modules: {
  324. a: {
  325. namespaced: true,
  326. state: {
  327. a: 1
  328. },
  329. getters: {
  330. b: () => 2
  331. },
  332. actions: {
  333. [TEST]: actionSpy
  334. },
  335. mutations: {
  336. [TEST]: mutationSpy
  337. }
  338. }
  339. }
  340. })
  341. expect(store.state.a.a).toBe(1)
  342. expect(store.getters['a/b']).toBe(2)
  343. store.dispatch('a/' + TEST)
  344. expect(actionSpy).toHaveBeenCalled()
  345. store.commit('a/' + TEST)
  346. expect(mutationSpy).toHaveBeenCalled()
  347. })
  348. it('module: nested namespace', () => {
  349. // mock module generator
  350. const actionSpys = []
  351. const mutationSpys = []
  352. const createModule = (name, namespaced, children) => {
  353. const actionSpy = jasmine.createSpy()
  354. const mutationSpy = jasmine.createSpy()
  355. actionSpys.push(actionSpy)
  356. mutationSpys.push(mutationSpy)
  357. return {
  358. namespaced,
  359. state: {
  360. [name]: true
  361. },
  362. getters: {
  363. [name]: state => state[name]
  364. },
  365. actions: {
  366. [name]: actionSpy
  367. },
  368. mutations: {
  369. [name]: mutationSpy
  370. },
  371. modules: children
  372. }
  373. }
  374. // mock module
  375. const modules = {
  376. a: createModule('a', true, { // a/a
  377. b: createModule('b', false, { // a/b - does not add namespace
  378. c: createModule('c', true) // a/c/c
  379. }),
  380. d: createModule('d', true) // a/d/d
  381. })
  382. }
  383. const store = new Vuex.Store({ modules })
  384. const expectedTypes = [
  385. 'a/a', 'a/b', 'a/c/c', 'a/d/d'
  386. ]
  387. // getters
  388. expectedTypes.forEach(type => {
  389. expect(store.getters[type]).toBe(true)
  390. })
  391. // actions
  392. expectedTypes.forEach(type => {
  393. store.dispatch(type)
  394. })
  395. actionSpys.forEach(spy => {
  396. expect(spy.calls.count()).toBe(1)
  397. })
  398. // mutations
  399. expectedTypes.forEach(type => {
  400. store.commit(type)
  401. })
  402. mutationSpys.forEach(spy => {
  403. expect(spy.calls.count()).toBe(1)
  404. })
  405. })
  406. it('module: getters are namespaced in namespaced module', () => {
  407. const store = new Vuex.Store({
  408. state: { value: 'root' },
  409. getters: {
  410. foo: state => state.value
  411. },
  412. modules: {
  413. a: {
  414. namespaced: true,
  415. state: { value: 'module' },
  416. getters: {
  417. foo: state => state.value,
  418. bar: (state, getters) => getters.foo,
  419. baz: (state, getters, rootState, rootGetters) => rootGetters.foo
  420. }
  421. }
  422. }
  423. })
  424. expect(store.getters['a/foo']).toBe('module')
  425. expect(store.getters['a/bar']).toBe('module')
  426. expect(store.getters['a/baz']).toBe('root')
  427. })
  428. it('module: action context is namespaced in namespaced module', done => {
  429. const rootActionSpy = jasmine.createSpy()
  430. const rootMutationSpy = jasmine.createSpy()
  431. const moduleActionSpy = jasmine.createSpy()
  432. const moduleMutationSpy = jasmine.createSpy()
  433. const store = new Vuex.Store({
  434. state: { value: 'root' },
  435. getters: { foo: state => state.value },
  436. actions: { foo: rootActionSpy },
  437. mutations: { foo: rootMutationSpy },
  438. modules: {
  439. a: {
  440. namespaced: true,
  441. state: { value: 'module' },
  442. getters: { foo: state => state.value },
  443. actions: {
  444. foo: moduleActionSpy,
  445. test ({ dispatch, commit, getters, rootGetters }) {
  446. expect(getters.foo).toBe('module')
  447. expect(rootGetters.foo).toBe('root')
  448. dispatch('foo')
  449. expect(moduleActionSpy.calls.count()).toBe(1)
  450. dispatch('foo', null, { root: true })
  451. expect(rootActionSpy.calls.count()).toBe(1)
  452. commit('foo')
  453. expect(moduleMutationSpy.calls.count()).toBe(1)
  454. commit('foo', null, { root: true })
  455. expect(rootMutationSpy.calls.count()).toBe(1)
  456. done()
  457. }
  458. },
  459. mutations: { foo: moduleMutationSpy }
  460. }
  461. }
  462. })
  463. store.dispatch('a/test')
  464. })
  465. it('module: use other module that has same namespace', done => {
  466. const actionSpy = jasmine.createSpy()
  467. const mutationSpy = jasmine.createSpy()
  468. const store = new Vuex.Store({
  469. modules: {
  470. parent: {
  471. namespaced: true,
  472. modules: {
  473. a: {
  474. state: { value: 'a' },
  475. getters: { foo: state => state.value },
  476. actions: { foo: actionSpy },
  477. mutations: { foo: mutationSpy }
  478. },
  479. b: {
  480. state: { value: 'b' },
  481. getters: { bar: (state, getters) => getters.foo },
  482. actions: {
  483. test ({ dispatch, commit, getters }) {
  484. expect(getters.foo).toBe('a')
  485. expect(getters.bar).toBe('a')
  486. dispatch('foo')
  487. expect(actionSpy).toHaveBeenCalled()
  488. commit('foo')
  489. expect(mutationSpy).toHaveBeenCalled()
  490. done()
  491. }
  492. }
  493. }
  494. }
  495. }
  496. }
  497. })
  498. store.dispatch('parent/test')
  499. })
  500. it('module: warn when module overrides state', () => {
  501. spyOn(console, 'warn')
  502. const store = new Vuex.Store({
  503. modules: {
  504. foo: {
  505. state () {
  506. return { value: 1 }
  507. },
  508. modules: {
  509. value: {
  510. state: () => 2
  511. }
  512. }
  513. }
  514. }
  515. })
  516. expect(store.state.foo.value).toBe(2)
  517. expect(console.warn).toHaveBeenCalledWith(
  518. `[vuex] state field "value" was overridden by a module with the same name at "foo.value"`
  519. )
  520. })
  521. it('dispatching multiple actions in different modules', done => {
  522. const store = new Vuex.Store({
  523. modules: {
  524. a: {
  525. actions: {
  526. [TEST] () {
  527. return 1
  528. }
  529. }
  530. },
  531. b: {
  532. actions: {
  533. [TEST] () {
  534. return new Promise(r => r(2))
  535. }
  536. }
  537. }
  538. }
  539. })
  540. store.dispatch(TEST).then(res => {
  541. expect(res[0]).toBe(1)
  542. expect(res[1]).toBe(2)
  543. done()
  544. })
  545. })
  546. it('root actions dispatched in namespaced modules', done => {
  547. const store = new Vuex.Store({
  548. modules: {
  549. a: {
  550. namespaced: true,
  551. actions: {
  552. [TEST]: {
  553. root: true,
  554. handler () {
  555. return 1
  556. }
  557. }
  558. }
  559. },
  560. b: {
  561. namespaced: true,
  562. actions: {
  563. [TEST]: {
  564. root: true,
  565. handler () {
  566. return new Promise(r => r(2))
  567. }
  568. }
  569. }
  570. },
  571. c: {
  572. namespaced: true,
  573. actions: {
  574. [TEST]: {
  575. handler () {
  576. // Should not be called
  577. return 3
  578. }
  579. }
  580. }
  581. },
  582. d: {
  583. namespaced: true,
  584. actions: {
  585. [TEST] () {
  586. // Should not be called
  587. return 4
  588. }
  589. }
  590. }
  591. }
  592. })
  593. store.dispatch(TEST).then(res => {
  594. expect(res.length).toBe(2)
  595. expect(res[0]).toBe(1)
  596. expect(res[1]).toBe(2)
  597. done()
  598. })
  599. })
  600. it('plugins', function () {
  601. let initState
  602. const actionSpy = jasmine.createSpy()
  603. const mutations = []
  604. const subscribeActionSpy = jasmine.createSpy()
  605. const store = new Vuex.Store({
  606. state: {
  607. a: 1
  608. },
  609. mutations: {
  610. [TEST] (state, n) {
  611. state.a += n
  612. }
  613. },
  614. actions: {
  615. [TEST]: actionSpy
  616. },
  617. plugins: [
  618. store => {
  619. initState = store.state
  620. store.subscribe((mut, state) => {
  621. expect(state).toBe(state)
  622. mutations.push(mut)
  623. })
  624. store.subscribeAction(subscribeActionSpy)
  625. }
  626. ]
  627. })
  628. expect(initState).toBe(store.state)
  629. store.commit(TEST, 2)
  630. store.dispatch(TEST, 2)
  631. expect(mutations.length).toBe(1)
  632. expect(mutations[0].type).toBe(TEST)
  633. expect(mutations[0].payload).toBe(2)
  634. expect(actionSpy).toHaveBeenCalled()
  635. expect(subscribeActionSpy).toHaveBeenCalledWith(
  636. { type: TEST, payload: 2 },
  637. store.state
  638. )
  639. })
  640. it('action before/after subscribers', (done) => {
  641. const beforeSpy = jasmine.createSpy()
  642. const afterSpy = jasmine.createSpy()
  643. const store = new Vuex.Store({
  644. actions: {
  645. [TEST]: () => Promise.resolve()
  646. },
  647. plugins: [
  648. store => {
  649. store.subscribeAction({
  650. before: beforeSpy,
  651. after: afterSpy
  652. })
  653. }
  654. ]
  655. })
  656. store.dispatch(TEST, 2)
  657. expect(beforeSpy).toHaveBeenCalledWith(
  658. { type: TEST, payload: 2 },
  659. store.state
  660. )
  661. expect(afterSpy).not.toHaveBeenCalled()
  662. Vue.nextTick(() => {
  663. expect(afterSpy).toHaveBeenCalledWith(
  664. { type: TEST, payload: 2 },
  665. store.state
  666. )
  667. done()
  668. })
  669. })
  670. })
  671. it('asserts a mutation should be a function', () => {
  672. expect(() => {
  673. new Vuex.Store({
  674. mutations: {
  675. test: null
  676. }
  677. })
  678. }).toThrowError(
  679. /mutations should be function but "mutations\.test" is null/
  680. )
  681. expect(() => {
  682. new Vuex.Store({
  683. modules: {
  684. foo: {
  685. modules: {
  686. bar: {
  687. mutations: {
  688. test: 123
  689. }
  690. }
  691. }
  692. }
  693. }
  694. })
  695. }).toThrowError(
  696. /mutations should be function but "mutations\.test" in module "foo\.bar" is 123/
  697. )
  698. })
  699. it('asserts an action should be a function', () => {
  700. expect(() => {
  701. new Vuex.Store({
  702. actions: {
  703. test: 'test'
  704. }
  705. })
  706. }).toThrowError(
  707. /actions should be function or object with "handler" function but "actions\.test" is "test"/
  708. )
  709. expect(() => {
  710. new Vuex.Store({
  711. modules: {
  712. foo: {
  713. modules: {
  714. bar: {
  715. actions: {
  716. test: 'error'
  717. }
  718. }
  719. }
  720. }
  721. }
  722. })
  723. }).toThrowError(
  724. /actions should be function or object with "handler" function but "actions\.test" in module "foo\.bar" is "error"/
  725. )
  726. })
  727. it('asserts a getter should be a function', () => {
  728. expect(() => {
  729. new Vuex.Store({
  730. getters: {
  731. test: undefined
  732. }
  733. })
  734. }).toThrowError(
  735. /getters should be function but "getters\.test" is undefined/
  736. )
  737. expect(() => {
  738. new Vuex.Store({
  739. modules: {
  740. foo: {
  741. modules: {
  742. bar: {
  743. getters: {
  744. test: true
  745. }
  746. }
  747. }
  748. }
  749. }
  750. })
  751. }).toThrowError(
  752. /getters should be function but "getters\.test" in module "foo\.bar" is true/
  753. )
  754. })
  755. })