@nextsparkjs/theme-default 0.1.0-beta.2 → 0.1.0-beta.21

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 (222) hide show
  1. package/package.json +8 -4
  2. package/templates/(public)/page.tsx +1 -1
  3. package/tests/cypress/e2e/_devtools/access.bdd.md +262 -0
  4. package/tests/cypress/e2e/_devtools/access.cy.ts +171 -0
  5. package/tests/cypress/e2e/_devtools/navigation.bdd.md +261 -0
  6. package/tests/cypress/e2e/_devtools/navigation.cy.ts +157 -0
  7. package/tests/cypress/e2e/_devtools/pages.bdd.md +303 -0
  8. package/tests/cypress/e2e/_devtools/pages.cy.ts +184 -0
  9. package/tests/cypress/e2e/_docs/README.md +215 -0
  10. package/tests/cypress/e2e/_docs/tutorials/sector7-superadmin-teams.narration.json +155 -0
  11. package/tests/cypress/e2e/_docs/tutorials/sector7-superadmin.cy.ts +390 -0
  12. package/tests/cypress/e2e/_docs/tutorials/teams-system.doc.cy.ts +349 -0
  13. package/tests/cypress/e2e/_docs/tutorials/teams-system.narration.json +165 -0
  14. package/tests/cypress/e2e/_selectors/auth.cy.ts +306 -0
  15. package/tests/cypress/e2e/_selectors/billing.cy.ts +89 -0
  16. package/tests/cypress/e2e/_selectors/dashboard-mobile.cy.ts +113 -0
  17. package/tests/cypress/e2e/_selectors/dashboard-navigation.cy.ts +89 -0
  18. package/tests/cypress/e2e/_selectors/dashboard-sidebar.cy.ts +60 -0
  19. package/tests/cypress/e2e/_selectors/dashboard-topnav.cy.ts +146 -0
  20. package/tests/cypress/e2e/_selectors/devtools.cy.ts +210 -0
  21. package/tests/cypress/e2e/_selectors/global-search.cy.ts +88 -0
  22. package/tests/cypress/e2e/_selectors/pages-editor.cy.ts +179 -0
  23. package/tests/cypress/e2e/_selectors/posts-editor.cy.ts +282 -0
  24. package/tests/cypress/e2e/_selectors/public.cy.ts +112 -0
  25. package/tests/cypress/e2e/_selectors/settings-api-keys.cy.ts +228 -0
  26. package/tests/cypress/e2e/_selectors/settings-billing.cy.ts +105 -0
  27. package/tests/cypress/e2e/_selectors/settings-layout.cy.ts +119 -0
  28. package/tests/cypress/e2e/_selectors/settings-password.cy.ts +71 -0
  29. package/tests/cypress/e2e/_selectors/settings-profile.cy.ts +82 -0
  30. package/tests/cypress/e2e/_selectors/settings-teams.cy.ts +68 -0
  31. package/tests/cypress/e2e/_selectors/superadmin.cy.ts +185 -0
  32. package/tests/cypress/e2e/_selectors/tasks.cy.ts +242 -0
  33. package/tests/cypress/e2e/_selectors/taxonomies.cy.ts +126 -0
  34. package/tests/cypress/e2e/_selectors/teams.cy.ts +142 -0
  35. package/tests/cypress/e2e/_superadmin/all-teams.bdd.md +261 -0
  36. package/tests/cypress/e2e/_superadmin/all-teams.cy.ts +177 -0
  37. package/tests/cypress/e2e/_superadmin/all-users.bdd.md +406 -0
  38. package/tests/cypress/e2e/_superadmin/all-users.cy.ts +294 -0
  39. package/tests/cypress/e2e/_superadmin/dashboard.bdd.md +235 -0
  40. package/tests/cypress/e2e/_superadmin/dashboard.cy.ts +149 -0
  41. package/tests/cypress/e2e/_superadmin/subscriptions-overview.bdd.md +290 -0
  42. package/tests/cypress/e2e/_superadmin/subscriptions-overview.cy.ts +194 -0
  43. package/tests/cypress/e2e/ai/ai-usage.cy.ts +209 -0
  44. package/tests/cypress/e2e/ai/chat-api.cy.ts +107 -0
  45. package/tests/cypress/e2e/ai/guardrails.cy.ts +332 -0
  46. package/tests/cypress/e2e/api/billing/BillingAPIController.js +319 -0
  47. package/tests/cypress/e2e/api/billing/check-action.cy.ts +326 -0
  48. package/tests/cypress/e2e/api/billing/checkout.cy.ts +358 -0
  49. package/tests/cypress/e2e/api/billing/lifecycle.cy.ts +423 -0
  50. package/tests/cypress/e2e/api/billing/plans/README.md +345 -0
  51. package/tests/cypress/e2e/api/billing/plans/business.cy.ts +412 -0
  52. package/tests/cypress/e2e/api/billing/plans/downgrade.cy.ts +510 -0
  53. package/tests/cypress/e2e/api/billing/plans/fixtures/billing-plans.json +163 -0
  54. package/tests/cypress/e2e/api/billing/plans/free.cy.ts +500 -0
  55. package/tests/cypress/e2e/api/billing/plans/pro.cy.ts +497 -0
  56. package/tests/cypress/e2e/api/billing/plans/starter.cy.ts +342 -0
  57. package/tests/cypress/e2e/api/billing/portal.cy.ts +313 -0
  58. package/tests/cypress/e2e/api/devtools/registries.bdd.md +300 -0
  59. package/tests/cypress/e2e/api/devtools/registries.cy.ts +368 -0
  60. package/tests/cypress/e2e/api/entities/blocks-scope.cy.ts +396 -0
  61. package/tests/cypress/e2e/api/entities/customers-crud.cy.ts +648 -0
  62. package/tests/cypress/e2e/api/entities/customers-metas.cy.ts +839 -0
  63. package/tests/cypress/e2e/api/entities/pages-crud.cy.ts +425 -0
  64. package/tests/cypress/e2e/api/entities/pages-status.cy.ts +335 -0
  65. package/tests/cypress/e2e/api/entities/post-categories-crud.cy.ts +610 -0
  66. package/tests/cypress/e2e/api/entities/posts-crud.cy.ts +709 -0
  67. package/tests/cypress/e2e/api/entities/posts-status.cy.ts +396 -0
  68. package/tests/cypress/e2e/api/entities/tasks-crud.cy.ts +602 -0
  69. package/tests/cypress/e2e/api/entities/tasks-metas.cy.ts +878 -0
  70. package/tests/cypress/e2e/api/entities/users-crud.cy.ts +469 -0
  71. package/tests/cypress/e2e/api/entities/users-metas.cy.ts +913 -0
  72. package/tests/cypress/e2e/api/entities/users-security.cy.ts +375 -0
  73. package/tests/cypress/e2e/api/scheduled-actions/cron-endpoint.bdd.md +375 -0
  74. package/tests/cypress/e2e/api/scheduled-actions/cron-endpoint.cy.ts +346 -0
  75. package/tests/cypress/e2e/api/scheduled-actions/devtools-endpoint.bdd.md +451 -0
  76. package/tests/cypress/e2e/api/scheduled-actions/devtools-endpoint.cy.ts +447 -0
  77. package/tests/cypress/e2e/api/scheduled-actions/scheduling.bdd.md +649 -0
  78. package/tests/cypress/e2e/api/scheduled-actions/scheduling.cy.ts +333 -0
  79. package/tests/cypress/e2e/api/settings/api-keys.crud.cy.ts +923 -0
  80. package/tests/cypress/e2e/uat/auth/app-roles/developer-login.bdd.md +231 -0
  81. package/tests/cypress/e2e/uat/auth/app-roles/developer-login.cy.ts +144 -0
  82. package/tests/cypress/e2e/uat/auth/app-roles/superadmin-login.bdd.md +118 -0
  83. package/tests/cypress/e2e/uat/auth/app-roles/superadmin-login.cy.ts +84 -0
  84. package/tests/cypress/e2e/uat/auth/custom-roles/editor-login.bdd.md +288 -0
  85. package/tests/cypress/e2e/uat/auth/custom-roles/editor-login.cy.ts +188 -0
  86. package/tests/cypress/e2e/uat/auth/login-logout.bdd.md +160 -0
  87. package/tests/cypress/e2e/uat/auth/login-logout.cy.ts +116 -0
  88. package/tests/cypress/e2e/uat/auth/password-reset.bdd.md +289 -0
  89. package/tests/cypress/e2e/uat/auth/password-reset.cy.ts +200 -0
  90. package/tests/cypress/e2e/uat/auth/team-roles/admin-login.bdd.md +225 -0
  91. package/tests/cypress/e2e/uat/auth/team-roles/admin-login.cy.ts +148 -0
  92. package/tests/cypress/e2e/uat/auth/team-roles/member-login.bdd.md +251 -0
  93. package/tests/cypress/e2e/uat/auth/team-roles/member-login.cy.ts +163 -0
  94. package/tests/cypress/e2e/uat/auth/team-roles/owner-login.bdd.md +231 -0
  95. package/tests/cypress/e2e/uat/auth/team-roles/owner-login.cy.ts +141 -0
  96. package/tests/cypress/e2e/uat/billing/extended.bdd.md +273 -0
  97. package/tests/cypress/e2e/uat/billing/extended.cy.ts +209 -0
  98. package/tests/cypress/e2e/uat/billing/feature-gates.bdd.md +407 -0
  99. package/tests/cypress/e2e/uat/billing/feature-gates.cy.ts +307 -0
  100. package/tests/cypress/e2e/uat/billing/page.bdd.md +329 -0
  101. package/tests/cypress/e2e/uat/billing/page.cy.ts +250 -0
  102. package/tests/cypress/e2e/uat/billing/status.bdd.md +190 -0
  103. package/tests/cypress/e2e/uat/billing/status.cy.ts +145 -0
  104. package/tests/cypress/e2e/uat/billing/team-switch.bdd.md +156 -0
  105. package/tests/cypress/e2e/uat/billing/team-switch.cy.ts +122 -0
  106. package/tests/cypress/e2e/uat/billing/usage.bdd.md +218 -0
  107. package/tests/cypress/e2e/uat/billing/usage.cy.ts +176 -0
  108. package/tests/cypress/e2e/uat/blocks/hero.bdd.md +124 -0
  109. package/tests/cypress/e2e/uat/blocks/hero.cy.ts +56 -0
  110. package/tests/cypress/e2e/uat/devtools/api-tester.cy.ts +390 -0
  111. package/tests/cypress/e2e/uat/entities/customers/member.bdd.md +275 -0
  112. package/tests/cypress/e2e/uat/entities/customers/member.cy.ts +122 -0
  113. package/tests/cypress/e2e/uat/entities/customers/owner.bdd.md +243 -0
  114. package/tests/cypress/e2e/uat/entities/customers/owner.cy.ts +165 -0
  115. package/tests/cypress/e2e/uat/entities/pages/block-crud.bdd.md +476 -0
  116. package/tests/cypress/e2e/uat/entities/pages/block-crud.cy.ts +486 -0
  117. package/tests/cypress/e2e/uat/entities/pages/block-editor.bdd.md +460 -0
  118. package/tests/cypress/e2e/uat/entities/pages/block-editor.cy.ts +301 -0
  119. package/tests/cypress/e2e/uat/entities/pages/list.bdd.md +432 -0
  120. package/tests/cypress/e2e/uat/entities/pages/list.cy.ts +273 -0
  121. package/tests/cypress/e2e/uat/entities/pages/public-rendering.bdd.md +696 -0
  122. package/tests/cypress/e2e/uat/entities/pages/public-rendering.cy.ts +340 -0
  123. package/tests/cypress/e2e/uat/entities/posts/categories-api-aware.bdd.md +161 -0
  124. package/tests/cypress/e2e/uat/entities/posts/categories-api-aware.cy.ts +104 -0
  125. package/tests/cypress/e2e/uat/entities/posts/categories.bdd.md +375 -0
  126. package/tests/cypress/e2e/uat/entities/posts/categories.cy.ts +241 -0
  127. package/tests/cypress/e2e/uat/entities/posts/editor.bdd.md +429 -0
  128. package/tests/cypress/e2e/uat/entities/posts/editor.cy.ts +257 -0
  129. package/tests/cypress/e2e/uat/entities/posts/list.bdd.md +340 -0
  130. package/tests/cypress/e2e/uat/entities/posts/list.cy.ts +177 -0
  131. package/tests/cypress/e2e/uat/entities/posts/public.bdd.md +614 -0
  132. package/tests/cypress/e2e/uat/entities/posts/public.cy.ts +249 -0
  133. package/tests/cypress/e2e/uat/entities/tasks/member.bdd.md +222 -0
  134. package/tests/cypress/e2e/uat/entities/tasks/member.cy.ts +165 -0
  135. package/tests/cypress/e2e/uat/entities/tasks/owner.bdd.md +419 -0
  136. package/tests/cypress/e2e/uat/entities/tasks/owner.cy.ts +191 -0
  137. package/tests/cypress/e2e/uat/roles/editor-role.bdd.md +552 -0
  138. package/tests/cypress/e2e/uat/roles/editor-role.cy.ts +210 -0
  139. package/tests/cypress/e2e/uat/roles/member-restrictions.bdd.md +450 -0
  140. package/tests/cypress/e2e/uat/roles/member-restrictions.cy.ts +189 -0
  141. package/tests/cypress/e2e/uat/roles/owner-full-crud.bdd.md +530 -0
  142. package/tests/cypress/e2e/uat/roles/owner-full-crud.cy.ts +247 -0
  143. package/tests/cypress/e2e/uat/scheduled-actions/devtools-ui.bdd.md +736 -0
  144. package/tests/cypress/e2e/uat/scheduled-actions/devtools-ui.cy.ts +740 -0
  145. package/tests/cypress/e2e/uat/teams/roles-matrix.bdd.md +553 -0
  146. package/tests/cypress/e2e/uat/teams/roles-matrix.cy.ts +185 -0
  147. package/tests/cypress/e2e/uat/teams/switcher.bdd.md +1151 -0
  148. package/tests/cypress/e2e/uat/teams/switcher.cy.ts +497 -0
  149. package/tests/cypress/e2e/uat/teams/team-switcher.md +198 -0
  150. package/tests/cypress/fixtures/blocks.json +218 -0
  151. package/tests/cypress/fixtures/entities.json +78 -0
  152. package/tests/cypress/fixtures/page-builder.json +21 -0
  153. package/tests/cypress/src/components/CategoriesPOM.ts +382 -0
  154. package/tests/cypress/src/components/CustomersPOM.ts +439 -0
  155. package/tests/cypress/src/components/DevKeyringPOM.ts +160 -0
  156. package/tests/cypress/src/components/EntityForm.ts +375 -0
  157. package/tests/cypress/src/components/EntityList.ts +389 -0
  158. package/tests/cypress/src/components/PageBuilderPOM.ts +710 -0
  159. package/tests/cypress/src/components/PostEditorPOM.ts +370 -0
  160. package/tests/cypress/src/components/PostsListPOM.ts +223 -0
  161. package/tests/cypress/src/components/PublicPagePOM.ts +447 -0
  162. package/tests/cypress/src/components/PublicPostPOM.ts +146 -0
  163. package/tests/cypress/src/components/TasksPOM.ts +272 -0
  164. package/tests/cypress/src/components/TeamSwitcherPOM.ts +450 -0
  165. package/tests/cypress/src/components/index.ts +21 -0
  166. package/tests/cypress/src/controllers/ApiKeysAPIController.js +178 -0
  167. package/tests/cypress/src/controllers/BaseAPIController.js +317 -0
  168. package/tests/cypress/src/controllers/CustomerAPIController.js +251 -0
  169. package/tests/cypress/src/controllers/PagesAPIController.js +226 -0
  170. package/tests/cypress/src/controllers/PostsAPIController.js +250 -0
  171. package/tests/cypress/src/controllers/TaskAPIController.js +240 -0
  172. package/tests/cypress/src/controllers/UsersAPIController.js +242 -0
  173. package/tests/cypress/src/controllers/index.js +25 -0
  174. package/tests/cypress/src/core/AuthPOM.ts +450 -0
  175. package/tests/cypress/src/core/BasePOM.ts +86 -0
  176. package/tests/cypress/src/core/BlockEditorBasePOM.ts +576 -0
  177. package/tests/cypress/src/core/DashboardEntityPOM.ts +692 -0
  178. package/tests/cypress/src/core/index.ts +14 -0
  179. package/tests/cypress/src/entities/CustomersPOM.ts +172 -0
  180. package/tests/cypress/src/entities/PagesPOM.ts +137 -0
  181. package/tests/cypress/src/entities/PostsPOM.ts +137 -0
  182. package/tests/cypress/src/entities/TasksPOM.ts +176 -0
  183. package/tests/cypress/src/entities/index.ts +14 -0
  184. package/tests/cypress/src/features/BillingPOM.ts +385 -0
  185. package/tests/cypress/src/features/DashboardPOM.ts +245 -0
  186. package/tests/cypress/src/features/DevtoolsPOM.ts +739 -0
  187. package/tests/cypress/src/features/PageBuilderPOM.ts +263 -0
  188. package/tests/cypress/src/features/PostEditorPOM.ts +313 -0
  189. package/tests/cypress/src/features/ScheduledActionsPOM.ts +463 -0
  190. package/tests/cypress/src/features/SettingsPOM.ts +362 -0
  191. package/tests/cypress/src/features/SuperadminPOM.ts +331 -0
  192. package/tests/cypress/src/features/SuperadminTeamRolesPOM.ts +285 -0
  193. package/tests/cypress/src/features/index.ts +28 -0
  194. package/tests/cypress/src/helpers/ApiInterceptor.ts +177 -0
  195. package/tests/cypress/src/index.ts +101 -0
  196. package/tests/cypress/src/pages/dashboard/Dashboard.js +677 -0
  197. package/tests/cypress/src/pages/dashboard/DashboardPage.js +43 -0
  198. package/tests/cypress/src/pages/dashboard/DashboardStats.js +546 -0
  199. package/tests/cypress/src/pages/dashboard/index.js +6 -0
  200. package/tests/cypress/src/pages/index.js +5 -0
  201. package/tests/cypress/src/pages/public/FeaturesPage.js +28 -0
  202. package/tests/cypress/src/pages/public/LandingPage.js +69 -0
  203. package/tests/cypress/src/pages/public/PricingPage.js +33 -0
  204. package/tests/cypress/src/pages/public/index.js +6 -0
  205. package/tests/cypress/src/selectors.ts +46 -0
  206. package/tests/cypress/src/session-helpers.ts +500 -0
  207. package/tests/cypress/support/doc-commands.ts +260 -0
  208. package/tests/cypress.config.ts +150 -0
  209. package/tests/jest/components/post-header.test.tsx +377 -0
  210. package/tests/jest/config/role-config.test.ts +529 -0
  211. package/tests/jest/jest.config.ts +81 -0
  212. package/tests/jest/langchain/COVERAGE.md +372 -0
  213. package/tests/jest/langchain/guardrails.test.ts +465 -0
  214. package/tests/jest/langchain/streaming.test.ts +367 -0
  215. package/tests/jest/langchain/token-tracker.test.ts +455 -0
  216. package/tests/jest/langchain/tracer-callbacks.test.ts +881 -0
  217. package/tests/jest/langchain/tracer.test.ts +823 -0
  218. package/tests/jest/user-roles/role-helpers.test.ts +432 -0
  219. package/tests/jest/validation/categories.test.ts +429 -0
  220. package/tests/jest/validation/posts.test.ts +546 -0
  221. package/tests/tsconfig.json +15 -0
  222. package/LICENSE +0 -21
@@ -0,0 +1,367 @@
1
+ /**
2
+ * Unit Tests - Streaming Service
3
+ *
4
+ * Tests token-by-token streaming functionality:
5
+ * - SSE encoder creation and encoding
6
+ * - Stream chunk types
7
+ * - StreamChat generator behavior with mocks
8
+ *
9
+ * Focus: Utility functions and mocked streaming behavior.
10
+ */
11
+
12
+ // Mock @langchain/core/messages before importing anything
13
+ jest.mock('@langchain/core/messages', () => ({
14
+ BaseMessage: class {},
15
+ HumanMessage: class {
16
+ constructor(public content: string) {}
17
+ },
18
+ AIMessage: class {
19
+ constructor(public content: string) {}
20
+ },
21
+ }))
22
+
23
+ // Mock dependencies
24
+ jest.mock('@/contents/plugins/langchain/lib/db-memory-store', () => ({
25
+ dbMemoryStore: {
26
+ getMessages: jest.fn().mockResolvedValue([]),
27
+ addMessages: jest.fn().mockResolvedValue(undefined),
28
+ },
29
+ }))
30
+
31
+ jest.mock('@/contents/plugins/langchain/lib/token-tracker', () => ({
32
+ tokenTracker: {
33
+ trackUsage: jest.fn().mockResolvedValue(undefined),
34
+ },
35
+ }))
36
+
37
+ import { createSSEEncoder } from '@/plugins/langchain/lib/streaming'
38
+ import type { StreamChunk } from '@/plugins/langchain/lib/streaming'
39
+
40
+ describe('Streaming Service', () => {
41
+ describe('createSSEEncoder', () => {
42
+ let encoder: ReturnType<typeof createSSEEncoder>
43
+
44
+ beforeEach(() => {
45
+ encoder = createSSEEncoder()
46
+ })
47
+
48
+ describe('encode', () => {
49
+ it('should encode token chunk correctly', () => {
50
+ const chunk: StreamChunk = { type: 'token', content: 'Hello' }
51
+ const result = encoder.encode(chunk)
52
+
53
+ const decoded = new TextDecoder().decode(result)
54
+ expect(decoded).toBe('data: {"type":"token","content":"Hello"}\n\n')
55
+ })
56
+
57
+ it('should encode done chunk with full content', () => {
58
+ const chunk: StreamChunk = {
59
+ type: 'done',
60
+ fullContent: 'Complete message',
61
+ agentUsed: 'test-agent',
62
+ tokenUsage: { inputTokens: 100, outputTokens: 50, totalTokens: 150 },
63
+ }
64
+ const result = encoder.encode(chunk)
65
+
66
+ const decoded = new TextDecoder().decode(result)
67
+ expect(decoded).toContain('"type":"done"')
68
+ expect(decoded).toContain('"fullContent":"Complete message"')
69
+ expect(decoded).toContain('"agentUsed":"test-agent"')
70
+ expect(decoded).toContain('"tokenUsage"')
71
+ })
72
+
73
+ it('should encode error chunk', () => {
74
+ const chunk: StreamChunk = { type: 'error', error: 'Something went wrong' }
75
+ const result = encoder.encode(chunk)
76
+
77
+ const decoded = new TextDecoder().decode(result)
78
+ expect(decoded).toBe('data: {"type":"error","error":"Something went wrong"}\n\n')
79
+ })
80
+
81
+ it('should encode tool_start chunk', () => {
82
+ const chunk: StreamChunk = { type: 'tool_start', toolName: 'search' }
83
+ const result = encoder.encode(chunk)
84
+
85
+ const decoded = new TextDecoder().decode(result)
86
+ expect(decoded).toContain('"type":"tool_start"')
87
+ expect(decoded).toContain('"toolName":"search"')
88
+ })
89
+
90
+ it('should encode tool_end chunk with result', () => {
91
+ const chunk: StreamChunk = {
92
+ type: 'tool_end',
93
+ toolName: 'calculator',
94
+ result: { value: 42 },
95
+ }
96
+ const result = encoder.encode(chunk)
97
+
98
+ const decoded = new TextDecoder().decode(result)
99
+ expect(decoded).toContain('"type":"tool_end"')
100
+ expect(decoded).toContain('"toolName":"calculator"')
101
+ expect(decoded).toContain('"result":{"value":42}')
102
+ })
103
+
104
+ it('should handle special characters in content', () => {
105
+ const chunk: StreamChunk = {
106
+ type: 'token',
107
+ content: 'Hello\nWorld\t"Special" chars: é ñ 中文',
108
+ }
109
+ const result = encoder.encode(chunk)
110
+
111
+ const decoded = new TextDecoder().decode(result)
112
+ expect(decoded).toContain('data: ')
113
+ expect(decoded).toContain('\\n') // Escaped newline
114
+ expect(decoded).toContain('\\t') // Escaped tab
115
+ expect(decoded).toContain('\\"') // Escaped quote
116
+ })
117
+
118
+ it('should handle empty content', () => {
119
+ const chunk: StreamChunk = { type: 'token', content: '' }
120
+ const result = encoder.encode(chunk)
121
+
122
+ const decoded = new TextDecoder().decode(result)
123
+ expect(decoded).toBe('data: {"type":"token","content":""}\n\n')
124
+ })
125
+
126
+ it('should return Uint8Array', () => {
127
+ const chunk: StreamChunk = { type: 'token', content: 'test' }
128
+ const result = encoder.encode(chunk)
129
+
130
+ expect(result).toBeInstanceOf(Uint8Array)
131
+ })
132
+ })
133
+
134
+ describe('encodeDone', () => {
135
+ it('should encode done marker', () => {
136
+ const result = encoder.encodeDone()
137
+
138
+ const decoded = new TextDecoder().decode(result)
139
+ expect(decoded).toBe('data: [DONE]\n\n')
140
+ })
141
+
142
+ it('should return Uint8Array', () => {
143
+ const result = encoder.encodeDone()
144
+
145
+ expect(result).toBeInstanceOf(Uint8Array)
146
+ })
147
+ })
148
+ })
149
+
150
+ describe('StreamChunk types', () => {
151
+ it('should support token chunk type', () => {
152
+ const chunk: StreamChunk = { type: 'token', content: 'text' }
153
+ expect(chunk.type).toBe('token')
154
+ expect(chunk.content).toBe('text')
155
+ })
156
+
157
+ it('should support done chunk type with all fields', () => {
158
+ const chunk: StreamChunk = {
159
+ type: 'done',
160
+ fullContent: 'Full response',
161
+ agentUsed: 'assistant',
162
+ tokenUsage: {
163
+ inputTokens: 100,
164
+ outputTokens: 200,
165
+ totalTokens: 300,
166
+ },
167
+ }
168
+
169
+ expect(chunk.type).toBe('done')
170
+ expect(chunk.fullContent).toBe('Full response')
171
+ expect(chunk.agentUsed).toBe('assistant')
172
+ expect(chunk.tokenUsage?.totalTokens).toBe(300)
173
+ })
174
+
175
+ it('should support done chunk type with minimal fields', () => {
176
+ const chunk: StreamChunk = {
177
+ type: 'done',
178
+ fullContent: 'Response',
179
+ }
180
+
181
+ expect(chunk.type).toBe('done')
182
+ expect(chunk.fullContent).toBe('Response')
183
+ expect(chunk.agentUsed).toBeUndefined()
184
+ expect(chunk.tokenUsage).toBeUndefined()
185
+ })
186
+
187
+ it('should support error chunk type', () => {
188
+ const chunk: StreamChunk = { type: 'error', error: 'Error message' }
189
+ expect(chunk.type).toBe('error')
190
+ expect(chunk.error).toBe('Error message')
191
+ })
192
+
193
+ it('should support tool_start chunk type', () => {
194
+ const chunk: StreamChunk = { type: 'tool_start', toolName: 'search' }
195
+ expect(chunk.type).toBe('tool_start')
196
+ expect(chunk.toolName).toBe('search')
197
+ })
198
+
199
+ it('should support tool_end chunk type', () => {
200
+ const chunk: StreamChunk = {
201
+ type: 'tool_end',
202
+ toolName: 'search',
203
+ result: ['result1', 'result2'],
204
+ }
205
+
206
+ expect(chunk.type).toBe('tool_end')
207
+ expect(chunk.toolName).toBe('search')
208
+ expect(chunk.result).toEqual(['result1', 'result2'])
209
+ })
210
+ })
211
+
212
+ describe('SSE format compliance', () => {
213
+ it('should produce valid SSE format with data prefix', () => {
214
+ const encoder = createSSEEncoder()
215
+ const chunk: StreamChunk = { type: 'token', content: 'test' }
216
+ const result = new TextDecoder().decode(encoder.encode(chunk))
217
+
218
+ expect(result.startsWith('data: ')).toBe(true)
219
+ })
220
+
221
+ it('should end with double newline', () => {
222
+ const encoder = createSSEEncoder()
223
+ const chunk: StreamChunk = { type: 'token', content: 'test' }
224
+ const result = new TextDecoder().decode(encoder.encode(chunk))
225
+
226
+ expect(result.endsWith('\n\n')).toBe(true)
227
+ })
228
+
229
+ it('should produce valid JSON in data field', () => {
230
+ const encoder = createSSEEncoder()
231
+ const chunk: StreamChunk = { type: 'token', content: 'test' }
232
+ const result = new TextDecoder().decode(encoder.encode(chunk))
233
+
234
+ // Extract JSON from SSE format
235
+ const jsonStr = result.slice(6, -2) // Remove 'data: ' prefix and '\n\n' suffix
236
+ expect(() => JSON.parse(jsonStr)).not.toThrow()
237
+ })
238
+
239
+ it('should handle multiple chunks correctly', () => {
240
+ const encoder = createSSEEncoder()
241
+ const chunks: StreamChunk[] = [
242
+ { type: 'token', content: 'Hello' },
243
+ { type: 'token', content: ' ' },
244
+ { type: 'token', content: 'World' },
245
+ { type: 'done', fullContent: 'Hello World' },
246
+ ]
247
+
248
+ const results = chunks.map(chunk => new TextDecoder().decode(encoder.encode(chunk)))
249
+
250
+ results.forEach(result => {
251
+ expect(result.startsWith('data: ')).toBe(true)
252
+ expect(result.endsWith('\n\n')).toBe(true)
253
+ })
254
+ })
255
+ })
256
+
257
+ describe('Edge cases', () => {
258
+ it('should handle very long content', () => {
259
+ const encoder = createSSEEncoder()
260
+ const longContent = 'a'.repeat(100000)
261
+ const chunk: StreamChunk = { type: 'token', content: longContent }
262
+
263
+ const result = encoder.encode(chunk)
264
+ const decoded = new TextDecoder().decode(result)
265
+
266
+ expect(decoded).toContain(longContent)
267
+ expect(decoded.startsWith('data: ')).toBe(true)
268
+ })
269
+
270
+ it('should handle unicode content', () => {
271
+ const encoder = createSSEEncoder()
272
+ const unicodeContent = '你好世界 🌍 مرحبا العالم'
273
+ const chunk: StreamChunk = { type: 'token', content: unicodeContent }
274
+
275
+ const result = encoder.encode(chunk)
276
+ const decoded = new TextDecoder().decode(result)
277
+
278
+ // Parse the JSON to verify unicode is preserved
279
+ const jsonStr = decoded.slice(6, -2)
280
+ const parsed = JSON.parse(jsonStr)
281
+ expect(parsed.content).toBe(unicodeContent)
282
+ })
283
+
284
+ it('should handle newlines in content', () => {
285
+ const encoder = createSSEEncoder()
286
+ const chunk: StreamChunk = {
287
+ type: 'token',
288
+ content: 'Line 1\nLine 2\nLine 3',
289
+ }
290
+
291
+ const result = encoder.encode(chunk)
292
+ const decoded = new TextDecoder().decode(result)
293
+
294
+ // Newlines should be escaped in JSON
295
+ expect(decoded).toContain('\\n')
296
+ expect(decoded.split('\n').length).toBe(3) // data: {...}\n\n = 3 parts
297
+ })
298
+
299
+ it('should handle null result in tool_end', () => {
300
+ const encoder = createSSEEncoder()
301
+ const chunk: StreamChunk = {
302
+ type: 'tool_end',
303
+ toolName: 'void_tool',
304
+ result: null,
305
+ }
306
+
307
+ const result = encoder.encode(chunk)
308
+ const decoded = new TextDecoder().decode(result)
309
+
310
+ expect(decoded).toContain('"result":null')
311
+ })
312
+
313
+ it('should handle complex nested result in tool_end', () => {
314
+ const encoder = createSSEEncoder()
315
+ const chunk: StreamChunk = {
316
+ type: 'tool_end',
317
+ toolName: 'complex_tool',
318
+ result: {
319
+ nested: {
320
+ array: [1, 2, { deep: 'value' }],
321
+ string: 'text',
322
+ },
323
+ number: 42,
324
+ },
325
+ }
326
+
327
+ const result = encoder.encode(chunk)
328
+ const decoded = new TextDecoder().decode(result)
329
+ const jsonStr = decoded.slice(6, -2)
330
+ const parsed = JSON.parse(jsonStr)
331
+
332
+ expect(parsed.result.nested.array).toHaveLength(3)
333
+ expect(parsed.result.nested.array[2].deep).toBe('value')
334
+ })
335
+ })
336
+
337
+ describe('Performance characteristics', () => {
338
+ it('should be efficient for single chunk encoding', () => {
339
+ const encoder = createSSEEncoder()
340
+ const chunk: StreamChunk = { type: 'token', content: 'test' }
341
+
342
+ const start = performance.now()
343
+ for (let i = 0; i < 1000; i++) {
344
+ encoder.encode(chunk)
345
+ }
346
+ const duration = performance.now() - start
347
+
348
+ // Should encode 1000 chunks in less than 100ms
349
+ expect(duration).toBeLessThan(100)
350
+ })
351
+
352
+ it('should reuse encoder instance efficiently', () => {
353
+ const encoder = createSSEEncoder()
354
+ const chunks: StreamChunk[] = Array.from({ length: 100 }, (_, i) => ({
355
+ type: 'token' as const,
356
+ content: `Token ${i}`,
357
+ }))
358
+
359
+ const results = chunks.map(chunk => encoder.encode(chunk))
360
+
361
+ expect(results).toHaveLength(100)
362
+ results.forEach(result => {
363
+ expect(result).toBeInstanceOf(Uint8Array)
364
+ })
365
+ })
366
+ })
367
+ })