1
0

modules.spec.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583
  1. import Vue from 'vue'
  2. import Vuex from '../../dist/vuex.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. })
  73. // #524
  74. it('should not fire an unrelated watcher', done => {
  75. const spy = jasmine.createSpy()
  76. const store = new Vuex.Store({
  77. modules: {
  78. a: {
  79. state: { value: 1 }
  80. },
  81. b: {}
  82. }
  83. })
  84. store.watch(state => state.a, spy)
  85. store.registerModule(['b', 'c'], {
  86. state: { value: 2 }
  87. })
  88. Vue.nextTick(() => {
  89. expect(spy).not.toHaveBeenCalled()
  90. done()
  91. })
  92. })
  93. describe('modules usage', () => {
  94. it('state as function (multiple module in same store)', () => {
  95. const module = {
  96. state () {
  97. return { a: 0 }
  98. },
  99. mutations: {
  100. [TEST] (state, n) {
  101. state.a += n
  102. }
  103. }
  104. }
  105. const store = new Vuex.Store({
  106. modules: {
  107. one: module,
  108. two: module
  109. }
  110. })
  111. expect(store.state.one.a).toBe(0)
  112. expect(store.state.two.a).toBe(0)
  113. store.commit(TEST, 1)
  114. expect(store.state.one.a).toBe(1)
  115. expect(store.state.two.a).toBe(1)
  116. })
  117. it('state as function (same module in multiple stores)', () => {
  118. const module = {
  119. state () {
  120. return { a: 0 }
  121. },
  122. mutations: {
  123. [TEST] (state, n) {
  124. state.a += n
  125. }
  126. }
  127. }
  128. const storeA = new Vuex.Store({
  129. modules: {
  130. foo: module
  131. }
  132. })
  133. const storeB = new Vuex.Store({
  134. modules: {
  135. bar: module
  136. }
  137. })
  138. expect(storeA.state.foo.a).toBe(0)
  139. expect(storeB.state.bar.a).toBe(0)
  140. storeA.commit(TEST, 1)
  141. expect(storeA.state.foo.a).toBe(1)
  142. expect(storeB.state.bar.a).toBe(0)
  143. storeB.commit(TEST, 2)
  144. expect(storeA.state.foo.a).toBe(1)
  145. expect(storeB.state.bar.a).toBe(2)
  146. })
  147. it('module: mutation', function () {
  148. const mutations = {
  149. [TEST] (state, n) {
  150. state.a += n
  151. }
  152. }
  153. const store = new Vuex.Store({
  154. state: {
  155. a: 1
  156. },
  157. mutations,
  158. modules: {
  159. nested: {
  160. state: { a: 2 },
  161. mutations,
  162. modules: {
  163. one: {
  164. state: { a: 3 },
  165. mutations
  166. },
  167. nested: {
  168. modules: {
  169. two: {
  170. state: { a: 4 },
  171. mutations
  172. },
  173. three: {
  174. state: { a: 5 },
  175. mutations
  176. }
  177. }
  178. }
  179. }
  180. },
  181. four: {
  182. state: { a: 6 },
  183. mutations
  184. }
  185. }
  186. })
  187. store.commit(TEST, 1)
  188. expect(store.state.a).toBe(2)
  189. expect(store.state.nested.a).toBe(3)
  190. expect(store.state.nested.one.a).toBe(4)
  191. expect(store.state.nested.nested.two.a).toBe(5)
  192. expect(store.state.nested.nested.three.a).toBe(6)
  193. expect(store.state.four.a).toBe(7)
  194. })
  195. it('module: action', function () {
  196. let calls = 0
  197. const makeAction = n => {
  198. return {
  199. [TEST] ({ state, rootState }) {
  200. calls++
  201. expect(state.a).toBe(n)
  202. expect(rootState).toBe(store.state)
  203. }
  204. }
  205. }
  206. const store = new Vuex.Store({
  207. state: {
  208. a: 1
  209. },
  210. actions: makeAction(1),
  211. modules: {
  212. nested: {
  213. state: { a: 2 },
  214. actions: makeAction(2),
  215. modules: {
  216. one: {
  217. state: { a: 3 },
  218. actions: makeAction(3)
  219. },
  220. nested: {
  221. modules: {
  222. two: {
  223. state: { a: 4 },
  224. actions: makeAction(4)
  225. },
  226. three: {
  227. state: { a: 5 },
  228. actions: makeAction(5)
  229. }
  230. }
  231. }
  232. }
  233. },
  234. four: {
  235. state: { a: 6 },
  236. actions: makeAction(6)
  237. }
  238. }
  239. })
  240. store.dispatch(TEST)
  241. expect(calls).toBe(6)
  242. })
  243. it('module: getters', function () {
  244. const makeGetter = n => ({
  245. [`getter${n}`]: (state, getters, rootState) => {
  246. expect(getters.constant).toBe(0)
  247. expect(rootState).toBe(store.state)
  248. return state.a
  249. }
  250. })
  251. const store = new Vuex.Store({
  252. state: {
  253. a: 1
  254. },
  255. getters: {
  256. constant: () => 0,
  257. ...makeGetter(1)
  258. },
  259. modules: {
  260. nested: {
  261. state: { a: 2 },
  262. getters: makeGetter(2),
  263. modules: {
  264. one: {
  265. state: { a: 3 },
  266. getters: makeGetter(3)
  267. },
  268. nested: {
  269. modules: {
  270. two: {
  271. state: { a: 4 },
  272. getters: makeGetter(4)
  273. },
  274. three: {
  275. state: { a: 5 },
  276. getters: makeGetter(5)
  277. }
  278. }
  279. }
  280. }
  281. },
  282. four: {
  283. state: { a: 6 },
  284. getters: makeGetter(6)
  285. }
  286. }
  287. })
  288. ;[1, 2, 3, 4, 5, 6].forEach(n => {
  289. expect(store.getters[`getter${n}`]).toBe(n)
  290. })
  291. })
  292. it('module: namespace', () => {
  293. const actionSpy = jasmine.createSpy()
  294. const mutationSpy = jasmine.createSpy()
  295. const store = new Vuex.Store({
  296. modules: {
  297. a: {
  298. namespaced: true,
  299. state: {
  300. a: 1
  301. },
  302. getters: {
  303. b: () => 2
  304. },
  305. actions: {
  306. [TEST]: actionSpy
  307. },
  308. mutations: {
  309. [TEST]: mutationSpy
  310. }
  311. }
  312. }
  313. })
  314. expect(store.state.a.a).toBe(1)
  315. expect(store.getters['a/b']).toBe(2)
  316. store.dispatch('a/' + TEST)
  317. expect(actionSpy).toHaveBeenCalled()
  318. store.commit('a/' + TEST)
  319. expect(mutationSpy).toHaveBeenCalled()
  320. })
  321. it('module: nested namespace', () => {
  322. // mock module generator
  323. const actionSpys = []
  324. const mutationSpys = []
  325. const createModule = (name, namespaced, children) => {
  326. const actionSpy = jasmine.createSpy()
  327. const mutationSpy = jasmine.createSpy()
  328. actionSpys.push(actionSpy)
  329. mutationSpys.push(mutationSpy)
  330. return {
  331. namespaced,
  332. state: {
  333. [name]: true
  334. },
  335. getters: {
  336. [name]: state => state[name]
  337. },
  338. actions: {
  339. [name]: actionSpy
  340. },
  341. mutations: {
  342. [name]: mutationSpy
  343. },
  344. modules: children
  345. }
  346. }
  347. // mock module
  348. const modules = {
  349. a: createModule('a', true, { // a/a
  350. b: createModule('b', false, { // a/b - does not add namespace
  351. c: createModule('c', true) // a/c/c
  352. }),
  353. d: createModule('d', true) // a/d/d
  354. })
  355. }
  356. const store = new Vuex.Store({ modules })
  357. const expectedTypes = [
  358. 'a/a', 'a/b', 'a/c/c', 'a/d/d'
  359. ]
  360. // getters
  361. expectedTypes.forEach(type => {
  362. expect(store.getters[type]).toBe(true)
  363. })
  364. // actions
  365. expectedTypes.forEach(type => {
  366. store.dispatch(type)
  367. })
  368. actionSpys.forEach(spy => {
  369. expect(spy.calls.count()).toBe(1)
  370. })
  371. // mutations
  372. expectedTypes.forEach(type => {
  373. store.commit(type)
  374. })
  375. mutationSpys.forEach(spy => {
  376. expect(spy.calls.count()).toBe(1)
  377. })
  378. })
  379. it('module: getters are namespaced in namespaced module', () => {
  380. const store = new Vuex.Store({
  381. state: { value: 'root' },
  382. getters: {
  383. foo: state => state.value
  384. },
  385. modules: {
  386. a: {
  387. namespaced: true,
  388. state: { value: 'module' },
  389. getters: {
  390. foo: state => state.value,
  391. bar: (state, getters) => getters.foo,
  392. baz: (state, getters, rootState, rootGetters) => rootGetters.foo
  393. }
  394. }
  395. }
  396. })
  397. expect(store.getters['a/foo']).toBe('module')
  398. expect(store.getters['a/bar']).toBe('module')
  399. expect(store.getters['a/baz']).toBe('root')
  400. })
  401. it('module: action context is namespaced in namespaced module', done => {
  402. const rootActionSpy = jasmine.createSpy()
  403. const rootMutationSpy = jasmine.createSpy()
  404. const moduleActionSpy = jasmine.createSpy()
  405. const moduleMutationSpy = jasmine.createSpy()
  406. const store = new Vuex.Store({
  407. state: { value: 'root' },
  408. getters: { foo: state => state.value },
  409. actions: { foo: rootActionSpy },
  410. mutations: { foo: rootMutationSpy },
  411. modules: {
  412. a: {
  413. namespaced: true,
  414. state: { value: 'module' },
  415. getters: { foo: state => state.value },
  416. actions: {
  417. foo: moduleActionSpy,
  418. test ({ dispatch, commit, getters, rootGetters }) {
  419. expect(getters.foo).toBe('module')
  420. expect(rootGetters.foo).toBe('root')
  421. dispatch('foo')
  422. expect(moduleActionSpy.calls.count()).toBe(1)
  423. dispatch('foo', null, { root: true })
  424. expect(rootActionSpy.calls.count()).toBe(1)
  425. commit('foo')
  426. expect(moduleMutationSpy.calls.count()).toBe(1)
  427. commit('foo', null, { root: true })
  428. expect(rootMutationSpy.calls.count()).toBe(1)
  429. done()
  430. }
  431. },
  432. mutations: { foo: moduleMutationSpy }
  433. }
  434. }
  435. })
  436. store.dispatch('a/test')
  437. })
  438. it('module: use other module that has same namespace', done => {
  439. const actionSpy = jasmine.createSpy()
  440. const mutationSpy = jasmine.createSpy()
  441. const store = new Vuex.Store({
  442. modules: {
  443. parent: {
  444. namespaced: true,
  445. modules: {
  446. a: {
  447. state: { value: 'a' },
  448. getters: { foo: state => state.value },
  449. actions: { foo: actionSpy },
  450. mutations: { foo: mutationSpy }
  451. },
  452. b: {
  453. state: { value: 'b' },
  454. getters: { bar: (state, getters) => getters.foo },
  455. actions: {
  456. test ({ dispatch, commit, getters }) {
  457. expect(getters.foo).toBe('a')
  458. expect(getters.bar).toBe('a')
  459. dispatch('foo')
  460. expect(actionSpy).toHaveBeenCalled()
  461. commit('foo')
  462. expect(mutationSpy).toHaveBeenCalled()
  463. done()
  464. }
  465. }
  466. }
  467. }
  468. }
  469. }
  470. })
  471. store.dispatch('parent/test')
  472. })
  473. it('dispatching multiple actions in different modules', done => {
  474. const store = new Vuex.Store({
  475. modules: {
  476. a: {
  477. actions: {
  478. [TEST] () {
  479. return 1
  480. }
  481. }
  482. },
  483. b: {
  484. actions: {
  485. [TEST] () {
  486. return new Promise(r => r(2))
  487. }
  488. }
  489. }
  490. }
  491. })
  492. store.dispatch(TEST).then(res => {
  493. expect(res[0]).toBe(1)
  494. expect(res[1]).toBe(2)
  495. done()
  496. })
  497. })
  498. it('plugins', function () {
  499. let initState
  500. const mutations = []
  501. const store = new Vuex.Store({
  502. state: {
  503. a: 1
  504. },
  505. mutations: {
  506. [TEST] (state, n) {
  507. state.a += n
  508. }
  509. },
  510. plugins: [
  511. store => {
  512. initState = store.state
  513. store.subscribe((mut, state) => {
  514. expect(state).toBe(store.state)
  515. mutations.push(mut)
  516. })
  517. }
  518. ]
  519. })
  520. expect(initState).toBe(store.state)
  521. store.commit(TEST, 2)
  522. expect(mutations.length).toBe(1)
  523. expect(mutations[0].type).toBe(TEST)
  524. expect(mutations[0].payload).toBe(2)
  525. })
  526. })
  527. })