@aleph-alpha/chat-kit 0.9.0 → 1.1.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 (263) hide show
  1. package/dist/{CkInput.vue_vue_type_script_setup_true_lang-DmcLXAgB.js → CkInput.vue_vue_type_script_setup_true_lang-DpYyyCgU.js} +481 -79
  2. package/dist/chat-kit.css +9 -14
  3. package/dist/components/base/CkAgentMessage/CkAgentMessage.stories.d.ts +20 -0
  4. package/dist/components/base/CkAgentMessage/CkAgentMessage.stories.d.ts.map +1 -1
  5. package/dist/components/base/CkAgentMessage/CkAgentMessage.vue.d.ts.map +1 -1
  6. package/dist/components/base/CkAgentMessage/index.d.ts +1 -0
  7. package/dist/components/base/CkAgentMessage/index.d.ts.map +1 -1
  8. package/dist/components/base/CkAgentMessage/types.d.ts +12 -0
  9. package/dist/components/base/CkAgentMessage/types.d.ts.map +1 -0
  10. package/dist/components/base/CkConversationLayout/CkConversationLayout.stories.d.ts +13 -0
  11. package/dist/components/base/CkConversationLayout/CkConversationLayout.stories.d.ts.map +1 -1
  12. package/dist/components/base/CkConversationLayout/CkConversationLayout.vue.d.ts.map +1 -1
  13. package/dist/components/base/CkConversationLayout/types.d.ts +9 -2
  14. package/dist/components/base/CkConversationLayout/types.d.ts.map +1 -1
  15. package/dist/components/base/CkCopyMessageActionButton/CkCopyMessageActionButton.stories.d.ts +8 -0
  16. package/dist/components/base/CkCopyMessageActionButton/CkCopyMessageActionButton.stories.d.ts.map +1 -1
  17. package/dist/components/base/CkCopyMessageActionButton/CkCopyMessageActionButton.vue.d.ts +3 -15
  18. package/dist/components/base/CkCopyMessageActionButton/CkCopyMessageActionButton.vue.d.ts.map +1 -1
  19. package/dist/components/base/CkCopyMessageActionButton/index.d.ts +1 -0
  20. package/dist/components/base/CkCopyMessageActionButton/index.d.ts.map +1 -1
  21. package/dist/components/base/CkCopyMessageActionButton/types.d.ts +45 -0
  22. package/dist/components/base/CkCopyMessageActionButton/types.d.ts.map +1 -0
  23. package/dist/components/base/CkHistoryItem/CkHistoryItem.stories.d.ts +31 -0
  24. package/dist/components/base/CkHistoryItem/CkHistoryItem.stories.d.ts.map +1 -0
  25. package/dist/components/base/CkHistoryItem/CkHistoryItem.vue.d.ts +31 -0
  26. package/dist/components/base/CkHistoryItem/CkHistoryItem.vue.d.ts.map +1 -0
  27. package/dist/components/base/CkHistoryItem/activeIndicator.d.ts +15 -0
  28. package/dist/components/base/CkHistoryItem/activeIndicator.d.ts.map +1 -0
  29. package/dist/components/base/CkHistoryItem/index.d.ts +3 -0
  30. package/dist/components/base/CkHistoryItem/index.d.ts.map +1 -0
  31. package/dist/components/base/CkHistoryItem/types.d.ts +96 -0
  32. package/dist/components/base/CkHistoryItem/types.d.ts.map +1 -0
  33. package/dist/components/base/CkHistoryWrapper/CkHistoryWrapper.stories.d.ts +16 -0
  34. package/dist/components/base/CkHistoryWrapper/CkHistoryWrapper.stories.d.ts.map +1 -0
  35. package/dist/components/base/CkHistoryWrapper/CkHistoryWrapper.vue.d.ts +17 -0
  36. package/dist/components/base/CkHistoryWrapper/CkHistoryWrapper.vue.d.ts.map +1 -0
  37. package/dist/components/base/CkHistoryWrapper/index.d.ts +3 -0
  38. package/dist/components/base/CkHistoryWrapper/index.d.ts.map +1 -0
  39. package/dist/components/base/CkHistoryWrapper/types.d.ts +17 -0
  40. package/dist/components/base/CkHistoryWrapper/types.d.ts.map +1 -0
  41. package/dist/components/base/CkMarkdownRenderer/CkMarkdownRenderer.stories.d.ts +28 -0
  42. package/dist/components/base/CkMarkdownRenderer/CkMarkdownRenderer.stories.d.ts.map +1 -1
  43. package/dist/components/base/CkMarkdownRenderer/CkMarkdownRenderer.vue.d.ts.map +1 -1
  44. package/dist/components/base/CkMarkdownRenderer/index.d.ts +1 -1
  45. package/dist/components/base/CkMarkdownRenderer/index.d.ts.map +1 -1
  46. package/dist/components/base/CkMarkdownRenderer/types.d.ts +16 -0
  47. package/dist/components/base/CkMarkdownRenderer/types.d.ts.map +1 -1
  48. package/dist/components/base/CkMessageActionButton/CkMessageActionButton.stories.d.ts +4 -0
  49. package/dist/components/base/CkMessageActionButton/CkMessageActionButton.stories.d.ts.map +1 -1
  50. package/dist/components/base/CkMessageActionButton/CkMessageActionButton.vue.d.ts +2 -5
  51. package/dist/components/base/CkMessageActionButton/CkMessageActionButton.vue.d.ts.map +1 -1
  52. package/dist/components/base/CkMessageActionButton/index.d.ts +1 -0
  53. package/dist/components/base/CkMessageActionButton/index.d.ts.map +1 -1
  54. package/dist/components/base/CkMessageActionButton/types.d.ts +22 -0
  55. package/dist/components/base/CkMessageActionButton/types.d.ts.map +1 -0
  56. package/dist/components/base/CkPromptActionButton/CkPromptActionButton.stories.d.ts +12 -0
  57. package/dist/components/base/CkPromptActionButton/CkPromptActionButton.stories.d.ts.map +1 -1
  58. package/dist/components/base/CkPromptActionButton/CkPromptActionButton.vue.d.ts +2 -7
  59. package/dist/components/base/CkPromptActionButton/CkPromptActionButton.vue.d.ts.map +1 -1
  60. package/dist/components/base/CkPromptActionButton/index.d.ts +1 -0
  61. package/dist/components/base/CkPromptActionButton/index.d.ts.map +1 -1
  62. package/dist/components/base/CkPromptActionButton/types.d.ts +33 -0
  63. package/dist/components/base/CkPromptActionButton/types.d.ts.map +1 -0
  64. package/dist/components/base/CkPromptInput/CkPromptInput.stories.d.ts +17 -0
  65. package/dist/components/base/CkPromptInput/CkPromptInput.stories.d.ts.map +1 -1
  66. package/dist/components/base/CkPromptInput/CkPromptInput.vue.d.ts +3 -9
  67. package/dist/components/base/CkPromptInput/CkPromptInput.vue.d.ts.map +1 -1
  68. package/dist/components/base/CkPromptInput/index.d.ts +1 -0
  69. package/dist/components/base/CkPromptInput/index.d.ts.map +1 -1
  70. package/dist/components/base/CkPromptInput/types.d.ts +39 -0
  71. package/dist/components/base/CkPromptInput/types.d.ts.map +1 -0
  72. package/dist/components/base/CkPromptInputDisclaimer/CkPromptInputDisclaimer.stories.d.ts +12 -0
  73. package/dist/components/base/CkPromptInputDisclaimer/CkPromptInputDisclaimer.stories.d.ts.map +1 -1
  74. package/dist/components/base/CkPromptInputDisclaimer/CkPromptInputDisclaimer.vue.d.ts +2 -4
  75. package/dist/components/base/CkPromptInputDisclaimer/CkPromptInputDisclaimer.vue.d.ts.map +1 -1
  76. package/dist/components/base/CkPromptInputDisclaimer/index.d.ts +1 -0
  77. package/dist/components/base/CkPromptInputDisclaimer/index.d.ts.map +1 -1
  78. package/dist/components/base/CkPromptInputDisclaimer/types.d.ts +18 -0
  79. package/dist/components/base/CkPromptInputDisclaimer/types.d.ts.map +1 -0
  80. package/dist/components/base/CkPromptStopButton/CkPromptStopButton.stories.d.ts +8 -0
  81. package/dist/components/base/CkPromptStopButton/CkPromptStopButton.stories.d.ts.map +1 -1
  82. package/dist/components/base/CkPromptStopButton/CkPromptStopButton.vue.d.ts +3 -6
  83. package/dist/components/base/CkPromptStopButton/CkPromptStopButton.vue.d.ts.map +1 -1
  84. package/dist/components/base/CkPromptStopButton/index.d.ts +1 -0
  85. package/dist/components/base/CkPromptStopButton/index.d.ts.map +1 -1
  86. package/dist/components/base/CkPromptStopButton/types.d.ts +28 -0
  87. package/dist/components/base/CkPromptStopButton/types.d.ts.map +1 -0
  88. package/dist/components/base/CkUserMessage/CkUserMessage.stories.d.ts +15 -0
  89. package/dist/components/base/CkUserMessage/CkUserMessage.stories.d.ts.map +1 -1
  90. package/dist/components/base/CkUserMessage/CkUserMessage.vue.d.ts.map +1 -1
  91. package/dist/components/base/CkUserMessage/types.d.ts +9 -2
  92. package/dist/components/base/CkUserMessage/types.d.ts.map +1 -1
  93. package/dist/components/base/icons/CkStopIcon.vue.d.ts.map +1 -1
  94. package/dist/components/base/index.d.ts +5 -1
  95. package/dist/components/base/index.d.ts.map +1 -1
  96. package/dist/components/composed/CkConversation/CkConversation.stories.d.ts +55 -0
  97. package/dist/components/composed/CkConversation/CkConversation.stories.d.ts.map +1 -1
  98. package/dist/components/composed/CkConversation/CkConversation.vue.d.ts.map +1 -1
  99. package/dist/components/composed/CkConversation/types.d.ts +25 -6
  100. package/dist/components/composed/CkConversation/types.d.ts.map +1 -1
  101. package/dist/components/composed/CkHistory/CkHistory.stories.d.ts +33 -0
  102. package/dist/components/composed/CkHistory/CkHistory.stories.d.ts.map +1 -0
  103. package/dist/components/composed/CkHistory/CkHistory.vue.d.ts +50 -0
  104. package/dist/components/composed/CkHistory/CkHistory.vue.d.ts.map +1 -0
  105. package/dist/components/composed/CkHistory/index.d.ts +3 -0
  106. package/dist/components/composed/CkHistory/index.d.ts.map +1 -0
  107. package/dist/components/composed/CkHistory/types.d.ts +71 -0
  108. package/dist/components/composed/CkHistory/types.d.ts.map +1 -0
  109. package/dist/components/composed/CkInput/CkInput.stories.d.ts +31 -1
  110. package/dist/components/composed/CkInput/CkInput.stories.d.ts.map +1 -1
  111. package/dist/components/composed/CkInput/CkInput.vue.d.ts +3 -12
  112. package/dist/components/composed/CkInput/CkInput.vue.d.ts.map +1 -1
  113. package/dist/components/composed/CkInput/index.d.ts +1 -0
  114. package/dist/components/composed/CkInput/index.d.ts.map +1 -1
  115. package/dist/components/composed/CkInput/types.d.ts +60 -0
  116. package/dist/components/composed/CkInput/types.d.ts.map +1 -0
  117. package/dist/components/composed/index.d.ts +2 -0
  118. package/dist/components/composed/index.d.ts.map +1 -1
  119. package/dist/components/index.d.ts +4 -4
  120. package/dist/components/index.d.ts.map +1 -1
  121. package/dist/components/index.js +14 -11
  122. package/dist/composables/index.d.ts +2 -0
  123. package/dist/composables/index.d.ts.map +1 -1
  124. package/dist/composables/index.js +3 -3
  125. package/dist/composables/useConversations.d.ts +72 -0
  126. package/dist/composables/useConversations.d.ts.map +1 -0
  127. package/dist/generateId-BHf0W4Rq.js +6 -0
  128. package/dist/handlers/index.js +2 -2
  129. package/dist/handlers/useChatKitLabels.d.ts +8 -0
  130. package/dist/handlers/useChatKitLabels.d.ts.map +1 -1
  131. package/dist/handlers/useMarkdownParser.d.ts.map +1 -1
  132. package/dist/helpers/conversationTree.d.ts.map +1 -1
  133. package/dist/helpers/index.d.ts +2 -0
  134. package/dist/helpers/index.d.ts.map +1 -1
  135. package/dist/helpers/secondsToDate.d.ts +6 -0
  136. package/dist/helpers/secondsToDate.d.ts.map +1 -0
  137. package/dist/helpers/toError.d.ts +7 -0
  138. package/dist/helpers/toError.d.ts.map +1 -0
  139. package/dist/index.d.ts +6 -6
  140. package/dist/index.d.ts.map +1 -1
  141. package/dist/index.js +20 -16
  142. package/dist/services/ChatService.d.ts.map +1 -1
  143. package/dist/services/MockChatService.d.ts.map +1 -1
  144. package/dist/services/index.d.ts +1 -1
  145. package/dist/services/index.d.ts.map +1 -1
  146. package/dist/services/index.js +4 -1
  147. package/dist/services/types.d.ts +53 -0
  148. package/dist/services/types.d.ts.map +1 -1
  149. package/dist/types.d.ts +13 -0
  150. package/dist/types.d.ts.map +1 -1
  151. package/dist/{useChatKitLabels-DNwoZ1x6.js → useChatKitLabels-Dvi2FmjE.js} +8 -2
  152. package/dist/useConversations-Ca242D2P.js +195 -0
  153. package/dist/{useMessageHandler-D7OdrrcB.js → useMessageHandler-uLHNac39.js} +1 -3
  154. package/package.json +5 -4
  155. package/src/components/base/CkAgentMessage/CkAgentMessage.spec.ts +91 -0
  156. package/src/components/base/CkAgentMessage/CkAgentMessage.stories.ts +215 -0
  157. package/src/components/base/CkAgentMessage/CkAgentMessage.vue +25 -0
  158. package/src/components/base/CkAgentMessage/index.ts +2 -0
  159. package/src/components/base/CkAgentMessage/types.ts +11 -0
  160. package/src/components/base/CkConversationLayout/CkConversationLayout.spec.ts +129 -0
  161. package/src/components/base/CkConversationLayout/CkConversationLayout.stories.ts +251 -0
  162. package/src/components/base/CkConversationLayout/CkConversationLayout.vue +206 -0
  163. package/src/components/base/CkConversationLayout/index.ts +2 -0
  164. package/src/components/base/CkConversationLayout/types.ts +18 -0
  165. package/src/components/base/CkCopyMessageActionButton/CkCopyMessageActionButton.spec.ts +89 -0
  166. package/src/components/base/CkCopyMessageActionButton/CkCopyMessageActionButton.stories.ts +70 -0
  167. package/src/components/base/CkCopyMessageActionButton/CkCopyMessageActionButton.vue +110 -0
  168. package/src/components/base/CkCopyMessageActionButton/index.ts +2 -0
  169. package/src/components/base/CkCopyMessageActionButton/types.ts +43 -0
  170. package/src/components/base/CkHistoryItem/CkHistoryItem.spec.ts +42 -0
  171. package/src/components/base/CkHistoryItem/CkHistoryItem.stories.ts +230 -0
  172. package/src/components/base/CkHistoryItem/CkHistoryItem.vue +285 -0
  173. package/src/components/base/CkHistoryItem/activeIndicator.ts +15 -0
  174. package/src/components/base/CkHistoryItem/index.ts +2 -0
  175. package/src/components/base/CkHistoryItem/types.ts +111 -0
  176. package/src/components/base/CkHistoryWrapper/CkHistoryWrapper.spec.ts +26 -0
  177. package/src/components/base/CkHistoryWrapper/CkHistoryWrapper.stories.ts +149 -0
  178. package/src/components/base/CkHistoryWrapper/CkHistoryWrapper.vue +36 -0
  179. package/src/components/base/CkHistoryWrapper/index.ts +2 -0
  180. package/src/components/base/CkHistoryWrapper/types.ts +16 -0
  181. package/src/components/base/CkMarkdownRenderer/CkMarkdownRenderer.spec.ts +348 -0
  182. package/src/components/base/CkMarkdownRenderer/CkMarkdownRenderer.stories.ts +382 -0
  183. package/src/components/base/CkMarkdownRenderer/CkMarkdownRenderer.vue +89 -0
  184. package/src/components/base/CkMarkdownRenderer/defaultMarkdownClasses.ts +19 -0
  185. package/src/components/base/CkMarkdownRenderer/index.ts +5 -0
  186. package/src/components/base/CkMarkdownRenderer/types.ts +53 -0
  187. package/src/components/base/CkMessageActionButton/CkMessageActionButton.spec.ts +40 -0
  188. package/src/components/base/CkMessageActionButton/CkMessageActionButton.stories.ts +56 -0
  189. package/src/components/base/CkMessageActionButton/CkMessageActionButton.vue +23 -0
  190. package/src/components/base/CkMessageActionButton/index.ts +2 -0
  191. package/src/components/base/CkMessageActionButton/types.ts +21 -0
  192. package/src/components/base/CkPromptActionButton/CkPromptActionButton.spec.ts +52 -0
  193. package/src/components/base/CkPromptActionButton/CkPromptActionButton.stories.ts +126 -0
  194. package/src/components/base/CkPromptActionButton/CkPromptActionButton.vue +25 -0
  195. package/src/components/base/CkPromptActionButton/index.ts +2 -0
  196. package/src/components/base/CkPromptActionButton/types.ts +32 -0
  197. package/src/components/base/CkPromptInput/CkPromptInput.spec.ts +181 -0
  198. package/src/components/base/CkPromptInput/CkPromptInput.stories.ts +224 -0
  199. package/src/components/base/CkPromptInput/CkPromptInput.vue +104 -0
  200. package/src/components/base/CkPromptInput/index.ts +2 -0
  201. package/src/components/base/CkPromptInput/types.ts +39 -0
  202. package/src/components/base/CkPromptInputDisclaimer/CkPromptInputDisclaimer.spec.ts +40 -0
  203. package/src/components/base/CkPromptInputDisclaimer/CkPromptInputDisclaimer.stories.ts +103 -0
  204. package/src/components/base/CkPromptInputDisclaimer/CkPromptInputDisclaimer.vue +16 -0
  205. package/src/components/base/CkPromptInputDisclaimer/index.ts +2 -0
  206. package/src/components/base/CkPromptInputDisclaimer/types.ts +17 -0
  207. package/src/components/base/CkPromptStopButton/CkPromptStopButton.spec.ts +64 -0
  208. package/src/components/base/CkPromptStopButton/CkPromptStopButton.stories.ts +81 -0
  209. package/src/components/base/CkPromptStopButton/CkPromptStopButton.vue +27 -0
  210. package/src/components/base/CkPromptStopButton/index.ts +2 -0
  211. package/src/components/base/CkPromptStopButton/types.ts +28 -0
  212. package/src/components/base/CkUserMessage/CkUserMessage.spec.ts +20 -0
  213. package/src/components/base/CkUserMessage/CkUserMessage.stories.ts +112 -0
  214. package/src/components/base/CkUserMessage/CkUserMessage.vue +19 -0
  215. package/src/components/base/CkUserMessage/index.ts +2 -0
  216. package/src/components/base/CkUserMessage/types.ts +14 -0
  217. package/src/components/base/icons/CkSendIcon.vue +22 -0
  218. package/src/components/base/icons/CkStopIcon.vue +18 -0
  219. package/src/components/base/icons/index.ts +2 -0
  220. package/src/components/base/index.ts +21 -0
  221. package/src/components/composed/CkConversation/CkConversation.spec.ts +439 -0
  222. package/src/components/composed/CkConversation/CkConversation.stories.ts +642 -0
  223. package/src/components/composed/CkConversation/CkConversation.vue +146 -0
  224. package/src/components/composed/CkConversation/index.ts +2 -0
  225. package/src/components/composed/CkConversation/types.ts +37 -0
  226. package/src/components/composed/CkHistory/CkHistory.spec.ts +327 -0
  227. package/src/components/composed/CkHistory/CkHistory.stories.ts +373 -0
  228. package/src/components/composed/CkHistory/CkHistory.vue +126 -0
  229. package/src/components/composed/CkHistory/index.ts +2 -0
  230. package/src/components/composed/CkHistory/types.ts +72 -0
  231. package/src/components/composed/CkInput/CkInput.spec.ts +191 -0
  232. package/src/components/composed/CkInput/CkInput.stories.ts +438 -0
  233. package/src/components/composed/CkInput/CkInput.vue +104 -0
  234. package/src/components/composed/CkInput/index.ts +2 -0
  235. package/src/components/composed/CkInput/types.ts +60 -0
  236. package/src/components/composed/index.ts +5 -0
  237. package/src/components/index.ts +34 -0
  238. package/src/composables/index.ts +9 -0
  239. package/src/composables/types.ts +36 -0
  240. package/src/composables/useChat.ts +18 -0
  241. package/src/composables/useConversations.spec.ts +557 -0
  242. package/src/composables/useConversations.ts +321 -0
  243. package/src/handlers/index.ts +9 -0
  244. package/src/handlers/useChatKitLabels.spec.ts +290 -0
  245. package/src/handlers/useChatKitLabels.ts +96 -0
  246. package/src/handlers/useMarkdownParser.spec.ts +112 -0
  247. package/src/handlers/useMarkdownParser.ts +47 -0
  248. package/src/handlers/useMessageHandler.spec.ts +535 -0
  249. package/src/handlers/useMessageHandler.ts +138 -0
  250. package/src/helpers/conversationTree.spec.ts +423 -0
  251. package/src/helpers/conversationTree.ts +117 -0
  252. package/src/helpers/generateId.ts +5 -0
  253. package/src/helpers/index.ts +4 -0
  254. package/src/helpers/secondsToDate.ts +7 -0
  255. package/src/helpers/toError.ts +8 -0
  256. package/src/index.ts +79 -0
  257. package/src/services/ChatService.ts +60 -0
  258. package/src/services/MockChatService.ts +47 -0
  259. package/src/services/index.ts +12 -0
  260. package/src/services/types.ts +92 -0
  261. package/src/test-setup.ts +12 -0
  262. package/src/test-utils/createTextPlugin.ts +9 -0
  263. package/src/types.ts +47 -0
@@ -0,0 +1,348 @@
1
+ import { render, screen } from '@testing-library/vue';
2
+ import { h } from 'vue';
3
+ import CkMarkdownRenderer from './CkMarkdownRenderer.vue';
4
+ import { defaultMarkdownClasses } from './defaultMarkdownClasses';
5
+ import { createTextPlugin } from '../../../test-utils/createTextPlugin';
6
+
7
+ describe('CkMarkdownRenderer', () => {
8
+ it('renders a plain text paragraph', () => {
9
+ render(CkMarkdownRenderer, { props: { content: 'Hello world' } });
10
+
11
+ expect(screen.getByText('Hello world')).toBeInTheDocument();
12
+ expect(screen.getByText('Hello world').tagName).toBe('P');
13
+ });
14
+
15
+ it('renders headings at the correct level', () => {
16
+ const { container } = render(CkMarkdownRenderer, {
17
+ props: { content: '# Title\n\n## Subtitle' },
18
+ });
19
+
20
+ const h1 = container.querySelector('h1');
21
+ const h2 = container.querySelector('h2');
22
+ expect(h1?.textContent).toBe('Title');
23
+ expect(h2?.textContent).toBe('Subtitle');
24
+ });
25
+
26
+ it('renders bold and italic text', () => {
27
+ const { container } = render(CkMarkdownRenderer, {
28
+ props: { content: '**bold** and *italic*' },
29
+ });
30
+
31
+ expect(container.querySelector('strong')?.textContent).toBe('bold');
32
+ expect(container.querySelector('em')?.textContent).toBe('italic');
33
+ });
34
+
35
+ it('renders unordered lists', () => {
36
+ const { container } = render(CkMarkdownRenderer, {
37
+ props: { content: '- item one\n- item two\n- item three' },
38
+ });
39
+
40
+ const items = container.querySelectorAll('li');
41
+ expect(items).toHaveLength(3);
42
+ expect(items[0].textContent).toBe('item one');
43
+ });
44
+
45
+ it('renders inline code without triggering the code-block slot', () => {
46
+ const { container } = render(CkMarkdownRenderer, {
47
+ props: { content: 'Use `console.log` for debugging' },
48
+ slots: {
49
+ 'code-block': () =>
50
+ h('div', { class: 'custom-block' }, 'SHOULD NOT APPEAR'),
51
+ },
52
+ });
53
+
54
+ expect(container.querySelector('.custom-block')).toBeNull();
55
+ const inlineCode = container.querySelector('p > code');
56
+ expect(inlineCode?.textContent).toBe('console.log');
57
+ });
58
+
59
+ it('renders a code block with default pre/code when no slot is provided', () => {
60
+ const markdown = '```javascript\nconsole.log("hi");\n```';
61
+
62
+ const { container } = render(CkMarkdownRenderer, {
63
+ props: { content: markdown },
64
+ });
65
+
66
+ const codeEl = container.querySelector('pre > code');
67
+ expect(codeEl).not.toBeNull();
68
+ expect(codeEl?.textContent).toContain('console.log("hi");');
69
+ expect(codeEl?.classList.contains('language-javascript')).toBe(true);
70
+ });
71
+
72
+ it('renders a code block without a language class when none is specified', () => {
73
+ const markdown = '```\nplain code\n```';
74
+
75
+ const { container } = render(CkMarkdownRenderer, {
76
+ props: { content: markdown },
77
+ });
78
+
79
+ const codeEl = container.querySelector('pre > code');
80
+ expect(codeEl).not.toBeNull();
81
+ expect(codeEl?.textContent).toContain('plain code');
82
+ });
83
+
84
+ it('delegates code blocks to the code-block scoped slot', () => {
85
+ const markdown = '```python\nprint("hello")\n```';
86
+
87
+ const { container } = render(CkMarkdownRenderer, {
88
+ props: { content: markdown },
89
+ slots: {
90
+ 'code-block': (props: { code: string; language: string }) =>
91
+ h(
92
+ 'div',
93
+ { class: 'custom-code', 'data-lang': props.language },
94
+ props.code,
95
+ ),
96
+ },
97
+ });
98
+
99
+ const custom = container.querySelector('.custom-code');
100
+ expect(custom).not.toBeNull();
101
+ expect(custom?.textContent).toContain('print("hello")');
102
+ expect(custom?.getAttribute('data-lang')).toBe('python');
103
+ expect(container.querySelector('pre > code')).toBeNull();
104
+ });
105
+
106
+ it('preserves order of mixed prose and code blocks', () => {
107
+ const markdown = 'Before\n\n```ts\nconst x = 1;\n```\n\nAfter';
108
+
109
+ const { container } = render(CkMarkdownRenderer, {
110
+ props: { content: markdown },
111
+ });
112
+
113
+ const root = container.querySelector('.markdown-renderer');
114
+ expect(root).not.toBeNull();
115
+
116
+ const text = root!.textContent!;
117
+ expect(text.indexOf('Before')).toBeLessThan(text.indexOf('const x = 1;'));
118
+ expect(text.indexOf('const x = 1;')).toBeLessThan(text.indexOf('After'));
119
+ });
120
+
121
+ it('renders nothing for empty content', () => {
122
+ const { container } = render(CkMarkdownRenderer, {
123
+ props: { content: '' },
124
+ });
125
+
126
+ const root = container.querySelector('.markdown-renderer');
127
+ expect(root).not.toBeNull();
128
+ expect(root?.children.length).toBe(0);
129
+ });
130
+
131
+ it('renders a table from GFM syntax', () => {
132
+ const markdown =
133
+ '| Name | Age |\n| --- | --- |\n| Alice | 30 |\n| Bob | 25 |';
134
+
135
+ const { container } = render(CkMarkdownRenderer, {
136
+ props: { content: markdown },
137
+ });
138
+
139
+ const table = container.querySelector('table');
140
+ expect(table).not.toBeNull();
141
+ const headerCells = table!.querySelectorAll('thead th');
142
+ expect(headerCells).toHaveLength(2);
143
+ expect(headerCells[0].textContent).toBe('Name');
144
+ expect(headerCells[1].textContent).toBe('Age');
145
+ const rows = table!.querySelectorAll('tbody tr');
146
+ expect(rows).toHaveLength(2);
147
+ expect(rows[0].querySelectorAll('td')[0].textContent).toBe('Alice');
148
+ });
149
+
150
+ it('renders strikethrough text', () => {
151
+ const { container } = render(CkMarkdownRenderer, {
152
+ props: { content: '~~deleted~~' },
153
+ });
154
+
155
+ const del = container.querySelector('del');
156
+ expect(del).not.toBeNull();
157
+ expect(del?.textContent).toBe('deleted');
158
+ });
159
+
160
+ it('renders task list checkboxes', () => {
161
+ const markdown = '- [x] done\n- [ ] pending';
162
+
163
+ const { container } = render(CkMarkdownRenderer, {
164
+ props: { content: markdown },
165
+ });
166
+
167
+ const checkboxes = container.querySelectorAll('input[type="checkbox"]');
168
+ expect(checkboxes).toHaveLength(2);
169
+ expect((checkboxes[0] as HTMLInputElement).checked).toBe(true);
170
+ expect((checkboxes[1] as HTMLInputElement).checked).toBe(false);
171
+ });
172
+
173
+ it('auto-links bare URLs', () => {
174
+ const { container } = render(CkMarkdownRenderer, {
175
+ props: { content: 'Visit https://example.com for details' },
176
+ });
177
+
178
+ const link = container.querySelector('a');
179
+ expect(link).not.toBeNull();
180
+ expect(link?.getAttribute('href')).toBe('https://example.com');
181
+ expect(link?.textContent).toBe('https://example.com');
182
+ });
183
+
184
+ describe('classes prop', () => {
185
+ it('applies custom classes to matching elements', () => {
186
+ const { container } = render(CkMarkdownRenderer, {
187
+ props: {
188
+ content:
189
+ '# Hello\n\nA **bold** paragraph with a [link](https://example.com)',
190
+ classes: {
191
+ h1: 'my-heading',
192
+ p: 'my-paragraph',
193
+ strong: 'my-bold',
194
+ a: 'my-link',
195
+ },
196
+ },
197
+ });
198
+
199
+ expect(
200
+ container.querySelector('h1')?.classList.contains('my-heading'),
201
+ ).toBe(true);
202
+ expect(
203
+ container.querySelector('p')?.classList.contains('my-paragraph'),
204
+ ).toBe(true);
205
+ expect(
206
+ container.querySelector('strong')?.classList.contains('my-bold'),
207
+ ).toBe(true);
208
+ expect(container.querySelector('a')?.classList.contains('my-link')).toBe(
209
+ true,
210
+ );
211
+ });
212
+
213
+ it('applies custom classes to table elements', () => {
214
+ const markdown = '| A | B |\n| --- | --- |\n| 1 | 2 |';
215
+
216
+ const { container } = render(CkMarkdownRenderer, {
217
+ props: {
218
+ content: markdown,
219
+ classes: {
220
+ table: 'tbl',
221
+ th: 'tbl-header',
222
+ td: 'tbl-cell',
223
+ tr: 'tbl-row',
224
+ },
225
+ },
226
+ });
227
+
228
+ expect(container.querySelector('table')?.classList.contains('tbl')).toBe(
229
+ true,
230
+ );
231
+ expect(
232
+ container.querySelector('th')?.classList.contains('tbl-header'),
233
+ ).toBe(true);
234
+ expect(
235
+ container.querySelector('td')?.classList.contains('tbl-cell'),
236
+ ).toBe(true);
237
+ expect(container.querySelector('tr')?.classList.contains('tbl-row')).toBe(
238
+ true,
239
+ );
240
+ });
241
+
242
+ it('merges custom classes with existing attributes', () => {
243
+ const markdown = '```javascript\ncode\n```';
244
+
245
+ const { container } = render(CkMarkdownRenderer, {
246
+ props: { content: markdown, classes: { code: 'my-code' } },
247
+ });
248
+
249
+ const codeEl = container.querySelector('pre > code');
250
+ expect(codeEl?.classList.contains('language-javascript')).toBe(true);
251
+ expect(codeEl?.classList.contains('my-code')).toBe(true);
252
+ });
253
+
254
+ it('applies default classes to elements not overridden in the map', () => {
255
+ const { container } = render(CkMarkdownRenderer, {
256
+ props: {
257
+ content: '# Title\n\nParagraph',
258
+ classes: { h1: 'styled' },
259
+ },
260
+ });
261
+
262
+ expect(container.querySelector('h1')?.classList.contains('styled')).toBe(
263
+ true,
264
+ );
265
+ expect(container.querySelector('p')?.className).toContain(
266
+ defaultMarkdownClasses.p,
267
+ );
268
+ });
269
+
270
+ it('applies default classes when classes prop is not provided', () => {
271
+ const { container } = render(CkMarkdownRenderer, {
272
+ props: { content: '# Title' },
273
+ });
274
+
275
+ expect(container.querySelector('h1')?.className).toContain(
276
+ defaultMarkdownClasses.h1,
277
+ );
278
+ });
279
+
280
+ it('applies no classes for an element when classes prop explicitly sets it to empty string', () => {
281
+ const { container } = render(CkMarkdownRenderer, {
282
+ props: { content: '# Title', classes: { h1: '' } },
283
+ });
284
+
285
+ expect(container.querySelector('h1')?.hasAttribute('class')).toBe(false);
286
+ });
287
+ });
288
+
289
+ it('sanitizes dangerous HTML in markdown', () => {
290
+ const markdown =
291
+ 'Safe text\n\n<script>alert("xss")</script>\n\n<img src=x onerror=alert(1)>';
292
+
293
+ const { container } = render(CkMarkdownRenderer, {
294
+ props: { content: markdown },
295
+ });
296
+
297
+ expect(container.querySelector('script')).toBeNull();
298
+ const img = container.querySelector('img');
299
+ if (img) {
300
+ expect(img.hasAttribute('onerror')).toBe(false);
301
+ }
302
+ });
303
+
304
+ describe('plugins prop', () => {
305
+ it('applies a custom remark plugin that transforms text', () => {
306
+ const uppercasePlugin = createTextPlugin((t) => t.toUpperCase());
307
+
308
+ const { container } = render(CkMarkdownRenderer, {
309
+ props: {
310
+ content: 'hello world',
311
+ plugins: [uppercasePlugin],
312
+ },
313
+ });
314
+
315
+ expect(container.querySelector('p')?.textContent).toBe('HELLO WORLD');
316
+ });
317
+
318
+ it('renders default output when plugins is undefined', () => {
319
+ render(CkMarkdownRenderer, {
320
+ props: { content: 'Hello world', plugins: undefined },
321
+ });
322
+
323
+ expect(screen.getByText('Hello world')).toBeInTheDocument();
324
+ });
325
+
326
+ it('renders default output when plugins is an empty array', () => {
327
+ render(CkMarkdownRenderer, {
328
+ props: { content: 'Hello world', plugins: [] },
329
+ });
330
+
331
+ expect(screen.getByText('Hello world')).toBeInTheDocument();
332
+ });
333
+
334
+ it('supports multiple plugins applied in order', () => {
335
+ const addPrefix = createTextPlugin((t) => `prefix:${t}`);
336
+ const uppercasePlugin = createTextPlugin((t) => t.toUpperCase());
337
+
338
+ const { container } = render(CkMarkdownRenderer, {
339
+ props: {
340
+ content: 'hello',
341
+ plugins: [addPrefix, uppercasePlugin],
342
+ },
343
+ });
344
+
345
+ expect(container.querySelector('p')?.textContent).toBe('PREFIX:HELLO');
346
+ });
347
+ });
348
+ });