@mmapp/react 0.1.0-alpha.1 → 0.1.0-alpha.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/README.md +112 -0
  2. package/dist/index.d.mts +1378 -94
  3. package/dist/index.d.ts +1378 -94
  4. package/dist/index.js +1094 -1309
  5. package/dist/index.mjs +1038 -1296
  6. package/package.json +4 -3
  7. package/package.json.backup +0 -41
  8. package/src/Blueprint.ts +0 -216
  9. package/src/__tests__/Blueprint.test.ts +0 -106
  10. package/src/__tests__/action-context.test.ts +0 -166
  11. package/src/__tests__/actionCreators.test.ts +0 -179
  12. package/src/__tests__/builders.test.ts +0 -336
  13. package/src/__tests__/defineBlueprint-composition.test.ts +0 -106
  14. package/src/__tests__/factories.test.ts +0 -229
  15. package/src/__tests__/loader.test.ts +0 -159
  16. package/src/__tests__/logger.test.ts +0 -70
  17. package/src/__tests__/type-inference.test.ts +0 -160
  18. package/src/__tests__/typed-transitions.test.ts +0 -126
  19. package/src/__tests__/useModuleConfig.test.ts +0 -61
  20. package/src/actionCreators.ts +0 -132
  21. package/src/actions.ts +0 -547
  22. package/src/atoms/index.ts +0 -600
  23. package/src/authoring.ts +0 -92
  24. package/src/browser-player.ts +0 -783
  25. package/src/builders.ts +0 -1342
  26. package/src/components/ExperienceWorkflowBridge.tsx +0 -123
  27. package/src/components/PlayerProvider.tsx +0 -43
  28. package/src/components/atoms/index.tsx +0 -269
  29. package/src/components/index.ts +0 -36
  30. package/src/conditions.ts +0 -692
  31. package/src/config/defineBlueprint.ts +0 -329
  32. package/src/config/defineModel.ts +0 -753
  33. package/src/config/defineWorkspace.ts +0 -24
  34. package/src/core/WorkflowRuntime.ts +0 -153
  35. package/src/factories.ts +0 -425
  36. package/src/grammar/index.ts +0 -173
  37. package/src/hooks/index.ts +0 -106
  38. package/src/hooks/useAuth.ts +0 -288
  39. package/src/hooks/useChannel.ts +0 -304
  40. package/src/hooks/useComputed.ts +0 -154
  41. package/src/hooks/useDomainSubscription.ts +0 -110
  42. package/src/hooks/useDuringAction.ts +0 -99
  43. package/src/hooks/useExperienceState.ts +0 -59
  44. package/src/hooks/useExpressionLibrary.ts +0 -129
  45. package/src/hooks/useForm.ts +0 -352
  46. package/src/hooks/useGeolocation.ts +0 -207
  47. package/src/hooks/useMapView.ts +0 -259
  48. package/src/hooks/useMiddleware.ts +0 -291
  49. package/src/hooks/useModel.ts +0 -363
  50. package/src/hooks/useModule.ts +0 -59
  51. package/src/hooks/useModuleConfig.ts +0 -61
  52. package/src/hooks/useMutation.ts +0 -237
  53. package/src/hooks/useNotification.ts +0 -151
  54. package/src/hooks/useOnChange.ts +0 -30
  55. package/src/hooks/useOnEnter.ts +0 -59
  56. package/src/hooks/useOnEvent.ts +0 -37
  57. package/src/hooks/useOnExit.ts +0 -27
  58. package/src/hooks/useOnTransition.ts +0 -30
  59. package/src/hooks/usePackage.ts +0 -128
  60. package/src/hooks/useParams.ts +0 -33
  61. package/src/hooks/usePlayer.ts +0 -308
  62. package/src/hooks/useQuery.ts +0 -184
  63. package/src/hooks/useRealtimeQuery.ts +0 -222
  64. package/src/hooks/useRole.ts +0 -191
  65. package/src/hooks/useRouteParams.ts +0 -100
  66. package/src/hooks/useRouter.ts +0 -347
  67. package/src/hooks/useServerAction.ts +0 -178
  68. package/src/hooks/useServerState.ts +0 -284
  69. package/src/hooks/useToast.ts +0 -164
  70. package/src/hooks/useTransition.ts +0 -39
  71. package/src/hooks/useView.ts +0 -102
  72. package/src/hooks/useWhileIn.ts +0 -48
  73. package/src/hooks/useWorkflow.ts +0 -63
  74. package/src/index.ts +0 -465
  75. package/src/loader/experience-workflow-loader.ts +0 -192
  76. package/src/loader/index.ts +0 -6
  77. package/src/local/LocalEngine.ts +0 -388
  78. package/src/local/LocalEngineAdapter.ts +0 -175
  79. package/src/local/LocalEngineContext.ts +0 -30
  80. package/src/logger.ts +0 -37
  81. package/src/mixins.ts +0 -1160
  82. package/src/providers/RuntimeContext.ts +0 -20
  83. package/src/providers/WorkflowProvider.tsx +0 -28
  84. package/src/routing/instance-key.ts +0 -107
  85. package/src/server/transition-context.ts +0 -172
  86. package/src/testing/index.ts +0 -9
  87. package/src/testing/useBlueprintTestRunner.ts +0 -91
  88. package/src/testing/useGraphAnalysis.ts +0 -18
  89. package/src/testing/useTestRunner.ts +0 -77
  90. package/src/testing.ts +0 -995
  91. package/src/types/workflow-inference.ts +0 -158
  92. package/src/types.ts +0 -114
  93. package/tsconfig.json +0 -27
  94. package/vitest.config.ts +0 -8
@@ -1,179 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { createActions } from '../actionCreators';
3
- import { setField } from '../actions';
4
- import { model, state, transition } from '../builders';
5
-
6
- // =============================================================================
7
- // createActions
8
- // =============================================================================
9
-
10
- describe('createActions', () => {
11
- const actions = createActions({
12
- authenticate: { handler: 'actions/auth.server.ts' },
13
- destroySession: { handler: 'actions/auth.server.ts' },
14
- resetPassword: { handler: 'actions/auth.server.ts', functionName: 'handleReset' },
15
- sendEmail: { handler: 'actions/email.server.ts', mode: 'manual' },
16
- });
17
-
18
- it('returns an object with a creator for each manifest entry', () => {
19
- expect(typeof actions.authenticate).toBe('function');
20
- expect(typeof actions.destroySession).toBe('function');
21
- expect(typeof actions.resetPassword).toBe('function');
22
- expect(typeof actions.sendEmail).toBe('function');
23
- });
24
-
25
- it('produces a valid ActionDefinition with no config', () => {
26
- const result = actions.destroySession();
27
-
28
- expect(result).toEqual({
29
- id: 'server-destroy-session',
30
- type: 'server:destroySession',
31
- mode: 'auto',
32
- config: {
33
- __handler: 'actions/auth.server.ts',
34
- },
35
- });
36
- });
37
-
38
- it('produces a valid ActionDefinition with config', () => {
39
- const result = actions.authenticate({ method: 'login', email: 'test@example.com' });
40
-
41
- expect(result).toEqual({
42
- id: 'server-authenticate',
43
- type: 'server:authenticate',
44
- mode: 'auto',
45
- config: {
46
- method: 'login',
47
- email: 'test@example.com',
48
- __handler: 'actions/auth.server.ts',
49
- },
50
- });
51
- });
52
-
53
- it('includes functionName in config when specified', () => {
54
- const result = actions.resetPassword({ token: 'abc123' });
55
-
56
- expect(result.config).toEqual({
57
- token: 'abc123',
58
- __handler: 'actions/auth.server.ts',
59
- __functionName: 'handleReset',
60
- });
61
- });
62
-
63
- it('respects the mode from the manifest entry', () => {
64
- const result = actions.sendEmail();
65
- expect(result.mode).toBe('manual');
66
- });
67
-
68
- it('defaults mode to auto when not specified', () => {
69
- const result = actions.authenticate();
70
- expect(result.mode).toBe('auto');
71
- });
72
-
73
- it('generates kebab-case IDs from camelCase names', () => {
74
- const result = actions.destroySession();
75
- expect(result.id).toBe('server-destroy-session');
76
- });
77
-
78
- it('preserves the original action name in the type', () => {
79
- const result = actions.resetPassword();
80
- expect(result.type).toBe('server:resetPassword');
81
- });
82
-
83
- it('produces actions compatible with .do() / onEnter / onExit arrays', () => {
84
- const result = actions.authenticate({ method: 'login' });
85
-
86
- // ActionDefinition requires id, type, and optional mode/config/condition
87
- expect(result).toHaveProperty('id');
88
- expect(result).toHaveProperty('type');
89
- expect(typeof result.id).toBe('string');
90
- expect(typeof result.type).toBe('string');
91
- });
92
-
93
- it('works with an empty manifest', () => {
94
- const empty = createActions({});
95
- expect(Object.keys(empty)).toHaveLength(0);
96
- });
97
-
98
- it('each call produces a fresh ActionDefinition', () => {
99
- const a = actions.authenticate({ method: 'login' });
100
- const b = actions.authenticate({ method: 'register' });
101
-
102
- expect(a).not.toBe(b);
103
- expect(a.config!.method).toBe('login');
104
- expect(b.config!.method).toBe('register');
105
- });
106
-
107
- it('user config does not overwrite __handler', () => {
108
- const result = actions.authenticate({ __handler: 'hacked.ts' });
109
- // User config is spread first, then __handler overwrites
110
- expect(result.config!.__handler).toBe('actions/auth.server.ts');
111
- });
112
- });
113
-
114
- // =============================================================================
115
- // Integration: createActions + builder API
116
- // =============================================================================
117
-
118
- describe('createActions integration with model builder', () => {
119
- const actions = createActions({
120
- authenticate: { handler: 'actions/auth.server.ts' },
121
- destroySession: { handler: 'actions/auth.server.ts' },
122
- });
123
-
124
- it('works with TransitionBuilder.do()', () => {
125
- const result = model('auth')
126
- .state('unauthenticated', state.initial())
127
- .state('authenticating')
128
- .state('authenticated')
129
- .transition('login',
130
- transition.from('unauthenticated').to('authenticating')
131
- .do(actions.authenticate({ method: 'login' }))
132
- )
133
- .build();
134
-
135
- expect(result.transitions.login.actions).toHaveLength(1);
136
- expect(result.transitions.login.actions![0].type).toBe('server:authenticate');
137
- expect(result.transitions.login.actions![0].config).toMatchObject({ method: 'login' });
138
- });
139
-
140
- it('works with StateBuilder.onEnter()', () => {
141
- const result = model('auth')
142
- .state('authenticated', state.initial()
143
- .onEnter(actions.authenticate({ method: 'token' }))
144
- )
145
- .build();
146
-
147
- expect(result.states.authenticated.onEnter).toHaveLength(1);
148
- expect(result.states.authenticated.onEnter![0].type).toBe('server:authenticate');
149
- });
150
-
151
- it('works with StateBuilder.onExit()', () => {
152
- const result = model('auth')
153
- .state('authenticated', state.initial()
154
- .onExit(actions.destroySession())
155
- )
156
- .build();
157
-
158
- expect(result.states.authenticated.onExit).toHaveLength(1);
159
- expect(result.states.authenticated.onExit![0].type).toBe('server:destroySession');
160
- });
161
-
162
- it('works alongside plain action helpers', () => {
163
- const result = model('auth')
164
- .state('unauthenticated', state.initial())
165
- .state('authenticated')
166
- .transition('login',
167
- transition.from('unauthenticated').to('authenticated')
168
- .do(
169
- actions.authenticate({ method: 'login' }),
170
- setField('lastLogin', 'NOW()'),
171
- )
172
- )
173
- .build();
174
-
175
- expect(result.transitions.login.actions).toHaveLength(2);
176
- expect(result.transitions.login.actions![0].type).toBe('server:authenticate');
177
- expect(result.transitions.login.actions![1].type).toBe('set_field');
178
- });
179
- });
@@ -1,336 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { model, field, state, transition, StateBuilder } from '../builders';
3
- import { setField, logEvent, serverAction } from '../actions';
4
-
5
- describe('StateBuilder.to() — inline transitions', () => {
6
- it('declares a single outgoing transition', () => {
7
- const result = model('test')
8
- .state('draft', state.initial().to('review', 'submit'))
9
- .state('review')
10
- .build();
11
-
12
- expect(result.transitions.submit).toBeDefined();
13
- expect(result.transitions.submit.from).toBe('draft');
14
- expect(result.transitions.submit.to).toBe('review');
15
- });
16
-
17
- it('declares multiple outgoing transitions from one state', () => {
18
- const result = model('test')
19
- .state('review', new StateBuilder()
20
- .to('approved', 'approve')
21
- .to('rejected', 'reject')
22
- )
23
- .build();
24
-
25
- expect(result.transitions.approve).toEqual({ from: 'review', to: 'approved' });
26
- expect(result.transitions.reject).toEqual({ from: 'review', to: 'rejected' });
27
- });
28
-
29
- it('supports transition configuration via callback', () => {
30
- const result = model('test')
31
- .state('draft', state.initial()
32
- .to('review', 'submit', t => t.require('title', 'body').roles('author'))
33
- )
34
- .state('review')
35
- .build();
36
-
37
- expect(result.transitions.submit.requiredFields).toEqual(['title', 'body']);
38
- expect(result.transitions.submit.roles).toEqual(['author']);
39
- });
40
-
41
- it('standalone transitions override inline transitions', () => {
42
- const result = model('test')
43
- .state('draft', state.initial().to('review', 'submit'))
44
- .state('review')
45
- .transition('submit', transition.from('draft').to('review').roles('admin'))
46
- .build();
47
-
48
- expect(result.transitions.submit.roles).toEqual(['admin']);
49
- });
50
-
51
- it('inline transitions work with actions', () => {
52
- const result = model('test')
53
- .state('active', new StateBuilder()
54
- .to('inactive', 'deactivate', t =>
55
- t.do(setField('active', 'false'), logEvent('deactivated'))
56
- )
57
- )
58
- .build();
59
-
60
- expect(result.transitions.deactivate.actions).toHaveLength(2);
61
- expect(result.transitions.deactivate.actions![0].type).toBe('set_field');
62
- expect(result.transitions.deactivate.actions![1].type).toBe('log_event');
63
- });
64
-
65
- it('coexists with standalone transitions on the same model', () => {
66
- const result = model('test')
67
- .state('draft', state.initial().to('review', 'submit'))
68
- .state('review')
69
- .state('published')
70
- .transition('publish', transition.from('review').to('published'))
71
- .build();
72
-
73
- expect(result.transitions.submit.from).toBe('draft');
74
- expect(result.transitions.publish.from).toBe('review');
75
- });
76
- });
77
-
78
- describe('ModelBuilder.line() — auto-named transitions', () => {
79
- it('auto-generates transition names from state pairs', () => {
80
- const result = model('order')
81
- .line(
82
- ['placed', state.initial()],
83
- 'confirmed',
84
- 'shipped',
85
- ['delivered', state.end()],
86
- )
87
- .build();
88
-
89
- expect(Object.keys(result.states).sort()).toEqual(
90
- ['confirmed', 'delivered', 'placed', 'shipped'].sort()
91
- );
92
- expect(result.states.placed.type).toBe('initial');
93
- expect(result.states.delivered.type).toBe('end');
94
- expect(result.transitions.placed_to_confirmed).toEqual({ from: 'placed', to: 'confirmed' });
95
- expect(result.transitions.confirmed_to_shipped).toEqual({ from: 'confirmed', to: 'shipped' });
96
- expect(result.transitions.shipped_to_delivered).toEqual({ from: 'shipped', to: 'delivered' });
97
- });
98
-
99
- it('configures incoming edge via tuple callback', () => {
100
- const result = model('test')
101
- .line(
102
- 'draft',
103
- ['review', t => t.require('title', 'body')],
104
- ['approved', t => t.roles('manager')],
105
- )
106
- .build();
107
-
108
- expect(result.transitions.draft_to_review.requiredFields).toEqual(['title', 'body']);
109
- expect(result.transitions.review_to_approved.roles).toEqual(['manager']);
110
- });
111
-
112
- it('branching via multiple lines', () => {
113
- const result = model('order')
114
- .line(
115
- ['placed', state.initial()],
116
- 'confirmed',
117
- 'shipped',
118
- )
119
- .line('placed', 'cancelled')
120
- .line('confirmed', 'cancelled')
121
- .build();
122
-
123
- expect(Object.keys(result.transitions).sort()).toEqual([
124
- 'confirmed_to_cancelled',
125
- 'confirmed_to_shipped',
126
- 'placed_to_cancelled',
127
- 'placed_to_confirmed',
128
- ].sort());
129
- });
130
-
131
- it('two-state line', () => {
132
- const result = model('test')
133
- .line('active', 'inactive')
134
- .build();
135
-
136
- expect(result.transitions.active_to_inactive).toEqual({ from: 'active', to: 'inactive' });
137
- });
138
-
139
- it('self-loop', () => {
140
- const result = model('test')
141
- .line('active', 'active')
142
- .build();
143
-
144
- expect(result.transitions.active_to_active).toEqual({ from: 'active', to: 'active' });
145
- });
146
-
147
- it('with actions on edges', () => {
148
- const result = model('test')
149
- .line(
150
- 'draft',
151
- ['published', t => t.do(logEvent('published'), setField('publishedAt', 'NOW()'))],
152
- )
153
- .build();
154
-
155
- const tr = result.transitions.draft_to_published;
156
- expect(tr.actions).toHaveLength(2);
157
- expect(tr.actions![0].type).toBe('log_event');
158
- expect(tr.actions![1].type).toBe('set_field');
159
- });
160
-
161
- it('works with field definitions', () => {
162
- const result = model('invoice')
163
- .field('amount', field.currency().required())
164
- .line(
165
- ['draft', state.initial()],
166
- ['sent', t => t.require('amount')],
167
- ['paid', state.end()],
168
- )
169
- .build();
170
-
171
- expect(result.fields.amount.type).toBe('currency');
172
- expect(result.transitions.draft_to_sent.requiredFields).toEqual(['amount']);
173
- });
174
-
175
- it('mixes with .state() and .transition() methods', () => {
176
- const result = model('test')
177
- .state('error', new StateBuilder().onEnter(setField('hasError', 'true')))
178
- .line(
179
- ['draft', state.initial()],
180
- 'review',
181
- ['published', state.end()],
182
- )
183
- .transition('fail', transition.from('review').to('error'))
184
- .build();
185
-
186
- expect(result.states.error.onEnter).toHaveLength(1);
187
- expect(result.states.draft.type).toBe('initial');
188
- expect(result.transitions.draft_to_review).toBeDefined();
189
- expect(result.transitions.fail.from).toBe('review');
190
- });
191
-
192
- it('shared states across lines are unified', () => {
193
- const result = model('auth')
194
- .line(
195
- ['unauthenticated', state.initial()],
196
- 'authenticating',
197
- 'authenticated',
198
- )
199
- .line(
200
- 'unauthenticated',
201
- 'authenticating',
202
- 'authenticated',
203
- )
204
- .build();
205
-
206
- // States appear only once
207
- expect(Object.keys(result.states)).toEqual([
208
- 'unauthenticated', 'authenticating', 'authenticated',
209
- ]);
210
- expect(result.states.unauthenticated.type).toBe('initial');
211
- });
212
- });
213
-
214
- describe('ModelBuilder.line() — named transitions via tuple', () => {
215
- it('overrides auto-name with explicit transition name', () => {
216
- const result = model('test')
217
- .line(
218
- 'draft',
219
- ['review', 'submit'],
220
- ['published', 'approve'],
221
- )
222
- .build();
223
-
224
- expect(result.transitions.submit).toEqual({ from: 'draft', to: 'review' });
225
- expect(result.transitions.approve).toEqual({ from: 'review', to: 'published' });
226
- // Auto-named versions should NOT exist
227
- expect(result.transitions.draft_to_review).toBeUndefined();
228
- expect(result.transitions.review_to_published).toBeUndefined();
229
- });
230
-
231
- it('named transition with configuration', () => {
232
- const result = model('test')
233
- .line(
234
- 'draft',
235
- ['review', 'submit', t => t.require('title')],
236
- ['published', 'approve', t => t.roles('manager')],
237
- )
238
- .build();
239
-
240
- expect(result.transitions.submit.requiredFields).toEqual(['title']);
241
- expect(result.transitions.approve.roles).toEqual(['manager']);
242
- });
243
-
244
- it('mix named and auto-named in same line', () => {
245
- const result = model('test')
246
- .line(
247
- 'draft',
248
- ['review', 'submit'], // named
249
- 'published', // auto-named: review_to_published
250
- )
251
- .build();
252
-
253
- expect(result.transitions.submit).toBeDefined();
254
- expect(result.transitions.review_to_published).toBeDefined();
255
- });
256
-
257
- it('merges from-states for named transitions across lines', () => {
258
- const result = model('order')
259
- .line('placed', ['cancelled', 'cancel'])
260
- .line('confirmed', ['cancelled', 'cancel'])
261
- .build();
262
-
263
- const cancelFrom = result.transitions.cancel.from;
264
- expect(Array.isArray(cancelFrom)).toBe(true);
265
- expect(cancelFrom).toContain('placed');
266
- expect(cancelFrom).toContain('confirmed');
267
- });
268
- });
269
-
270
- describe('ModelBuilder.line() — complete auth example', () => {
271
- it('models auth with auto-named transitions', () => {
272
- const result = model('mod-authentication')
273
- .field('errorMessage', field.string(''))
274
-
275
- // Login flow
276
- .line(
277
- ['unauthenticated', state.initial().onEnter(setField('errorMessage', '""'))],
278
- ['authenticating', t => t.do(serverAction('authenticate', { method: 'login' }))],
279
- 'authenticated',
280
- )
281
-
282
- // Error handling
283
- .line('authenticating', 'error')
284
- .line('error', 'unauthenticated')
285
-
286
- // Logout
287
- .line(
288
- 'authenticated',
289
- ['unauthenticated', t => t.do(serverAction('destroy_session'))],
290
- )
291
- .build();
292
-
293
- expect(result.states.unauthenticated.type).toBe('initial');
294
- expect(result.transitions.unauthenticated_to_authenticating.actions![0].type).toBe('server:authenticate');
295
- expect(result.transitions.authenticated_to_unauthenticated.actions![0].type).toBe('server:destroy_session');
296
- expect(result.transitions.authenticating_to_error).toBeDefined();
297
- expect(result.transitions.error_to_unauthenticated).toBeDefined();
298
- });
299
-
300
- it('models auth with named transitions', () => {
301
- const result = model('mod-authentication')
302
- .field('errorMessage', field.string(''))
303
-
304
- .line(
305
- ['unauthenticated', state.initial().onEnter(setField('errorMessage', '""'))],
306
- ['authenticating', 'login', t => t.do(serverAction('authenticate', { method: 'login' }))],
307
- ['authenticated', 'login_success'],
308
- )
309
- .line(
310
- 'unauthenticated',
311
- ['authenticating', 'signup', t => t.do(serverAction('authenticate', { method: 'signup' }))],
312
- ['authenticated', 'signup_success'],
313
- )
314
- .line('authenticating', ['error', 'login_error'])
315
- .line('authenticating', ['error', 'signup_error'])
316
- .line('error', ['unauthenticated', 'retry'])
317
- .line(
318
- 'authenticated',
319
- ['unauthenticated', 'logout', t => t.do(serverAction('destroy_session'))],
320
- )
321
- .build();
322
-
323
- expect(result.states.unauthenticated.type).toBe('initial');
324
- expect(result.transitions.login.actions![0].type).toBe('server:authenticate');
325
- expect(result.transitions.signup.actions![0].type).toBe('server:authenticate');
326
- expect(result.transitions.logout.actions![0].type).toBe('server:destroy_session');
327
- expect(result.transitions.login_success.from).toBe('authenticating');
328
- expect(result.transitions.signup_success.from).toBe('authenticating');
329
- expect(result.transitions.retry.from).toBe('error');
330
- expect(result.transitions.retry.to).toBe('unauthenticated');
331
-
332
- // login_error and signup_error merge from-states
333
- const loginErrorFrom = result.transitions.login_error.from;
334
- expect(loginErrorFrom).toBe('authenticating');
335
- });
336
- });
@@ -1,106 +0,0 @@
1
- /**
2
- * Tests for BlueprintDependency composition types — routeConfig, slotMapping.
3
- */
4
-
5
- import { describe, it, expect } from 'vitest';
6
- import { defineBlueprint, type BlueprintDependency, type ModuleRouteConfig } from '../config/defineBlueprint';
7
-
8
- describe('BlueprintDependency composition types', () => {
9
- it('accepts routeConfig with prefix', () => {
10
- const dep: BlueprintDependency = {
11
- slug: 'mod-authentication',
12
- version: '>=3.0.0',
13
- routeConfig: {
14
- prefix: '/auth',
15
- },
16
- };
17
- expect(dep.routeConfig?.prefix).toBe('/auth');
18
- });
19
-
20
- it('accepts routeConfig with per-route remapping', () => {
21
- const dep: BlueprintDependency = {
22
- slug: 'mod-authentication',
23
- routeConfig: {
24
- prefix: '/auth',
25
- routes: {
26
- '/login': '/',
27
- '/signup': '/join',
28
- '/profile': false,
29
- },
30
- },
31
- };
32
- expect(dep.routeConfig?.routes?.['/login']).toBe('/');
33
- expect(dep.routeConfig?.routes?.['/profile']).toBe(false);
34
- });
35
-
36
- it('accepts slotMapping', () => {
37
- const dep: BlueprintDependency = {
38
- slug: 'mod-authentication',
39
- slotMapping: {
40
- 'auth:login-providers': 'app:social-login',
41
- 'auth:profile-sections': 'app:settings-tabs',
42
- },
43
- };
44
- expect(dep.slotMapping?.['auth:login-providers']).toBe('app:social-login');
45
- });
46
-
47
- it('accepts config', () => {
48
- const dep: BlueprintDependency = {
49
- slug: 'mod-authentication',
50
- config: {
51
- layout: 'card',
52
- methods: ['email-password', 'google'],
53
- },
54
- };
55
- expect(dep.config?.layout).toBe('card');
56
- });
57
-
58
- it('works in defineBlueprint with full dependency config', () => {
59
- const blueprint = defineBlueprint({
60
- slug: 'employee-salary-tracking',
61
- name: 'Employee Salary Tracking',
62
- version: '1.0.0',
63
- dependencies: [
64
- {
65
- slug: 'mod-authentication',
66
- version: '>=3.0.0',
67
- required: true,
68
- routeConfig: {
69
- prefix: '/auth',
70
- routes: { '/login': '/' },
71
- },
72
- slotMapping: {
73
- 'auth:profile-sections': 'app:settings-tabs',
74
- },
75
- config: {
76
- layout: 'split',
77
- },
78
- },
79
- ],
80
- });
81
-
82
- expect(blueprint.dependencies).toHaveLength(1);
83
- expect(blueprint.dependencies![0].routeConfig?.prefix).toBe('/auth');
84
- expect(blueprint.dependencies![0].slotMapping?.['auth:profile-sections']).toBe('app:settings-tabs');
85
- });
86
-
87
- it('ModuleRouteConfig type works independently', () => {
88
- const config: ModuleRouteConfig = {
89
- prefix: '/account',
90
- routes: {
91
- '/login': '/signin',
92
- '/register': false,
93
- },
94
- };
95
- expect(config.prefix).toBe('/account');
96
- });
97
-
98
- it('backward compatible — all new fields are optional', () => {
99
- const dep: BlueprintDependency = {
100
- slug: 'mod-auth',
101
- };
102
- expect(dep.routeConfig).toBeUndefined();
103
- expect(dep.slotMapping).toBeUndefined();
104
- expect(dep.config).toBeUndefined();
105
- });
106
- });