@truedat/ai 8.5.6 → 8.5.8

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@truedat/ai",
3
- "version": "8.5.6",
3
+ "version": "8.5.8",
4
4
  "description": "Truedat Web Artificial Intelligence package",
5
5
  "sideEffects": false,
6
6
  "module": "src/index.js",
@@ -50,7 +50,7 @@
50
50
  "@testing-library/jest-dom": "^6.6.3",
51
51
  "@testing-library/react": "^16.3.0",
52
52
  "@testing-library/user-event": "^14.6.1",
53
- "@truedat/test": "8.5.6",
53
+ "@truedat/test": "8.5.8",
54
54
  "identity-obj-proxy": "^3.0.0",
55
55
  "jest": "^29.7.0",
56
56
  "redux-saga-test-plan": "^4.0.6"
@@ -80,5 +80,5 @@
80
80
  "semantic-ui-react": "^3.0.0-beta.2",
81
81
  "swr": "^2.3.3"
82
82
  },
83
- "gitHead": "41e5e6138f5622558bae4151e720c040c4581162"
83
+ "gitHead": "61ad9443b3d822d30dcfa977f5fad3494b3ed5b4"
84
84
  }
@@ -0,0 +1,128 @@
1
+ import { useEffect, useState, useCallback } from "react";
2
+ import { useParams, useLocation } from "react-router";
3
+ import PropTypes from "prop-types";
4
+ import { FormattedMessage } from "react-intl";
5
+ import { Icon, Loader, Message } from "semantic-ui-react";
6
+ import { ConceptSelectorTable } from "@truedat/bg/concepts/relations/components/ConceptSelector";
7
+ import useAssistantSocket from "./assistant/hooks/useAssistantSocket";
8
+ import ThinkingOutLoud from "./ThinkingOutLoud";
9
+ import { useAgentLayerRun } from "../hooks/useAgentLayerRun";
10
+ import {
11
+ reasonColumnDefinition,
12
+ similarityColumnDefinition,
13
+ } from "./structureSuggestionColumns";
14
+
15
+ const extraColumns = [similarityColumnDefinition, reasonColumnDefinition];
16
+
17
+ export const ConceptSuggestions = ({ selectedConcept, handleConceptSelected }) => {
18
+ const { id } = useParams();
19
+ const { state } = useLocation();
20
+ const prompt = state?.prompt;
21
+
22
+ const { trigger: fetchSuggestions, data: runData } = useAgentLayerRun();
23
+
24
+ const [logItems, setLogItems] = useState([]);
25
+ const [concepts, setConcepts] = useState(null);
26
+ const [hasError, setHasError] = useState(false);
27
+
28
+ const agentStateId = runData?.data?.data?.agent_state_id
29
+ ? String(runData.data.data.agent_state_id)
30
+ : null;
31
+
32
+ useEffect(() => {
33
+ if (id) {
34
+ fetchSuggestions({
35
+ workflow: "suggest_structure_concept_links",
36
+ params: { structure_id: id, prompt },
37
+ });
38
+ }
39
+ }, [id, prompt]);
40
+
41
+ const handleServerMessage = useCallback((event, payload) => {
42
+ const priority = payload?.priority ?? null;
43
+ const source = payload?.source ?? null;
44
+
45
+ if (event === "stream" && Number(priority) === 0) {
46
+ const content = payload?.content;
47
+ let parsed;
48
+ try {
49
+ const raw = typeof content === "string" ? JSON.parse(content) : content;
50
+ parsed = Array.isArray(raw) ? raw : (raw?.result ?? []);
51
+ } catch {
52
+ parsed = [];
53
+ }
54
+ setConcepts(parsed);
55
+ return;
56
+ }
57
+
58
+ if (event === "error" && Number(priority) !== 2) {
59
+ setHasError(true);
60
+ return;
61
+ }
62
+
63
+ if (event === "log" || (event === "error" && Number(priority) === 2)) {
64
+ const content =
65
+ event === "log"
66
+ ? (() => {
67
+ const log = payload?.content;
68
+ return log && typeof log === "object"
69
+ ? log.message ?? log.level ?? JSON.stringify(log)
70
+ : String(log ?? "");
71
+ })()
72
+ : (() => {
73
+ const err = payload?.error;
74
+ return err && typeof err === "object"
75
+ ? err.message ?? err.reason ?? JSON.stringify(err)
76
+ : String(err ?? "");
77
+ })();
78
+
79
+ setLogItems((prev) => [
80
+ ...prev,
81
+ { eventType: event, content: content || " ", priority, source },
82
+ ]);
83
+ }
84
+ }, []);
85
+
86
+ useAssistantSocket(!agentStateId, {
87
+ conversationId: agentStateId,
88
+ onServerMessage: handleServerMessage,
89
+ });
90
+
91
+ if (hasError) {
92
+ return (
93
+ <Message icon negative>
94
+ <Icon name="warning circle" />
95
+ <Message.Content>
96
+ <Message.Header>
97
+ <FormattedMessage id="concepts.suggestions.error.header" />
98
+ </Message.Header>
99
+ <FormattedMessage id="concepts.suggestions.error.body" />
100
+ </Message.Content>
101
+ </Message>
102
+ );
103
+ }
104
+
105
+ if (concepts !== null) {
106
+ return (
107
+ <ConceptSelectorTable
108
+ concepts={concepts}
109
+ selectedConcept={selectedConcept}
110
+ handleConceptSelected={handleConceptSelected}
111
+ extraColumns={extraColumns}
112
+ />
113
+ );
114
+ }
115
+
116
+ if (logItems.length === 0) {
117
+ return <Loader active inline="centered" />;
118
+ }
119
+
120
+ return <ThinkingOutLoud items={logItems} active={true} />;
121
+ };
122
+
123
+ ConceptSuggestions.propTypes = {
124
+ selectedConcept: PropTypes.object,
125
+ handleConceptSelected: PropTypes.func,
126
+ };
127
+
128
+ export default ConceptSuggestions;
@@ -0,0 +1,61 @@
1
+ import { useState } from "react";
2
+ import PropTypes from "prop-types";
3
+ import { Button, Form, Popup } from "semantic-ui-react";
4
+ import { FormattedMessage, useIntl } from "react-intl";
5
+
6
+ export const SuggestLinkButton = ({ onSubmit, floated }) => {
7
+ const { formatMessage } = useIntl();
8
+ const [open, setOpen] = useState(false);
9
+ const [prompt, setPrompt] = useState("");
10
+
11
+ const handleSubmit = () => {
12
+ setOpen(false);
13
+ onSubmit(prompt);
14
+ };
15
+
16
+ return (
17
+ <Popup
18
+ on="click"
19
+ open={open}
20
+ onOpen={() => setOpen(true)}
21
+ onClose={() => setOpen(false)}
22
+ content={
23
+ <Form style={{ minWidth: "300px" }}>
24
+ <Form.Field>
25
+ <label>{formatMessage({ id: "links.suggest.prompt.label" })}</label>
26
+ <Form.Input
27
+ value={prompt}
28
+ onChange={(_, { value }) => setPrompt(value)}
29
+ placeholder={formatMessage({
30
+ id: "links.suggest.prompt.placeholder",
31
+ })}
32
+ />
33
+ </Form.Field>
34
+ <Button
35
+ primary
36
+ content={formatMessage({ id: "links.suggest.submit" })}
37
+ onClick={handleSubmit}
38
+ />
39
+ </Form>
40
+ }
41
+ position="bottom center"
42
+ trigger={
43
+ <Button
44
+ className="td-icon-text-control"
45
+ secondary
46
+ active={open}
47
+ floated={floated}
48
+ icon="lightbulb outline"
49
+ content={<FormattedMessage id="links.actions.suggest" />}
50
+ />
51
+ }
52
+ />
53
+ );
54
+ };
55
+
56
+ SuggestLinkButton.propTypes = {
57
+ onSubmit: PropTypes.func.isRequired,
58
+ floated: PropTypes.string,
59
+ };
60
+
61
+ export default SuggestLinkButton;
@@ -0,0 +1,268 @@
1
+ import { act, waitFor } from "@testing-library/react";
2
+ import { useParams, useLocation } from "react-router";
3
+ import { render } from "@truedat/test/render";
4
+ import { ConceptSelectorTable } from "@truedat/bg/concepts/relations/components/ConceptSelector";
5
+ import useAssistantSocket from "../assistant/hooks/useAssistantSocket";
6
+ import ThinkingOutLoud from "../ThinkingOutLoud";
7
+ import { useAgentLayerRun } from "../../hooks/useAgentLayerRun";
8
+ import ConceptSuggestions from "../ConceptSuggestions";
9
+
10
+ jest.mock("react-router", () => ({
11
+ ...jest.requireActual("react-router"),
12
+ useParams: jest.fn(),
13
+ useLocation: jest.fn(),
14
+ }));
15
+
16
+ jest.mock("@truedat/bg/concepts/relations/components/ConceptSelector", () => ({
17
+ ConceptSelectorTable: jest.fn(() => <div>MockConceptSelectorTable</div>),
18
+ }));
19
+
20
+ jest.mock("../ThinkingOutLoud", () => ({
21
+ __esModule: true,
22
+ default: jest.fn(() => <div>MockThinkingOutLoud</div>),
23
+ getSourceLabel: jest.fn((s) => s),
24
+ }));
25
+
26
+ jest.mock("../assistant/hooks/useAssistantSocket", () => jest.fn());
27
+
28
+ jest.mock("../../hooks/useAgentLayerRun", () => ({
29
+ useAgentLayerRun: jest.fn(),
30
+ }));
31
+
32
+ describe("ConceptSuggestions", () => {
33
+ const mockFetchSuggestions = jest.fn();
34
+
35
+ beforeEach(() => {
36
+ jest.clearAllMocks();
37
+
38
+ useParams.mockReturnValue({ id: "42" });
39
+ useLocation.mockReturnValue({ state: { prompt: "find concepts" } });
40
+ useAssistantSocket.mockImplementation(() => {});
41
+
42
+ useAgentLayerRun.mockReturnValue({
43
+ trigger: mockFetchSuggestions,
44
+ data: null,
45
+ });
46
+ });
47
+
48
+ it("calls fetchSuggestions on mount with structure_id and prompt", async () => {
49
+ render(
50
+ <ConceptSuggestions
51
+ selectedConcept={null}
52
+ handleConceptSelected={jest.fn()}
53
+ />
54
+ );
55
+
56
+ await waitFor(() =>
57
+ expect(mockFetchSuggestions).toHaveBeenCalledWith({
58
+ workflow: "suggest_structure_concept_links",
59
+ params: { structure_id: "42", prompt: "find concepts" },
60
+ })
61
+ );
62
+ });
63
+
64
+ it("calls fetchSuggestions without prompt when prompt is missing", async () => {
65
+ useLocation.mockReturnValue({ state: null });
66
+
67
+ render(
68
+ <ConceptSuggestions
69
+ selectedConcept={null}
70
+ handleConceptSelected={jest.fn()}
71
+ />
72
+ );
73
+
74
+ await waitFor(() =>
75
+ expect(mockFetchSuggestions).toHaveBeenCalledWith({
76
+ workflow: "suggest_structure_concept_links",
77
+ params: { structure_id: "42", prompt: undefined },
78
+ })
79
+ );
80
+ });
81
+
82
+ it("shows spinner while waiting for first log item", () => {
83
+ useAgentLayerRun.mockReturnValue({
84
+ trigger: mockFetchSuggestions,
85
+ data: { data: { data: { agent_state_id: "state-1" } } },
86
+ });
87
+
88
+ const rendered = render(
89
+ <ConceptSuggestions
90
+ selectedConcept={null}
91
+ handleConceptSelected={jest.fn()}
92
+ />
93
+ );
94
+
95
+ expect(rendered.queryByText(/mockthinkingoutloud/i)).not.toBeInTheDocument();
96
+ expect(rendered.queryByText(/mockconceptselectortable/i)).not.toBeInTheDocument();
97
+ });
98
+
99
+ it("shows ThinkingOutLoud once log items arrive", async () => {
100
+ let capturedOnServerMessage;
101
+
102
+ useAssistantSocket.mockImplementation((_, { onServerMessage }) => {
103
+ capturedOnServerMessage = onServerMessage;
104
+ });
105
+
106
+ useAgentLayerRun.mockReturnValue({
107
+ trigger: mockFetchSuggestions,
108
+ data: { data: { data: { agent_state_id: "state-1" } } },
109
+ });
110
+
111
+ const rendered = render(
112
+ <ConceptSuggestions
113
+ selectedConcept={null}
114
+ handleConceptSelected={jest.fn()}
115
+ />
116
+ );
117
+
118
+ act(() => {
119
+ capturedOnServerMessage("log", {
120
+ priority: 1,
121
+ source: "agent_x",
122
+ content: "Processing...",
123
+ });
124
+ });
125
+
126
+ await waitFor(() =>
127
+ expect(rendered.queryByText(/mockthinkingoutloud/i)).toBeInTheDocument()
128
+ );
129
+ });
130
+
131
+ it("disables socket when agent_state_id is not yet available", () => {
132
+ render(
133
+ <ConceptSuggestions
134
+ selectedConcept={null}
135
+ handleConceptSelected={jest.fn()}
136
+ />
137
+ );
138
+
139
+ expect(useAssistantSocket).toHaveBeenCalledWith(
140
+ true,
141
+ expect.objectContaining({ conversationId: null })
142
+ );
143
+ });
144
+
145
+ it("connects socket with agent_state_id when available", () => {
146
+ useAgentLayerRun.mockReturnValue({
147
+ trigger: mockFetchSuggestions,
148
+ data: { data: { data: { agent_state_id: "state-1" } } },
149
+ });
150
+
151
+ render(
152
+ <ConceptSuggestions
153
+ selectedConcept={null}
154
+ handleConceptSelected={jest.fn()}
155
+ />
156
+ );
157
+
158
+ expect(useAssistantSocket).toHaveBeenCalledWith(
159
+ false,
160
+ expect.objectContaining({ conversationId: "state-1" })
161
+ );
162
+ });
163
+
164
+ it("shows ConceptSelectorTable when stream priority-0 arrives", async () => {
165
+ const concepts = [{ id: "c1" }, { id: "c2" }];
166
+ let capturedOnServerMessage;
167
+
168
+ useAssistantSocket.mockImplementation((_, { onServerMessage }) => {
169
+ capturedOnServerMessage = onServerMessage;
170
+ });
171
+
172
+ useAgentLayerRun.mockReturnValue({
173
+ trigger: mockFetchSuggestions,
174
+ data: { data: { data: { agent_state_id: "state-1" } } },
175
+ });
176
+
177
+ const rendered = render(
178
+ <ConceptSuggestions
179
+ selectedConcept={null}
180
+ handleConceptSelected={jest.fn()}
181
+ />
182
+ );
183
+
184
+ act(() => {
185
+ capturedOnServerMessage("stream", {
186
+ priority: 0,
187
+ content: JSON.stringify({ result: concepts }),
188
+ });
189
+ });
190
+
191
+ await waitFor(() =>
192
+ expect(rendered.queryByText(/mockconceptselectortable/i)).toBeInTheDocument()
193
+ );
194
+
195
+ expect(rendered.queryByText(/mockthinkingoutloud/i)).not.toBeInTheDocument();
196
+
197
+ const props = ConceptSelectorTable.mock.calls.at(-1)[0];
198
+ expect(props.concepts).toEqual(concepts);
199
+ });
200
+
201
+ it("shows error message when error priority-0 arrives", async () => {
202
+ let capturedOnServerMessage;
203
+
204
+ useAssistantSocket.mockImplementation((_, { onServerMessage }) => {
205
+ capturedOnServerMessage = onServerMessage;
206
+ });
207
+
208
+ useAgentLayerRun.mockReturnValue({
209
+ trigger: mockFetchSuggestions,
210
+ data: { data: { data: { agent_state_id: "state-1" } } },
211
+ });
212
+
213
+ const rendered = render(
214
+ <ConceptSuggestions
215
+ selectedConcept={null}
216
+ handleConceptSelected={jest.fn()}
217
+ />
218
+ );
219
+
220
+ act(() => {
221
+ capturedOnServerMessage("error", {
222
+ priority: 0,
223
+ error: { message: "Something failed" },
224
+ });
225
+ });
226
+
227
+ await waitFor(() =>
228
+ expect(rendered.queryByText(/mockthinkingoutloud/i)).not.toBeInTheDocument()
229
+ );
230
+
231
+ expect(rendered.queryByText(/mockconceptselectortable/i)).not.toBeInTheDocument();
232
+ });
233
+
234
+ it("adds log events to ThinkingOutLoud items", async () => {
235
+ let capturedOnServerMessage;
236
+
237
+ useAssistantSocket.mockImplementation((_, { onServerMessage }) => {
238
+ capturedOnServerMessage = onServerMessage;
239
+ });
240
+
241
+ useAgentLayerRun.mockReturnValue({
242
+ trigger: mockFetchSuggestions,
243
+ data: { data: { data: { agent_state_id: "state-1" } } },
244
+ });
245
+
246
+ render(
247
+ <ConceptSuggestions
248
+ selectedConcept={null}
249
+ handleConceptSelected={jest.fn()}
250
+ />
251
+ );
252
+
253
+ act(() => {
254
+ capturedOnServerMessage("log", {
255
+ priority: 1,
256
+ source: "agent_x",
257
+ content: "Processing...",
258
+ });
259
+ });
260
+
261
+ await waitFor(() => {
262
+ const lastCall = ThinkingOutLoud.mock.calls.at(-1)[0];
263
+ expect(lastCall.items).toHaveLength(1);
264
+ expect(lastCall.items[0].content).toBe("Processing...");
265
+ expect(lastCall.items[0].eventType).toBe("log");
266
+ });
267
+ });
268
+ });
@@ -0,0 +1,40 @@
1
+ import { fireEvent, screen, waitFor } from "@testing-library/react";
2
+ import { render } from "@truedat/test/render";
3
+ import { SuggestLinkButton } from "../SuggestLinkButton";
4
+
5
+ jest.mock("react-intl", () => ({
6
+ ...jest.requireActual("react-intl"),
7
+ useIntl: () => ({ formatMessage: ({ id }) => id }),
8
+ FormattedMessage: ({ id }) => id,
9
+ }));
10
+
11
+ describe("SuggestLinkButton", () => {
12
+ it("renders the trigger button", () => {
13
+ render(<SuggestLinkButton onSubmit={jest.fn()} />);
14
+ expect(screen.getByText("links.actions.suggest")).toBeInTheDocument();
15
+ });
16
+
17
+ it("calls onSubmit with the prompt value when submitted", async () => {
18
+ const onSubmit = jest.fn();
19
+ render(<SuggestLinkButton onSubmit={onSubmit} />);
20
+
21
+ fireEvent.click(screen.getByText("links.actions.suggest"));
22
+
23
+ const input = screen.getByPlaceholderText("links.suggest.prompt.placeholder");
24
+ fireEvent.change(input, { target: { value: "my prompt" } });
25
+
26
+ fireEvent.click(screen.getByText("links.suggest.submit"));
27
+
28
+ await waitFor(() => expect(onSubmit).toHaveBeenCalledWith("my prompt"));
29
+ });
30
+
31
+ it("calls onSubmit with empty string when no prompt entered", async () => {
32
+ const onSubmit = jest.fn();
33
+ render(<SuggestLinkButton onSubmit={onSubmit} />);
34
+
35
+ fireEvent.click(screen.getByText("links.actions.suggest"));
36
+ fireEvent.click(screen.getByText("links.suggest.submit"));
37
+
38
+ await waitFor(() => expect(onSubmit).toHaveBeenCalledWith(""));
39
+ });
40
+ });
@@ -1,11 +1,4 @@
1
- import {
2
- Header,
3
- Icon,
4
- Segment,
5
- Grid,
6
- Container,
7
- Button,
8
- } from "semantic-ui-react";
1
+ import { Header, Icon, Segment, Button } from "semantic-ui-react";
9
2
  import { FormattedMessage, useIntl } from "react-intl";
10
3
  import { Link } from "react-router";
11
4
  import { ACTION_NEW } from "@truedat/core/routes";
@@ -32,19 +25,17 @@ export function ActionsContent() {
32
25
  </Header.Subheader>
33
26
  </Header.Content>
34
27
  </Header>
35
- <Grid>
36
- <Grid.Column width={8}>{/* filters */}</Grid.Column>
37
- <Grid.Column width={8}>
38
- <Container textAlign="right">
39
- <Button
40
- primary
41
- as={Link}
42
- to={ACTION_NEW}
43
- content={<FormattedMessage id="ai.actions.actions.create" />}
44
- />
45
- </Container>
46
- </Grid.Column>
47
- </Grid>
28
+
29
+ <Button
30
+ primary
31
+ floated="right"
32
+ as={Link}
33
+ to={ACTION_NEW}
34
+ content={<FormattedMessage id="ai.actions.actions.create" />}
35
+ />
36
+
37
+ <Segment clearing basic />
38
+
48
39
  <ActionsTable />
49
40
  </Segment>
50
41
  );
@@ -71,13 +71,17 @@ exports[`<Action /> matches the latest snapshot 1`] = `
71
71
  aria-hidden="true"
72
72
  class="ellipsis vertical icon"
73
73
  />
74
+ <i
75
+ aria-hidden="true"
76
+ class="dropdown icon"
77
+ />
74
78
  <div
75
79
  class="left menu transition"
76
80
  >
77
81
  <a
78
82
  aria-checked="false"
79
83
  aria-selected="true"
80
- class="selected item"
84
+ class="selected item td-icon-text-control"
81
85
  data-discover="true"
82
86
  href="/tasks/3/edit"
83
87
  role="option"
@@ -12,13 +12,17 @@ exports[`<ActionActions /> matches the latest snapshot 1`] = `
12
12
  aria-hidden="true"
13
13
  class="ellipsis vertical icon"
14
14
  />
15
+ <i
16
+ aria-hidden="true"
17
+ class="dropdown icon"
18
+ />
15
19
  <div
16
20
  class="left menu transition"
17
21
  >
18
22
  <a
19
23
  aria-checked="false"
20
24
  aria-selected="true"
21
- class="selected item"
25
+ class="selected item td-icon-text-control"
22
26
  data-discover="true"
23
27
  href="/tasks/3/edit"
24
28
  role="option"
@@ -235,7 +235,7 @@ exports[`<ActionEdit /> matches the latest snapshot 1`] = `
235
235
  class="ai-action-edit-form-actions actions"
236
236
  >
237
237
  <a
238
- class="ui secondary button"
238
+ class="ui secondary button td-icon-text-control"
239
239
  data-discover="true"
240
240
  href="/"
241
241
  role="button"
@@ -191,7 +191,7 @@ exports[`<ActionForm /> matches the latest snapshot when is creating 1`] = `
191
191
  class="ai-action-edit-form-actions actions"
192
192
  >
193
193
  <a
194
- class="ui secondary button"
194
+ class="ui secondary button td-icon-text-control"
195
195
  data-discover="true"
196
196
  href="/"
197
197
  role="button"
@@ -386,7 +386,7 @@ exports[`<ActionForm /> matches the latest snapshot when is editing 1`] = `
386
386
  class="ai-action-edit-form-actions actions"
387
387
  >
388
388
  <a
389
- class="ui secondary button"
389
+ class="ui secondary button td-icon-text-control"
390
390
  data-discover="true"
391
391
  href="/"
392
392
  role="button"
@@ -250,7 +250,7 @@ exports[`<ActionNew /> matches the latest snapshot 1`] = `
250
250
  class="ai-action-edit-form-actions actions"
251
251
  >
252
252
  <a
253
- class="ui secondary button"
253
+ class="ui secondary button td-icon-text-control"
254
254
  data-discover="true"
255
255
  href="/"
256
256
  role="button"
@@ -23,29 +23,17 @@ exports[`<Actions /> matches the latest snapshot 1`] = `
23
23
  </div>
24
24
  </div>
25
25
  </h2>
26
- <div
27
- class="ui grid"
26
+ <a
27
+ class="ui primary right floated button"
28
+ data-discover="true"
29
+ href="/tasks/new"
30
+ role="button"
28
31
  >
29
- <div
30
- class="eight wide column"
31
- />
32
- <div
33
- class="eight wide column"
34
- >
35
- <div
36
- class="ui right aligned container"
37
- >
38
- <a
39
- class="ui primary button"
40
- data-discover="true"
41
- href="/tasks/new"
42
- role="button"
43
- >
44
- ai.actions.actions.create
45
- </a>
46
- </div>
47
- </div>
48
- </div>
32
+ ai.actions.actions.create
33
+ </a>
34
+ <div
35
+ class="ui basic clearing segment"
36
+ />
49
37
  <h4
50
38
  class="ui header"
51
39
  >
@@ -33,6 +33,21 @@ const defaultProps = {
33
33
  };
34
34
 
35
35
  describe("<AssistantConversations />", () => {
36
+ beforeAll(() => {
37
+ jest.spyOn(Date.prototype, "toLocaleString").mockImplementation(
38
+ function (_, options) {
39
+ return new Intl.DateTimeFormat("en", {
40
+ ...options,
41
+ timeZone: "UTC",
42
+ }).format(this);
43
+ }
44
+ );
45
+ });
46
+
47
+ afterAll(() => {
48
+ jest.restoreAllMocks();
49
+ });
50
+
36
51
  beforeEach(() => {
37
52
  jest.clearAllMocks();
38
53
  });