digital-products 2.1.3 → 2.3.0

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 (122) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +2 -0
  3. package/dist/api.js +7 -7
  4. package/dist/api.js.map +1 -1
  5. package/dist/app.js +6 -6
  6. package/dist/app.js.map +1 -1
  7. package/dist/client.d.ts +157 -0
  8. package/dist/client.d.ts.map +1 -0
  9. package/dist/client.js +69 -0
  10. package/dist/client.js.map +1 -0
  11. package/dist/content.js +7 -7
  12. package/dist/content.js.map +1 -1
  13. package/dist/data.d.ts.map +1 -1
  14. package/dist/data.js +6 -6
  15. package/dist/data.js.map +1 -1
  16. package/dist/dataset.js +5 -5
  17. package/dist/dataset.js.map +1 -1
  18. package/dist/index.d.ts +92 -13
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +139 -15
  21. package/dist/index.js.map +1 -1
  22. package/dist/mcp.d.ts +1 -1
  23. package/dist/mcp.d.ts.map +1 -1
  24. package/dist/mcp.js +17 -10
  25. package/dist/mcp.js.map +1 -1
  26. package/dist/product.js +2 -2
  27. package/dist/product.js.map +1 -1
  28. package/dist/sdk.d.ts.map +1 -1
  29. package/dist/sdk.js +52 -16
  30. package/dist/sdk.js.map +1 -1
  31. package/dist/site.d.ts.map +1 -1
  32. package/dist/site.js +12 -8
  33. package/dist/site.js.map +1 -1
  34. package/dist/types.d.ts +830 -12
  35. package/dist/types.d.ts.map +1 -1
  36. package/dist/types.js +495 -2
  37. package/dist/types.js.map +1 -1
  38. package/dist/worker.d.ts +205 -0
  39. package/dist/worker.d.ts.map +1 -0
  40. package/dist/worker.js +356 -0
  41. package/dist/worker.js.map +1 -0
  42. package/package.json +29 -13
  43. package/src/api.ts +7 -7
  44. package/src/app.ts +6 -6
  45. package/src/client.ts +192 -0
  46. package/src/content.ts +7 -7
  47. package/src/data.ts +12 -7
  48. package/src/dataset.ts +5 -5
  49. package/src/index.ts +151 -15
  50. package/src/mcp.ts +18 -11
  51. package/src/product.ts +2 -2
  52. package/src/sdk.ts +54 -15
  53. package/src/site.ts +12 -8
  54. package/src/types.ts +821 -12
  55. package/src/worker.ts +525 -0
  56. package/test/product.test.ts +53 -198
  57. package/test/unified-types.test.ts +589 -0
  58. package/test/worker.test.ts +912 -0
  59. package/vitest.config.ts +42 -0
  60. package/wrangler.jsonc +36 -0
  61. package/.turbo/turbo-build.log +0 -5
  62. package/LICENSE +0 -21
  63. package/dist/features/define.d.ts +0 -63
  64. package/dist/features/define.d.ts.map +0 -1
  65. package/dist/features/define.js +0 -72
  66. package/dist/features/define.js.map +0 -1
  67. package/dist/features/flags.d.ts +0 -98
  68. package/dist/features/flags.d.ts.map +0 -1
  69. package/dist/features/flags.js +0 -145
  70. package/dist/features/flags.js.map +0 -1
  71. package/dist/features/toggles.d.ts +0 -75
  72. package/dist/features/toggles.d.ts.map +0 -1
  73. package/dist/features/toggles.js +0 -107
  74. package/dist/features/toggles.js.map +0 -1
  75. package/dist/tiers/define.d.ts +0 -63
  76. package/dist/tiers/define.d.ts.map +0 -1
  77. package/dist/tiers/define.js +0 -78
  78. package/dist/tiers/define.js.map +0 -1
  79. package/dist/tiers/entitlements.d.ts +0 -94
  80. package/dist/tiers/entitlements.d.ts.map +0 -1
  81. package/dist/tiers/entitlements.js +0 -94
  82. package/dist/tiers/entitlements.js.map +0 -1
  83. package/src/api.js +0 -128
  84. package/src/app.js +0 -106
  85. package/src/content.js +0 -77
  86. package/src/data.js +0 -106
  87. package/src/dataset.js +0 -49
  88. package/src/entities/ai.js +0 -858
  89. package/src/entities/content.js +0 -783
  90. package/src/entities/index.js +0 -88
  91. package/src/entities/interfaces.js +0 -929
  92. package/src/entities/lifecycle.js +0 -803
  93. package/src/entities/products.js +0 -797
  94. package/src/entities/web.js +0 -657
  95. package/src/features/define.ts +0 -130
  96. package/src/features/flags.ts +0 -247
  97. package/src/features/toggles.ts +0 -189
  98. package/src/index.js +0 -35
  99. package/src/mcp.js +0 -139
  100. package/src/pricing/billing.ts +0 -386
  101. package/src/pricing/plans.ts +0 -214
  102. package/src/product.js +0 -53
  103. package/src/registry.js +0 -31
  104. package/src/sdk.js +0 -127
  105. package/src/site.js +0 -112
  106. package/src/tiers/define.ts +0 -137
  107. package/src/tiers/entitlements.ts +0 -201
  108. package/src/types.js +0 -4
  109. package/test/analytics/events.test.ts +0 -319
  110. package/test/analytics/experiments.test.ts +0 -327
  111. package/test/features/define.test.ts +0 -187
  112. package/test/features/flags.test.ts +0 -259
  113. package/test/features/toggles.test.ts +0 -178
  114. package/test/lifecycle/stages.test.ts +0 -233
  115. package/test/lifecycle/transitions.test.ts +0 -207
  116. package/test/onboarding/flows.test.ts +0 -307
  117. package/test/pricing/billing.test.ts +0 -287
  118. package/test/pricing/plans.test.ts +0 -307
  119. package/test/roadmap/milestones.test.ts +0 -231
  120. package/test/roadmap/priorities.test.ts +0 -239
  121. package/test/tiers/define.test.ts +0 -192
  122. package/test/tiers/entitlements.test.ts +0 -220
@@ -1,231 +0,0 @@
1
- /**
2
- * Tests for Roadmap Milestones
3
- * Phase 1: RED - These tests should FAIL initially
4
- */
5
-
6
- import { describe, it, expect, beforeEach } from 'vitest'
7
- import {
8
- Milestone,
9
- createMilestone,
10
- MilestoneRegistry,
11
- createMilestoneRegistry,
12
- MilestoneStatus,
13
- } from '../../src/roadmap/milestones.js'
14
-
15
- describe('Roadmap Milestones', () => {
16
- describe('Milestone', () => {
17
- it('creates a milestone', () => {
18
- const milestone = createMilestone({
19
- id: 'v2-release',
20
- name: 'Version 2.0 Release',
21
- description: 'Major version release',
22
- targetDate: new Date('2024-06-01'),
23
- })
24
-
25
- expect(milestone.id).toBe('v2-release')
26
- expect(milestone.name).toBe('Version 2.0 Release')
27
- expect(milestone.status).toBe('planned')
28
- })
29
-
30
- it('supports feature list', () => {
31
- const milestone = createMilestone({
32
- id: 'q1-goals',
33
- name: 'Q1 Goals',
34
- features: ['dark-mode', 'api-v2', 'mobile-app'],
35
- targetDate: new Date('2024-03-31'),
36
- })
37
-
38
- expect(milestone.features).toHaveLength(3)
39
- })
40
-
41
- it('supports dependencies', () => {
42
- const milestone = createMilestone({
43
- id: 'dependent',
44
- name: 'Dependent Milestone',
45
- dependencies: ['prerequisite-1', 'prerequisite-2'],
46
- targetDate: new Date('2024-09-01'),
47
- })
48
-
49
- expect(milestone.dependencies).toContain('prerequisite-1')
50
- })
51
-
52
- it('tracks progress', () => {
53
- const milestone = createMilestone({
54
- id: 'progress-test',
55
- name: 'Progress Test',
56
- features: ['f1', 'f2', 'f3', 'f4'],
57
- completedFeatures: ['f1', 'f2'],
58
- targetDate: new Date('2024-06-01'),
59
- })
60
-
61
- expect(milestone.progress).toBe(50)
62
- })
63
-
64
- it('has customizable status', () => {
65
- const milestone = createMilestone({
66
- id: 'in-progress',
67
- name: 'In Progress',
68
- status: 'in_progress',
69
- targetDate: new Date('2024-06-01'),
70
- })
71
-
72
- expect(milestone.status).toBe('in_progress')
73
- })
74
-
75
- it('supports metadata', () => {
76
- const milestone = createMilestone({
77
- id: 'with-meta',
78
- name: 'With Metadata',
79
- metadata: {
80
- owner: 'product-team',
81
- priority: 'high',
82
- },
83
- targetDate: new Date('2024-06-01'),
84
- })
85
-
86
- expect(milestone.metadata?.owner).toBe('product-team')
87
- })
88
- })
89
-
90
- describe('MilestoneRegistry', () => {
91
- let registry: MilestoneRegistry
92
-
93
- beforeEach(() => {
94
- registry = createMilestoneRegistry()
95
- })
96
-
97
- it('registers and retrieves milestones', () => {
98
- const milestone = createMilestone({
99
- id: 'test',
100
- name: 'Test',
101
- targetDate: new Date('2024-06-01'),
102
- })
103
-
104
- registry.register(milestone)
105
- expect(registry.get('test')).toEqual(milestone)
106
- })
107
-
108
- it('lists milestones sorted by date', () => {
109
- registry.register(
110
- createMilestone({ id: 'm3', name: 'M3', targetDate: new Date('2024-09-01') })
111
- )
112
- registry.register(
113
- createMilestone({ id: 'm1', name: 'M1', targetDate: new Date('2024-03-01') })
114
- )
115
- registry.register(
116
- createMilestone({ id: 'm2', name: 'M2', targetDate: new Date('2024-06-01') })
117
- )
118
-
119
- const sorted = registry.listSorted()
120
- expect(sorted[0].id).toBe('m1')
121
- expect(sorted[1].id).toBe('m2')
122
- expect(sorted[2].id).toBe('m3')
123
- })
124
-
125
- it('filters by status', () => {
126
- registry.register(
127
- createMilestone({
128
- id: 'planned',
129
- name: 'Planned',
130
- status: 'planned',
131
- targetDate: new Date('2024-06-01'),
132
- })
133
- )
134
- registry.register(
135
- createMilestone({
136
- id: 'completed',
137
- name: 'Completed',
138
- status: 'completed',
139
- targetDate: new Date('2024-03-01'),
140
- })
141
- )
142
-
143
- const planned = registry.listByStatus('planned')
144
- expect(planned).toHaveLength(1)
145
- })
146
-
147
- it('finds upcoming milestones', () => {
148
- registry.register(
149
- createMilestone({ id: 'past', name: 'Past', targetDate: new Date('2020-01-01') })
150
- )
151
- registry.register(
152
- createMilestone({ id: 'future', name: 'Future', targetDate: new Date('2030-01-01') })
153
- )
154
-
155
- const upcoming = registry.listUpcoming()
156
- expect(upcoming).toHaveLength(1)
157
- expect(upcoming[0].id).toBe('future')
158
- })
159
-
160
- it('calculates overall progress', () => {
161
- registry.register(
162
- createMilestone({
163
- id: 'm1',
164
- name: 'M1',
165
- features: ['f1', 'f2'],
166
- completedFeatures: ['f1'],
167
- targetDate: new Date('2024-06-01'),
168
- })
169
- )
170
- registry.register(
171
- createMilestone({
172
- id: 'm2',
173
- name: 'M2',
174
- features: ['f3', 'f4'],
175
- completedFeatures: ['f3', 'f4'],
176
- targetDate: new Date('2024-06-01'),
177
- })
178
- )
179
-
180
- const progress = registry.getOverallProgress()
181
- expect(progress).toBe(75) // 3 out of 4 features
182
- })
183
-
184
- it('gets milestone timeline', () => {
185
- registry.register(
186
- createMilestone({ id: 'm1', name: 'M1', targetDate: new Date('2024-03-01') })
187
- )
188
- registry.register(
189
- createMilestone({ id: 'm2', name: 'M2', targetDate: new Date('2024-06-01') })
190
- )
191
-
192
- const timeline = registry.getTimeline()
193
- expect(timeline).toHaveLength(2)
194
- expect(timeline[0].date).toBeDefined()
195
- })
196
-
197
- it('updates milestone progress', () => {
198
- registry.register(
199
- createMilestone({
200
- id: 'update-test',
201
- name: 'Update Test',
202
- features: ['f1', 'f2', 'f3'],
203
- completedFeatures: [],
204
- targetDate: new Date('2024-06-01'),
205
- })
206
- )
207
-
208
- registry.completeFeature('update-test', 'f1')
209
- const milestone = registry.get('update-test')
210
- expect(milestone?.completedFeatures).toContain('f1')
211
- expect(milestone?.progress).toBeCloseTo(33, 0)
212
- })
213
-
214
- it('detects blocked milestones', () => {
215
- registry.register(createMilestone({ id: 'blocker', name: 'Blocker', status: 'planned', targetDate: new Date('2024-06-01') }))
216
- registry.register(
217
- createMilestone({
218
- id: 'blocked',
219
- name: 'Blocked',
220
- dependencies: ['blocker'],
221
- status: 'planned',
222
- targetDate: new Date('2024-09-01'),
223
- })
224
- )
225
-
226
- const blocked = registry.getBlockedMilestones()
227
- expect(blocked).toHaveLength(1)
228
- expect(blocked[0].id).toBe('blocked')
229
- })
230
- })
231
- })
@@ -1,239 +0,0 @@
1
- /**
2
- * Tests for Roadmap Priorities
3
- * Phase 1: RED - These tests should FAIL initially
4
- */
5
-
6
- import { describe, it, expect, beforeEach } from 'vitest'
7
- import {
8
- RoadmapItem,
9
- createRoadmapItem,
10
- PriorityMatrix,
11
- createPriorityMatrix,
12
- ImpactEffortScore,
13
- } from '../../src/roadmap/priorities.js'
14
-
15
- describe('Roadmap Priorities', () => {
16
- describe('RoadmapItem', () => {
17
- it('creates a roadmap item', () => {
18
- const item = createRoadmapItem({
19
- id: 'feature-x',
20
- name: 'Feature X',
21
- description: 'A new feature',
22
- impact: 8,
23
- effort: 5,
24
- })
25
-
26
- expect(item.id).toBe('feature-x')
27
- expect(item.impact).toBe(8)
28
- expect(item.effort).toBe(5)
29
- })
30
-
31
- it('calculates priority score', () => {
32
- const item = createRoadmapItem({
33
- id: 'high-priority',
34
- name: 'High Priority',
35
- impact: 10,
36
- effort: 2,
37
- })
38
-
39
- expect(item.priorityScore).toBe(5) // impact / effort
40
- })
41
-
42
- it('supports custom scoring function', () => {
43
- const item = createRoadmapItem({
44
- id: 'custom',
45
- name: 'Custom Score',
46
- impact: 8,
47
- effort: 4,
48
- confidence: 0.9,
49
- scoreFn: (i, e, c) => (i * (c || 1)) / e,
50
- })
51
-
52
- expect(item.priorityScore).toBeCloseTo(1.8, 1)
53
- })
54
-
55
- it('supports confidence level', () => {
56
- const item = createRoadmapItem({
57
- id: 'uncertain',
58
- name: 'Uncertain',
59
- impact: 8,
60
- effort: 4,
61
- confidence: 0.5,
62
- })
63
-
64
- expect(item.confidence).toBe(0.5)
65
- })
66
-
67
- it('supports time estimate', () => {
68
- const item = createRoadmapItem({
69
- id: 'timed',
70
- name: 'Timed',
71
- impact: 5,
72
- effort: 5,
73
- estimatedDays: 14,
74
- })
75
-
76
- expect(item.estimatedDays).toBe(14)
77
- })
78
-
79
- it('supports categories', () => {
80
- const item = createRoadmapItem({
81
- id: 'categorized',
82
- name: 'Categorized',
83
- impact: 5,
84
- effort: 5,
85
- category: 'infrastructure',
86
- tags: ['backend', 'performance'],
87
- })
88
-
89
- expect(item.category).toBe('infrastructure')
90
- expect(item.tags).toContain('backend')
91
- })
92
- })
93
-
94
- describe('PriorityMatrix', () => {
95
- let matrix: PriorityMatrix
96
-
97
- beforeEach(() => {
98
- matrix = createPriorityMatrix()
99
- })
100
-
101
- it('adds items to matrix', () => {
102
- matrix.addItem(
103
- createRoadmapItem({
104
- id: 'item-1',
105
- name: 'Item 1',
106
- impact: 7,
107
- effort: 3,
108
- })
109
- )
110
-
111
- expect(matrix.getItem('item-1')).toBeDefined()
112
- })
113
-
114
- it('ranks items by priority score', () => {
115
- matrix.addItem(createRoadmapItem({ id: 'low', name: 'Low', impact: 2, effort: 8 }))
116
- matrix.addItem(createRoadmapItem({ id: 'high', name: 'High', impact: 9, effort: 2 }))
117
- matrix.addItem(createRoadmapItem({ id: 'mid', name: 'Mid', impact: 5, effort: 5 }))
118
-
119
- const ranked = matrix.getRanked()
120
- expect(ranked[0].id).toBe('high')
121
- expect(ranked[ranked.length - 1].id).toBe('low')
122
- })
123
-
124
- it('groups by quadrant', () => {
125
- // High impact, low effort = Quick Wins
126
- matrix.addItem(createRoadmapItem({ id: 'qw', name: 'Quick Win', impact: 9, effort: 2 }))
127
- // High impact, high effort = Big Bets
128
- matrix.addItem(createRoadmapItem({ id: 'bb', name: 'Big Bet', impact: 9, effort: 9 }))
129
- // Low impact, low effort = Fill-ins
130
- matrix.addItem(createRoadmapItem({ id: 'fi', name: 'Fill-in', impact: 2, effort: 2 }))
131
- // Low impact, high effort = Time Sinks
132
- matrix.addItem(createRoadmapItem({ id: 'ts', name: 'Time Sink', impact: 2, effort: 9 }))
133
-
134
- const quadrants = matrix.getQuadrants()
135
- expect(quadrants.quickWins[0].id).toBe('qw')
136
- expect(quadrants.bigBets[0].id).toBe('bb')
137
- expect(quadrants.fillIns[0].id).toBe('fi')
138
- expect(quadrants.timeSinks[0].id).toBe('ts')
139
- })
140
-
141
- it('filters by category', () => {
142
- matrix.addItem(
143
- createRoadmapItem({
144
- id: 'ux',
145
- name: 'UX',
146
- impact: 5,
147
- effort: 5,
148
- category: 'ux',
149
- })
150
- )
151
- matrix.addItem(
152
- createRoadmapItem({
153
- id: 'backend',
154
- name: 'Backend',
155
- impact: 5,
156
- effort: 5,
157
- category: 'backend',
158
- })
159
- )
160
-
161
- const ux = matrix.filterByCategory('ux')
162
- expect(ux).toHaveLength(1)
163
- })
164
-
165
- it('suggests next items', () => {
166
- matrix.addItem(createRoadmapItem({ id: 'high', name: 'High', impact: 10, effort: 2 }))
167
- matrix.addItem(createRoadmapItem({ id: 'mid', name: 'Mid', impact: 5, effort: 5 }))
168
- matrix.addItem(createRoadmapItem({ id: 'low', name: 'Low', impact: 2, effort: 8 }))
169
-
170
- const suggestions = matrix.suggestNext(2)
171
- expect(suggestions).toHaveLength(2)
172
- expect(suggestions[0].id).toBe('high')
173
- })
174
-
175
- it('calculates capacity planning', () => {
176
- matrix.addItem(
177
- createRoadmapItem({
178
- id: 'item-1',
179
- name: 'Item 1',
180
- impact: 8,
181
- effort: 5,
182
- estimatedDays: 10,
183
- })
184
- )
185
- matrix.addItem(
186
- createRoadmapItem({
187
- id: 'item-2',
188
- name: 'Item 2',
189
- impact: 7,
190
- effort: 3,
191
- estimatedDays: 5,
192
- })
193
- )
194
-
195
- const plan = matrix.planCapacity({ availableDays: 12 })
196
- expect(plan.scheduled).toHaveLength(1)
197
- expect(plan.scheduled[0].id).toBe('item-2') // Fits within capacity
198
- expect(plan.deferred).toHaveLength(1)
199
- })
200
-
201
- it('supports custom scoring', () => {
202
- matrix = createPriorityMatrix({
203
- scoringFn: (item) => item.impact * (item.confidence || 1) / item.effort,
204
- })
205
-
206
- matrix.addItem(
207
- createRoadmapItem({
208
- id: 'confident',
209
- name: 'Confident',
210
- impact: 8,
211
- effort: 4,
212
- confidence: 1.0,
213
- })
214
- )
215
- matrix.addItem(
216
- createRoadmapItem({
217
- id: 'uncertain',
218
- name: 'Uncertain',
219
- impact: 10,
220
- effort: 4,
221
- confidence: 0.5,
222
- })
223
- )
224
-
225
- const ranked = matrix.getRanked()
226
- expect(ranked[0].id).toBe('confident') // Higher when accounting for confidence
227
- })
228
-
229
- it('exports to formats', () => {
230
- matrix.addItem(createRoadmapItem({ id: 'test', name: 'Test', impact: 5, effort: 5 }))
231
-
232
- const json = matrix.export('json')
233
- expect(JSON.parse(json)).toHaveLength(1)
234
-
235
- const csv = matrix.export('csv')
236
- expect(csv).toContain('test')
237
- })
238
- })
239
- })
@@ -1,192 +0,0 @@
1
- /**
2
- * Tests for Tier definitions
3
- * Phase 1: RED - These tests should FAIL initially
4
- */
5
-
6
- import { describe, it, expect } from 'vitest'
7
- import {
8
- Tier,
9
- defineTier,
10
- TierRegistry,
11
- createTierRegistry,
12
- TierLevel,
13
- } from '../../src/tiers/define.js'
14
-
15
- describe('Tier Definition', () => {
16
- describe('Tier()', () => {
17
- it('creates a free tier', () => {
18
- const tier = Tier({
19
- id: 'free',
20
- name: 'Free',
21
- level: 0,
22
- description: 'Free forever',
23
- })
24
-
25
- expect(tier.id).toBe('free')
26
- expect(tier.name).toBe('Free')
27
- expect(tier.level).toBe(0)
28
- })
29
-
30
- it('creates a pro tier with higher level', () => {
31
- const tier = Tier({
32
- id: 'pro',
33
- name: 'Pro',
34
- level: 1,
35
- description: 'Professional features',
36
- })
37
-
38
- expect(tier.level).toBe(1)
39
- })
40
-
41
- it('creates an enterprise tier', () => {
42
- const tier = Tier({
43
- id: 'enterprise',
44
- name: 'Enterprise',
45
- level: 2,
46
- description: 'Enterprise features',
47
- customPricing: true,
48
- })
49
-
50
- expect(tier.level).toBe(2)
51
- expect(tier.customPricing).toBe(true)
52
- })
53
-
54
- it('supports feature limits', () => {
55
- const tier = Tier({
56
- id: 'starter',
57
- name: 'Starter',
58
- level: 0,
59
- limits: {
60
- users: 5,
61
- storage: '1GB',
62
- apiCalls: 1000,
63
- },
64
- })
65
-
66
- expect(tier.limits?.users).toBe(5)
67
- expect(tier.limits?.storage).toBe('1GB')
68
- expect(tier.limits?.apiCalls).toBe(1000)
69
- })
70
-
71
- it('supports included features list', () => {
72
- const tier = Tier({
73
- id: 'business',
74
- name: 'Business',
75
- level: 1,
76
- features: ['analytics', 'exports', 'api-access'],
77
- })
78
-
79
- expect(tier.features).toContain('analytics')
80
- expect(tier.features).toHaveLength(3)
81
- })
82
-
83
- it('supports metadata', () => {
84
- const tier = Tier({
85
- id: 'growth',
86
- name: 'Growth',
87
- level: 1,
88
- metadata: {
89
- popular: true,
90
- badge: 'Most Popular',
91
- },
92
- })
93
-
94
- expect(tier.metadata?.popular).toBe(true)
95
- })
96
- })
97
-
98
- describe('defineTier()', () => {
99
- it('creates and validates a tier', () => {
100
- const tier = defineTier({
101
- id: 'custom',
102
- name: 'Custom',
103
- level: 3,
104
- description: 'Custom tier',
105
- })
106
-
107
- expect(tier.id).toBe('custom')
108
- })
109
-
110
- it('throws for negative tier level', () => {
111
- expect(() =>
112
- defineTier({
113
- id: 'invalid',
114
- name: 'Invalid',
115
- level: -1,
116
- })
117
- ).toThrow('Tier level must be non-negative')
118
- })
119
-
120
- it('throws for missing id', () => {
121
- expect(() =>
122
- defineTier({
123
- id: '',
124
- name: 'No ID',
125
- level: 0,
126
- })
127
- ).toThrow('Tier ID is required')
128
- })
129
- })
130
-
131
- describe('TierRegistry', () => {
132
- it('registers and retrieves tiers', () => {
133
- const registry = createTierRegistry()
134
-
135
- const tier = Tier({
136
- id: 'basic',
137
- name: 'Basic',
138
- level: 0,
139
- })
140
-
141
- registry.register(tier)
142
- expect(registry.get('basic')).toEqual(tier)
143
- })
144
-
145
- it('lists tiers sorted by level', () => {
146
- const registry = createTierRegistry()
147
-
148
- registry.register(Tier({ id: 'enterprise', name: 'Enterprise', level: 2 }))
149
- registry.register(Tier({ id: 'free', name: 'Free', level: 0 }))
150
- registry.register(Tier({ id: 'pro', name: 'Pro', level: 1 }))
151
-
152
- const tiers = registry.listSorted()
153
- expect(tiers[0].id).toBe('free')
154
- expect(tiers[1].id).toBe('pro')
155
- expect(tiers[2].id).toBe('enterprise')
156
- })
157
-
158
- it('compares tiers', () => {
159
- const registry = createTierRegistry()
160
-
161
- registry.register(Tier({ id: 'free', name: 'Free', level: 0 }))
162
- registry.register(Tier({ id: 'pro', name: 'Pro', level: 1 }))
163
-
164
- expect(registry.compare('free', 'pro')).toBeLessThan(0)
165
- expect(registry.compare('pro', 'free')).toBeGreaterThan(0)
166
- expect(registry.compare('pro', 'pro')).toBe(0)
167
- })
168
-
169
- it('checks if tier has access to another tier features', () => {
170
- const registry = createTierRegistry()
171
-
172
- registry.register(Tier({ id: 'free', name: 'Free', level: 0 }))
173
- registry.register(Tier({ id: 'pro', name: 'Pro', level: 1 }))
174
- registry.register(Tier({ id: 'enterprise', name: 'Enterprise', level: 2 }))
175
-
176
- expect(registry.hasAccessTo('enterprise', 'pro')).toBe(true)
177
- expect(registry.hasAccessTo('enterprise', 'free')).toBe(true)
178
- expect(registry.hasAccessTo('free', 'pro')).toBe(false)
179
- })
180
-
181
- it('gets upgrade path between tiers', () => {
182
- const registry = createTierRegistry()
183
-
184
- registry.register(Tier({ id: 'free', name: 'Free', level: 0 }))
185
- registry.register(Tier({ id: 'pro', name: 'Pro', level: 1 }))
186
- registry.register(Tier({ id: 'enterprise', name: 'Enterprise', level: 2 }))
187
-
188
- const path = registry.getUpgradePath('free')
189
- expect(path).toEqual(['pro', 'enterprise'])
190
- })
191
- })
192
- })