@n8n/chat 0.9.0 → 0.9.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.
- package/__stories__/App.stories.d.ts +16 -0
- package/__stories__/App.stories.js +38 -0
- package/__stories__/App.stories.mjs +32 -0
- package/__tests__/index.spec.d.ts +1 -0
- package/__tests__/index.spec.js +146 -0
- package/__tests__/index.spec.mjs +172 -0
- package/__tests__/setup.js +3 -0
- package/__tests__/setup.mjs +1 -0
- package/__tests__/utils/create.d.ts +5 -0
- package/__tests__/utils/create.js +16 -0
- package/__tests__/utils/create.mjs +10 -0
- package/__tests__/utils/fetch.d.ts +3 -0
- package/__tests__/utils/fetch.js +20 -0
- package/__tests__/utils/fetch.mjs +9 -0
- package/__tests__/utils/index.js +38 -0
- package/__tests__/utils/index.mjs +3 -0
- package/__tests__/utils/selectors.d.ts +12 -0
- package/__tests__/utils/selectors.js +58 -0
- package/__tests__/utils/selectors.mjs +41 -0
- package/api/generic.d.ts +6 -0
- package/api/generic.js +68 -0
- package/api/generic.mjs +54 -0
- package/api/index.js +27 -0
- package/api/index.mjs +2 -0
- package/api/message.d.ts +3 -0
- package/api/message.js +33 -0
- package/api/message.mjs +30 -0
- package/chat.bundle.es.js +10761 -0
- package/chat.bundle.umd.js +18 -0
- package/chat.es.js +6872 -0
- package/chat.umd.js +18 -0
- package/components/Button.vue +34 -0
- package/components/ChatWindow.vue +104 -0
- package/{src/components → components}/GetStarted.vue +7 -7
- package/{src/components → components}/GetStartedFooter.vue +2 -2
- package/{src/components → components}/Input.vue +34 -39
- package/{src/components → components}/Layout.vue +26 -42
- package/{src/components → components}/Message.vue +39 -46
- package/components/MessageTyping.vue +101 -0
- package/{src/components → components}/MessagesList.vue +4 -4
- package/{src/components → components}/PoweredBy.vue +6 -7
- package/components/index.js +76 -0
- package/components/index.mjs +10 -0
- package/composables/index.js +38 -0
- package/composables/index.mjs +3 -0
- package/composables/useChat.d.ts +1 -0
- package/composables/useChat.js +11 -0
- package/composables/useChat.mjs +5 -0
- package/composables/useI18n.d.ts +4 -0
- package/composables/useI18n.js +23 -0
- package/composables/useI18n.mjs +12 -0
- package/composables/useOptions.d.ts +3 -0
- package/composables/useOptions.js +14 -0
- package/composables/useOptions.mjs +8 -0
- package/constants/defaults.d.ts +3 -0
- package/constants/defaults.js +32 -0
- package/constants/defaults.mjs +26 -0
- package/constants/index.js +38 -0
- package/constants/index.mjs +3 -0
- package/constants/localStorage.d.ts +2 -0
- package/constants/localStorage.js +8 -0
- package/{src/constants/localStorage.ts → constants/localStorage.mjs} +1 -1
- package/constants/symbols.d.ts +3 -0
- package/constants/symbols.js +8 -0
- package/constants/symbols.mjs +2 -0
- package/css/index.css +31 -0
- package/event-buses/chatEventBus.d.ts +1 -0
- package/event-buses/chatEventBus.js +8 -0
- package/event-buses/chatEventBus.mjs +2 -0
- package/event-buses/index.js +16 -0
- package/event-buses/index.mjs +1 -0
- package/index.d.ts +3 -0
- package/index.js +43 -0
- package/index.mjs +36 -0
- package/main.css +151 -0
- package/package.json +35 -2
- package/plugins/chat.d.ts +3 -0
- package/plugins/chat.js +91 -0
- package/plugins/chat.mjs +90 -0
- package/plugins/index.js +16 -0
- package/plugins/index.mjs +1 -0
- package/style.css +1 -0
- package/types/App.vue.d.ts +8 -0
- package/types/__stories__/App.stories.d.ts +17 -0
- package/types/__tests__/index.spec.d.ts +1 -0
- package/types/__tests__/setup.d.ts +0 -0
- package/types/__tests__/utils/create.d.ts +5 -0
- package/types/__tests__/utils/fetch.d.ts +4 -0
- package/types/__tests__/utils/index.d.ts +3 -0
- package/types/__tests__/utils/selectors.d.ts +12 -0
- package/types/api/generic.d.ts +6 -0
- package/types/api/index.d.ts +2 -0
- package/types/api/message.d.ts +3 -0
- package/types/chat.d.ts +11 -0
- package/types/chat.js +1 -0
- package/types/chat.mjs +0 -0
- package/types/components/Button.vue.d.ts +9 -0
- package/types/components/Chat.vue.d.ts +2 -0
- package/types/components/ChatWindow.vue.d.ts +2 -0
- package/types/components/GetStarted.vue.d.ts +2 -0
- package/types/components/GetStartedFooter.vue.d.ts +2 -0
- package/types/components/Input.vue.d.ts +2 -0
- package/types/components/Layout.vue.d.ts +11 -0
- package/types/components/Message.vue.d.ts +21 -0
- package/types/components/MessageTyping.vue.d.ts +15 -0
- package/types/components/MessagesList.vue.d.ts +14 -0
- package/types/components/PoweredBy.vue.d.ts +2 -0
- package/types/components/index.d.ts +10 -0
- package/types/composables/index.d.ts +3 -0
- package/types/composables/useChat.d.ts +2 -0
- package/types/composables/useI18n.d.ts +4 -0
- package/types/composables/useOptions.d.ts +4 -0
- package/types/constants/defaults.d.ts +3 -0
- package/types/constants/index.d.ts +3 -0
- package/types/constants/localStorage.d.ts +2 -0
- package/types/constants/symbols.d.ts +4 -0
- package/types/event-buses/chatEventBus.d.ts +1 -0
- package/types/event-buses/index.d.ts +1 -0
- package/types/index.js +49 -0
- package/types/index.mjs +4 -0
- package/types/messages.d.ts +6 -0
- package/types/messages.js +1 -0
- package/types/messages.mjs +0 -0
- package/types/options.d.ts +25 -0
- package/types/options.js +1 -0
- package/types/options.mjs +0 -0
- package/types/plugins/chat.d.ts +3 -0
- package/types/plugins/index.d.ts +1 -0
- package/types/types/chat.d.ts +11 -0
- package/types/types/index.d.ts +4 -0
- package/types/types/messages.d.ts +6 -0
- package/types/types/options.d.ts +25 -0
- package/types/types/webhook.d.ts +16 -0
- package/types/utils/event-bus.d.ts +8 -0
- package/types/utils/mount.d.ts +1 -0
- package/types/webhook.d.ts +16 -0
- package/types/webhook.js +1 -0
- package/types/webhook.mjs +0 -0
- package/utils/event-bus.d.ts +8 -0
- package/utils/event-bus.js +38 -0
- package/utils/event-bus.mjs +32 -0
- package/utils/index.d.ts +2 -0
- package/utils/index.js +27 -0
- package/utils/index.mjs +2 -0
- package/utils/mount.d.ts +1 -0
- package/utils/mount.js +19 -0
- package/utils/mount.mjs +13 -0
- package/.eslintignore +0 -2
- package/.eslintrc.cjs +0 -10
- package/.np-config.json +0 -5
- package/.storybook/main.ts +0 -27
- package/.storybook/preview.scss +0 -4
- package/.storybook/preview.ts +0 -16
- package/.vscode/extensions.json +0 -3
- package/build.config.js +0 -21
- package/env.d.ts +0 -1
- package/index.html +0 -13
- package/resources/images/fullscreen.png +0 -0
- package/resources/images/windowed.png +0 -0
- package/resources/workflow-manual.json +0 -238
- package/resources/workflow.json +0 -119
- package/scripts/pack.js +0 -11
- package/scripts/postbuild.js +0 -36
- package/src/__stories__/App.stories.ts +0 -43
- package/src/__tests__/index.spec.ts +0 -218
- package/src/__tests__/utils/create.ts +0 -16
- package/src/__tests__/utils/fetch.ts +0 -18
- package/src/__tests__/utils/selectors.ts +0 -53
- package/src/api/generic.ts +0 -63
- package/src/api/message.ts +0 -37
- package/src/components/Button.vue +0 -41
- package/src/components/ChatWindow.vue +0 -125
- package/src/components/MessageTyping.vue +0 -109
- package/src/composables/useChat.ts +0 -7
- package/src/composables/useI18n.ts +0 -16
- package/src/composables/useOptions.ts +0 -11
- package/src/constants/defaults.ts +0 -29
- package/src/constants/symbols.ts +0 -8
- package/src/css/_tokens.scss +0 -36
- package/src/css/index.scss +0 -1
- package/src/event-buses/chatEventBus.ts +0 -3
- package/src/index.ts +0 -42
- package/src/main.scss +0 -5
- package/src/plugins/chat.ts +0 -105
- package/src/types/chat.ts +0 -12
- package/src/types/messages.ts +0 -6
- package/src/types/options.ts +0 -28
- package/src/types/webhook.ts +0 -17
- package/src/utils/event-bus.ts +0 -51
- package/src/utils/mount.ts +0 -16
- package/tsconfig.json +0 -27
- package/vite.config.ts +0 -59
- package/vitest.config.ts +0 -20
- /package/{src/App.vue → App.vue} +0 -0
- /package/{src/__tests__/setup.ts → __tests__/setup.d.ts} +0 -0
- /package/{src/__tests__/utils/index.ts → __tests__/utils/index.d.ts} +0 -0
- /package/{src/api/index.ts → api/index.d.ts} +0 -0
- /package/{src/components → components}/Chat.vue +0 -0
- /package/{src/components/index.ts → components/index.d.ts} +0 -0
- /package/{src/composables/index.ts → composables/index.d.ts} +0 -0
- /package/{src/constants/index.ts → constants/index.d.ts} +0 -0
- /package/{src/event-buses/index.ts → event-buses/index.d.ts} +0 -0
- /package/{public/favicon.ico → favicon.ico} +0 -0
- /package/{src/plugins/index.ts → plugins/index.d.ts} +0 -0
- /package/{src/shims.d.ts → shims.d.ts} +0 -0
- /package/{src/types/index.ts → types/index.d.ts} +0 -0
- /package/{src/utils/index.ts → types/utils/index.d.ts} +0 -0
|
@@ -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 _vue = require("vue");
|
|
8
|
+
var _index = require("@n8n/chat/index");
|
|
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 { onMounted } from "vue";
|
|
2
|
+
import { createChat } from "@n8n/chat/index";
|
|
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 @@
|
|
|
1
|
+
import "@testing-library/jest-dom";
|
|
@@ -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,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 () => await 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 () => await 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,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;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.getChatInput = getChatInput;
|
|
7
|
+
exports.getChatInputSendButton = getChatInputSendButton;
|
|
8
|
+
exports.getChatInputTextarea = getChatInputTextarea;
|
|
9
|
+
exports.getChatMessage = getChatMessage;
|
|
10
|
+
exports.getChatMessageByText = getChatMessageByText;
|
|
11
|
+
exports.getChatMessageTyping = getChatMessageTyping;
|
|
12
|
+
exports.getChatMessages = getChatMessages;
|
|
13
|
+
exports.getChatWindowToggle = getChatWindowToggle;
|
|
14
|
+
exports.getChatWindowWrapper = getChatWindowWrapper;
|
|
15
|
+
exports.getChatWrapper = getChatWrapper;
|
|
16
|
+
exports.getGetStartedButton = getGetStartedButton;
|
|
17
|
+
exports.getMountingTarget = getMountingTarget;
|
|
18
|
+
var _vue = require("@testing-library/vue");
|
|
19
|
+
var _constants = require("@n8n/chat/constants");
|
|
20
|
+
function getMountingTarget(target = _constants.defaultMountingTarget) {
|
|
21
|
+
return document.querySelector(target);
|
|
22
|
+
}
|
|
23
|
+
function getChatWindowWrapper() {
|
|
24
|
+
return document.querySelector(".chat-window-wrapper");
|
|
25
|
+
}
|
|
26
|
+
function getChatWindowToggle() {
|
|
27
|
+
return document.querySelector(".chat-window-toggle");
|
|
28
|
+
}
|
|
29
|
+
function getChatWrapper() {
|
|
30
|
+
return document.querySelector(".chat-wrapper");
|
|
31
|
+
}
|
|
32
|
+
function getChatMessages() {
|
|
33
|
+
return document.querySelectorAll(".chat-message:not(.chat-message-typing)");
|
|
34
|
+
}
|
|
35
|
+
function getChatMessage(index) {
|
|
36
|
+
const messages = getChatMessages();
|
|
37
|
+
return index < 0 ? messages[messages.length + index] : messages[index];
|
|
38
|
+
}
|
|
39
|
+
function getChatMessageByText(text) {
|
|
40
|
+
return _vue.screen.queryByText(text, {
|
|
41
|
+
selector: ".chat-message:not(.chat-message-typing) .chat-message-markdown p"
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
function getChatMessageTyping() {
|
|
45
|
+
return document.querySelector(".chat-message-typing");
|
|
46
|
+
}
|
|
47
|
+
function getGetStartedButton() {
|
|
48
|
+
return document.querySelector(".chat-get-started .chat-button");
|
|
49
|
+
}
|
|
50
|
+
function getChatInput() {
|
|
51
|
+
return document.querySelector(".chat-input");
|
|
52
|
+
}
|
|
53
|
+
function getChatInputTextarea() {
|
|
54
|
+
return document.querySelector(".chat-input textarea");
|
|
55
|
+
}
|
|
56
|
+
function getChatInputSendButton() {
|
|
57
|
+
return document.querySelector(".chat-input .chat-input-send-button");
|
|
58
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { screen } from "@testing-library/vue";
|
|
2
|
+
import { defaultMountingTarget } from "@n8n/chat/constants";
|
|
3
|
+
export function getMountingTarget(target = defaultMountingTarget) {
|
|
4
|
+
return document.querySelector(target);
|
|
5
|
+
}
|
|
6
|
+
export function getChatWindowWrapper() {
|
|
7
|
+
return document.querySelector(".chat-window-wrapper");
|
|
8
|
+
}
|
|
9
|
+
export function getChatWindowToggle() {
|
|
10
|
+
return document.querySelector(".chat-window-toggle");
|
|
11
|
+
}
|
|
12
|
+
export function getChatWrapper() {
|
|
13
|
+
return document.querySelector(".chat-wrapper");
|
|
14
|
+
}
|
|
15
|
+
export function getChatMessages() {
|
|
16
|
+
return document.querySelectorAll(".chat-message:not(.chat-message-typing)");
|
|
17
|
+
}
|
|
18
|
+
export function getChatMessage(index) {
|
|
19
|
+
const messages = getChatMessages();
|
|
20
|
+
return index < 0 ? messages[messages.length + index] : messages[index];
|
|
21
|
+
}
|
|
22
|
+
export function getChatMessageByText(text) {
|
|
23
|
+
return screen.queryByText(text, {
|
|
24
|
+
selector: ".chat-message:not(.chat-message-typing) .chat-message-markdown p"
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
export function getChatMessageTyping() {
|
|
28
|
+
return document.querySelector(".chat-message-typing");
|
|
29
|
+
}
|
|
30
|
+
export function getGetStartedButton() {
|
|
31
|
+
return document.querySelector(".chat-get-started .chat-button");
|
|
32
|
+
}
|
|
33
|
+
export function getChatInput() {
|
|
34
|
+
return document.querySelector(".chat-input");
|
|
35
|
+
}
|
|
36
|
+
export function getChatInputTextarea() {
|
|
37
|
+
return document.querySelector(".chat-input textarea");
|
|
38
|
+
}
|
|
39
|
+
export function getChatInputSendButton() {
|
|
40
|
+
return document.querySelector(".chat-input .chat-input-send-button");
|
|
41
|
+
}
|
package/api/generic.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare function authenticatedFetch<T>(...args: Parameters<typeof fetch>): Promise<T>;
|
|
2
|
+
export declare function get<T>(url: string, query?: object, options?: RequestInit): Promise<T>;
|
|
3
|
+
export declare function post<T>(url: string, body?: object, options?: RequestInit): Promise<T>;
|
|
4
|
+
export declare function put<T>(url: string, body?: object, options?: RequestInit): Promise<T>;
|
|
5
|
+
export declare function patch<T>(url: string, body?: object, options?: RequestInit): Promise<T>;
|
|
6
|
+
export declare function del<T>(url: string, body?: object, options?: RequestInit): Promise<T>;
|