@servicetitan/titan-chat-ui 1.1.2 → 2.0.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 (91) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/components/chat/__tests-cy__/chat-messages.test.js +69 -1
  3. package/dist/components/chat/__tests-cy__/chat-messages.test.js.map +1 -1
  4. package/dist/components/chat/chat-input.js +2 -2
  5. package/dist/components/chat/chat-input.js.map +1 -1
  6. package/dist/components/chat/chat-log.d.ts +8 -0
  7. package/dist/components/chat/chat-log.d.ts.map +1 -0
  8. package/dist/components/chat/chat-log.js +15 -0
  9. package/dist/components/chat/chat-log.js.map +1 -0
  10. package/dist/components/chat/chat-message-template-agent.d.ts.map +1 -1
  11. package/dist/components/chat/chat-message-template-agent.js +2 -2
  12. package/dist/components/chat/chat-message-template-agent.js.map +1 -1
  13. package/dist/components/chat/chat-message-template-user.js +2 -2
  14. package/dist/components/chat/chat-message-template-user.js.map +1 -1
  15. package/dist/components/chat/chat-message.d.ts +1 -5
  16. package/dist/components/chat/chat-message.d.ts.map +1 -1
  17. package/dist/components/chat/chat-message.js +4 -4
  18. package/dist/components/chat/chat-message.js.map +1 -1
  19. package/dist/components/chat/chat-messages.d.ts +3 -0
  20. package/dist/components/chat/chat-messages.d.ts.map +1 -1
  21. package/dist/components/chat/chat-messages.js +35 -7
  22. package/dist/components/chat/chat-messages.js.map +1 -1
  23. package/dist/components/chat/chat.d.ts.map +1 -1
  24. package/dist/components/chat/chat.js +3 -1
  25. package/dist/components/chat/chat.js.map +1 -1
  26. package/dist/components/messages/__tests-cy__/message-agent.test.js +1 -1
  27. package/dist/components/messages/__tests-cy__/message-agent.test.js.map +1 -1
  28. package/dist/components/messages/message-agent.d.ts +1 -0
  29. package/dist/components/messages/message-agent.d.ts.map +1 -1
  30. package/dist/components/messages/message-agent.js +3 -5
  31. package/dist/components/messages/message-agent.js.map +1 -1
  32. package/dist/components/messages/message-agent.module.less +29 -37
  33. package/dist/components/messages/message-footer.d.ts +1 -1
  34. package/dist/components/messages/message-footer.d.ts.map +1 -1
  35. package/dist/components/messages/message-footer.js +1 -1
  36. package/dist/components/messages/message-footer.js.map +1 -1
  37. package/dist/components/messages/message-user.d.ts.map +1 -1
  38. package/dist/components/messages/message-user.js +2 -2
  39. package/dist/components/messages/message-user.js.map +1 -1
  40. package/dist/components/messages/message-user.module.less +3 -6
  41. package/dist/index.d.ts +3 -0
  42. package/dist/index.d.ts.map +1 -1
  43. package/dist/index.js +3 -0
  44. package/dist/index.js.map +1 -1
  45. package/dist/models/chat-customizations.d.ts +14 -6
  46. package/dist/models/chat-customizations.d.ts.map +1 -1
  47. package/dist/models/support-chat.d.ts +4 -4
  48. package/dist/models/support-chat.d.ts.map +1 -1
  49. package/dist/models/support-chat.js +18 -0
  50. package/dist/models/support-chat.js.map +1 -1
  51. package/dist/stores/__mocks-cy__/chat-ui.store.mock.d.ts +6 -1
  52. package/dist/stores/__mocks-cy__/chat-ui.store.mock.d.ts.map +1 -1
  53. package/dist/stores/__mocks-cy__/chat-ui.store.mock.js +30 -0
  54. package/dist/stores/__mocks-cy__/chat-ui.store.mock.js.map +1 -1
  55. package/dist/stores/__tests__/chat-input.store.test.d.ts +2 -0
  56. package/dist/stores/__tests__/chat-input.store.test.d.ts.map +1 -0
  57. package/dist/stores/__tests__/chat-input.store.test.js +32 -0
  58. package/dist/stores/__tests__/chat-input.store.test.js.map +1 -0
  59. package/dist/stores/chat-ui.store.d.ts +19 -10
  60. package/dist/stores/chat-ui.store.d.ts.map +1 -1
  61. package/dist/stores/chat-ui.store.js +47 -48
  62. package/dist/stores/chat-ui.store.js.map +1 -1
  63. package/dist/utils/test-utils.d.ts +5 -0
  64. package/dist/utils/test-utils.d.ts.map +1 -0
  65. package/dist/utils/test-utils.js +17 -0
  66. package/dist/utils/test-utils.js.map +1 -0
  67. package/package.json +2 -2
  68. package/src/components/chat/__tests-cy__/chat-messages.test.tsx +78 -1
  69. package/src/components/chat/chat-input.tsx +4 -4
  70. package/src/components/chat/chat-log.tsx +29 -0
  71. package/src/components/chat/chat-message-template-agent.tsx +7 -3
  72. package/src/components/chat/chat-message-template-user.tsx +3 -3
  73. package/src/components/chat/chat-message.tsx +58 -52
  74. package/src/components/chat/chat-messages.tsx +55 -11
  75. package/src/components/chat/chat.tsx +14 -8
  76. package/src/components/messages/__tests-cy__/message-agent.test.tsx +1 -1
  77. package/src/components/messages/message-agent.module.less +29 -37
  78. package/src/components/messages/message-agent.module.less.d.ts +0 -1
  79. package/src/components/messages/message-agent.tsx +5 -4
  80. package/src/components/messages/message-footer.tsx +4 -3
  81. package/src/components/messages/message-user.module.less +3 -6
  82. package/src/components/messages/message-user.tsx +6 -5
  83. package/src/index.ts +3 -0
  84. package/src/models/chat-customizations.ts +17 -6
  85. package/src/models/support-chat.ts +22 -10
  86. package/src/stores/__mocks-cy__/chat-ui.store.mock.ts +6 -0
  87. package/src/stores/__tests__/chat-input.store.test.ts +39 -0
  88. package/src/stores/chat-ui.store.ts +54 -55
  89. package/src/utils/test-utils.ts +22 -0
  90. package/tsconfig.json +1 -2
  91. package/tsconfig.tsbuildinfo +1 -1
@@ -1,23 +1,67 @@
1
1
  import { Stack } from '@servicetitan/design-system';
2
- import { useDependencies } from '@servicetitan/react-ioc';
3
- import { observer } from 'mobx-react';
4
- import { FC } from 'react';
5
- import { CHAT_UI_STORE_TOKEN } from '../../stores';
2
+ import { FC, useMemo } from 'react';
3
+ import { ChatMessageModelBase } from '../../models';
4
+ import { formatChatMessageDate } from '../../utils/text-utils';
6
5
  import { ChatMessage } from './chat-message';
7
6
  import { ChatMessageTyping } from './chat-message-typing';
8
7
 
9
8
  interface IChatMessagesProps {
10
9
  className?: string;
10
+ messages: ChatMessageModelBase[];
11
+ showTypingIndicator?: boolean;
11
12
  }
12
13
 
13
- export const ChatMessages: FC<IChatMessagesProps> = observer(({ className }) => {
14
- const [chatUiStore] = useDependencies(CHAT_UI_STORE_TOKEN);
14
+ export const ChatMessages: FC<IChatMessagesProps> = ({
15
+ className,
16
+ messages,
17
+ showTypingIndicator,
18
+ }) => {
19
+ // Split messages into chunks divided by participant name and message time (with 1 minute accuracy)
20
+ const messagesChunks = useMemo(
21
+ () =>
22
+ messages.reduce((acc: ChatMessageModelBase[][][], message) => {
23
+ if (!acc.length) {
24
+ acc.push([[message]]);
25
+ return acc;
26
+ }
27
+ const lastMessage = acc.at(-1)!.at(-1)!.at(-1)!;
28
+ if (lastMessage.participant.name !== message.participant.name) {
29
+ acc.push([[message]]);
30
+ } else if (
31
+ formatChatMessageDate(lastMessage.timestamp) !==
32
+ formatChatMessageDate(message.timestamp)
33
+ ) {
34
+ acc.at(-1)!.push([message]);
35
+ } else {
36
+ acc.at(-1)!.at(-1)!.push(message);
37
+ }
38
+ return acc;
39
+ }, []),
40
+ [messages]
41
+ );
42
+
15
43
  return (
16
44
  <Stack direction="column" spacing="2" className={className} data-cy="titan-chat-messages">
17
- {chatUiStore.messages.map(message => (
18
- <ChatMessage key={message.id} message={message} />
19
- ))}
20
- {chatUiStore.isAgentTyping && <ChatMessageTyping />}
45
+ {messagesChunks
46
+ .map(sameParticipantMessages => {
47
+ // Set omit avatar for all messages except the first one
48
+ const firstParticipantMessage = sameParticipantMessages[0][0];
49
+ return sameParticipantMessages.map(sameDateMessages => {
50
+ const lastTimestampMessage = sameDateMessages.at(-1)!;
51
+ return sameDateMessages.map(message => {
52
+ return (
53
+ <ChatMessage
54
+ key={message.id}
55
+ message={message}
56
+ omitAvatar={message !== firstParticipantMessage}
57
+ omitTimestamp={message !== lastTimestampMessage}
58
+ />
59
+ );
60
+ });
61
+ });
62
+ })
63
+ .flat(2)}
64
+ {showTypingIndicator && <ChatMessageTyping />}
21
65
  </Stack>
22
66
  );
23
- });
67
+ };
@@ -20,6 +20,7 @@ export const Chat: FC<IChatProps> = observer(({ className, customizations }) =>
20
20
  const [chatUiStore] = useDependencies(CHAT_UI_STORE_TOKEN);
21
21
 
22
22
  const footerComponent = customizations?.footerComponent;
23
+ const loadingComponent = customizations?.loadingComponent;
23
24
 
24
25
  useEffect(() => {
25
26
  chatUiStore.setCustomizationContext(customizations);
@@ -36,18 +37,23 @@ export const Chat: FC<IChatProps> = observer(({ className, customizations }) =>
36
37
  return (
37
38
  <Stack direction="column" className={className} data-cy="titan-chat">
38
39
  {chatUiStore.isStarting ? (
39
- <ChatConnecting className="p-3" />
40
+ (loadingComponent ?? <ChatConnecting className="p-x-3 p-y-2" />)
40
41
  ) : (
41
42
  <Fragment>
42
- <div ref={scrollRef} className="flex-grow-1 of-y-auto">
43
- <ChatMessages className="p-3" />
43
+ <div ref={scrollRef} className="p-x-3 p-y-2 flex-grow-1 of-y-auto">
44
+ <ChatMessages
45
+ messages={chatUiStore.messages}
46
+ showTypingIndicator={chatUiStore.isAgentTyping}
47
+ />
44
48
  </div>
45
49
  <ChatNotifications />
46
- <div>
47
- <ChatInputFile className="m-3 m-b-0 p-3 border-radius-1 border border-style-dashed" />
48
- <ChatInput className="p-x-3 p-y-2" />
49
- {Boolean(footerComponent) && footerComponent}
50
- </div>
50
+ {!chatUiStore.customizations?.input?.isDisabled && (
51
+ <Stack className="p-x-3 p-y-2" direction="column" spacing="2">
52
+ <ChatInputFile className="box-sizing-border-box border-radius-1 border border-style-dashed" />
53
+ <ChatInput />
54
+ {Boolean(footerComponent) && footerComponent}
55
+ </Stack>
56
+ )}
51
57
  </Fragment>
52
58
  )}
53
59
  </Stack>
@@ -44,7 +44,7 @@ describe('[MessageAgent]', () => {
44
44
  <BodyText>message agent content</BodyText>
45
45
  </MessageAgent>
46
46
  );
47
- cy.getCy('chat-message-agent-center').should('have.class', 'w-100');
47
+ cy.getCy('chat-message-agent-center').should('contain.text', 'message agent content');
48
48
  });
49
49
 
50
50
  it('should render with custom data-cy', () => {
@@ -9,51 +9,43 @@
9
9
  max-width: 100%;
10
10
  column-gap: @spacing-2;
11
11
  row-gap: @spacing-1;
12
- grid-template-areas:
13
- 'avatar content .'
14
- '. footer .';
15
- grid-template-rows: auto auto;
12
+ grid-template-areas: 'avatar content .';
13
+ grid-template-rows: auto;
16
14
  grid-template-columns: minmax(@spacing-5, auto) 1fr minmax(@spacing-5, auto);
17
15
 
18
16
  &.fullWidth {
19
- width: 100%;
20
- grid-template-areas:
21
- 'avatar content'
22
- '. footer ';
23
- grid-template-rows: auto auto;
17
+ //width: 100%;
18
+ grid-template-areas: 'avatar content';
19
+ grid-template-rows: auto;
24
20
  grid-template-columns: minmax(@spacing-5, auto) 1fr;
25
21
  }
26
- }
27
-
28
- .messageFooter {
29
- grid-area: footer;
30
- }
31
-
32
- .messageAvatar {
33
- grid-area: avatar;
34
- }
35
22
 
36
- .messageContent {
37
- max-width: 100%;
38
- grid-area: content;
39
- overflow-wrap: anywhere;
40
- justify-self: flex-start;
41
- }
42
-
43
- .messageBubble {
44
- color: @color-neutral-200;
45
- padding: @spacing-2 @spacing-3;
46
- background-color: @color-blue-100;
47
- position: relative;
48
- border-radius: @spacing-0 @spacing-3 @spacing-3 @spacing-3;
49
- box-sizing: border-box;
23
+ .messageAvatar {
24
+ grid-area: avatar;
25
+ }
50
26
 
51
- &.error {
52
- color: @color-neutral-400;
53
- background-color: @color-red-100;
27
+ .messageContent {
28
+ max-width: 100%;
29
+ grid-area: content;
30
+ overflow-wrap: anywhere;
31
+ justify-self: flex-start;
32
+ }
54
33
 
55
- &::after {
56
- background: @color-red-200;
34
+ .messageBubble {
35
+ color: @color-neutral-200;
36
+ padding: @spacing-2 @spacing-3;
37
+ background-color: @color-blue-100;
38
+ position: relative;
39
+ border-radius: @spacing-0 @spacing-3 @spacing-3 @spacing-3;
40
+ box-sizing: border-box;
41
+
42
+ &.error {
43
+ color: @color-neutral-400;
44
+ background-color: @color-red-100;
45
+
46
+ &::after {
47
+ background: @color-red-200;
48
+ }
57
49
  }
58
50
  }
59
51
  }
@@ -4,6 +4,5 @@ export const fullWidth: string;
4
4
  export const messageAvatar: string;
5
5
  export const messageBubble: string;
6
6
  export const messageContent: string;
7
- export const messageFooter: string;
8
7
  export const messageRoot: string;
9
8
 
@@ -11,6 +11,7 @@ export interface IMessageAgentProps extends IDataCyProps {
11
11
  isError?: boolean;
12
12
  fullWidth?: boolean;
13
13
  subtle?: boolean;
14
+ omitAvatar?: boolean;
14
15
  }
15
16
 
16
17
  export const MessageAgent: FC<PropsWithChildren<IMessageAgentProps>> = ({
@@ -19,6 +20,7 @@ export const MessageAgent: FC<PropsWithChildren<IMessageAgentProps>> = ({
19
20
  fullWidth,
20
21
  isError,
21
22
  messageFooter,
23
+ omitAvatar,
22
24
  subtle,
23
25
  ...rest
24
26
  }) => {
@@ -33,14 +35,12 @@ export const MessageAgent: FC<PropsWithChildren<IMessageAgentProps>> = ({
33
35
  data-cy2={dataCy2}
34
36
  >
35
37
  <div className={Styles.messageAvatar}>
36
- {avatar ? <MessageAvatar {...avatar} /> : <div />}
38
+ {!omitAvatar && avatar ? <MessageAvatar {...avatar} /> : <div />}
37
39
  </div>
38
40
  <Stack
39
41
  direction="column"
40
42
  spacing="1"
41
- className={classNames(Styles.messageContent, {
42
- 'w-100': Boolean(fullWidth),
43
- })}
43
+ className={classNames(Styles.messageContent)}
44
44
  data-cy={`${dataCy}-center`}
45
45
  >
46
46
  {subtle ? (
@@ -51,6 +51,7 @@ export const MessageAgent: FC<PropsWithChildren<IMessageAgentProps>> = ({
51
51
  [Styles.error]: Boolean(isError),
52
52
  })}
53
53
  data-cy={`${dataCy}-content`}
54
+ data-cy2="chat-message-content"
54
55
  >
55
56
  {children}
56
57
  </div>
@@ -3,15 +3,16 @@ import { FC } from 'react';
3
3
  import { formatChatMessageDate } from '../../utils/text-utils';
4
4
 
5
5
  export interface IMessageFooterProps {
6
- timestamp: Date;
6
+ timestamp?: Date;
7
7
  name?: string;
8
8
  }
9
9
 
10
10
  export const MessageFooter: FC<IMessageFooterProps> = ({ name, timestamp }) => {
11
11
  return (
12
12
  <Eyebrow>
13
- {name && `${name} • `}
14
- {formatChatMessageDate(timestamp)}
13
+ {Boolean(name) && name}
14
+ {name && timestamp && ' • '}
15
+ {timestamp && formatChatMessageDate(timestamp)}
15
16
  </Eyebrow>
16
17
  );
17
18
  };
@@ -3,18 +3,15 @@
3
3
  /* stylelint-disable declaration-property-value-no-unknown */
4
4
  .messageRoot {
5
5
  display: grid;
6
- grid-template-areas:
7
- '. content'
8
- '. footer';
9
- grid-template-rows: auto auto;
6
+ grid-template-areas: '. content';
7
+ grid-template-rows: auto;
10
8
  grid-template-columns: minmax(@spacing-5, auto) 1fr;
11
9
  column-gap: @spacing-2;
12
10
  row-gap: @spacing-1;
13
11
  }
14
12
 
15
13
  .messageFooter {
16
- grid-area: footer;
17
- justify-self: end;
14
+ text-align: right;
18
15
  }
19
16
 
20
17
  .messageContent {
@@ -35,15 +35,16 @@ export const MessageUser: FC<PropsWithChildren<IMessageUserProps>> = ({
35
35
  [Styles.error]: isError,
36
36
  })}
37
37
  data-cy={`${dataCy}-content`}
38
+ data-cy2="chat-message-content"
38
39
  >
39
40
  {children}
40
41
  </div>
42
+ {Boolean(messageFooter) && (
43
+ <div className={Styles.messageFooter} data-cy={`${dataCy}-footer`}>
44
+ {messageFooter}
45
+ </div>
46
+ )}
41
47
  </Stack>
42
- {Boolean(messageFooter) && (
43
- <div className={Styles.messageFooter} data-cy={`${dataCy}-footer`}>
44
- {messageFooter}
45
- </div>
46
- )}
47
48
  </div>
48
49
  );
49
50
  };
package/src/index.ts CHANGED
@@ -1,3 +1,6 @@
1
+ export { ChatMessages } from './components/chat/chat-messages';
2
+ export { Chat, IChatProps } from './components/chat/chat';
3
+ export { ChatLog } from './components/chat/chat-log';
1
4
  export { MessageAgent, IMessageAgentProps } from './components/messages/message-agent';
2
5
  export { MessageUser, IMessageUserProps } from './components/messages/message-user';
3
6
  export { MessageSystem, IMessageSystemProps } from './components/messages/message-system';
@@ -4,11 +4,15 @@ import { ChatMessageModelBase } from './support-chat';
4
4
 
5
5
  export interface IChatMessageProps<T extends ChatMessageModelBase = ChatMessageModelBase> {
6
6
  message: T;
7
+ omitAvatar?: boolean;
8
+ omitTimestamp?: boolean;
7
9
  }
8
10
 
9
- export interface ChatMessageTemplateCustomization {
10
- component: FC<PropsWithChildren<IChatMessageProps>>;
11
- predicate: (message: ChatMessageModelBase) => boolean;
11
+ export interface ChatMessageTemplateCustomization<
12
+ T extends ChatMessageModelBase = ChatMessageModelBase,
13
+ > {
14
+ component: FC<PropsWithChildren<IChatMessageProps<T>>>;
15
+ predicate: (message: T) => boolean;
12
16
  }
13
17
 
14
18
  export type ChatMessageTemplateCustomizations = Partial<{
@@ -16,19 +20,26 @@ export type ChatMessageTemplateCustomizations = Partial<{
16
20
  user: ChatMessageTemplateCustomization;
17
21
  }>;
18
22
 
19
- export interface ChatMessageCustomization {
20
- component: FC<IChatMessageProps>;
21
- predicate: (message: ChatMessageModelBase) => boolean;
23
+ export interface ChatMessageCustomization<T extends ChatMessageModelBase = ChatMessageModelBase> {
24
+ predicate: (message: T) => boolean;
25
+ component?: FC<IChatMessageProps<T>>;
22
26
  isSystem?: boolean; // System messages are messages that are not sent by the user or the agent and rendered without chat bubbles
27
+ isIgnored?: boolean;
23
28
  }
24
29
 
25
30
  export interface ChatMessageTypingCustomization {
26
31
  component: FC<IMessageTypingProps>;
27
32
  }
28
33
 
34
+ export interface ChatInputCustomization {
35
+ isDisabled?: boolean;
36
+ }
37
+
29
38
  export type ChatCustomizations = Partial<{
30
39
  messageTemplates: ChatMessageTemplateCustomizations;
31
40
  messages: ChatMessageCustomization[];
32
41
  messageTyping: ChatMessageTypingCustomization;
42
+ input: ChatInputCustomization;
33
43
  footerComponent: ReactNode;
44
+ loadingComponent: ReactNode;
34
45
  }>;
@@ -28,14 +28,6 @@ export interface ChatParticipantModel {
28
28
  icon: ChatParticipantIcon;
29
29
  }
30
30
 
31
- export type ChatMessageModelType =
32
- | 'welcome'
33
- | 'message'
34
- | 'timeout'
35
- | 'file'
36
- | 'askSupport'
37
- | 'askSupportButtons';
38
-
39
31
  export interface ChatConfiguration {
40
32
  agentName?: string;
41
33
  agentIcon?: ChatParticipantIcon;
@@ -44,7 +36,7 @@ export interface ChatConfiguration {
44
36
  export interface ChatMessageModelBase {
45
37
  id: string;
46
38
  timestamp: Date;
47
- type: ChatMessageModelType;
39
+ type: string;
48
40
  participant: ChatParticipantModel;
49
41
  state: ChatMessageState;
50
42
  data?: any;
@@ -69,7 +61,7 @@ export interface ChatMessageModelFile extends ChatMessageModelBase {
69
61
  fileName: string;
70
62
  }
71
63
 
72
- type ChatErrorRecoverStrategy =
64
+ export type ChatErrorRecoverStrategy =
73
65
  | {
74
66
  recoverButtonTitle: string;
75
67
  }
@@ -94,6 +86,26 @@ export class ChatError<T = any> extends Error {
94
86
  this.data = options?.data;
95
87
  Object.setPrototypeOf(this, ChatError.prototype);
96
88
  }
89
+
90
+ toJSON(): Record<string, any> {
91
+ return {
92
+ name: this.name,
93
+ message: this.message,
94
+ title: this.title,
95
+ recoverStrategy: this.recoverStrategy,
96
+ data: this.data,
97
+ };
98
+ }
99
+
100
+ fromJSON(json: Record<string, any>): ChatError<T> {
101
+ const error = new ChatError<T>(json.message, {
102
+ title: json.title,
103
+ recoverStrategy: json.recoverStrategy,
104
+ data: json.data,
105
+ });
106
+ error.name = json.name;
107
+ return error;
108
+ }
97
109
  }
98
110
 
99
111
  export enum ChatRunState {
@@ -2,6 +2,7 @@ import { FileDescriptor } from '@servicetitan/form';
2
2
  import {
3
3
  ChatConfiguration,
4
4
  ChatCustomizations,
5
+ ChatEndReason,
5
6
  ChatError,
6
7
  ChatErrorOptions,
7
8
  ChatMessageModelBase,
@@ -84,6 +85,8 @@ export class ChatUiStoreMock implements IChatUiStore {
84
85
  off: <T>(event: string, listener: ChatUiEventListener<T>) => void = cy.stub();
85
86
  on: <T>(event: string, listener: ChatUiEventListener<T>) => void = cy.stub();
86
87
  resetError: (runState: ChatRunState) => void = cy.stub();
88
+ reset: (resetState?: boolean) => void = cy.stub();
89
+ resetFile: () => void = cy.stub();
87
90
  setAgentTyping: (typing: boolean) => void = cy.stub();
88
91
  setChatError: (chatError: ChatError) => void = cy.stub();
89
92
  setError: <T = any>(message: string, options?: ChatErrorOptions<T>) => void = cy.stub();
@@ -104,4 +107,7 @@ export class ChatUiStoreMock implements IChatUiStore {
104
107
  setStatus: (status: ChatRunState) => void = cy.stub();
105
108
  setTimer: (timer?: ChatTimer) => void = cy.stub();
106
109
  restartTimers: () => Promise<void> = cy.stub();
110
+ restart: () => Promise<void> = cy.stub();
111
+ cancelChat: () => Promise<void> = cy.stub();
112
+ endChat: (endReason?: ChatEndReason) => Promise<void> = cy.stub();
107
113
  }
@@ -0,0 +1,39 @@
1
+ import { expect } from '@jest/globals';
2
+ import { Container } from '@servicetitan/react-ioc';
3
+ import { initTestContainer } from '../../utils/test-utils';
4
+ import { ChatInputStore } from '../chat-input.store';
5
+
6
+ const initContainer = initTestContainer(ChatInputStore, () => {});
7
+
8
+ describe('[ChatInputStore]', () => {
9
+ let container: Container;
10
+ let store: ChatInputStore;
11
+
12
+ beforeEach(() => {
13
+ container = initContainer();
14
+ store = container.get(ChatInputStore);
15
+ });
16
+
17
+ test('should have proper form state', async () => {
18
+ expect(store.formState.$.message).toBeDefined();
19
+ expect(store.formState.$.message.value).toBe('');
20
+ const validateResult = await store.formState.validate();
21
+ expect(validateResult.hasError).toBe(false);
22
+
23
+ store.formState.$.message.onChange('test');
24
+ expect(store.formState.$.message.value).toBe('test');
25
+ const validateResult2 = await store.formState.validate();
26
+ expect(validateResult2.hasError).toBe(false);
27
+
28
+ store.formState.$.message.onChange('a'.repeat(6000));
29
+ expect(store.formState.$.message.value).toBe('a'.repeat(6000));
30
+ const validateResult3 = await store.formState.validate();
31
+ expect(validateResult3.hasError).toBe(false);
32
+
33
+ store.formState.$.message.onChange('a'.repeat(6001));
34
+ expect(store.formState.$.message.value).toBe('a'.repeat(6001));
35
+ const validateResult4 = await store.formState.validate();
36
+ expect(validateResult4.hasError).toBe(true);
37
+ expect(store.formState.$.message.error).toBe('Message character max is 6000.');
38
+ });
39
+ });