vuex-dispatch-synthesizer.test.ts 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. /**
  2. * Vuex string-keyed dispatch/commit bridge.
  3. *
  4. * Vuex dispatches actions/mutations by a runtime STRING key — `dispatch('user/login')`,
  5. * `commit('SET_TOKEN')` — with no static edge to the handler (an object-literal
  6. * method in a store module). This bridges the key to its function node: the last
  7. * `/` segment is the action/mutation name, the preceding segment is the namespace
  8. * (≈ the module file). It resolves to a node IN A STORE FILE (excluding a same-named
  9. * `api/` helper — a real collision), disambiguated by the namespace appearing in the
  10. * path, or the same file for a root `commit('M')` inside an action. Redux-style
  11. * `dispatch(actionCreator())` (no string key) produces nothing.
  12. *
  13. * Also exercises the canonical Vuex MODULE shape `export default { namespaced,
  14. * actions: {…}, mutations: {…} }` — whose methods only become nodes via the
  15. * store-collection extraction this bridge depends on.
  16. */
  17. import { describe, it, expect, beforeEach, afterEach } from 'vitest';
  18. import * as fs from 'node:fs';
  19. import * as path from 'node:path';
  20. import * as os from 'node:os';
  21. import { CodeGraph } from '../src';
  22. describe('vuex-dispatch synthesizer', () => {
  23. let dir: string;
  24. beforeEach(() => { dir = fs.mkdtempSync(path.join(os.tmpdir(), 'vuex-dispatch-')); });
  25. afterEach(() => { fs.rmSync(dir, { recursive: true, force: true }); });
  26. it('bridges namespaced dispatch + local commit to the right store handler, excluding an api collision', async () => {
  27. fs.mkdirSync(path.join(dir, 'store', 'modules'), { recursive: true });
  28. fs.mkdirSync(path.join(dir, 'api'), { recursive: true });
  29. // Canonical Vuex module: `export default { namespaced, actions, mutations }`.
  30. fs.writeFileSync(
  31. path.join(dir, 'store', 'modules', 'user.js'),
  32. `import { login as apiLogin } from '../../api/user';
  33. export default {
  34. namespaced: true,
  35. state: { token: '' },
  36. mutations: {
  37. SET_TOKEN(state, t) { state.token = t; },
  38. },
  39. actions: {
  40. login({ commit }, info) {
  41. apiLogin(info);
  42. commit('SET_TOKEN', info.token); // root/local key → SET_TOKEN in THIS module
  43. },
  44. },
  45. };
  46. `
  47. );
  48. // Collision: an api helper ALSO named `login` — must never be the dispatch target.
  49. fs.writeFileSync(
  50. path.join(dir, 'api', 'user.js'),
  51. `export function login(info) { return info; }
  52. `
  53. );
  54. // Consumer dispatches by namespaced string key.
  55. fs.writeFileSync(
  56. path.join(dir, 'app.js'),
  57. `import store from './store';
  58. export function bootstrap() {
  59. store.dispatch('user/login', { token: 'x' });
  60. }
  61. `
  62. );
  63. // Redux-style control: a non-string dispatch must produce no vuex edge.
  64. fs.writeFileSync(
  65. path.join(dir, 'reduxy.js'),
  66. `export function reduxy(dispatch) {
  67. dispatch(someAction());
  68. }
  69. `
  70. );
  71. const cg = await CodeGraph.init(dir, { silent: true });
  72. await cg.indexAll();
  73. const db = (cg as any).db.db;
  74. const edges = db
  75. .prepare(
  76. `SELECT s.name source, t.name target, t.file_path tf, json_extract(e.metadata,'$.via') via
  77. FROM edges e JOIN nodes s ON s.id = e.source JOIN nodes t ON t.id = e.target
  78. WHERE json_extract(e.metadata,'$.synthesizedBy') = 'vuex-dispatch'`
  79. )
  80. .all();
  81. // bootstrap → login, resolving to the STORE module (not api/user.js).
  82. const loginEdge = edges.find((r: any) => r.source === 'bootstrap' && r.target === 'login');
  83. expect(loginEdge).toBeTruthy();
  84. expect(loginEdge.tf).toMatch(/store[\\/]modules[\\/]user\.js$/);
  85. expect(loginEdge.via).toBe('user/login');
  86. // The api helper of the same name was never targeted.
  87. expect(edges.some((r: any) => /api[\\/]user\.js$/.test(r.tf))).toBe(false);
  88. // Local commit('SET_TOKEN') inside the action → the same module's mutation.
  89. expect(edges.some((r: any) => r.source === 'login' && r.target === 'SET_TOKEN')).toBe(true);
  90. // Redux-style non-string dispatch contributed nothing.
  91. expect(edges.some((r: any) => r.source === 'reduxy')).toBe(false);
  92. cg.close?.();
  93. });
  94. });