@n8n/chat 0.6.0 → 0.6.1

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 (205) hide show
  1. package/{src/App.vue → App.vue} +2 -2
  2. package/README.md +27 -4
  3. package/__stories__/App.stories.d.ts +16 -0
  4. package/__stories__/App.stories.js +38 -0
  5. package/__stories__/App.stories.mjs +32 -0
  6. package/__tests__/index.spec.d.ts +1 -0
  7. package/__tests__/index.spec.js +146 -0
  8. package/__tests__/index.spec.mjs +172 -0
  9. package/__tests__/setup.js +3 -0
  10. package/__tests__/setup.mjs +1 -0
  11. package/__tests__/utils/create.d.ts +5 -0
  12. package/__tests__/utils/create.js +16 -0
  13. package/__tests__/utils/create.mjs +10 -0
  14. package/__tests__/utils/fetch.d.ts +3 -0
  15. package/__tests__/utils/fetch.js +20 -0
  16. package/__tests__/utils/fetch.mjs +9 -0
  17. package/__tests__/utils/index.js +38 -0
  18. package/__tests__/utils/index.mjs +3 -0
  19. package/__tests__/utils/selectors.d.ts +12 -0
  20. package/__tests__/utils/selectors.js +58 -0
  21. package/__tests__/utils/selectors.mjs +41 -0
  22. package/api/generic.d.ts +6 -0
  23. package/api/generic.js +68 -0
  24. package/api/generic.mjs +54 -0
  25. package/api/index.js +27 -0
  26. package/api/index.mjs +2 -0
  27. package/api/message.d.ts +3 -0
  28. package/api/message.js +33 -0
  29. package/api/message.mjs +30 -0
  30. package/chat.bundle.es.js +10753 -0
  31. package/chat.bundle.umd.js +18 -0
  32. package/chat.es.js +6864 -0
  33. package/chat.umd.js +18 -0
  34. package/components/Button.vue +34 -0
  35. package/{src/components → components}/Chat.vue +19 -15
  36. package/components/ChatWindow.vue +104 -0
  37. package/components/GetStarted.vue +24 -0
  38. package/{src/components → components}/GetStartedFooter.vue +4 -4
  39. package/{src/components → components}/Input.vue +35 -40
  40. package/components/Layout.vue +66 -0
  41. package/components/Message.vue +94 -0
  42. package/components/MessageTyping.vue +101 -0
  43. package/{src/components → components}/MessagesList.vue +8 -8
  44. package/{src/components → components}/PoweredBy.vue +6 -7
  45. package/components/index.js +76 -0
  46. package/components/index.mjs +10 -0
  47. package/composables/index.js +38 -0
  48. package/composables/index.mjs +3 -0
  49. package/composables/useChat.d.ts +1 -0
  50. package/composables/useChat.js +11 -0
  51. package/composables/useChat.mjs +5 -0
  52. package/composables/useI18n.d.ts +4 -0
  53. package/composables/useI18n.js +23 -0
  54. package/composables/useI18n.mjs +12 -0
  55. package/composables/useOptions.d.ts +3 -0
  56. package/composables/useOptions.js +14 -0
  57. package/composables/useOptions.mjs +8 -0
  58. package/constants/defaults.d.ts +3 -0
  59. package/constants/defaults.js +32 -0
  60. package/constants/defaults.mjs +26 -0
  61. package/constants/index.js +38 -0
  62. package/constants/index.mjs +3 -0
  63. package/constants/localStorage.d.ts +2 -0
  64. package/constants/localStorage.js +8 -0
  65. package/{src/constants/localStorage.ts → constants/localStorage.mjs} +1 -1
  66. package/constants/symbols.d.ts +3 -0
  67. package/constants/symbols.js +8 -0
  68. package/constants/symbols.mjs +2 -0
  69. package/css/index.css +31 -0
  70. package/event-buses/chatEventBus.d.ts +1 -0
  71. package/event-buses/chatEventBus.js +8 -0
  72. package/event-buses/chatEventBus.mjs +2 -0
  73. package/event-buses/index.js +16 -0
  74. package/event-buses/index.mjs +1 -0
  75. package/index.d.ts +3 -0
  76. package/index.js +43 -0
  77. package/index.mjs +36 -0
  78. package/main.css +151 -0
  79. package/package.json +41 -5
  80. package/plugins/chat.d.ts +3 -0
  81. package/plugins/chat.js +85 -0
  82. package/plugins/chat.mjs +83 -0
  83. package/plugins/index.js +16 -0
  84. package/plugins/index.mjs +1 -0
  85. package/style.css +1 -0
  86. package/types/App.vue.d.ts +8 -0
  87. package/types/__stories__/App.stories.d.ts +17 -0
  88. package/types/__tests__/index.spec.d.ts +1 -0
  89. package/types/__tests__/setup.d.ts +0 -0
  90. package/types/__tests__/utils/create.d.ts +5 -0
  91. package/types/__tests__/utils/fetch.d.ts +4 -0
  92. package/types/__tests__/utils/index.d.ts +3 -0
  93. package/types/__tests__/utils/selectors.d.ts +12 -0
  94. package/types/api/generic.d.ts +6 -0
  95. package/types/api/index.d.ts +2 -0
  96. package/types/api/message.d.ts +3 -0
  97. package/types/chat.d.ts +11 -0
  98. package/types/chat.js +1 -0
  99. package/types/chat.mjs +0 -0
  100. package/types/components/Button.vue.d.ts +9 -0
  101. package/types/components/Chat.vue.d.ts +2 -0
  102. package/types/components/ChatWindow.vue.d.ts +2 -0
  103. package/types/components/GetStarted.vue.d.ts +2 -0
  104. package/types/components/GetStartedFooter.vue.d.ts +2 -0
  105. package/types/components/Input.vue.d.ts +2 -0
  106. package/types/components/Layout.vue.d.ts +11 -0
  107. package/types/components/Message.vue.d.ts +21 -0
  108. package/types/components/MessageTyping.vue.d.ts +15 -0
  109. package/types/components/MessagesList.vue.d.ts +14 -0
  110. package/types/components/PoweredBy.vue.d.ts +2 -0
  111. package/types/components/index.d.ts +10 -0
  112. package/types/composables/index.d.ts +3 -0
  113. package/types/composables/useChat.d.ts +2 -0
  114. package/types/composables/useI18n.d.ts +4 -0
  115. package/types/composables/useOptions.d.ts +4 -0
  116. package/types/constants/defaults.d.ts +3 -0
  117. package/types/constants/index.d.ts +3 -0
  118. package/types/constants/localStorage.d.ts +2 -0
  119. package/types/constants/symbols.d.ts +4 -0
  120. package/types/event-buses/chatEventBus.d.ts +1 -0
  121. package/types/event-buses/index.d.ts +1 -0
  122. package/types/index.js +49 -0
  123. package/types/index.mjs +4 -0
  124. package/types/messages.d.ts +6 -0
  125. package/types/messages.js +1 -0
  126. package/types/messages.mjs +0 -0
  127. package/types/options.d.ts +25 -0
  128. package/types/options.js +1 -0
  129. package/types/options.mjs +0 -0
  130. package/types/plugins/chat.d.ts +3 -0
  131. package/types/plugins/index.d.ts +1 -0
  132. package/types/types/chat.d.ts +11 -0
  133. package/types/types/index.d.ts +4 -0
  134. package/types/types/messages.d.ts +6 -0
  135. package/types/types/options.d.ts +25 -0
  136. package/types/types/webhook.d.ts +15 -0
  137. package/types/utils/event-bus.d.ts +8 -0
  138. package/types/utils/mount.d.ts +1 -0
  139. package/types/webhook.d.ts +15 -0
  140. package/types/webhook.js +1 -0
  141. package/types/webhook.mjs +0 -0
  142. package/utils/event-bus.d.ts +8 -0
  143. package/utils/event-bus.js +38 -0
  144. package/utils/event-bus.mjs +32 -0
  145. package/utils/index.d.ts +2 -0
  146. package/utils/index.js +27 -0
  147. package/utils/index.mjs +2 -0
  148. package/utils/mount.d.ts +1 -0
  149. package/utils/mount.js +19 -0
  150. package/utils/mount.mjs +13 -0
  151. package/.eslintignore +0 -2
  152. package/.eslintrc.cjs +0 -51
  153. package/.np-config.json +0 -5
  154. package/.storybook/main.ts +0 -27
  155. package/.storybook/preview.scss +0 -4
  156. package/.storybook/preview.ts +0 -16
  157. package/.vscode/extensions.json +0 -3
  158. package/env.d.ts +0 -1
  159. package/index.html +0 -13
  160. package/resources/workflow.json +0 -293
  161. package/scripts/pack.js +0 -11
  162. package/scripts/postbuild.js +0 -16
  163. package/src/__stories__/App.stories.ts +0 -43
  164. package/src/__tests__/index.spec.ts +0 -223
  165. package/src/__tests__/utils/create.ts +0 -16
  166. package/src/__tests__/utils/fetch.ts +0 -18
  167. package/src/__tests__/utils/selectors.ts +0 -53
  168. package/src/api/generic.ts +0 -64
  169. package/src/api/message.ts +0 -31
  170. package/src/components/Button.vue +0 -41
  171. package/src/components/ChatWindow.vue +0 -125
  172. package/src/components/GetStarted.vue +0 -24
  173. package/src/components/Layout.vue +0 -82
  174. package/src/components/Message.vue +0 -97
  175. package/src/components/MessageTyping.vue +0 -109
  176. package/src/composables/useChat.ts +0 -7
  177. package/src/composables/useI18n.ts +0 -16
  178. package/src/composables/useOptions.ts +0 -11
  179. package/src/constants/defaults.ts +0 -25
  180. package/src/constants/symbols.ts +0 -8
  181. package/src/event-buses/chatEventBus.ts +0 -3
  182. package/src/index.ts +0 -42
  183. package/src/main.scss +0 -40
  184. package/src/plugins/chat.ts +0 -101
  185. package/src/types/chat.ts +0 -12
  186. package/src/types/messages.ts +0 -6
  187. package/src/types/options.ts +0 -23
  188. package/src/types/webhook.ts +0 -17
  189. package/src/utils/event-bus.ts +0 -51
  190. package/src/utils/mount.ts +0 -16
  191. package/tsconfig.json +0 -27
  192. package/vite.config.ts +0 -51
  193. package/vitest.config.ts +0 -20
  194. /package/{src/__tests__/setup.ts → __tests__/setup.d.ts} +0 -0
  195. /package/{src/__tests__/utils/index.ts → __tests__/utils/index.d.ts} +0 -0
  196. /package/{src/api/index.ts → api/index.d.ts} +0 -0
  197. /package/{src/components/index.ts → components/index.d.ts} +0 -0
  198. /package/{src/composables/index.ts → composables/index.d.ts} +0 -0
  199. /package/{src/constants/index.ts → constants/index.d.ts} +0 -0
  200. /package/{src/event-buses/index.ts → event-buses/index.d.ts} +0 -0
  201. /package/{public/favicon.ico → favicon.ico} +0 -0
  202. /package/{src/plugins/index.ts → plugins/index.d.ts} +0 -0
  203. /package/{src/shims.d.ts → shims.d.ts} +0 -0
  204. /package/{src/types/index.ts → types/index.d.ts} +0 -0
  205. /package/{src/utils/index.ts → types/utils/index.d.ts} +0 -0
@@ -1,10 +1,10 @@
1
1
  <script lang="ts" setup>
2
- import { Chat, ChatWindow } from '@/components';
2
+ import { Chat, ChatWindow } from '@n8n/chat/components';
3
3
  import { computed, onMounted } from 'vue';
4
4
  import hljs from 'highlight.js/lib/core';
5
5
  import hljsXML from 'highlight.js/lib/languages/xml';
6
6
  import hljsJavascript from 'highlight.js/lib/languages/javascript';
7
- import { useOptions } from '@/composables';
7
+ import { useOptions } from '@n8n/chat/composables';
8
8
 
9
9
  defineProps({});
10
10
 
package/README.md CHANGED
@@ -1,10 +1,16 @@
1
1
  # n8n Chat
2
2
  This is an embeddable Chat widget for n8n. It allows the execution of AI-Powered Workflows through a Chat window.
3
3
 
4
+ **Windowed Example**
5
+ ![n8n Chat Windowed](https://raw.githubusercontent.com/n8n-io/n8n/master/packages/%40n8n/chat/resources/images/windowed.png)
6
+
7
+ **Fullscreen Example**
8
+ ![n8n Chat Fullscreen](https://raw.githubusercontent.com/n8n-io/n8n/master/packages/%40n8n/chat/resources/images/fullscreen.png)
9
+
4
10
  ## Prerequisites
5
- Create a n8n workflow which you want to execute via chat. The workflow has to be triggered using a **Webhook** node and return data using the **Respond to Webhook** node.
11
+ Create a n8n workflow which you want to execute via chat. The workflow has to be triggered using a **Chat Trigger** node.
6
12
 
7
- Open the **Webhook** node and add your domain to the **Domain Allowlist** field. This makes sure that only requests from your domain are accepted.
13
+ Open the **Chat Trigger** node and add your domain to the **Allowed Origins (CORS)** field. This makes sure that only requests from your domain are accepted.
8
14
 
9
15
  [See example workflow](https://github.com/n8n-io/n8n/blob/master/packages/%40n8n/chat/resources/workflow.json)
10
16
 
@@ -17,8 +23,6 @@ Each request is accompanied by an `action` query parameter, where `action` can b
17
23
  - `loadPreviousSession` - When the user opens the Chatbot again and the previous chat session should be loaded
18
24
  - `sendMessage` - When the user sends a message
19
25
 
20
- We use the `Switch` node to handle the different actions.
21
-
22
26
  ## Installation
23
27
 
24
28
  Open the **Webhook** node and replace `YOUR_PRODUCTION_WEBHOOK_URL` with your production URL. This is the URL that the Chat widget will use to send requests to.
@@ -106,6 +110,10 @@ createChat({
106
110
  },
107
111
  target: '#n8n-chat',
108
112
  mode: 'window',
113
+ chatInputKey: 'chatInput',
114
+ chatSessionKey: 'sessionId',
115
+ metadata: {},
116
+ showWelcomeScreen: false,
109
117
  defaultLanguage: 'en',
110
118
  initialMessages: [
111
119
  'Hi there! 👋',
@@ -148,6 +156,21 @@ createChat({
148
156
  - In `window` mode, the Chat window will be embedded in the target element as a chat toggle button and a fixed size chat window.
149
157
  - In `fullscreen` mode, the Chat will take up the entire width and height of its target container.
150
158
 
159
+ ### `showWelcomeScreen`
160
+ - **Type**: `boolean`
161
+ - **Default**: `false`
162
+ - **Description**: Whether to show the welcome screen when the Chat window is opened.
163
+
164
+ ### `chatSessionKey`
165
+ - **Type**: `string`
166
+ - **Default**: `'sessionId'`
167
+ - **Description**: The key to use for sending the chat history session ID for the AI Memory node.
168
+
169
+ ### `chatInputKey`
170
+ - **Type**: `string`
171
+ - **Default**: `'chatInput'`
172
+ - **Description**: The key to use for sending the chat input for the AI Agent node.
173
+
151
174
  ### `defaultLanguage`
152
175
  - **Type**: `string`
153
176
  - **Default**: `'en'`
@@ -0,0 +1,16 @@
1
+ import type { StoryObj } from '@storybook/vue3';
2
+ declare const meta: {
3
+ title: string;
4
+ render: (args: ChatOptions) => {
5
+ setup(): {};
6
+ template: string;
7
+ };
8
+ parameters: {
9
+ layout: string;
10
+ };
11
+ tags: string[];
12
+ };
13
+ export default meta;
14
+ type Story = StoryObj<typeof meta>;
15
+ export declare const Fullscreen: Story;
16
+ export declare const Windowed: Story;
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ module.exports = exports.Windowed = exports.Fullscreen = void 0;
7
+ var _index = require("@n8n/chat/index");
8
+ var _vue = require("vue");
9
+ const webhookUrl = "http://localhost:5678/webhook/f406671e-c954-4691-b39a-66c90aa2f103/chat";
10
+ const meta = {
11
+ title: "Chat",
12
+ render: args => ({
13
+ setup() {
14
+ (0, _vue.onMounted)(() => {
15
+ (0, _index.createChat)(args);
16
+ });
17
+ return {};
18
+ },
19
+ template: '<div id="n8n-chat" />'
20
+ }),
21
+ parameters: {
22
+ layout: "fullscreen"
23
+ },
24
+ tags: ["autodocs"]
25
+ };
26
+ module.exports = meta;
27
+ const Fullscreen = exports.Fullscreen = {
28
+ args: {
29
+ webhookUrl,
30
+ mode: "fullscreen"
31
+ }
32
+ };
33
+ const Windowed = exports.Windowed = {
34
+ args: {
35
+ webhookUrl,
36
+ mode: "window"
37
+ }
38
+ };
@@ -0,0 +1,32 @@
1
+ import { createChat } from "@n8n/chat/index";
2
+ import { onMounted } from "vue";
3
+ const webhookUrl = "http://localhost:5678/webhook/f406671e-c954-4691-b39a-66c90aa2f103/chat";
4
+ const meta = {
5
+ title: "Chat",
6
+ render: (args) => ({
7
+ setup() {
8
+ onMounted(() => {
9
+ createChat(args);
10
+ });
11
+ return {};
12
+ },
13
+ template: '<div id="n8n-chat" />'
14
+ }),
15
+ parameters: {
16
+ layout: "fullscreen"
17
+ },
18
+ tags: ["autodocs"]
19
+ };
20
+ export default meta;
21
+ export const Fullscreen = {
22
+ args: {
23
+ webhookUrl,
24
+ mode: "fullscreen"
25
+ }
26
+ };
27
+ export const Windowed = {
28
+ args: {
29
+ webhookUrl,
30
+ mode: "window"
31
+ }
32
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,146 @@
1
+ "use strict";
2
+
3
+ var _vue = require("@testing-library/vue");
4
+ var _utils = require("@n8n/chat/__tests__/utils");
5
+ var _index = require("@n8n/chat/index");
6
+ describe("createChat()", () => {
7
+ let app;
8
+ afterEach(() => {
9
+ vi.clearAllMocks();
10
+ app.unmount();
11
+ });
12
+ describe("mode", () => {
13
+ it("should create fullscreen chat app with default options", () => {
14
+ const fetchSpy = vi.spyOn(window, "fetch");
15
+ fetchSpy.mockImplementationOnce((0, _utils.createFetchResponse)((0, _utils.createGetLatestMessagesResponse)()));
16
+ app = (0, _index.createChat)({
17
+ mode: "fullscreen"
18
+ });
19
+ expect((0, _utils.getMountingTarget)()).toBeVisible();
20
+ expect((0, _utils.getChatWrapper)()).toBeVisible();
21
+ expect((0, _utils.getChatWindowWrapper)()).not.toBeInTheDocument();
22
+ });
23
+ it("should create window chat app with default options", () => {
24
+ const fetchSpy = vi.spyOn(window, "fetch");
25
+ fetchSpy.mockImplementationOnce((0, _utils.createFetchResponse)((0, _utils.createGetLatestMessagesResponse)()));
26
+ app = (0, _index.createChat)({
27
+ mode: "window"
28
+ });
29
+ expect((0, _utils.getMountingTarget)()).toBeDefined();
30
+ expect((0, _utils.getChatWindowWrapper)()).toBeVisible();
31
+ expect((0, _utils.getChatWrapper)()).not.toBeVisible();
32
+ });
33
+ it("should open window chat app using toggle button", async () => {
34
+ const fetchSpy = vi.spyOn(window, "fetch");
35
+ fetchSpy.mockImplementationOnce((0, _utils.createFetchResponse)((0, _utils.createGetLatestMessagesResponse)()));
36
+ app = (0, _index.createChat)();
37
+ expect((0, _utils.getMountingTarget)()).toBeVisible();
38
+ expect((0, _utils.getChatWindowWrapper)()).toBeVisible();
39
+ const trigger = (0, _utils.getChatWindowToggle)();
40
+ await _vue.fireEvent.click(trigger);
41
+ expect((0, _utils.getChatWrapper)()).toBeVisible();
42
+ });
43
+ });
44
+ describe("loadPreviousMessages", () => {
45
+ it("should load previous messages on mount", async () => {
46
+ const fetchSpy = vi.spyOn(global, "fetch");
47
+ fetchSpy.mockImplementation((0, _utils.createFetchResponse)((0, _utils.createGetLatestMessagesResponse)()));
48
+ app = (0, _index.createChat)({
49
+ mode: "fullscreen",
50
+ showWelcomeScreen: true
51
+ });
52
+ const getStartedButton = (0, _utils.getGetStartedButton)();
53
+ await _vue.fireEvent.click(getStartedButton);
54
+ expect(fetchSpy.mock.calls[0][1]).toEqual(expect.objectContaining({
55
+ method: "POST",
56
+ headers: {
57
+ "Content-Type": "application/json"
58
+ },
59
+ body: expect.stringContaining('"action":"loadPreviousSession"'),
60
+ mode: "cors",
61
+ cache: "no-cache"
62
+ }));
63
+ });
64
+ });
65
+ describe("initialMessages", () => {
66
+ it.each(["fullscreen", "window"])("should show initial default messages in %s mode", async mode => {
67
+ const fetchSpy = vi.spyOn(window, "fetch");
68
+ fetchSpy.mockImplementationOnce((0, _utils.createFetchResponse)((0, _utils.createGetLatestMessagesResponse)()));
69
+ const initialMessages = ["Hello tester!", "How are you?"];
70
+ app = (0, _index.createChat)({
71
+ mode,
72
+ initialMessages
73
+ });
74
+ if (mode === "window") {
75
+ const trigger = (0, _utils.getChatWindowToggle)();
76
+ await _vue.fireEvent.click(trigger);
77
+ }
78
+ expect((0, _utils.getChatMessages)().length).toBe(initialMessages.length);
79
+ expect((0, _utils.getChatMessageByText)(initialMessages[0])).toBeInTheDocument();
80
+ expect((0, _utils.getChatMessageByText)(initialMessages[1])).toBeInTheDocument();
81
+ });
82
+ });
83
+ describe("sendMessage", () => {
84
+ it.each(["window", "fullscreen"])("should send a message and render a text message in %s mode", async mode => {
85
+ const input = "Hello User World!";
86
+ const output = "Hello Bot World!";
87
+ const fetchSpy = vi.spyOn(window, "fetch");
88
+ fetchSpy.mockImplementationOnce((0, _utils.createFetchResponse)(_utils.createGetLatestMessagesResponse)).mockImplementationOnce((0, _utils.createFetchResponse)((0, _utils.createSendMessageResponse)(output)));
89
+ app = (0, _index.createChat)({
90
+ mode
91
+ });
92
+ if (mode === "window") {
93
+ const trigger = (0, _utils.getChatWindowToggle)();
94
+ await _vue.fireEvent.click(trigger);
95
+ }
96
+ expect((0, _utils.getChatMessageTyping)()).not.toBeInTheDocument();
97
+ expect((0, _utils.getChatMessages)().length).toBe(2);
98
+ await (0, _vue.waitFor)(() => expect((0, _utils.getChatInputTextarea)()).toBeInTheDocument());
99
+ const textarea = (0, _utils.getChatInputTextarea)();
100
+ const sendButton = (0, _utils.getChatInputSendButton)();
101
+ await _vue.fireEvent.update(textarea, input);
102
+ expect(sendButton).not.toBeDisabled();
103
+ await _vue.fireEvent.click(sendButton);
104
+ expect(fetchSpy.mock.calls[1][1]).toEqual(expect.objectContaining({
105
+ method: "POST",
106
+ headers: {
107
+ "Content-Type": "application/json"
108
+ },
109
+ body: expect.stringMatching(/"action":"sendMessage"/),
110
+ mode: "cors",
111
+ cache: "no-cache"
112
+ }));
113
+ expect(fetchSpy.mock.calls[1][1]?.body).toContain(`"${input}"`);
114
+ expect((0, _utils.getChatMessages)().length).toBe(3);
115
+ expect((0, _utils.getChatMessageByText)(input)).toBeInTheDocument();
116
+ expect((0, _utils.getChatMessageTyping)()).toBeVisible();
117
+ await (0, _vue.waitFor)(() => expect((0, _utils.getChatMessageTyping)()).not.toBeInTheDocument());
118
+ expect((0, _utils.getChatMessageByText)(output)).toBeInTheDocument();
119
+ });
120
+ it.each(["fullscreen", "window"])("should send a message and render a code markdown message in %s mode", async mode => {
121
+ const input = "Teach me javascript!";
122
+ const output = '# Code\n```js\nconsole.log("Hello World!");\n```';
123
+ const fetchSpy = vi.spyOn(window, "fetch");
124
+ fetchSpy.mockImplementationOnce((0, _utils.createFetchResponse)(_utils.createGetLatestMessagesResponse)).mockImplementationOnce((0, _utils.createFetchResponse)((0, _utils.createSendMessageResponse)(output)));
125
+ app = (0, _index.createChat)({
126
+ mode
127
+ });
128
+ if (mode === "window") {
129
+ const trigger = (0, _utils.getChatWindowToggle)();
130
+ await _vue.fireEvent.click(trigger);
131
+ }
132
+ await (0, _vue.waitFor)(() => expect((0, _utils.getChatInputTextarea)()).toBeInTheDocument());
133
+ const textarea = (0, _utils.getChatInputTextarea)();
134
+ const sendButton = (0, _utils.getChatInputSendButton)();
135
+ await _vue.fireEvent.update(textarea, input);
136
+ await _vue.fireEvent.click(sendButton);
137
+ expect((0, _utils.getChatMessageByText)(input)).toBeInTheDocument();
138
+ expect((0, _utils.getChatMessages)().length).toBe(3);
139
+ await (0, _vue.waitFor)(() => expect((0, _utils.getChatMessageTyping)()).not.toBeInTheDocument());
140
+ const lastMessage = (0, _utils.getChatMessage)(-1);
141
+ expect(lastMessage).toBeInTheDocument();
142
+ expect(lastMessage.querySelector("h1")).toHaveTextContent("Code");
143
+ expect(lastMessage.querySelector("code")).toHaveTextContent('console.log("Hello World!");');
144
+ });
145
+ });
146
+ });
@@ -0,0 +1,172 @@
1
+ import { fireEvent, waitFor } from "@testing-library/vue";
2
+ import {
3
+ createFetchResponse,
4
+ createGetLatestMessagesResponse,
5
+ createSendMessageResponse,
6
+ getChatInputSendButton,
7
+ getChatInputTextarea,
8
+ getChatMessage,
9
+ getChatMessageByText,
10
+ getChatMessages,
11
+ getChatMessageTyping,
12
+ getChatWindowToggle,
13
+ getChatWindowWrapper,
14
+ getChatWrapper,
15
+ getGetStartedButton,
16
+ getMountingTarget
17
+ } from "@n8n/chat/__tests__/utils";
18
+ import { createChat } from "@n8n/chat/index";
19
+ describe("createChat()", () => {
20
+ let app;
21
+ afterEach(() => {
22
+ vi.clearAllMocks();
23
+ app.unmount();
24
+ });
25
+ describe("mode", () => {
26
+ it("should create fullscreen chat app with default options", () => {
27
+ const fetchSpy = vi.spyOn(window, "fetch");
28
+ fetchSpy.mockImplementationOnce(createFetchResponse(createGetLatestMessagesResponse()));
29
+ app = createChat({
30
+ mode: "fullscreen"
31
+ });
32
+ expect(getMountingTarget()).toBeVisible();
33
+ expect(getChatWrapper()).toBeVisible();
34
+ expect(getChatWindowWrapper()).not.toBeInTheDocument();
35
+ });
36
+ it("should create window chat app with default options", () => {
37
+ const fetchSpy = vi.spyOn(window, "fetch");
38
+ fetchSpy.mockImplementationOnce(createFetchResponse(createGetLatestMessagesResponse()));
39
+ app = createChat({
40
+ mode: "window"
41
+ });
42
+ expect(getMountingTarget()).toBeDefined();
43
+ expect(getChatWindowWrapper()).toBeVisible();
44
+ expect(getChatWrapper()).not.toBeVisible();
45
+ });
46
+ it("should open window chat app using toggle button", async () => {
47
+ const fetchSpy = vi.spyOn(window, "fetch");
48
+ fetchSpy.mockImplementationOnce(createFetchResponse(createGetLatestMessagesResponse()));
49
+ app = createChat();
50
+ expect(getMountingTarget()).toBeVisible();
51
+ expect(getChatWindowWrapper()).toBeVisible();
52
+ const trigger = getChatWindowToggle();
53
+ await fireEvent.click(trigger);
54
+ expect(getChatWrapper()).toBeVisible();
55
+ });
56
+ });
57
+ describe("loadPreviousMessages", () => {
58
+ it("should load previous messages on mount", async () => {
59
+ const fetchSpy = vi.spyOn(global, "fetch");
60
+ fetchSpy.mockImplementation(createFetchResponse(createGetLatestMessagesResponse()));
61
+ app = createChat({
62
+ mode: "fullscreen",
63
+ showWelcomeScreen: true
64
+ });
65
+ const getStartedButton = getGetStartedButton();
66
+ await fireEvent.click(getStartedButton);
67
+ expect(fetchSpy.mock.calls[0][1]).toEqual(
68
+ expect.objectContaining({
69
+ method: "POST",
70
+ headers: {
71
+ "Content-Type": "application/json"
72
+ },
73
+ body: expect.stringContaining('"action":"loadPreviousSession"'),
74
+ mode: "cors",
75
+ cache: "no-cache"
76
+ })
77
+ );
78
+ });
79
+ });
80
+ describe("initialMessages", () => {
81
+ it.each(["fullscreen", "window"])(
82
+ "should show initial default messages in %s mode",
83
+ async (mode) => {
84
+ const fetchSpy = vi.spyOn(window, "fetch");
85
+ fetchSpy.mockImplementationOnce(createFetchResponse(createGetLatestMessagesResponse()));
86
+ const initialMessages = ["Hello tester!", "How are you?"];
87
+ app = createChat({
88
+ mode,
89
+ initialMessages
90
+ });
91
+ if (mode === "window") {
92
+ const trigger = getChatWindowToggle();
93
+ await fireEvent.click(trigger);
94
+ }
95
+ expect(getChatMessages().length).toBe(initialMessages.length);
96
+ expect(getChatMessageByText(initialMessages[0])).toBeInTheDocument();
97
+ expect(getChatMessageByText(initialMessages[1])).toBeInTheDocument();
98
+ }
99
+ );
100
+ });
101
+ describe("sendMessage", () => {
102
+ it.each(["window", "fullscreen"])(
103
+ "should send a message and render a text message in %s mode",
104
+ async (mode) => {
105
+ const input = "Hello User World!";
106
+ const output = "Hello Bot World!";
107
+ const fetchSpy = vi.spyOn(window, "fetch");
108
+ fetchSpy.mockImplementationOnce(createFetchResponse(createGetLatestMessagesResponse)).mockImplementationOnce(createFetchResponse(createSendMessageResponse(output)));
109
+ app = createChat({
110
+ mode
111
+ });
112
+ if (mode === "window") {
113
+ const trigger = getChatWindowToggle();
114
+ await fireEvent.click(trigger);
115
+ }
116
+ expect(getChatMessageTyping()).not.toBeInTheDocument();
117
+ expect(getChatMessages().length).toBe(2);
118
+ await waitFor(() => expect(getChatInputTextarea()).toBeInTheDocument());
119
+ const textarea = getChatInputTextarea();
120
+ const sendButton = getChatInputSendButton();
121
+ await fireEvent.update(textarea, input);
122
+ expect(sendButton).not.toBeDisabled();
123
+ await fireEvent.click(sendButton);
124
+ expect(fetchSpy.mock.calls[1][1]).toEqual(
125
+ expect.objectContaining({
126
+ method: "POST",
127
+ headers: {
128
+ "Content-Type": "application/json"
129
+ },
130
+ body: expect.stringMatching(/"action":"sendMessage"/),
131
+ mode: "cors",
132
+ cache: "no-cache"
133
+ })
134
+ );
135
+ expect(fetchSpy.mock.calls[1][1]?.body).toContain(`"${input}"`);
136
+ expect(getChatMessages().length).toBe(3);
137
+ expect(getChatMessageByText(input)).toBeInTheDocument();
138
+ expect(getChatMessageTyping()).toBeVisible();
139
+ await waitFor(() => expect(getChatMessageTyping()).not.toBeInTheDocument());
140
+ expect(getChatMessageByText(output)).toBeInTheDocument();
141
+ }
142
+ );
143
+ it.each(["fullscreen", "window"])(
144
+ "should send a message and render a code markdown message in %s mode",
145
+ async (mode) => {
146
+ const input = "Teach me javascript!";
147
+ const output = '# Code\n```js\nconsole.log("Hello World!");\n```';
148
+ const fetchSpy = vi.spyOn(window, "fetch");
149
+ fetchSpy.mockImplementationOnce(createFetchResponse(createGetLatestMessagesResponse)).mockImplementationOnce(createFetchResponse(createSendMessageResponse(output)));
150
+ app = createChat({
151
+ mode
152
+ });
153
+ if (mode === "window") {
154
+ const trigger = getChatWindowToggle();
155
+ await fireEvent.click(trigger);
156
+ }
157
+ await waitFor(() => expect(getChatInputTextarea()).toBeInTheDocument());
158
+ const textarea = getChatInputTextarea();
159
+ const sendButton = getChatInputSendButton();
160
+ await fireEvent.update(textarea, input);
161
+ await fireEvent.click(sendButton);
162
+ expect(getChatMessageByText(input)).toBeInTheDocument();
163
+ expect(getChatMessages().length).toBe(3);
164
+ await waitFor(() => expect(getChatMessageTyping()).not.toBeInTheDocument());
165
+ const lastMessage = getChatMessage(-1);
166
+ expect(lastMessage).toBeInTheDocument();
167
+ expect(lastMessage.querySelector("h1")).toHaveTextContent("Code");
168
+ expect(lastMessage.querySelector("code")).toHaveTextContent('console.log("Hello World!");');
169
+ }
170
+ );
171
+ });
172
+ });
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+
3
+ require("@testing-library/jest-dom");
@@ -0,0 +1 @@
1
+ import "@testing-library/jest-dom";
@@ -0,0 +1,5 @@
1
+ import { createChat } from '@n8n/chat/index';
2
+ export declare function createTestChat(options?: Parameters<typeof createChat>[0]): {
3
+ unmount: () => void;
4
+ container: Element;
5
+ };
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.createTestChat = createTestChat;
7
+ var _index = require("@n8n/chat/index");
8
+ function createTestChat(options = {}) {
9
+ const app = (0, _index.createChat)(options);
10
+ const container = app._container;
11
+ const unmount = () => app.unmount();
12
+ return {
13
+ unmount,
14
+ container
15
+ };
16
+ }
@@ -0,0 +1,10 @@
1
+ import { createChat } from "@n8n/chat/index";
2
+ export function createTestChat(options = {}) {
3
+ const app = createChat(options);
4
+ const container = app._container;
5
+ const unmount = () => app.unmount();
6
+ return {
7
+ unmount,
8
+ container
9
+ };
10
+ }
@@ -0,0 +1,3 @@
1
+ export declare function createFetchResponse<T>(data: T): () => Promise<Response>;
2
+ export declare const createGetLatestMessagesResponse: (data?: LoadPreviousSessionResponse) => LoadPreviousSessionResponse;
3
+ export declare const createSendMessageResponse: (output: SendMessageResponse) => SendMessageResponse;
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.createFetchResponse = createFetchResponse;
7
+ exports.createSendMessageResponse = exports.createGetLatestMessagesResponse = void 0;
8
+ function createFetchResponse(data) {
9
+ return async () => ({
10
+ json: async () => new Promise(resolve => resolve(data))
11
+ });
12
+ }
13
+ const createGetLatestMessagesResponse = (data = []) => ({
14
+ data
15
+ });
16
+ exports.createGetLatestMessagesResponse = createGetLatestMessagesResponse;
17
+ const createSendMessageResponse = output => ({
18
+ output
19
+ });
20
+ exports.createSendMessageResponse = createSendMessageResponse;
@@ -0,0 +1,9 @@
1
+ export function createFetchResponse(data) {
2
+ return async () => ({
3
+ json: async () => new Promise((resolve) => resolve(data))
4
+ });
5
+ }
6
+ export const createGetLatestMessagesResponse = (data = []) => ({ data });
7
+ export const createSendMessageResponse = (output) => ({
8
+ output
9
+ });
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ var _create = require("./create");
7
+ Object.keys(_create).forEach(function (key) {
8
+ if (key === "default" || key === "__esModule") return;
9
+ if (key in exports && exports[key] === _create[key]) return;
10
+ Object.defineProperty(exports, key, {
11
+ enumerable: true,
12
+ get: function () {
13
+ return _create[key];
14
+ }
15
+ });
16
+ });
17
+ var _fetch = require("./fetch");
18
+ Object.keys(_fetch).forEach(function (key) {
19
+ if (key === "default" || key === "__esModule") return;
20
+ if (key in exports && exports[key] === _fetch[key]) return;
21
+ Object.defineProperty(exports, key, {
22
+ enumerable: true,
23
+ get: function () {
24
+ return _fetch[key];
25
+ }
26
+ });
27
+ });
28
+ var _selectors = require("./selectors");
29
+ Object.keys(_selectors).forEach(function (key) {
30
+ if (key === "default" || key === "__esModule") return;
31
+ if (key in exports && exports[key] === _selectors[key]) return;
32
+ Object.defineProperty(exports, key, {
33
+ enumerable: true,
34
+ get: function () {
35
+ return _selectors[key];
36
+ }
37
+ });
38
+ });
@@ -0,0 +1,3 @@
1
+ export * from "./create.mjs";
2
+ export * from "./fetch.mjs";
3
+ export * from "./selectors.mjs";
@@ -0,0 +1,12 @@
1
+ export declare function getMountingTarget(target?: any): any;
2
+ export declare function getChatWindowWrapper(): Element | null;
3
+ export declare function getChatWindowToggle(): Element | null;
4
+ export declare function getChatWrapper(): Element | null;
5
+ export declare function getChatMessages(): NodeListOf<Element>;
6
+ export declare function getChatMessage(index: number): Element;
7
+ export declare function getChatMessageByText(text: string): HTMLElement | null;
8
+ export declare function getChatMessageTyping(): Element | null;
9
+ export declare function getGetStartedButton(): Element | null;
10
+ export declare function getChatInput(): Element | null;
11
+ export declare function getChatInputTextarea(): Element | null;
12
+ export declare function getChatInputSendButton(): Element | null;