@servicetitan/titan-chatbot-api 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/api-client/__mocks__/chatbot-api-client.mock.d.ts +11 -0
  3. package/dist/api-client/__mocks__/chatbot-api-client.mock.d.ts.map +1 -0
  4. package/dist/api-client/__mocks__/chatbot-api-client.mock.js +47 -0
  5. package/dist/api-client/__mocks__/chatbot-api-client.mock.js.map +1 -0
  6. package/dist/api-client/base/chatbot-api-client.d.ts +27 -0
  7. package/dist/api-client/base/chatbot-api-client.d.ts.map +1 -0
  8. package/dist/api-client/base/chatbot-api-client.js +10 -0
  9. package/dist/api-client/base/chatbot-api-client.js.map +1 -0
  10. package/dist/api-client/help-center/__tests__/converter-from-models.test.d.ts +2 -0
  11. package/dist/api-client/help-center/__tests__/converter-from-models.test.d.ts.map +1 -0
  12. package/dist/api-client/help-center/__tests__/converter-from-models.test.js +34 -0
  13. package/dist/api-client/help-center/__tests__/converter-from-models.test.js.map +1 -0
  14. package/dist/api-client/help-center/__tests__/converter-to-models.test.d.ts +2 -0
  15. package/dist/api-client/help-center/__tests__/converter-to-models.test.d.ts.map +1 -0
  16. package/dist/api-client/help-center/__tests__/converter-to-models.test.js +82 -0
  17. package/dist/api-client/help-center/__tests__/converter-to-models.test.js.map +1 -0
  18. package/dist/api-client/help-center/chatbot-api-client.d.ts +31 -0
  19. package/dist/api-client/help-center/chatbot-api-client.d.ts.map +1 -0
  20. package/dist/api-client/help-center/chatbot-api-client.js +90 -0
  21. package/dist/api-client/help-center/chatbot-api-client.js.map +1 -0
  22. package/dist/api-client/help-center/converter-from-models.d.ts +13 -0
  23. package/dist/api-client/help-center/converter-from-models.d.ts.map +1 -0
  24. package/dist/api-client/help-center/converter-from-models.js +113 -0
  25. package/dist/api-client/help-center/converter-from-models.js.map +1 -0
  26. package/dist/api-client/help-center/converter-to-models.d.ts +13 -0
  27. package/dist/api-client/help-center/converter-to-models.d.ts.map +1 -0
  28. package/dist/api-client/help-center/converter-to-models.js +95 -0
  29. package/dist/api-client/help-center/converter-to-models.js.map +1 -0
  30. package/dist/api-client/help-center/index.d.ts +2 -0
  31. package/dist/api-client/help-center/index.d.ts.map +1 -0
  32. package/dist/api-client/help-center/index.js +2 -0
  33. package/dist/api-client/help-center/index.js.map +1 -0
  34. package/dist/api-client/help-center/native-client.d.ts +1260 -0
  35. package/dist/api-client/help-center/native-client.d.ts.map +1 -0
  36. package/dist/api-client/help-center/native-client.js +6169 -0
  37. package/dist/api-client/help-center/native-client.js.map +1 -0
  38. package/dist/api-client/index.d.ts +8 -0
  39. package/dist/api-client/index.d.ts.map +1 -0
  40. package/dist/api-client/index.js +8 -0
  41. package/dist/api-client/index.js.map +1 -0
  42. package/dist/api-client/models/__mocks__/models.mock.d.ts +13 -0
  43. package/dist/api-client/models/__mocks__/models.mock.d.ts.map +1 -0
  44. package/dist/api-client/models/__mocks__/models.mock.js +114 -0
  45. package/dist/api-client/models/__mocks__/models.mock.js.map +1 -0
  46. package/dist/api-client/models/index.d.ts +22 -0
  47. package/dist/api-client/models/index.d.ts.map +1 -0
  48. package/dist/api-client/models/index.js +15 -0
  49. package/dist/api-client/models/index.js.map +1 -0
  50. package/dist/api-client/titan-chat/chatbot-api-client.d.ts +34 -0
  51. package/dist/api-client/titan-chat/chatbot-api-client.d.ts.map +1 -0
  52. package/dist/api-client/titan-chat/chatbot-api-client.js +72 -0
  53. package/dist/api-client/titan-chat/chatbot-api-client.js.map +1 -0
  54. package/dist/api-client/titan-chat/index.d.ts +2 -0
  55. package/dist/api-client/titan-chat/index.d.ts.map +1 -0
  56. package/dist/api-client/titan-chat/index.js +2 -0
  57. package/dist/api-client/titan-chat/index.js.map +1 -0
  58. package/dist/api-client/titan-chat/native-client.d.ts +225 -0
  59. package/dist/api-client/titan-chat/native-client.d.ts.map +1 -0
  60. package/dist/api-client/titan-chat/native-client.js +931 -0
  61. package/dist/api-client/titan-chat/native-client.js.map +1 -0
  62. package/dist/api-client/utils/model-utils.d.ts +4 -0
  63. package/dist/api-client/utils/model-utils.d.ts.map +1 -0
  64. package/dist/api-client/utils/model-utils.js +58 -0
  65. package/dist/api-client/utils/model-utils.js.map +1 -0
  66. package/dist/index.d.ts +6 -0
  67. package/dist/index.d.ts.map +1 -0
  68. package/dist/index.js +7 -0
  69. package/dist/index.js.map +1 -0
  70. package/dist/models/chatbot-customizations.d.ts +15 -0
  71. package/dist/models/chatbot-customizations.d.ts.map +1 -0
  72. package/dist/models/chatbot-customizations.js +2 -0
  73. package/dist/models/chatbot-customizations.js.map +1 -0
  74. package/dist/models/index.d.ts +2 -0
  75. package/dist/models/index.d.ts.map +1 -0
  76. package/dist/models/index.js +2 -0
  77. package/dist/models/index.js.map +1 -0
  78. package/dist/stores/__tests__/chatbot-ui-backend.store.test.d.ts +2 -0
  79. package/dist/stores/__tests__/chatbot-ui-backend.store.test.d.ts.map +1 -0
  80. package/dist/stores/__tests__/chatbot-ui-backend.store.test.js +341 -0
  81. package/dist/stores/__tests__/chatbot-ui-backend.store.test.js.map +1 -0
  82. package/dist/stores/__tests__/chatbot-ui.store.test.d.ts +2 -0
  83. package/dist/stores/__tests__/chatbot-ui.store.test.d.ts.map +1 -0
  84. package/dist/stores/__tests__/chatbot-ui.store.test.js +166 -0
  85. package/dist/stores/__tests__/chatbot-ui.store.test.js.map +1 -0
  86. package/dist/stores/__tests__/filter.store.test.d.ts +2 -0
  87. package/dist/stores/__tests__/filter.store.test.d.ts.map +1 -0
  88. package/dist/stores/__tests__/filter.store.test.js +316 -0
  89. package/dist/stores/__tests__/filter.store.test.js.map +1 -0
  90. package/dist/stores/__tests__/initialize.store.test.d.ts +2 -0
  91. package/dist/stores/__tests__/initialize.store.test.d.ts.map +1 -0
  92. package/dist/stores/__tests__/initialize.store.test.js +54 -0
  93. package/dist/stores/__tests__/initialize.store.test.js.map +1 -0
  94. package/dist/stores/chatbot-ui-backend.store.d.ts +61 -0
  95. package/dist/stores/chatbot-ui-backend.store.d.ts.map +1 -0
  96. package/dist/stores/chatbot-ui-backend.store.js +396 -0
  97. package/dist/stores/chatbot-ui-backend.store.js.map +1 -0
  98. package/dist/stores/chatbot-ui.store.d.ts +25 -0
  99. package/dist/stores/chatbot-ui.store.d.ts.map +1 -0
  100. package/dist/stores/chatbot-ui.store.js +87 -0
  101. package/dist/stores/chatbot-ui.store.js.map +1 -0
  102. package/dist/stores/filter.store.d.ts +30 -0
  103. package/dist/stores/filter.store.d.ts.map +1 -0
  104. package/dist/stores/filter.store.js +334 -0
  105. package/dist/stores/filter.store.js.map +1 -0
  106. package/dist/stores/index.d.ts +4 -0
  107. package/dist/stores/index.d.ts.map +1 -0
  108. package/dist/stores/index.js +4 -0
  109. package/dist/stores/index.js.map +1 -0
  110. package/dist/stores/initialize.store.d.ts +17 -0
  111. package/dist/stores/initialize.store.d.ts.map +1 -0
  112. package/dist/stores/initialize.store.js +98 -0
  113. package/dist/stores/initialize.store.js.map +1 -0
  114. package/dist/utils/__tests__/axios-utils.test.d.ts +2 -0
  115. package/dist/utils/__tests__/axios-utils.test.d.ts.map +1 -0
  116. package/dist/utils/__tests__/axios-utils.test.js +33 -0
  117. package/dist/utils/__tests__/axios-utils.test.js.map +1 -0
  118. package/dist/utils/axios-utils.d.ts +5 -0
  119. package/dist/utils/axios-utils.d.ts.map +1 -0
  120. package/dist/utils/axios-utils.js +23 -0
  121. package/dist/utils/axios-utils.js.map +1 -0
  122. package/dist/utils/test-utils.d.ts +5 -0
  123. package/dist/utils/test-utils.d.ts.map +1 -0
  124. package/dist/utils/test-utils.js +17 -0
  125. package/dist/utils/test-utils.js.map +1 -0
  126. package/package.json +45 -0
  127. package/src/api-client/__mocks__/chatbot-api-client.mock.ts +11 -0
  128. package/src/api-client/base/chatbot-api-client.ts +33 -0
  129. package/src/api-client/help-center/__tests__/converter-from-models.test.ts +41 -0
  130. package/src/api-client/help-center/__tests__/converter-to-models.test.ts +89 -0
  131. package/src/api-client/help-center/chatbot-api-client.ts +107 -0
  132. package/src/api-client/help-center/converter-from-models.ts +132 -0
  133. package/src/api-client/help-center/converter-to-models.ts +124 -0
  134. package/src/api-client/help-center/index.ts +1 -0
  135. package/src/api-client/help-center/native-client.ts +5662 -0
  136. package/src/api-client/index.ts +12 -0
  137. package/src/api-client/models/__mocks__/models.mock.ts +141 -0
  138. package/src/api-client/models/index.ts +48 -0
  139. package/src/api-client/titan-chat/chatbot-api-client.ts +77 -0
  140. package/src/api-client/titan-chat/index.ts +1 -0
  141. package/src/api-client/titan-chat/native-client.ts +826 -0
  142. package/src/api-client/utils/model-utils.ts +68 -0
  143. package/src/cypress.d.ts +10 -0
  144. package/src/index.ts +6 -0
  145. package/src/models/chatbot-customizations.ts +16 -0
  146. package/src/models/index.ts +1 -0
  147. package/src/stores/__tests__/chatbot-ui-backend.store.test.ts +426 -0
  148. package/src/stores/__tests__/chatbot-ui.store.test.ts +196 -0
  149. package/src/stores/__tests__/filter.store.test.ts +363 -0
  150. package/src/stores/__tests__/initialize.store.test.ts +73 -0
  151. package/src/stores/chatbot-ui-backend.store.ts +401 -0
  152. package/src/stores/chatbot-ui.store.ts +82 -0
  153. package/src/stores/filter.store.ts +250 -0
  154. package/src/stores/index.ts +12 -0
  155. package/src/stores/initialize.store.ts +62 -0
  156. package/src/utils/__tests__/axios-utils.test.ts +40 -0
  157. package/src/utils/axios-utils.ts +25 -0
  158. package/src/utils/test-utils.ts +22 -0
  159. package/tsconfig.json +19 -0
  160. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,196 @@
1
+ import { expect } from '@jest/globals';
2
+ import { ChatError, ChatUiEvent } from '@servicetitan/titan-chat-ui-common';
3
+ import { Models, ModelsMocks } from '../../api-client';
4
+ import { initTestContainer } from '../../utils/test-utils';
5
+ import { ChatbotUiEvent, ChatbotUiStore } from '../chatbot-ui.store';
6
+ import { FilterStore } from '../filter.store';
7
+
8
+ const mockEventEmitter = {
9
+ on: jest.fn(),
10
+ off: jest.fn(),
11
+ emit: jest.fn(),
12
+ listeners: jest.fn(),
13
+ listenerCount: jest.fn(),
14
+ };
15
+ jest.mock('events', () => ({
16
+ EventEmitter: class {
17
+ on = mockEventEmitter.on;
18
+ off = mockEventEmitter.off;
19
+ emit = mockEventEmitter.emit;
20
+ listeners = mockEventEmitter.listeners;
21
+ listenerCount = mockEventEmitter.listenerCount;
22
+ },
23
+ }));
24
+
25
+ const initContainer = initTestContainer(ChatbotUiStore, () => {});
26
+
27
+ describe('[ChatbotUiStore]', () => {
28
+ let store: ChatbotUiStore;
29
+
30
+ beforeEach(() => {
31
+ const container = initContainer();
32
+ store = container.get(ChatbotUiStore);
33
+ jest.useFakeTimers();
34
+ jest.setSystemTime(Date.parse('2021-01-01T00:00:00.000Z'));
35
+ });
36
+
37
+ afterEach(() => {
38
+ jest.useRealTimers();
39
+ jest.clearAllMocks();
40
+ mockEventEmitter.on.mockClear();
41
+ mockEventEmitter.off.mockClear();
42
+ mockEventEmitter.emit.mockClear();
43
+ mockEventEmitter.listeners.mockClear();
44
+ mockEventEmitter.listenerCount.mockClear();
45
+ });
46
+
47
+ test('should have default state', () => {
48
+ expect(store.filterStore).toBeInstanceOf(FilterStore);
49
+ });
50
+
51
+ test('should setFilters', () => {
52
+ const frontendModel = ModelsMocks.mockFrontendModel();
53
+ store.setFilters(frontendModel);
54
+
55
+ expect(store.filterStore.filters.length).toEqual(1);
56
+ expect(store.filterStore.filters[0].key).toEqual('Sources');
57
+ });
58
+
59
+ describe('with chat events', () => {
60
+ test('should sendSessionFeedback', async () => {
61
+ mockEmit();
62
+ const feedback = new Models.Feedback({
63
+ sessionId: 1,
64
+ rating: Models.FeedbackRatings.ThumbsDown,
65
+ description: 'description',
66
+ });
67
+ await store.sendSessionFeedback(feedback);
68
+ expect(mockEventEmitter.emit).toHaveBeenCalledWith(
69
+ ChatbotUiEvent.eventChatbotSessionFeedback,
70
+ expect.any(Function),
71
+ expect.any(Function),
72
+ feedback
73
+ );
74
+ });
75
+
76
+ test('should sendMessageFeedback', async () => {
77
+ mockEmit();
78
+ const feedback = new Models.Feedback({
79
+ sessionId: 1,
80
+ messageId: 1,
81
+ linkUrl: 'https://example.com/feedback',
82
+ options: [Models.FeedbackOptions.Unclear],
83
+ rating: Models.FeedbackRatings.ThumbsUp,
84
+ description: 'description',
85
+ });
86
+ const message: Models.IBotMessage = {
87
+ id: 1,
88
+ sessionId: 1,
89
+ answer: 'message text',
90
+ guardFlag: 'N',
91
+ isGuardrailed: false,
92
+ };
93
+ await store.sendMessageFeedback(message, feedback);
94
+ expect(mockEventEmitter.emit).toHaveBeenCalledWith(
95
+ ChatbotUiEvent.eventChatbotMessageFeedback,
96
+ expect.any(Function),
97
+ expect.any(Function),
98
+ feedback
99
+ );
100
+ expect(feedback.messageId).toBe(message.id);
101
+ });
102
+
103
+ test('should recover', async () => {
104
+ mockEmit();
105
+ const spyRecoverCustomization = jest.fn().mockResolvedValue(undefined);
106
+ store.customizations.error = {
107
+ recover: spyRecoverCustomization,
108
+ };
109
+ store.setError('Test error');
110
+
111
+ await store.recover();
112
+
113
+ expect(spyRecoverCustomization).toHaveBeenCalledWith(store.error);
114
+ expect(mockEventEmitter.emit).not.toHaveBeenCalled();
115
+ await checkScrollTriggered();
116
+ });
117
+
118
+ test('should recover without customizations', async () => {
119
+ mockEmit();
120
+ const spyRecoverCustomization = jest.fn().mockResolvedValue(undefined);
121
+ store.setError('Test error');
122
+
123
+ await store.recover();
124
+
125
+ expect(spyRecoverCustomization).not.toHaveBeenCalled();
126
+ expect(mockEventEmitter.emit).toHaveBeenCalledWith(
127
+ ChatUiEvent.eventRecover,
128
+ expect.any(Function),
129
+ expect.any(Function),
130
+ new ChatError('Test error')
131
+ );
132
+ await checkScrollTriggered();
133
+ });
134
+
135
+ test('should sendMessageText without message', async () => {
136
+ mockEmit();
137
+ await store.sendMessageText('');
138
+ expect(mockEventEmitter.emit).not.toHaveBeenCalledWith(
139
+ ChatUiEvent.eventMessageSend,
140
+ expect.any(Function),
141
+ expect.any(Function),
142
+ expect.objectContaining({
143
+ type: 'message',
144
+ message: '',
145
+ }),
146
+ store.filterStore.export()
147
+ );
148
+ expect(mockEventEmitter.emit).toHaveBeenCalledWith(
149
+ ChatUiEvent.eventTimerRestart,
150
+ expect.any(Function),
151
+ expect.any(Function)
152
+ );
153
+ await checkScrollTriggered(false);
154
+ });
155
+
156
+ test('should sendMessageText', async () => {
157
+ mockEmit();
158
+ const messageText = 'Hello, world!';
159
+ await store.sendMessageText(messageText);
160
+
161
+ expect(mockEventEmitter.emit).toHaveBeenCalledWith(
162
+ ChatUiEvent.eventMessageSend,
163
+ expect.any(Function),
164
+ expect.any(Function),
165
+ expect.objectContaining({
166
+ type: 'message',
167
+ message: messageText,
168
+ }),
169
+ store.filterStore.export()
170
+ );
171
+ expect(mockEventEmitter.emit).toHaveBeenCalledWith(
172
+ ChatUiEvent.eventTimerRestart,
173
+ expect.any(Function),
174
+ expect.any(Function)
175
+ );
176
+ await checkScrollTriggered();
177
+ });
178
+
179
+ function mockEmit() {
180
+ mockEventEmitter.listeners.mockImplementation(() => ['1']);
181
+ mockEventEmitter.listenerCount.mockReturnValue(1);
182
+ mockEventEmitter.emit.mockImplementation(
183
+ async (eventName: string, resolve: () => void) => {
184
+ await Promise.resolve();
185
+ resolve();
186
+ }
187
+ );
188
+ }
189
+
190
+ async function checkScrollTriggered(triggered = true) {
191
+ const counter = store.scrollCounter;
192
+ await jest.runOnlyPendingTimersAsync();
193
+ expect(counter < store.scrollCounter).toBe(triggered);
194
+ }
195
+ });
196
+ });
@@ -0,0 +1,363 @@
1
+ import { expect } from '@jest/globals';
2
+ import { Models, ModelsMocks } from '../../api-client';
3
+ import { FilterStore } from '../filter.store';
4
+
5
+ describe('[FilterStore]', () => {
6
+ let store: FilterStore;
7
+
8
+ const validateFilters = (key: string, subOptionsLength: number) => {
9
+ const filter = store.filters.find(f => f.key === key);
10
+ expect(filter?.subOptions?.length ?? 0).toEqual(subOptionsLength);
11
+ };
12
+
13
+ const validateSelected = (key: string, expected?: string[]) => {
14
+ if (!expected) {
15
+ expect(store.selectedOptions[key]).toBeUndefined();
16
+ }
17
+ expect(store.selectedOptions[key]).toEqual(expected);
18
+ };
19
+
20
+ beforeEach(() => {
21
+ store = new FilterStore();
22
+ });
23
+
24
+ afterEach(() => {
25
+ jest.clearAllMocks();
26
+ });
27
+
28
+ test('should validate defaults', () => {
29
+ expect(Object.keys(store.filters).length).toEqual(0);
30
+ expect(Object.keys(store.selectedOptions).length).toEqual(0);
31
+ });
32
+
33
+ test('Should initialize properly', () => {
34
+ store.initFilters(ModelsMocks.mockFrontendModel());
35
+ // We initialized 1 root filter (Sources) with 2 subgroups
36
+ expect(store.filters.length).toEqual(1);
37
+ expect(store.filters[0].key).toEqual('Sources');
38
+ expect(store.filters[0].displayName).toEqual('Sources');
39
+ expect(store.filters[0].type).toEqual(Models.OptionType.Group);
40
+ expect(store.filters[0].subOptions!.length).toEqual(2);
41
+
42
+ const sourceKb = store.filters[0].subOptions![0];
43
+ expect(sourceKb.key).toEqual('KnowledgeBase');
44
+ expect(sourceKb.displayName).toEqual('Knowledge Base');
45
+ expect(sourceKb.type).toEqual(Models.OptionType.Selectable);
46
+ expect(sourceKb.subOptions!.length).toEqual(2);
47
+
48
+ const contentTypes = sourceKb.subOptions![0];
49
+ expect(contentTypes.key).toEqual('ContentTypes');
50
+ expect(contentTypes.displayName).toEqual('Content Types');
51
+ expect(contentTypes.type).toEqual(Models.OptionType.Group);
52
+ expect(contentTypes.subOptions!.length).toEqual(3);
53
+
54
+ const productAreas = sourceKb.subOptions![1];
55
+ expect(productAreas.key).toEqual('ProductAreas');
56
+ expect(productAreas.displayName).toEqual('Product Areas');
57
+ expect(productAreas.type).toEqual(Models.OptionType.Group);
58
+ expect(productAreas.subOptions!.length).toEqual(3);
59
+ });
60
+
61
+ test('Should fail when selecting option from non-existing filter', () => {
62
+ store.initFilters(ModelsMocks.mockFrontendModel());
63
+ expect(() => store.selectOption('non-existing', 'subFilter1')).toThrow(
64
+ 'Filter with key "non-existing" does not exist.'
65
+ );
66
+ });
67
+
68
+ test('Should fail when selecting non-existing option from existing filter', () => {
69
+ store.initFilters(ModelsMocks.mockFrontendModel());
70
+ expect(() => store.selectOption('Sources', 'non-existing-option')).toThrow(
71
+ 'Option "non-existing-option" does not exist in the filter "Sources".'
72
+ );
73
+ });
74
+
75
+ test('Should select value', () => {
76
+ store.initFilters(ModelsMocks.mockFrontendModel());
77
+
78
+ expect(store.filters.length).toEqual(1);
79
+
80
+ // Source filter: select Jarvis option: 1 filter + 1 subfilter
81
+ store.selectOption('Sources', 'Jarvis');
82
+ validateSelected('Sources', ['Jarvis']);
83
+ expect(store.filters.length).toEqual(2);
84
+ expect(store.filters[0].key).toEqual('Sources');
85
+ expect(store.filters[1].key).toEqual('ContentTypes');
86
+ expect(store.filters[1].subOptions!.length).toEqual(3);
87
+
88
+ // Source filter: select knowledgeBase option (options with the same key are merged)
89
+ store.selectOption('Sources', 'KnowledgeBase');
90
+ validateSelected('Sources', ['Jarvis', 'KnowledgeBase']);
91
+ expect(store.filters.length).toEqual(3);
92
+ expect(store.filters[0].key).toEqual('Sources');
93
+ expect(store.filters[1].key).toEqual('ContentTypes');
94
+ expect(store.filters[1].subOptions!.map(o => o.key)).toEqual([
95
+ 'kbReleaseNotes',
96
+ 'kbFaq',
97
+ 'kbHowTo',
98
+ 'xxx',
99
+ 'yyy',
100
+ ]);
101
+
102
+ store.selectOption('ContentTypes', 'kbReleaseNotes');
103
+ expect(store.filters.length).toEqual(3);
104
+ validateSelected('Sources', ['Jarvis', 'KnowledgeBase']);
105
+ validateSelected('ContentTypes', ['kbReleaseNotes']);
106
+ validateSelected('ProductAreas');
107
+
108
+ store.selectOption('ProductAreas', 'Call Booking');
109
+ expect(store.filters.length).toEqual(3);
110
+ validateSelected('Sources', ['Jarvis', 'KnowledgeBase']);
111
+ validateSelected('ContentTypes', ['kbReleaseNotes']);
112
+ validateSelected('ProductAreas', ['Call Booking']);
113
+ });
114
+
115
+ test('Should deselect value', () => {
116
+ store.initFilters(ModelsMocks.mockFrontendModel());
117
+ store.selectOption('Sources', 'KnowledgeBase');
118
+ store.selectOption('Sources', 'Jarvis');
119
+ store.selectOption('ContentTypes', 'kbReleaseNotes');
120
+ store.selectOption('ContentTypes', 'yyy');
121
+ store.selectOption('ContentTypes', 'kbHowTo');
122
+ store.selectOption('ProductAreas', 'Marketing');
123
+ store.selectOption('ProductAreas', 'Marketing Pro');
124
+
125
+ expect(store.filters.length).toEqual(3);
126
+ validateFilters('Sources', 2);
127
+ validateFilters('ContentTypes', 5);
128
+ validateFilters('ProductAreas', 3);
129
+ validateSelected('Sources', ['KnowledgeBase', 'Jarvis']);
130
+ validateSelected('ContentTypes', ['kbReleaseNotes', 'yyy', 'kbHowTo']);
131
+ validateSelected('ProductAreas', ['Marketing', 'Marketing Pro']);
132
+
133
+ store.deselectOption('Sources', 'Jarvis');
134
+ expect(store.filters.length).toEqual(3);
135
+ validateFilters('Sources', 2);
136
+ validateFilters('ContentTypes', 3);
137
+ validateFilters('ProductAreas', 3);
138
+ validateSelected('Sources', ['KnowledgeBase']);
139
+ validateSelected('ContentTypes', ['kbReleaseNotes', 'kbHowTo']);
140
+ validateSelected('ProductAreas', ['Marketing', 'Marketing Pro']);
141
+
142
+ store.deselectOption('ContentTypes', 'kbHowTo');
143
+ expect(store.filters.length).toEqual(3);
144
+ validateFilters('Sources', 2);
145
+ validateFilters('ContentTypes', 3);
146
+ validateFilters('ProductAreas', 3);
147
+ validateSelected('Sources', ['KnowledgeBase']);
148
+ validateSelected('ContentTypes', ['kbReleaseNotes']);
149
+ validateSelected('ProductAreas', ['Marketing', 'Marketing Pro']);
150
+
151
+ store.deselectOption('ContentTypes', 'kbReleaseNotes');
152
+ expect(store.filters.length).toEqual(3);
153
+ validateFilters('Sources', 2);
154
+ validateFilters('ContentTypes', 3);
155
+ validateFilters('ProductAreas', 3);
156
+ validateSelected('Sources', ['KnowledgeBase']);
157
+ validateSelected('ContentTypes', []);
158
+ validateSelected('ProductAreas', ['Marketing', 'Marketing Pro']);
159
+
160
+ store.deselectOption('ProductAreas', 'Marketing Pro');
161
+ expect(store.filters.length).toEqual(3);
162
+ validateFilters('Sources', 2);
163
+ validateFilters('ContentTypes', 3);
164
+ validateFilters('ProductAreas', 3);
165
+ validateSelected('Sources', ['KnowledgeBase']);
166
+ validateSelected('ContentTypes', []);
167
+ validateSelected('ProductAreas', ['Marketing']);
168
+
169
+ store.deselectOption('Sources', 'KnowledgeBase');
170
+ expect(store.filters.length).toEqual(1);
171
+ validateFilters('Sources', 2);
172
+ validateFilters('ContentTypes', 0);
173
+ validateFilters('ProductAreas', 0);
174
+ validateSelected('Sources', []);
175
+ validateSelected('ContentTypes', []);
176
+ validateSelected('ProductAreas', []);
177
+ });
178
+
179
+ test('Should deselect value 2', () => {
180
+ store.initFilters(ModelsMocks.mockFrontendModel());
181
+
182
+ store.selectOption('Sources', 'KnowledgeBase');
183
+ validateFilters('Sources', 2);
184
+ validateFilters('ContentTypes', 3);
185
+ validateFilters('ProductAreas', 3);
186
+
187
+ store.selectOption('Sources', 'Jarvis');
188
+ expect(store.filters.length).toEqual(3);
189
+ validateFilters('Sources', 2);
190
+ validateFilters('ContentTypes', 5);
191
+ validateFilters('ProductAreas', 3);
192
+
193
+ store.deselectOption('Sources', 'KnowledgeBase');
194
+ validateFilters('Sources', 2);
195
+ validateFilters('ContentTypes', 3);
196
+ validateFilters('ProductAreas', 0);
197
+
198
+ store.selectOption('Sources', 'KnowledgeBase');
199
+ validateFilters('Sources', 2);
200
+ validateFilters('ContentTypes', 5);
201
+ validateFilters('ProductAreas', 3);
202
+ });
203
+
204
+ test('Should select all', () => {
205
+ store.initFilters(ModelsMocks.mockFrontendModel());
206
+ store.selectAll('Sources');
207
+ store.selectAll('ProductAreas');
208
+ store.selectAll('ContentTypes');
209
+
210
+ validateSelected('Sources', ['KnowledgeBase', 'Jarvis']);
211
+ validateSelected('ContentTypes', ['kbReleaseNotes', 'kbFaq', 'kbHowTo', 'xxx', 'yyy']);
212
+ validateSelected('ProductAreas', ['Call Booking', 'Marketing', 'Marketing Pro']);
213
+ validateFilters('Sources', 2);
214
+ validateFilters('ContentTypes', 5);
215
+ validateFilters('ProductAreas', 3);
216
+ });
217
+
218
+ test('Should deselect all', () => {
219
+ store.initFilters(ModelsMocks.mockFrontendModel());
220
+ store.selectAll('Sources');
221
+ store.selectAll('ContentTypes');
222
+ store.selectAll('ProductAreas');
223
+
224
+ store.deselectAll('ContentTypes');
225
+ store.deselectAll('ProductAreas');
226
+ store.deselectAll('Sources');
227
+
228
+ validateSelected('Sources', []);
229
+ validateSelected('ContentTypes', []);
230
+ validateSelected('ProductAreas', []);
231
+ validateFilters('Sources', 2);
232
+ validateFilters('ContentTypes', 0);
233
+ validateFilters('ProductAreas', 0);
234
+ });
235
+
236
+ test('should getFilterLabel', () => {
237
+ const filterKey = 'contentTypes';
238
+ const label = store.getFilterLabel(filterKey);
239
+ expect(label).toEqual('Content Types');
240
+ });
241
+
242
+ test('should export', () => {
243
+ store.initFilters(ModelsMocks.mockFrontendModel());
244
+ store.selectOption('Sources', 'KnowledgeBase');
245
+ store.selectOption('Sources', 'Jarvis');
246
+ store.selectOption('ContentTypes', 'kbReleaseNotes');
247
+ store.selectOption('ContentTypes', 'yyy');
248
+ store.selectOption('ContentTypes', 'kbHowTo');
249
+ store.selectOption('ProductAreas', 'Marketing');
250
+ store.selectOption('ProductAreas', 'Marketing Pro');
251
+
252
+ const exported = store.export();
253
+ expect(exported).toEqual({
254
+ subOptions: {
255
+ Sources: {
256
+ subOptions: {
257
+ KnowledgeBase: {
258
+ subOptions: {
259
+ ContentTypes: {
260
+ values: ['kbReleaseNotes', 'kbHowTo'],
261
+ },
262
+ ProductAreas: {
263
+ values: ['Marketing', 'Marketing Pro'],
264
+ },
265
+ },
266
+ },
267
+ Jarvis: {
268
+ subOptions: {
269
+ ContentTypes: {
270
+ values: ['kbReleaseNotes', 'yyy'],
271
+ },
272
+ },
273
+ },
274
+ },
275
+ },
276
+ },
277
+ });
278
+ });
279
+
280
+ test('should export 2', () => {
281
+ store.initFilters(ModelsMocks.mockFrontendModel());
282
+ const exported = store.export();
283
+ expect(exported).toBeUndefined();
284
+ });
285
+
286
+ test('should export 3', () => {
287
+ store.initFilters(ModelsMocks.mockFrontendModel());
288
+ store.selectOption('Sources', 'KnowledgeBase');
289
+ const exported = store.export();
290
+ expect(exported).toEqual({
291
+ subOptions: {
292
+ Sources: {
293
+ subOptions: {
294
+ KnowledgeBase: {
295
+ subOptions: {},
296
+ },
297
+ },
298
+ },
299
+ },
300
+ });
301
+ });
302
+
303
+ test('should export 4', () => {
304
+ store.initFilters(ModelsMocks.mockFrontendModel());
305
+ store.selectOption('Sources', 'KnowledgeBase');
306
+ store.selectOption('ProductAreas', 'Marketing');
307
+ store.deselectOption('ProductAreas', 'Marketing');
308
+ store.selectOption('ProductAreas', 'Marketing');
309
+
310
+ const exported = store.export();
311
+ expect(exported).toEqual({
312
+ subOptions: {
313
+ Sources: {
314
+ subOptions: {
315
+ KnowledgeBase: {
316
+ subOptions: {
317
+ ProductAreas: {
318
+ values: ['Marketing'],
319
+ },
320
+ },
321
+ },
322
+ },
323
+ },
324
+ },
325
+ });
326
+ });
327
+
328
+ test('should export 5', () => {
329
+ store.initFilters(ModelsMocks.mockFrontendModel());
330
+ store.selectOption('Sources', 'KnowledgeBase');
331
+ store.selectOption('Sources', 'Jarvis');
332
+ store.selectOption('ContentTypes', 'kbReleaseNotes');
333
+
334
+ const exported = store.export();
335
+ expect(exported).toEqual({
336
+ subOptions: {
337
+ Sources: {
338
+ subOptions: {
339
+ KnowledgeBase: {
340
+ subOptions: {
341
+ ContentTypes: {
342
+ values: ['kbReleaseNotes'],
343
+ },
344
+ },
345
+ },
346
+ Jarvis: {
347
+ subOptions: {
348
+ ContentTypes: {
349
+ values: ['kbReleaseNotes'],
350
+ },
351
+ },
352
+ },
353
+ },
354
+ },
355
+ },
356
+ });
357
+ });
358
+
359
+ test('should export 6', () => {
360
+ const exported = store.export();
361
+ expect(exported).toBeUndefined();
362
+ });
363
+ });
@@ -0,0 +1,73 @@
1
+ import { expect } from '@jest/globals';
2
+ import { Container } from '@servicetitan/react-ioc';
3
+ import { initTestContainer } from '../../utils/test-utils';
4
+ import { InitializeStore, InitializeStoreStatus } from '../initialize.store';
5
+
6
+ const initContainer = initTestContainer(InitializeStore, () => {});
7
+
8
+ describe('[InitializeStore]', () => {
9
+ let store: InitializeStore;
10
+ let container: Container;
11
+
12
+ beforeEach(() => {
13
+ container = initContainer();
14
+ store = container.get(InitializeStore);
15
+ });
16
+
17
+ afterEach(() => {
18
+ jest.clearAllMocks();
19
+ });
20
+
21
+ test('should have correct default values', () => {
22
+ expect(store.isInitialized).toBe(false);
23
+ expect(store.initializeStatus).toBe(InitializeStoreStatus.None);
24
+ });
25
+
26
+ test('should initialize correctly', async () => {
27
+ const initializePromise = store.initialize();
28
+ expect(store.isInitialized).toBe(false);
29
+ expect(store.initializeStatus).toBe(InitializeStoreStatus.Pending);
30
+
31
+ await initializePromise;
32
+
33
+ expect(store.isInitialized).toBe(true);
34
+ expect(store.initializeStatus).toBe(InitializeStoreStatus.Success);
35
+ });
36
+
37
+ test('should create only one promise', async () => {
38
+ const spy = jest.spyOn(store as any, 'initializeInternal');
39
+
40
+ const initializePromise = store.initialize();
41
+ const initializePromise2 = store.initialize();
42
+
43
+ await initializePromise;
44
+ await initializePromise2;
45
+
46
+ expect(store.isInitialized).toBe(true);
47
+ expect(store.initializeStatus).toBe(InitializeStoreStatus.Success);
48
+ expect(spy).toHaveBeenCalledTimes(1); // Should be called once
49
+ });
50
+
51
+ test('should initialize correctly with force', async () => {
52
+ const spy = jest.spyOn(store as any, 'initializeInternal');
53
+ const initializePromise = store.initialize();
54
+ expect(store.isInitialized).toBe(false);
55
+ expect(store.initializeStatus).toBe(InitializeStoreStatus.Pending);
56
+
57
+ await initializePromise;
58
+
59
+ expect(store.isInitialized).toBe(true);
60
+ expect(store.initializeStatus).toBe(InitializeStoreStatus.Success);
61
+
62
+ const initializePromise2 = store.initialize(true);
63
+ expect(store.isInitialized).toBe(false);
64
+ expect(store.initializeStatus).toBe(InitializeStoreStatus.Pending);
65
+
66
+ await initializePromise2;
67
+
68
+ expect(store.isInitialized).toBe(true);
69
+ expect(store.initializeStatus).toBe(InitializeStoreStatus.Success);
70
+
71
+ expect(spy).toHaveBeenCalledTimes(2); // Should be called twice because of reset
72
+ });
73
+ });