box-ui-elements 24.0.0-beta.2 β†’ 24.0.0-beta.4

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 (124) hide show
  1. package/DEVELOPING.md +1 -1
  2. package/dist/explorer.js +1 -1
  3. package/dist/openwith.js +1 -1
  4. package/dist/picker.js +1 -1
  5. package/dist/preview.js +1 -1
  6. package/dist/sharing.js +1 -1
  7. package/dist/sidebar.js +1 -1
  8. package/dist/uploader.js +1 -1
  9. package/es/elements/common/content-answers/ContentAnswersModal.js +1 -0
  10. package/es/elements/common/content-answers/ContentAnswersModal.js.map +1 -1
  11. package/es/elements/common/withBlueprintModernization.js +16 -0
  12. package/es/elements/common/withBlueprintModernization.js.map +1 -0
  13. package/es/elements/content-explorer/ContentExplorer.js +2 -1
  14. package/es/elements/content-explorer/ContentExplorer.js.map +1 -1
  15. package/es/elements/content-explorer/stories/tests/ContentExplorer-visual.stories.js +5 -0
  16. package/es/elements/content-explorer/stories/tests/ContentExplorer-visual.stories.js.flow +6 -0
  17. package/es/elements/content-explorer/stories/tests/ContentExplorer-visual.stories.js.map +1 -1
  18. package/es/elements/content-picker/ContentPicker.js +4 -1
  19. package/es/elements/content-picker/ContentPicker.js.flow +4 -1
  20. package/es/elements/content-picker/ContentPicker.js.map +1 -1
  21. package/es/elements/content-picker/stories/tests/ContentPicker-visual.stories.js +5 -0
  22. package/es/elements/content-picker/stories/tests/ContentPicker-visual.stories.js.flow +6 -0
  23. package/es/elements/content-picker/stories/tests/ContentPicker-visual.stories.js.map +1 -1
  24. package/es/elements/content-preview/ContentPreview.js +3 -1
  25. package/es/elements/content-preview/ContentPreview.js.flow +3 -0
  26. package/es/elements/content-preview/ContentPreview.js.map +1 -1
  27. package/es/elements/content-preview/stories/tests/ContentPreview-visual.stories.js +5 -0
  28. package/es/elements/content-preview/stories/tests/ContentPreview-visual.stories.js.flow +7 -1
  29. package/es/elements/content-preview/stories/tests/ContentPreview-visual.stories.js.map +1 -1
  30. package/es/elements/content-sharing/ContentSharing.js +4 -1
  31. package/es/elements/content-sharing/ContentSharing.js.flow +4 -1
  32. package/es/elements/content-sharing/ContentSharing.js.map +1 -1
  33. package/es/elements/content-sidebar/ContentSidebar.js +3 -1
  34. package/es/elements/content-sidebar/ContentSidebar.js.flow +3 -0
  35. package/es/elements/content-sidebar/ContentSidebar.js.map +1 -1
  36. package/es/elements/content-sidebar/stories/tests/ContentSidebar-visual.stories.js +5 -0
  37. package/es/elements/content-sidebar/stories/tests/ContentSidebar-visual.stories.js.map +1 -1
  38. package/es/elements/content-uploader/ContentUploader.js +3 -1
  39. package/es/elements/content-uploader/ContentUploader.js.map +1 -1
  40. package/es/elements/content-uploader/stories/tests/ContentUploader-visual.stories.js +5 -0
  41. package/es/elements/content-uploader/stories/tests/ContentUploader-visual.stories.js.flow +6 -0
  42. package/es/elements/content-uploader/stories/tests/ContentUploader-visual.stories.js.map +1 -1
  43. package/es/features/classification/applied-by-ai-classification-reason/AppliedByAiClassificationReason.js +51 -0
  44. package/es/features/classification/applied-by-ai-classification-reason/AppliedByAiClassificationReason.js.map +1 -0
  45. package/es/features/classification/applied-by-ai-classification-reason/AppliedByAiClassificationReason.scss +29 -0
  46. package/es/features/classification/applied-by-ai-classification-reason/messages.js +13 -0
  47. package/es/features/classification/applied-by-ai-classification-reason/messages.js.map +1 -0
  48. package/es/features/classification/types.js +2 -0
  49. package/es/features/classification/types.js.map +1 -0
  50. package/es/src/elements/common/__tests__/withBlueprintModernization.test.d.ts +1 -0
  51. package/es/src/elements/common/withBlueprintModernization.d.ts +3 -0
  52. package/es/src/elements/content-sidebar/stories/tests/ContentSidebar-visual.stories.d.ts +5 -0
  53. package/es/src/features/classification/applied-by-ai-classification-reason/AppliedByAiClassificationReason.d.ts +6 -0
  54. package/es/src/features/classification/applied-by-ai-classification-reason/__tests__/AppliedByAiClassificationReason.test.d.ts +1 -0
  55. package/es/src/features/classification/applied-by-ai-classification-reason/messages.d.ts +13 -0
  56. package/es/src/features/classification/types.d.ts +6 -0
  57. package/i18n/bn-IN.js +4 -2
  58. package/i18n/bn-IN.properties +2 -2
  59. package/i18n/da-DK.js +4 -2
  60. package/i18n/da-DK.properties +2 -2
  61. package/i18n/de-DE.js +4 -2
  62. package/i18n/de-DE.properties +2 -2
  63. package/i18n/en-AU.js +2 -0
  64. package/i18n/en-CA.js +2 -0
  65. package/i18n/en-GB.js +2 -0
  66. package/i18n/en-US.js +2 -0
  67. package/i18n/en-US.properties +4 -0
  68. package/i18n/en-x-pseudo.js +2 -0
  69. package/i18n/es-419.js +4 -2
  70. package/i18n/es-419.properties +2 -2
  71. package/i18n/es-ES.js +4 -2
  72. package/i18n/es-ES.properties +2 -2
  73. package/i18n/fi-FI.js +4 -2
  74. package/i18n/fi-FI.properties +2 -2
  75. package/i18n/fr-CA.js +4 -2
  76. package/i18n/fr-CA.properties +2 -2
  77. package/i18n/fr-FR.js +4 -2
  78. package/i18n/fr-FR.properties +2 -2
  79. package/i18n/hi-IN.js +4 -2
  80. package/i18n/hi-IN.properties +2 -2
  81. package/i18n/it-IT.js +4 -2
  82. package/i18n/it-IT.properties +2 -2
  83. package/i18n/ja-JP.js +2 -0
  84. package/i18n/ko-KR.js +4 -2
  85. package/i18n/ko-KR.properties +2 -2
  86. package/i18n/nb-NO.js +4 -2
  87. package/i18n/nb-NO.properties +2 -2
  88. package/i18n/nl-NL.js +4 -2
  89. package/i18n/nl-NL.properties +2 -2
  90. package/i18n/pl-PL.js +4 -2
  91. package/i18n/pl-PL.properties +2 -2
  92. package/i18n/pt-BR.js +4 -2
  93. package/i18n/pt-BR.properties +2 -2
  94. package/i18n/ru-RU.js +4 -2
  95. package/i18n/ru-RU.properties +2 -2
  96. package/i18n/sv-SE.js +4 -2
  97. package/i18n/sv-SE.properties +2 -2
  98. package/i18n/tr-TR.js +4 -2
  99. package/i18n/tr-TR.properties +2 -2
  100. package/i18n/zh-CN.js +4 -2
  101. package/i18n/zh-CN.properties +2 -2
  102. package/i18n/zh-TW.js +4 -2
  103. package/i18n/zh-TW.properties +2 -2
  104. package/package.json +1 -1
  105. package/src/elements/common/__tests__/withBlueprintModernization.test.tsx +91 -0
  106. package/src/elements/common/content-answers/ContentAnswersModal.tsx +1 -0
  107. package/src/elements/common/content-answers/__tests__/ContentAnswersModal.test.tsx +1 -2
  108. package/src/elements/common/withBlueprintModernization.tsx +24 -0
  109. package/src/elements/content-explorer/ContentExplorer.tsx +4 -1
  110. package/src/elements/content-explorer/stories/tests/ContentExplorer-visual.stories.js +6 -0
  111. package/src/elements/content-picker/ContentPicker.js +4 -1
  112. package/src/elements/content-picker/stories/tests/ContentPicker-visual.stories.js +6 -0
  113. package/src/elements/content-preview/ContentPreview.js +3 -0
  114. package/src/elements/content-preview/stories/tests/ContentPreview-visual.stories.js +7 -1
  115. package/src/elements/content-sharing/ContentSharing.js +4 -1
  116. package/src/elements/content-sidebar/ContentSidebar.js +3 -0
  117. package/src/elements/content-sidebar/stories/tests/ContentSidebar-visual.stories.tsx +6 -0
  118. package/src/elements/content-uploader/ContentUploader.tsx +3 -1
  119. package/src/elements/content-uploader/stories/tests/ContentUploader-visual.stories.js +6 -0
  120. package/src/features/classification/applied-by-ai-classification-reason/AppliedByAiClassificationReason.scss +29 -0
  121. package/src/features/classification/applied-by-ai-classification-reason/AppliedByAiClassificationReason.tsx +55 -0
  122. package/src/features/classification/applied-by-ai-classification-reason/__tests__/AppliedByAiClassificationReason.test.tsx +105 -0
  123. package/src/features/classification/applied-by-ai-classification-reason/messages.ts +18 -0
  124. package/src/features/classification/types.ts +7 -0
@@ -100,6 +100,7 @@ import '../common/fonts.scss';
100
100
  import '../common/base.scss';
101
101
  import '../common/modal.scss';
102
102
  import './ContentExplorer.scss';
103
+ import { withBlueprintModernization } from '../common/withBlueprintModernization';
103
104
 
104
105
  const GRID_VIEW_MAX_COLUMNS = 7;
105
106
  const GRID_VIEW_MIN_COLUMNS = 1;
@@ -1974,4 +1975,6 @@ class ContentExplorer extends Component<ContentExplorerProps, State> {
1974
1975
  }
1975
1976
 
1976
1977
  export { ContentExplorer as ContentExplorerComponent };
1977
- export default flow([makeResponsive, withFeatureConsumer, withFeatureProvider])(ContentExplorer);
1978
+ export default flow([makeResponsive, withFeatureConsumer, withFeatureProvider, withBlueprintModernization])(
1979
+ ContentExplorer,
1980
+ );
@@ -28,6 +28,12 @@ export const basic = {
28
28
  },
29
29
  };
30
30
 
31
+ export const Modernization = {
32
+ args: {
33
+ enableModernizedComponents: true,
34
+ },
35
+ };
36
+
31
37
  export const openExistingFolder = {
32
38
  play: async ({ canvasElement }) => {
33
39
  const canvas = within(canvasElement);
@@ -9,6 +9,7 @@ import React, { Component } from 'react';
9
9
  import type { Node } from 'react';
10
10
  import classNames from 'classnames';
11
11
  import debounce from 'lodash/debounce';
12
+ import flow from 'lodash/flow';
12
13
  import getProp from 'lodash/get';
13
14
  import uniqueid from 'lodash/uniqueId';
14
15
  import noop from 'lodash/noop';
@@ -18,6 +19,8 @@ import UploadDialog from '../common/upload-dialog';
18
19
  import CreateFolderDialog from '../common/create-folder-dialog';
19
20
  import Internationalize from '../common/Internationalize';
20
21
  import makeResponsive from '../common/makeResponsive';
22
+ // $FlowFixMe
23
+ import { withBlueprintModernization } from '../common/withBlueprintModernization';
21
24
  // $FlowFixMe TypeScript file
22
25
  import ThemingStyles from '../common/theming';
23
26
  import Pagination from '../../features/pagination';
@@ -1345,4 +1348,4 @@ class ContentPicker extends Component<Props, State> {
1345
1348
  }
1346
1349
 
1347
1350
  export { ContentPicker as ContentPickerComponent };
1348
- export default makeResponsive(ContentPicker);
1351
+ export default flow([makeResponsive, withBlueprintModernization])(ContentPicker);
@@ -8,6 +8,12 @@ import { DEFAULT_HOSTNAME_API } from '../../../../constants';
8
8
 
9
9
  export const basic = {};
10
10
 
11
+ export const Modernization = {
12
+ args: {
13
+ enableModernizedComponents: true,
14
+ },
15
+ };
16
+
11
17
  export const withPagination = {
12
18
  args: {
13
19
  initialPageSize: 1,
@@ -35,6 +35,8 @@ import { withLogger } from '../common/logger';
35
35
  import { PREVIEW_FIELDS_TO_FETCH } from '../../utils/fields';
36
36
  import { mark } from '../../utils/performance';
37
37
  import { withFeatureConsumer, withFeatureProvider } from '../common/feature-checking';
38
+ // $FlowFixMe
39
+ import { withBlueprintModernization } from '../common/withBlueprintModernization';
38
40
  import { EVENT_JS_READY } from '../common/logger/constants';
39
41
  import ReloadNotification from './ReloadNotification';
40
42
  import API from '../../api';
@@ -1418,6 +1420,7 @@ export default flow([
1418
1420
  withNavRouter,
1419
1421
  withFeatureConsumer,
1420
1422
  withFeatureProvider,
1423
+ withBlueprintModernization,
1421
1424
  withLogger(ORIGIN_CONTENT_PREVIEW),
1422
1425
  withErrorBoundary(ORIGIN_CONTENT_PREVIEW),
1423
1426
  ])(ContentPreview);
@@ -30,6 +30,12 @@ export const basic = {
30
30
  },
31
31
  };
32
32
 
33
+ export const Modernization = {
34
+ args: {
35
+ enableModernizedComponents: true,
36
+ },
37
+ };
38
+
33
39
  export const closeModal = {
34
40
  play: async ({ canvasElement }) => {
35
41
  const canvas = within(canvasElement);
@@ -92,7 +98,7 @@ export const hoverOverCitation = {
92
98
 
93
99
  expect(modal.getByText('Based on:')).toBeInTheDocument();
94
100
 
95
- const citations = await modal.getAllByTestId('content-answers-citation-status')
101
+ const citations = await modal.getAllByTestId('content-answers-citation-status');
96
102
  const citation = citations[0];
97
103
  expect(citation).toBeInTheDocument();
98
104
  await userEvent.hover(citation);
@@ -9,6 +9,8 @@
9
9
  import 'regenerator-runtime/runtime';
10
10
  import * as React from 'react';
11
11
  import API from '../../api';
12
+ // $FlowFixMe
13
+ import { withBlueprintModernization } from '../common/withBlueprintModernization';
12
14
  import SharingModal from './SharingModal';
13
15
  import { CLIENT_NAME_CONTENT_SHARING, DEFAULT_HOSTNAME_API } from '../../constants';
14
16
  import type { ItemType, StringMap } from '../../common/types/core';
@@ -118,4 +120,5 @@ function ContentSharing({
118
120
  );
119
121
  }
120
122
 
121
- export default ContentSharing;
123
+ export { ContentSharing as ContentSharingComponent };
124
+ export default withBlueprintModernization(ContentSharing);
@@ -21,6 +21,8 @@ import { EVENT_JS_READY } from '../common/logger/constants';
21
21
  import { mark } from '../../utils/performance';
22
22
  import { SIDEBAR_FIELDS_TO_FETCH, SIDEBAR_FIELDS_TO_FETCH_ARCHIVE } from '../../utils/fields';
23
23
  import { withErrorBoundary } from '../common/error-boundary';
24
+ // $FlowFixMe
25
+ import { withBlueprintModernization } from '../common/withBlueprintModernization';
24
26
  import {
25
27
  isFeatureEnabled as isFeatureEnabledInContext,
26
28
  withFeatureConsumer,
@@ -432,6 +434,7 @@ export { ContentSidebar as ContentSidebarComponent };
432
434
  export default flow([
433
435
  withFeatureConsumer,
434
436
  withFeatureProvider,
437
+ withBlueprintModernization,
435
438
  withLogger(ORIGIN_CONTENT_SIDEBAR),
436
439
  withErrorBoundary(ORIGIN_CONTENT_SIDEBAR),
437
440
  ])(ContentSidebar);
@@ -23,6 +23,12 @@ export default {
23
23
  },
24
24
  };
25
25
 
26
+ export const Modernization = {
27
+ args: {
28
+ enableModernizedComponents: true,
29
+ },
30
+ };
31
+
26
32
  export const ContentSidebarWithBoxAIDisabled: StoryObj<typeof BoxAISidebar> = {
27
33
  args: {
28
34
  features: {
@@ -2,6 +2,7 @@ import 'regenerator-runtime/runtime';
2
2
  import React, { Component } from 'react';
3
3
  import classNames from 'classnames';
4
4
  import cloneDeep from 'lodash/cloneDeep';
5
+ import flow from 'lodash/flow';
5
6
  import getProp from 'lodash/get';
6
7
  import noop from 'lodash/noop';
7
8
  import uniqueid from 'lodash/uniqueId';
@@ -14,6 +15,7 @@ import API from '../../api';
14
15
  import Browser from '../../utils/Browser';
15
16
  import Internationalize from '../common/Internationalize';
16
17
  import makeResponsive from '../common/makeResponsive';
18
+ import { withBlueprintModernization } from '../common/withBlueprintModernization';
17
19
  import ThemingStyles, { Theme } from '../common/theming';
18
20
  import FolderUpload from '../../api/uploads/FolderUpload';
19
21
  import { getTypedFileId, getTypedFolderId } from '../../utils/file';
@@ -1323,5 +1325,5 @@ class ContentUploader extends Component<ContentUploaderProps, State> {
1323
1325
  }
1324
1326
  }
1325
1327
 
1326
- export default makeResponsive(ContentUploader);
1328
+ export default flow([makeResponsive, withBlueprintModernization])(ContentUploader);
1327
1329
  export { ContentUploader as ContentUploaderComponent, CHUNKED_UPLOAD_MIN_SIZE_BYTES };
@@ -23,6 +23,12 @@ export const basic = {
23
23
  },
24
24
  };
25
25
 
26
+ export const Modernization = {
27
+ args: {
28
+ enableModernizedComponents: true,
29
+ },
30
+ };
31
+
26
32
  export const singleUpload = {
27
33
  play: async ({ canvasElement }) => {
28
34
  const canvas = within(canvasElement);
@@ -0,0 +1,29 @@
1
+ $reason-font-size: 13px; // Overriding Blueprint to match current sidebar styles
2
+
3
+ .AppliedByAiClassificationReason {
4
+ .AppliedByAiClassificationReason-headerText {
5
+ font-size: $reason-font-size;
6
+ }
7
+
8
+ .AppliedByAiClassificationReason-answer {
9
+ font-size: $reason-font-size;
10
+ line-height: var(--body-default-line-height);
11
+ }
12
+
13
+ .AppliedByAiClassificationReason-references {
14
+ > * > * {
15
+ font-size: $reason-font-size;
16
+ }
17
+ }
18
+ }
19
+
20
+ .AppliedByAiClassificationReason-header {
21
+ display: flex;
22
+ align-items: center;
23
+ gap: var(--space-2);
24
+ margin: 0 0 var(--space-2);
25
+ }
26
+
27
+ .AppliedByAiClassificationReason-references {
28
+ margin-top: var(--space-2);
29
+ }
@@ -0,0 +1,55 @@
1
+ import React from 'react';
2
+ import { AnswerContent, References } from '@box/box-ai-content-answers';
3
+ import { Card, Text } from '@box/blueprint-web';
4
+ import BoxAIIconColor from '@box/blueprint-web-assets/icons/Logo/BoxAiLogo';
5
+ import { Size5 } from '@box/blueprint-web-assets/tokens/tokens';
6
+ import { FormattedDate, FormattedMessage } from 'react-intl';
7
+
8
+ import { isValidDate } from '../../../utils/datetime';
9
+ import type { AiClassificationReason } from '../types';
10
+
11
+ import messages from './messages';
12
+
13
+ import './AppliedByAiClassificationReason.scss';
14
+
15
+ export type AppliedByAiClassificationReasonProps = AiClassificationReason;
16
+
17
+ const AppliedByAiClassificationReason = ({ answer, modifiedAt, citations }: AppliedByAiClassificationReasonProps) => {
18
+ const modifiedDate = new Date(modifiedAt);
19
+ const isModifiedDateAvailable = Boolean(modifiedAt) && isValidDate(modifiedDate);
20
+
21
+ const formattedModifiedAt = isModifiedDateAvailable && (
22
+ <FormattedDate value={modifiedDate} month="long" year="numeric" day="numeric" />
23
+ );
24
+
25
+ return (
26
+ <Card className="AppliedByAiClassificationReason">
27
+ <h3 className="AppliedByAiClassificationReason-header">
28
+ <BoxAIIconColor data-testid="box-ai-icon" height={Size5} width={Size5} />
29
+ <Text
30
+ className="AppliedByAiClassificationReason-headerText"
31
+ as="span"
32
+ color="textOnLightSecondary"
33
+ variant="bodyDefaultSemibold"
34
+ >
35
+ {isModifiedDateAvailable ? (
36
+ <FormattedMessage
37
+ {...messages.appliedByBoxAiOnDate}
38
+ values={{ modifiedAt: formattedModifiedAt }}
39
+ />
40
+ ) : (
41
+ <FormattedMessage {...messages.appliedByBoxAi} />
42
+ )}
43
+ </Text>
44
+ </h3>
45
+ <AnswerContent className="AppliedByAiClassificationReason-answer" answer={answer} />
46
+ {citations && (
47
+ <div className="AppliedByAiClassificationReason-references">
48
+ <References citations={citations} />
49
+ </div>
50
+ )}
51
+ </Card>
52
+ );
53
+ };
54
+
55
+ export default AppliedByAiClassificationReason;
@@ -0,0 +1,105 @@
1
+ import * as React from 'react';
2
+
3
+ import { render, screen } from '../../../../test-utils/testing-library';
4
+ import AppliedByAiClassificationReason from '../AppliedByAiClassificationReason';
5
+
6
+ import messages from '../messages';
7
+
8
+ describe('AppliedByAiClassificationReason', () => {
9
+ let defaultProps;
10
+ let modifiedAtDisplayDate;
11
+
12
+ beforeEach(() => {
13
+ defaultProps = {
14
+ answer: 'This file is marked as Internal Only because it contains non-public financial results.',
15
+ modifiedAt: '2024-01-15T10:30:00Z',
16
+ };
17
+ modifiedAtDisplayDate = 'January 15, 2024';
18
+ });
19
+
20
+ const renderComponent = (props = {}) => {
21
+ return render(<AppliedByAiClassificationReason {...defaultProps} {...props} />);
22
+ };
23
+
24
+ test('should render AI classification reason with icon, applied date, and reasoning', () => {
25
+ const expectedIconSize = '1.25rem';
26
+
27
+ renderComponent();
28
+
29
+ const boxAiIcon = screen.getByTestId('box-ai-icon');
30
+ const appliedByWithDate = screen.getByRole('heading', {
31
+ level: 3,
32
+ name: messages.appliedByBoxAiOnDate.defaultMessage.replace('{modifiedAt}', modifiedAtDisplayDate),
33
+ });
34
+ const reasonText = screen.getByText(defaultProps.answer);
35
+ const citationsLabel = screen.queryByTestId('content-answers-references-label');
36
+ const noReferencesIconContainer = screen.queryByTestId('content-answers-references-no-references');
37
+
38
+ expect(boxAiIcon).toBeVisible();
39
+ expect(boxAiIcon).toHaveAttribute('height', expectedIconSize);
40
+ expect(boxAiIcon).toHaveAttribute('width', expectedIconSize);
41
+ expect(appliedByWithDate).toBeVisible();
42
+ expect(reasonText).toBeVisible();
43
+ // Assert none of the Reference components are rendered
44
+ expect(citationsLabel).toBeNull();
45
+ expect(noReferencesIconContainer).toBeNull();
46
+ });
47
+
48
+ test('should render no references icon when an empty citations array provided', () => {
49
+ renderComponent({ citations: [] });
50
+
51
+ const noReferencesIconContainer = screen.queryByTestId('content-answers-references-no-references');
52
+ const noReferencesIcon = noReferencesIconContainer.querySelector('svg');
53
+
54
+ expect(noReferencesIcon).toBeVisible();
55
+ });
56
+
57
+ test('should render references when non-empty citations are provided', () => {
58
+ const expectedCitationsCount = 5;
59
+ const expectedCitations = Array.from({ length: expectedCitationsCount }, () => ({
60
+ content: 'file content for citation',
61
+ fileId: 'fileId',
62
+ location: 'cited location',
63
+ title: 'file title',
64
+ }));
65
+
66
+ renderComponent({ citations: expectedCitations });
67
+
68
+ const citationsLabel = screen.queryByTestId('content-answers-references-label');
69
+ const citationElements = screen.getAllByTestId('content-answers-citation-status');
70
+
71
+ expect(citationsLabel).toBeVisible();
72
+ expect(citationElements).toHaveLength(expectedCitationsCount);
73
+ });
74
+
75
+ test.each([null, undefined, 'invalid date str'])(
76
+ 'should render applied by without date when modifiedAt is invalid: %s',
77
+ invalidModifiedAt => {
78
+ renderComponent({ modifiedAt: invalidModifiedAt });
79
+
80
+ const appliedByWithoutDate = screen.getByRole('heading', {
81
+ level: 3,
82
+ name: messages.appliedByBoxAi.defaultMessage,
83
+ });
84
+
85
+ expect(appliedByWithoutDate).toBeVisible();
86
+ },
87
+ );
88
+
89
+ test('should render long answer text correctly', () => {
90
+ const longAnswer = 'A'.repeat(1000);
91
+ renderComponent({ answer: longAnswer });
92
+
93
+ expect(screen.getByText(longAnswer)).toBeVisible();
94
+ });
95
+
96
+ test('should render answer with special and unicode characters correctly', () => {
97
+ const nonPlainAnswer = 'Answer with special characters and unicode !@#$%^&*()_+-=[]{}|;:,.<>? πŸš€πŸŒŸπŸŽ‰δΈ­ζ–‡ζ—₯本θͺž';
98
+
99
+ renderComponent({ answer: nonPlainAnswer });
100
+
101
+ const reasonText = screen.getByText(nonPlainAnswer);
102
+
103
+ expect(reasonText).toBeVisible();
104
+ });
105
+ });
@@ -0,0 +1,18 @@
1
+ import { defineMessages } from 'react-intl';
2
+
3
+ const messages = defineMessages({
4
+ appliedByBoxAi: {
5
+ defaultMessage: 'Box AI',
6
+ description:
7
+ 'Title of the card that shows the reason why the AI classification was applied when no date is available.',
8
+ id: 'boxui.classification.appliedByBoxAi',
9
+ },
10
+ appliedByBoxAiOnDate: {
11
+ defaultMessage: 'Box AI on {modifiedAt}',
12
+ description:
13
+ 'Title of the card that shows the reason why the AI classification was applied on a specific date.',
14
+ id: 'boxui.classification.appliedByBoxAiOnDate',
15
+ },
16
+ });
17
+
18
+ export default messages;
@@ -0,0 +1,7 @@
1
+ import type { CitationType } from '@box/box-ai-content-answers';
2
+
3
+ export type AiClassificationReason = {
4
+ answer: string;
5
+ modifiedAt?: string;
6
+ citations?: CitationType[];
7
+ };