box-ui-elements 23.4.0-beta.30 → 23.4.0-beta.32

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 (77) hide show
  1. package/dist/explorer.css +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.css +1 -1
  6. package/dist/preview.js +1 -1
  7. package/dist/sharing.js +1 -1
  8. package/dist/sidebar.css +1 -1
  9. package/dist/sidebar.js +1 -1
  10. package/dist/uploader.js +1 -1
  11. package/es/common/types/metadata.js.flow +6 -0
  12. package/es/common/types/metadata.js.map +1 -1
  13. package/es/elements/content-sidebar/SidebarNav.js +14 -3
  14. package/es/elements/content-sidebar/SidebarNav.js.flow +23 -3
  15. package/es/elements/content-sidebar/SidebarNav.js.map +1 -1
  16. package/es/elements/content-sidebar/SidebarNavTablist.js +58 -17
  17. package/es/elements/content-sidebar/SidebarNavTablist.js.flow +80 -21
  18. package/es/elements/content-sidebar/SidebarNavTablist.js.map +1 -1
  19. package/es/features/metadata-instance-editor/CascadePolicy.js +53 -23
  20. package/es/features/metadata-instance-editor/CascadePolicy.js.flow +69 -27
  21. package/es/features/metadata-instance-editor/CascadePolicy.js.map +1 -1
  22. package/es/features/metadata-instance-editor/Instance.js +26 -4
  23. package/es/features/metadata-instance-editor/Instance.js.flow +33 -4
  24. package/es/features/metadata-instance-editor/Instance.js.map +1 -1
  25. package/es/features/metadata-instance-editor/constants.js +4 -1
  26. package/es/features/metadata-instance-editor/constants.js.flow +10 -1
  27. package/es/features/metadata-instance-editor/constants.js.map +1 -1
  28. package/es/features/metadata-instance-editor/messages.js +16 -0
  29. package/es/features/metadata-instance-editor/messages.js.flow +21 -0
  30. package/es/features/metadata-instance-editor/messages.js.map +1 -1
  31. package/es/features/metadata-instance-editor/stories/tests/CascadePolicy-visual.stories.js +32 -0
  32. package/es/features/metadata-instance-editor/stories/tests/CascadePolicy-visual.stories.js.flow +36 -0
  33. package/es/features/metadata-instance-editor/stories/tests/CascadePolicy-visual.stories.js.map +1 -0
  34. package/i18n/bn-IN.js +4 -0
  35. package/i18n/da-DK.js +4 -0
  36. package/i18n/de-DE.js +4 -0
  37. package/i18n/en-AU.js +4 -0
  38. package/i18n/en-CA.js +4 -0
  39. package/i18n/en-GB.js +4 -0
  40. package/i18n/en-US.js +4 -0
  41. package/i18n/en-US.properties +8 -0
  42. package/i18n/en-x-pseudo.js +4 -0
  43. package/i18n/es-419.js +4 -0
  44. package/i18n/es-ES.js +4 -0
  45. package/i18n/fi-FI.js +4 -0
  46. package/i18n/fr-CA.js +4 -0
  47. package/i18n/fr-FR.js +4 -0
  48. package/i18n/hi-IN.js +4 -0
  49. package/i18n/it-IT.js +4 -0
  50. package/i18n/ja-JP.js +4 -0
  51. package/i18n/ko-KR.js +4 -0
  52. package/i18n/nb-NO.js +4 -0
  53. package/i18n/nl-NL.js +4 -0
  54. package/i18n/pl-PL.js +4 -0
  55. package/i18n/pt-BR.js +4 -0
  56. package/i18n/ru-RU.js +4 -0
  57. package/i18n/sv-SE.js +4 -0
  58. package/i18n/tr-TR.js +4 -0
  59. package/i18n/zh-CN.js +4 -0
  60. package/i18n/zh-TW.js +4 -0
  61. package/package.json +7 -7
  62. package/src/common/types/metadata.js +6 -0
  63. package/src/elements/content-sidebar/SidebarNav.js +23 -3
  64. package/src/elements/content-sidebar/SidebarNavTablist.js +80 -21
  65. package/src/elements/content-sidebar/__tests__/SidebarNav.test.js +99 -147
  66. package/src/elements/content-sidebar/__tests__/SidebarNavTablist.test.js +189 -42
  67. package/src/features/metadata-instance-editor/CascadePolicy.js +69 -27
  68. package/src/features/metadata-instance-editor/Instance.js +33 -4
  69. package/src/features/metadata-instance-editor/__tests__/CascadePolicy.test.js +70 -63
  70. package/src/features/metadata-instance-editor/__tests__/Instance.test.js +34 -19
  71. package/src/features/metadata-instance-editor/__tests__/Instances.test.js +15 -10
  72. package/src/features/metadata-instance-editor/__tests__/MetadataInstanceEditor.test.js +53 -10
  73. package/src/features/metadata-instance-editor/__tests__/__snapshots__/Instance.test.js.snap +2 -1
  74. package/src/features/metadata-instance-editor/constants.js +10 -1
  75. package/src/features/metadata-instance-editor/messages.js +21 -0
  76. package/src/features/metadata-instance-editor/stories/tests/CascadePolicy-visual.stories.js +36 -0
  77. package/src/features/metadata-instance-editor/__tests__/__snapshots__/CascadePolicy.test.js.snap +0 -108
@@ -1,10 +1,16 @@
1
1
  // @flow
2
2
  import * as React from 'react';
3
- import { FormattedMessage } from 'react-intl';
4
3
 
5
- import { BoxAiAgentSelector } from '@box/box-ai-agent-selector';
4
+ import { InlineNotice } from '@box/blueprint-web';
5
+ import { useCallback } from 'react';
6
+ import { FormattedMessage, useIntl } from 'react-intl';
7
+
8
+ // $FlowFixMe
9
+ import { BoxAiAdvancedColor, BoxAiColor } from '@box/blueprint-web-assets/icons/Medium';
10
+ import { type AgentType } from '@box/box-ai-agent-selector';
11
+
6
12
  // $FlowFixMe
7
- import BoxAiLogo from '@box/blueprint-web-assets/icons/Logo/BoxAiLogo';
13
+ import { BoxAiAgentSelectorWithApiContainer } from '@box/box-ai-agent-selector';
8
14
 
9
15
  import Toggle from '../../components/toggle';
10
16
  import { RadioButton, RadioGroup } from '../../components/radio';
@@ -12,23 +18,11 @@ import Link from '../../components/link/Link';
12
18
  import IconAlertDefault from '../../icons/general/IconAlertDefault';
13
19
  import messages from './messages';
14
20
  import './CascadePolicy.scss';
21
+ import { STANDARD_AGENT_ID, ENHANCED_AGENT_ID } from './constants';
15
22
 
16
23
  const COMMUNITY_LINK = 'https://support.box.com/hc/en-us/articles/360044195873-Cascading-metadata-in-folders';
17
24
  const AI_LINK = 'https://www.box.com/ai';
18
25
 
19
- const agents = [
20
- {
21
- id: '1',
22
- name: 'Basic',
23
- isEnterpriseDefault: true,
24
- },
25
- {
26
- id: '2',
27
- name: 'Enhanced (Gemini 2.5 Pro)',
28
- isEnterpriseDefault: false,
29
- },
30
- ];
31
-
32
26
  type Props = {
33
27
  canEdit: boolean,
34
28
  canUseAIFolderExtraction: boolean,
@@ -37,8 +31,9 @@ type Props = {
37
31
  isCascadingEnabled: boolean,
38
32
  isCascadingOverwritten: boolean,
39
33
  isCustomMetadata: boolean,
40
- isExistingAIExtractionCascadePolicy: boolean,
34
+ isExistingCascadePolicy: boolean,
41
35
  onAIFolderExtractionToggle: (value: boolean) => void,
36
+ onAIAgentSelect?: (agent: AgentType | null) => void,
42
37
  onCascadeModeChange: (value: boolean) => void,
43
38
  onCascadeToggle: (value: boolean) => void,
44
39
  shouldShowCascadeOptions: boolean,
@@ -52,26 +47,71 @@ const CascadePolicy = ({
52
47
  isCascadingOverwritten,
53
48
  isCustomMetadata,
54
49
  isAIFolderExtractionEnabled,
55
- isExistingAIExtractionCascadePolicy,
50
+ isExistingCascadePolicy,
56
51
  onAIFolderExtractionToggle,
52
+ onAIAgentSelect,
57
53
  onCascadeToggle,
58
54
  onCascadeModeChange,
59
55
  shouldShowCascadeOptions,
60
56
  }: Props) => {
57
+ const { formatMessage } = useIntl();
58
+
61
59
  const readOnlyState = isCascadingEnabled ? (
62
60
  <div className="metadata-cascade-notice">
63
61
  <FormattedMessage {...messages.metadataCascadePolicyEnabledInfo} />
64
62
  </div>
65
63
  ) : null;
66
64
 
65
+ const agents = React.useMemo(
66
+ () => [
67
+ {
68
+ id: STANDARD_AGENT_ID,
69
+ name: formatMessage(messages.standardAgentName),
70
+ isEnterpriseDefault: true,
71
+ },
72
+ {
73
+ id: ENHANCED_AGENT_ID,
74
+ name: formatMessage(messages.enhancedAgentName),
75
+ isEnterpriseDefault: false,
76
+ customIcon: BoxAiAdvancedColor,
77
+ },
78
+ ],
79
+ [formatMessage],
80
+ );
81
+
82
+ // BoxAiAgentSelectorWithApiContainer expects a function that returns a Promise<AgentListResponse>
83
+ // Since we're passing in our own agents, we don't need to make an API call,
84
+ // so we wrap the store data in a Promise to satisfy the component's interface requirements.
85
+ const agentFetcher = useCallback(() => {
86
+ return Promise.resolve({ agents });
87
+ }, [agents]);
88
+
89
+ const handleAgentSelect = useCallback(
90
+ (agent: AgentType | null) => {
91
+ if (onAIAgentSelect) {
92
+ onAIAgentSelect(agent);
93
+ }
94
+ },
95
+ [onAIAgentSelect],
96
+ );
97
+
67
98
  return canEdit ? (
68
99
  <>
100
+ {isExistingCascadePolicy && (
101
+ <InlineNotice
102
+ variant="info"
103
+ variantIconAriaLabel={formatMessage(messages.cascadePolicyOptionsDisabledNoticeIconAriaLabel)}
104
+ >
105
+ <FormattedMessage {...messages.cascadePolicyOptionsDisabledNotice} />
106
+ </InlineNotice>
107
+ )}
69
108
  <div className="metadata-cascade-editor">
70
109
  <div className="metadata-cascade-enable" data-testid="metadata-cascade-enable">
71
110
  <div>
72
111
  <FormattedMessage tagName="strong" {...messages.enableCascadePolicy} />
73
112
  {!isCustomMetadata && (
74
113
  <Toggle
114
+ aria-label={formatMessage(messages.enableCascadePolicy)}
75
115
  className={`metadata-cascade-toggle ${
76
116
  isCascadingEnabled ? 'cascade-on' : 'cascade-off'
77
117
  }`}
@@ -113,10 +153,12 @@ const CascadePolicy = ({
113
153
  value={isCascadingOverwritten ? 'overwrite' : 'skip'}
114
154
  >
115
155
  <RadioButton
156
+ isDisabled={isExistingCascadePolicy}
116
157
  label={<FormattedMessage {...messages.cascadePolicySkipMode} />}
117
158
  value="skip"
118
159
  />
119
160
  <RadioButton
161
+ isDisabled={isExistingCascadePolicy}
120
162
  label={<FormattedMessage {...messages.cascadePolicyOverwriteMode} />}
121
163
  value="overwrite"
122
164
  />
@@ -128,12 +170,13 @@ const CascadePolicy = ({
128
170
  <div className="metadata-cascade-editor" data-testid="ai-folder-extraction">
129
171
  <div className="metadata-cascade-enable">
130
172
  <div>
131
- <BoxAiLogo className="metadata-cascade-ai-logo" width={16} height={16} />
173
+ <BoxAiColor className="metadata-cascade-ai-logo" width={16} height={16} />
132
174
  <FormattedMessage tagName="strong" {...messages.enableAIAutofill} />
133
175
  <Toggle
176
+ aria-label={formatMessage(messages.enableAIAutofill)}
134
177
  className="metadata-cascade-toggle"
135
178
  isOn={isAIFolderExtractionEnabled}
136
- isDisabled={isExistingAIExtractionCascadePolicy}
179
+ isDisabled={isExistingCascadePolicy}
137
180
  label=""
138
181
  onChange={e => onAIFolderExtractionToggle(e.target.checked)}
139
182
  />
@@ -145,14 +188,13 @@ const CascadePolicy = ({
145
188
  <FormattedMessage {...messages.aiAutofillLearnMore} />
146
189
  </Link>
147
190
  </div>
148
- {canUseAIFolderExtractionAgentSelector && (
191
+ {canUseAIFolderExtractionAgentSelector && isAIFolderExtractionEnabled && (
149
192
  <div className="metadata-cascade-ai-agent-selector">
150
- <BoxAiAgentSelector
151
- agents={agents}
152
- onErrorAction={() => {}}
153
- requestState="success"
154
- selectedAgent={agents[0]}
155
- variant="sidebar"
193
+ <BoxAiAgentSelectorWithApiContainer
194
+ disabled={isExistingCascadePolicy}
195
+ fetcher={agentFetcher}
196
+ onSelectAgent={handleAgentSelect}
197
+ recordAction={() => {}}
156
198
  />
157
199
  </div>
158
200
  )}
@@ -6,6 +6,7 @@ import isEqual from 'lodash/isEqual';
6
6
  import cloneDeep from 'lodash/cloneDeep';
7
7
  import noop from 'lodash/noop';
8
8
 
9
+ import type { AgentType } from '@box/box-ai-agent-selector';
9
10
  import Collapsible from '../../components/collapsible/Collapsible';
10
11
  import Form from '../../components/form-elements/form/Form';
11
12
  import LoadingIndicatorWrapper from '../../components/loading-indicator/LoadingIndicatorWrapper';
@@ -24,7 +25,12 @@ import MetadataInstanceConfirmDialog from './MetadataInstanceConfirmDialog';
24
25
  import Footer from './Footer';
25
26
  import messages from './messages';
26
27
  import { FIELD_TYPE_FLOAT, FIELD_TYPE_INTEGER } from '../metadata-instance-fields/constants';
27
- import { CASCADE_POLICY_TYPE_AI_EXTRACT, TEMPLATE_CUSTOM_PROPERTIES } from './constants';
28
+ import {
29
+ CASCADE_POLICY_TYPE_AI_EXTRACT,
30
+ TEMPLATE_CUSTOM_PROPERTIES,
31
+ ENHANCED_AGENT_CONFIGURATION,
32
+ ENHANCED_AGENT_ID,
33
+ } from './constants';
28
34
  import {
29
35
  JSON_PATCH_OP_REMOVE,
30
36
  JSON_PATCH_OP_ADD,
@@ -38,6 +44,7 @@ import type {
38
44
  MetadataFields,
39
45
  MetadataTemplate,
40
46
  MetadataCascadePolicy,
47
+ MetadataCascadePolicyConfiguration,
41
48
  MetadataCascadingPolicyData,
42
49
  MetadataTemplateField,
43
50
  MetadataFieldValue,
@@ -69,6 +76,7 @@ type Props = {
69
76
  };
70
77
 
71
78
  type State = {
79
+ cascadePolicyConfiguration: MetadataCascadePolicyConfiguration | null,
72
80
  data: Object,
73
81
  errors: { [string]: React.Node },
74
82
  isAIFolderExtractionEnabled: boolean,
@@ -212,6 +220,7 @@ class Instance extends React.PureComponent<Props, State> {
212
220
  onSave,
213
221
  }: Props = this.props;
214
222
  const {
223
+ cascadePolicyConfiguration,
215
224
  data: currentData,
216
225
  errors,
217
226
  isAIFolderExtractionEnabled,
@@ -229,6 +238,7 @@ class Instance extends React.PureComponent<Props, State> {
229
238
  // reset state if cascading policy is removed
230
239
  isAIFolderExtractionEnabled: isCascadingEnabled ? isAIFolderExtractionEnabled : false,
231
240
  });
241
+
232
242
  onSave(
233
243
  id,
234
244
  this.createJSONPatch(currentData, originalData),
@@ -239,6 +249,7 @@ class Instance extends React.PureComponent<Props, State> {
239
249
  isEnabled: isCascadingEnabled,
240
250
  overwrite: isCascadingOverwritten,
241
251
  isAIFolderExtractionEnabled,
252
+ cascadePolicyConfiguration,
242
253
  }
243
254
  : undefined,
244
255
  cloneDeep(currentData),
@@ -342,6 +353,23 @@ class Instance extends React.PureComponent<Props, State> {
342
353
  this.setState({ isAIFolderExtractionEnabled: value }, this.setDirty);
343
354
  };
344
355
 
356
+ /**
357
+ * Handles the selection of an AI agent
358
+ * @param {AgentType | null} agent - The selected agent
359
+ */
360
+ onAIAgentSelect = (agent: AgentType | null): void => {
361
+ // '2' is the id for the enhanced agent
362
+ if (agent && agent.id === ENHANCED_AGENT_ID) {
363
+ this.setState({
364
+ cascadePolicyConfiguration: {
365
+ agent: ENHANCED_AGENT_CONFIGURATION,
366
+ },
367
+ });
368
+ } else {
369
+ this.setState({ cascadePolicyConfiguration: null });
370
+ }
371
+ };
372
+
345
373
  /**
346
374
  * Returns the state from props
347
375
  *
@@ -351,6 +379,7 @@ class Instance extends React.PureComponent<Props, State> {
351
379
  const isCascadingEnabled = this.isCascadingEnabledThroughProps(props);
352
380
 
353
381
  return {
382
+ cascadePolicyConfiguration: null,
354
383
  data: cloneDeep(props.data),
355
384
  errors: {},
356
385
  isAIFolderExtractionEnabled: this.isAIFolderExtractionEnabledThroughProps(props),
@@ -641,8 +670,7 @@ class Instance extends React.PureComponent<Props, State> {
641
670
  // Animate short and tall cards at consistent speeds.
642
671
  const animationDuration = (fields.length + 1) * 50;
643
672
 
644
- const isExistingAIExtractionCascadePolicy =
645
- this.isCascadingEnabledThroughProps(this.props) && this.isAIFolderExtractionEnabledThroughProps(this.props);
673
+ const isExistingCascadePolicy = this.isCascadingEnabledThroughProps(this.props);
646
674
 
647
675
  return (
648
676
  <div ref={this.collapsibleRef}>
@@ -681,7 +709,8 @@ class Instance extends React.PureComponent<Props, State> {
681
709
  isCascadingEnabled={isCascadingEnabled}
682
710
  isCascadingOverwritten={isCascadingOverwritten}
683
711
  isCustomMetadata={isProperties}
684
- isExistingAIExtractionCascadePolicy={isExistingAIExtractionCascadePolicy}
712
+ isExistingCascadePolicy={isExistingCascadePolicy}
713
+ onAIAgentSelect={this.onAIAgentSelect}
685
714
  onAIFolderExtractionToggle={this.onAIFolderExtractionToggle}
686
715
  onCascadeModeChange={this.onCascadeModeChange}
687
716
  onCascadeToggle={this.onCascadeToggle}
@@ -13,49 +13,66 @@ describe('features/metadata-instance-editor/CascadePolicy', () => {
13
13
  });
14
14
 
15
15
  test('should correctly render cascade policy read only mode', () => {
16
- const wrapper = shallow(<CascadePolicy id="fakeId" isCascadingEnabled shouldShowCascadeOptions />);
17
- expect(wrapper).toMatchSnapshot();
16
+ render(<CascadePolicy isCascadingEnabled shouldShowCascadeOptions canEdit={false} />);
17
+ expect(
18
+ screen.getByText(
19
+ 'This template and its values are being cascaded to all items in this folder and its subfolders.',
20
+ ),
21
+ ).toBeInTheDocument();
18
22
  });
23
+
19
24
  test('should correctly render cascade policy in edit mode', () => {
20
- const wrapper = shallow(
25
+ render(
21
26
  <CascadePolicy
22
- id="fakeId"
27
+ canEdit
23
28
  isCascadingEnabled
24
- isEditable
25
29
  onCascadeModeChange={jest.fn()}
26
30
  onCascadeToggle={jest.fn()}
27
31
  shouldShowCascadeOptions
28
32
  />,
29
33
  );
30
- expect(wrapper).toMatchSnapshot();
34
+ expect(screen.getByTestId('metadata-cascade-enable')).toBeInTheDocument();
35
+ expect(screen.getByText('Enable Cascade Policy')).toBeInTheDocument();
31
36
  });
37
+
32
38
  test('should correctly render cascade policy in edit mode and overwrite is on', () => {
33
- const wrapper = shallow(
39
+ render(
34
40
  <CascadePolicy
35
- id="fakeId"
41
+ canEdit
36
42
  isCascadingEnabled
37
- isEditable
43
+ isCascadingOverwritten
38
44
  onCascadeModeChange={jest.fn()}
39
45
  onCascadeToggle={jest.fn()}
40
- shouldCascadeOverwrite
41
46
  shouldShowCascadeOptions
42
47
  />,
43
48
  );
44
- expect(wrapper).toMatchSnapshot();
49
+ expect(screen.getByTestId('metadata-cascade-enable')).toBeInTheDocument();
50
+ expect(screen.getByText('Enable Cascade Policy')).toBeInTheDocument();
51
+ expect(screen.getByLabelText('Overwrite all existing template values')).toBeInTheDocument();
45
52
  });
53
+
46
54
  test('should correctly render cascade policy when the template is Custom Metadata', () => {
47
- const wrapper = shallow(
55
+ render(
48
56
  <CascadePolicy
49
57
  canEdit
50
- id="fakeId"
51
58
  isCustomMetadata
52
- isEditable
53
59
  onCascadeModeChange={jest.fn()}
54
60
  onCascadeToggle={jest.fn()}
55
61
  shouldShowCascadeOptions
56
62
  />,
57
63
  );
58
- expect(wrapper).toMatchSnapshot();
64
+ expect(
65
+ screen.getByText('Cascade policy cannot be applied to custom metadata at this time.'),
66
+ ).toBeInTheDocument();
67
+ });
68
+
69
+ test('should render InlineNotice when isExistingCascadePolicy is true', () => {
70
+ render(<CascadePolicy canEdit isExistingCascadePolicy shouldShowCascadeOptions />);
71
+ expect(
72
+ screen.getByText(
73
+ 'This cascade policy cannot be edited. To modify it, deactivate the current policy and then re-enable it to set up a new one.',
74
+ ),
75
+ ).toBeInTheDocument();
59
76
  });
60
77
 
61
78
  test('should render AI folder extraction toggle when canEdit, canUseAIFolderExtraction, and shouldShowCascadeOptions are true', () => {
@@ -96,60 +113,74 @@ describe('features/metadata-instance-editor/CascadePolicy', () => {
96
113
  });
97
114
 
98
115
  describe('AI Agent Selector', () => {
99
- test('should render AI agent selector with default to basic when AI features are enabled', () => {
116
+ test('should render AI agent selector with default to basic when AI features are enabled', async () => {
100
117
  render(
101
118
  <CascadePolicy
102
119
  canEdit
103
120
  canUseAIFolderExtraction
104
121
  canUseAIFolderExtractionAgentSelector
105
122
  shouldShowCascadeOptions
123
+ isAIFolderExtractionEnabled
124
+ onAIFolderExtractionToggle={jest.fn()}
106
125
  />,
107
126
  );
108
- expect(screen.getByRole('combobox', { name: 'Basic' })).toBeInTheDocument();
127
+
128
+ const aiToggle = screen.getByRole('switch', { name: 'Box AI Autofill' });
129
+ await userEvent.click(aiToggle); // Enable AI
130
+
131
+ expect(aiToggle).toBeChecked();
132
+
133
+ expect(screen.getByRole('combobox', { name: 'Standard' })).toBeInTheDocument();
109
134
  });
110
135
 
111
136
  test('should not render AI agent selector when canUseAIFolderExtractionAgentSelector is false', () => {
112
137
  render(<CascadePolicy canEdit canUseAIFolderExtraction shouldShowCascadeOptions />);
113
- expect(screen.queryByRole('combobox', { name: 'Basic' })).not.toBeInTheDocument();
138
+ expect(screen.queryByRole('combobox', { name: 'Standard' })).not.toBeInTheDocument();
114
139
  });
115
- });
116
140
 
117
- describe('AI Autofill Toggle', () => {
118
- test('should disable toggle when isExistingAIExtractionCascadePolicy is true', () => {
141
+ test('should call onAIAgentSelect when an agent is selected', async () => {
142
+ const onAIAgentSelect = jest.fn();
119
143
  render(
120
144
  <CascadePolicy
121
145
  canEdit
122
146
  canUseAIFolderExtraction
147
+ canUseAIFolderExtractionAgentSelector
123
148
  shouldShowCascadeOptions
124
- isExistingAIExtractionCascadePolicy
149
+ isAIFolderExtractionEnabled
150
+ onAIAgentSelect={onAIAgentSelect}
151
+ onAIFolderExtractionToggle={jest.fn()}
125
152
  />,
126
153
  );
127
154
 
128
- const aiSection = screen.getByTestId('ai-folder-extraction');
129
- const toggle = within(aiSection).getByRole('switch');
155
+ const aiToggle = screen.getByRole('switch', { name: 'Box AI Autofill' });
156
+ await userEvent.click(aiToggle); // Enable AI
130
157
 
131
- expect(toggle).toBeDisabled();
132
- });
158
+ expect(aiToggle).toBeChecked();
133
159
 
134
- test('should enable toggle when isExistingAIExtractionCascadePolicy is false', () => {
135
- render(
136
- <CascadePolicy
137
- canEdit
138
- canUseAIFolderExtraction
139
- shouldShowCascadeOptions
140
- isExistingAIExtractionCascadePolicy={false}
141
- />,
142
- );
160
+ // Find the combobox by its accessible name
161
+ const combobox = screen.getByRole('combobox', { name: 'Standard' });
143
162
 
144
- const aiSection = screen.getByTestId('ai-folder-extraction');
145
- const toggle = within(aiSection).getByRole('switch');
163
+ // Open the combobox (simulate user click)
164
+ await userEvent.click(combobox);
146
165
 
147
- expect(toggle).not.toBeDisabled();
166
+ // Find the option for "Advanced" and select it
167
+ const option = await screen.findByRole('option', { name: 'Enhanced' });
168
+ await userEvent.click(option);
169
+
170
+ // The expected agent object (should match the one in CascadePolicy.js)
171
+ const expectedAgent = {
172
+ id: '1',
173
+ name: 'Standard',
174
+ isEnterpriseDefault: true,
175
+ };
176
+
177
+ expect(onAIAgentSelect).toHaveBeenCalledWith(expectedAgent);
148
178
  });
179
+ });
149
180
 
181
+ describe('AI Autofill Toggle', () => {
150
182
  test('should call onAIFolderExtractionToggle when toggle is clicked and enabled', async () => {
151
183
  const onAIFolderExtractionToggle = jest.fn();
152
-
153
184
  render(
154
185
  <CascadePolicy
155
186
  canEdit
@@ -159,35 +190,11 @@ describe('features/metadata-instance-editor/CascadePolicy', () => {
159
190
  onAIFolderExtractionToggle={onAIFolderExtractionToggle}
160
191
  />,
161
192
  );
162
-
163
193
  const aiSection = screen.getByTestId('ai-folder-extraction');
164
194
  const toggle = within(aiSection).getByRole('switch');
165
-
166
195
  await userEvent.click(toggle);
167
-
168
196
  expect(onAIFolderExtractionToggle).toHaveBeenCalledTimes(1);
169
197
  expect(onAIFolderExtractionToggle).toHaveBeenCalledWith(true);
170
198
  });
171
-
172
- test('should not call onAIFolderExtractionToggle when toggle is clicked but disabled', async () => {
173
- const onAIFolderExtractionToggle = jest.fn();
174
-
175
- render(
176
- <CascadePolicy
177
- canEdit
178
- canUseAIFolderExtraction
179
- shouldShowCascadeOptions
180
- isExistingAIExtractionCascadePolicy={true}
181
- onAIFolderExtractionToggle={onAIFolderExtractionToggle}
182
- />,
183
- );
184
-
185
- const aiSection = screen.getByTestId('ai-folder-extraction');
186
- const toggle = within(aiSection).getByRole('switch');
187
-
188
- await userEvent.click(toggle);
189
-
190
- expect(onAIFolderExtractionToggle).not.toHaveBeenCalled();
191
- });
192
199
  });
193
200
  });
@@ -834,12 +834,17 @@ describe('Instance Component - React Testing Library', () => {
834
834
 
835
835
  describe('AI Folder Extraction Toggle Interaction', () => {
836
836
  test('should toggle AI folder extraction, disable/enable fields', async () => {
837
- render(<Instance {...getBaseProps()} />);
837
+ render(<Instance {...getBaseProps({ cascadePolicy: { canEdit: true } })} />);
838
838
 
839
839
  // Click Edit button to enable editing
840
840
  const editButton = screen.getByRole('button', { name: 'Edit Metadata' }); // Assuming 'Edit Metadata' is the rendered name
841
841
  await userEvent.click(editButton);
842
842
 
843
+ // Click Enable Cascade Policy
844
+ const metadataCascadeEnable = await screen.findByTestId('metadata-cascade-enable');
845
+ const enableCascadePolicy = await within(metadataCascadeEnable).findByRole('switch');
846
+ await userEvent.click(enableCascadePolicy);
847
+
843
848
  // Initially, fields are enabled as default policy is not 'ai_extract'
844
849
  expect(screen.getByRole('textbox', { name: 'String Field example of a string field' })).not.toBeDisabled();
845
850
 
@@ -858,53 +863,63 @@ describe('Instance Component - React Testing Library', () => {
858
863
 
859
864
  describe('Props passed to CascadePolicy', () => {
860
865
  test('should pass canUseAIFolderExtractionAgentSelector to CascadePolicy', async () => {
861
- render(<Instance {...getBaseProps({ canUseAIFolderExtractionAgentSelector: true })} />);
862
-
863
- const editButton = screen.queryByRole('button', { name: 'Edit Metadata' });
864
- if (editButton) await userEvent.click(editButton); // Enter edit mode to ensure CascadePolicy options are visible
865
-
866
- expect(screen.getByRole('combobox', { name: 'Basic' })).toBeInTheDocument();
867
- });
868
-
869
- test('should pass isExistingAIExtractionCascadePolicy=true to CascadePolicy if policy is ai_extract', async () => {
870
866
  render(
871
867
  <Instance
872
868
  {...getBaseProps({
869
+ canUseAIFolderExtractionAgentSelector: true,
873
870
  cascadePolicy: {
874
- id: 'policy-ai',
871
+ id: 'policy-1',
875
872
  canEdit: true,
876
873
  isEnabled: true,
877
- scope: 'enterprise_123',
878
874
  cascadePolicyType: CASCADE_POLICY_TYPE_AI_EXTRACT,
879
875
  },
880
876
  })}
881
877
  />,
882
878
  );
879
+
883
880
  const editButton = screen.queryByRole('button', { name: 'Edit Metadata' });
884
- if (editButton) await userEvent.click(editButton);
881
+ if (editButton) await userEvent.click(editButton); // Enter edit mode to ensure CascadePolicy options are visible
885
882
 
886
- const aiSection = screen.getByTestId('ai-folder-extraction');
887
- const aiToggle = within(aiSection).getByRole('switch');
888
- expect(aiToggle).toBeDisabled();
883
+ const cascadeToggle = screen.getByRole('switch', { name: 'Enable Cascade Policy' });
884
+ expect(cascadeToggle).toBeChecked();
885
+
886
+ const aiToggle = screen.getByRole('switch', { name: 'Box AI Autofill' });
887
+ expect(aiToggle).toBeChecked();
888
+
889
+ expect(screen.getByRole('combobox', { name: 'Standard' })).toBeInTheDocument();
889
890
  });
890
891
 
891
- test('should pass isExistingAIExtractionCascadePolicy=false to CascadePolicy if policy is not ai_extract', async () => {
892
+ test('should disable CascadePolicy options when a cascade already exists', async () => {
892
893
  render(<Instance {...getBaseProps()} />);
894
+
895
+ // Enter edit mode
893
896
  const editButton = screen.queryByRole('button', { name: 'Edit Metadata' });
894
897
  if (editButton) await userEvent.click(editButton);
895
898
 
899
+ // Cascade options should be disabled
900
+ const skipRadio = screen.getByLabelText(/skip/i);
901
+ const overwriteRadio = screen.getByLabelText(/overwrite/i);
902
+ expect(skipRadio).toBeDisabled();
903
+ expect(overwriteRadio).toBeDisabled();
904
+
905
+ // AI toggle should be disabled
896
906
  const aiSection = screen.getByTestId('ai-folder-extraction');
897
907
  const aiToggle = within(aiSection).getByRole('switch');
898
- expect(aiToggle).not.toBeDisabled();
908
+ expect(aiToggle).toBeDisabled();
899
909
  });
900
910
  });
901
911
 
902
912
  describe('Props passed to TemplatedInstance', () => {
903
913
  test('should pass isDisabled=true to TemplatedInstance when AI folder extraction is enabled', async () => {
904
- render(<Instance {...getBaseProps()} />);
914
+ render(<Instance {...getBaseProps({ cascadePolicy: { canEdit: true } })} />);
905
915
  const editButton = screen.getByRole('button', { name: 'Edit Metadata' });
906
916
  await userEvent.click(editButton);
907
917
 
918
+ // Click Enable Cascade Policy
919
+ const metadataCascadeEnable = await screen.findByTestId('metadata-cascade-enable');
920
+ const enableCascadePolicy = await within(metadataCascadeEnable).findByRole('switch');
921
+ await userEvent.click(enableCascadePolicy);
922
+
908
923
  const aiSection = screen.getByTestId('ai-folder-extraction');
909
924
  const aiToggle = within(aiSection).getByRole('switch');
910
925
 
@@ -3,6 +3,7 @@ import userEvent from '@testing-library/user-event';
3
3
  import { render, screen } from '../../../test-utils/testing-library';
4
4
 
5
5
  import Instances from '../Instances';
6
+ import { CASCADE_POLICY_TYPE_AI_EXTRACT } from '../constants';
6
7
 
7
8
  // Templates
8
9
 
@@ -254,18 +255,22 @@ describe('features/metadata-editor-editor/Instances', () => {
254
255
 
255
256
  describe('Instances component - canUseAIFolderExtractionAgentSelector prop', () => {
256
257
  test('should pass canUseAIFolderExtractionAgentSelector to child Instance components, showing agent selector', async () => {
257
- render(
258
- <Instances
259
- {...getInstancesBaseProps({
260
- canUseAIFolderExtractionAgentSelector: true,
261
- })}
262
- />,
263
- );
258
+ const props = getInstancesBaseProps({
259
+ canUseAIFolderExtractionAgentSelector: true,
260
+ });
261
+ props.editors[0].instance.cascadePolicy.cascadePolicyType = CASCADE_POLICY_TYPE_AI_EXTRACT;
262
+ render(<Instances {...props} />);
264
263
 
265
264
  const editButton = screen.getByRole('button', { name: 'Edit Metadata' });
266
265
  await userEvent.click(editButton);
267
266
 
268
- expect(screen.getByRole('combobox', { name: 'Basic' })).toBeInTheDocument();
267
+ const cascadeToggle = screen.getByRole('switch', { name: 'Enable Cascade Policy' });
268
+ expect(cascadeToggle).toBeChecked();
269
+
270
+ const aiToggle = screen.getByRole('switch', { name: 'Box AI Autofill' });
271
+ expect(aiToggle).toBeChecked();
272
+
273
+ expect(screen.getByRole('combobox', { name: 'Standard' })).toBeInTheDocument();
269
274
  });
270
275
 
271
276
  test('should not show agent selector in child Instance if canUseAIFolderExtractionAgentSelector is false', async () => {
@@ -280,7 +285,7 @@ describe('Instances component - canUseAIFolderExtractionAgentSelector prop', ()
280
285
  const editButton = screen.getByRole('button', { name: 'Edit Metadata' });
281
286
  await userEvent.click(editButton);
282
287
 
283
- expect(screen.queryByRole('combobox', { name: 'Basic' })).not.toBeInTheDocument();
288
+ expect(screen.queryByRole('combobox', { name: 'Standard' })).not.toBeInTheDocument();
284
289
  });
285
290
 
286
291
  test('should not show agent selector in child Instance if canUseAIFolderExtractionAgentSelector is undefined', async () => {
@@ -291,6 +296,6 @@ describe('Instances component - canUseAIFolderExtractionAgentSelector prop', ()
291
296
  const editButton = screen.getByRole('button', { name: 'Edit Metadata' });
292
297
  await userEvent.click(editButton);
293
298
 
294
- expect(screen.queryByRole('combobox', { name: 'Basic' })).not.toBeInTheDocument();
299
+ expect(screen.queryByRole('combobox', { name: 'Standard' })).not.toBeInTheDocument();
295
300
  });
296
301
  });