diff --git a/package.json b/package.json index 5a70951010..dcd719f34c 100644 --- a/package.json +++ b/package.json @@ -92,7 +92,11 @@ "last 2 Edge versions", "last 2 iOS versions" ], - "development": ["last 1 Chrome versions", "last 1 Firefox versions", "last 1 Safari versions"] + "development": [ + "last 1 Chrome versions", + "last 1 Firefox versions", + "last 1 Safari versions" + ] }, "husky": { "hooks": { @@ -128,7 +132,7 @@ "@box/cldr-data": "^34.2.0", "@box/frontend": "^10.0.0", "@box/languages": "^1.0.0", - "@box/metadata-editor": "^0.54.0", + "@box/metadata-editor": "^0.61.1", "@box/react-virtualized": "9.22.3-rc-box.9", "@cfaester/enzyme-adapter-react-18": "^0.8.0", "@chromatic-com/storybook": "^1.6.1", @@ -306,7 +310,7 @@ "@box/blueprint-web-assets": "^4.21.0", "@box/box-ai-content-answers": "^0.50.5", "@box/cldr-data": ">=34.2.0", - "@box/metadata-editor": "^0.54.0", + "@box/metadata-editor": "^0.61.1", "@box/react-virtualized": "9.22.3-rc-box.9", "@hapi/address": "^2.1.4", "axios": "^0.25.0", @@ -366,6 +370,8 @@ } }, "msw": { - "workerDirectory": [".storybook/public"] + "workerDirectory": [ + ".storybook/public" + ] } } diff --git a/src/elements/content-sidebar/MetadataInstanceEditor.tsx b/src/elements/content-sidebar/MetadataInstanceEditor.tsx index d03839e06e..f801d1e1ad 100644 --- a/src/elements/content-sidebar/MetadataInstanceEditor.tsx +++ b/src/elements/content-sidebar/MetadataInstanceEditor.tsx @@ -16,7 +16,7 @@ export interface MetadataInstanceEditorProps { template: MetadataTemplateInstance; onSubmit: (values: FormValues, operations: JSONPatchOperations) => Promise; setIsUnsavedChangesModalOpen: (isUnsavedChangesModalOpen: boolean) => void; - onUnsavedChangesModalCancel: () => void; + onDiscardUnsavedChanges: () => void; } const MetadataInstanceEditor: React.FC = ({ @@ -28,15 +28,19 @@ const MetadataInstanceEditor: React.FC = ({ setIsUnsavedChangesModalOpen, template, onCancel, - onUnsavedChangesModalCancel, + onDiscardUnsavedChanges, }) => { const handleCancel = () => { onCancel(); }; return ( - + Promise.resolve([])} + isAiSuggestionsFeatureEnabled={isBoxAiSuggestionsEnabled} + > = ({ onSubmit={onSubmit} setIsUnsavedChangesModalOpen={setIsUnsavedChangesModalOpen} onDelete={onDelete} - onUnsavedChangesModalCancel={onUnsavedChangesModalCancel} + onDiscardUnsavedChanges={onDiscardUnsavedChanges} /> ); diff --git a/src/elements/content-sidebar/MetadataSidebarRedesign.tsx b/src/elements/content-sidebar/MetadataSidebarRedesign.tsx index 1fdb50157d..9addb817bd 100644 --- a/src/elements/content-sidebar/MetadataSidebarRedesign.tsx +++ b/src/elements/content-sidebar/MetadataSidebarRedesign.tsx @@ -93,15 +93,25 @@ function MetadataSidebarRedesign({ const [pendingTemplateToEdit, setPendingTemplateToEdit] = React.useState(null); React.useEffect(() => { - setSelectedTemplates(templateInstances); - }, [templateInstances, templateInstances.length]); + // disable only pre-existing template instances from dropdown if not editing or editing pre-exiting one + const isEditingTemplateAlreadyExisting = + editingTemplate && + templateInstances.some( + t => t.templateKey === editingTemplate.templateKey && t.scope === editingTemplate.scope, + ); + + if (!editingTemplate || isEditingTemplateAlreadyExisting) { + setSelectedTemplates(templateInstances); + } else { + setSelectedTemplates([...templateInstances, editingTemplate]); + } + }, [editingTemplate, templateInstances, templateInstances.length]); const handleTemplateSelect = (selectedTemplate: MetadataTemplate) => { if (editingTemplate) { setPendingTemplateToEdit(convertTemplateToTemplateInstance(file, selectedTemplate)); setIsUnsavedChangesModalOpen(true); } else { - setSelectedTemplates([...selectedTemplates, selectedTemplate]); setEditingTemplate(convertTemplateToTemplateInstance(file, selectedTemplate)); setIsDeleteButtonDisabled(true); } @@ -109,21 +119,20 @@ function MetadataSidebarRedesign({ const handleCancel = () => { setEditingTemplate(null); - setSelectedTemplates(templateInstances); }; - const handleCancelUnsavedChanges = () => { + const handleDiscardUnsavedChanges = () => { // check if user tried to edit another template before unsaved changes modal if (pendingTemplateToEdit) { setEditingTemplate(pendingTemplateToEdit); - setSelectedTemplates([...templateInstances, pendingTemplateToEdit]); setIsDeleteButtonDisabled(true); setPendingTemplateToEdit(null); - setIsUnsavedChangesModalOpen(false); } else { handleCancel(); } + + setIsUnsavedChangesModalOpen(false); }; const handleDeleteInstance = async (metadataInstance: MetadataTemplateInstance) => { @@ -190,7 +199,7 @@ function MetadataSidebarRedesign({ isDeleteButtonDisabled={isDeleteButtonDisabled} isUnsavedChangesModalOpen={isUnsavedChangesModalOpen} onCancel={handleCancel} - onUnsavedChangesModalCancel={handleCancelUnsavedChanges} + onDiscardUnsavedChanges={handleDiscardUnsavedChanges} onSubmit={handleSubmit} onDelete={handleDeleteInstance} template={editingTemplate} diff --git a/src/elements/content-sidebar/__tests__/MetadataInstanceEditor.test.tsx b/src/elements/content-sidebar/__tests__/MetadataInstanceEditor.test.tsx index a16ee03ba6..45e6be2ad7 100644 --- a/src/elements/content-sidebar/__tests__/MetadataInstanceEditor.test.tsx +++ b/src/elements/content-sidebar/__tests__/MetadataInstanceEditor.test.tsx @@ -5,7 +5,7 @@ import { screen, render } from '../../../test-utils/testing-library'; import MetadataInstanceEditor, { MetadataInstanceEditorProps } from '../MetadataInstanceEditor'; const mockOnCancel = jest.fn(); -const mockOnUnsavedChangesModalCancel = jest.fn(); +const mockOnDiscardUnsavedChanges = jest.fn(); const mockSetIsUnsavedChangesModalOpen = jest.fn(); describe('MetadataInstanceEditor', () => { @@ -57,7 +57,7 @@ describe('MetadataInstanceEditor', () => { onDelete: jest.fn(), onSubmit: jest.fn(), setIsUnsavedChangesModalOpen: mockSetIsUnsavedChangesModalOpen, - onUnsavedChangesModalCancel: mockOnUnsavedChangesModalCancel, + onDiscardUnsavedChanges: mockOnDiscardUnsavedChanges, }; test('should render MetadataInstanceForm with correct props', () => { @@ -108,7 +108,7 @@ describe('MetadataInstanceEditor', () => { expect(mockOnCancel).toHaveBeenCalled(); }); - test('Should call onUnsavedChangesModalCancel instead onCancel when canceling through UnsavedChangesModal', async () => { + test('Should call onDiscardUnsavedChanges instead onCancel when canceling through UnsavedChangesModal', async () => { const props: MetadataInstanceEditorProps = { ...defaultProps, template: mockCustomMetadataTemplateWithField, @@ -127,10 +127,10 @@ describe('MetadataInstanceEditor', () => { const unsavedChangesModal = await findByText('Unsaved Changes'); expect(unsavedChangesModal).toBeInTheDocument(); - const unsavedChangesModalCancelButton = await findByRole('button', { name: 'Cancel' }); + const unsavedChangesModalDiscardButton = await findByRole('button', { name: 'Discard Changes' }); - await userEvent.click(unsavedChangesModalCancelButton); + await userEvent.click(unsavedChangesModalDiscardButton); - expect(mockOnUnsavedChangesModalCancel).toHaveBeenCalled(); + expect(mockOnDiscardUnsavedChanges).toHaveBeenCalled(); }); }); diff --git a/src/elements/content-sidebar/stories/tests/MetadataSidebarRedesign-visual.stories.tsx b/src/elements/content-sidebar/stories/tests/MetadataSidebarRedesign-visual.stories.tsx index 440853d26a..09d555ff7e 100644 --- a/src/elements/content-sidebar/stories/tests/MetadataSidebarRedesign-visual.stories.tsx +++ b/src/elements/content-sidebar/stories/tests/MetadataSidebarRedesign-visual.stories.tsx @@ -278,3 +278,60 @@ export const MetadataInstanceEditorAddTemplateAgainAfterCancel: StoryObj = { + args: { + fileId: '416047501580', + metadataSidebarProps: defaultMetadataSidebarProps, + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + + // open and edit a new template + const addTemplateButton = await canvas.findByRole('button', { name: 'Add template' }, { timeout: 5000 }); + + await userEvent.click(addTemplateButton); + + const templateMetadataOption = canvas.getByRole('option', { name: 'My Template' }); + + await userEvent.click(templateMetadataOption); + + const input = await canvas.findByRole('textbox'); + + await userEvent.type(input, 'Lorem ipsum dolor.'); + + // open another template while editing the first one (with discarding changes) + await userEvent.click(addTemplateButton); + + const templateMetadataOptionA = canvas.getByRole('option', { name: 'My Template' }); + const templateMetadataOptionB = canvas.getByRole('option', { name: 'Virus Scan' }); + + expect(templateMetadataOptionA).toHaveAttribute('aria-disabled'); + expect(templateMetadataOptionB).not.toHaveAttribute('aria-disabled'); + + await userEvent.click(templateMetadataOptionB); + + const unsavedChangesModal = await screen.findByRole( + 'heading', + { level: 2, name: 'Unsaved Changes' }, + { timeout: 5000 }, + ); + expect(unsavedChangesModal).toBeInTheDocument(); + + const unsavedChangesModalDiscardButton = await screen.findByRole('button', { name: 'Discard Changes' }); + + await userEvent.click(unsavedChangesModalDiscardButton); + + const newTemplateHeader = await canvas.findByRole('heading', { name: 'Virus Scan' }); + expect(newTemplateHeader).toBeInTheDocument(); + + // check if template buttons disabled correctly after switching editors + await userEvent.click(addTemplateButton); + + const templateMetadataOptionAAfterSwitch = canvas.getByRole('option', { name: 'My Template' }); + const templateMetadataOptionBAfterSwitch = canvas.getByRole('option', { name: 'Virus Scan' }); + + expect(templateMetadataOptionAAfterSwitch).not.toHaveAttribute('aria-disabled'); + expect(templateMetadataOptionBAfterSwitch).toHaveAttribute('aria-disabled'); + }, +}; diff --git a/src/test-utils/testing-library.tsx b/src/test-utils/testing-library.tsx index d42dc1a266..c8014de528 100644 --- a/src/test-utils/testing-library.tsx +++ b/src/test-utils/testing-library.tsx @@ -9,7 +9,7 @@ import { AutofillContextProvider } from '@box/metadata-editor'; jest.unmock('react-intl'); const Wrapper = ({ children }) => ( - + Promise.resolve([])}> {children} diff --git a/yarn.lock b/yarn.lock index 1ec64e09e3..ac7f49aae7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1569,10 +1569,10 @@ resolved "https://registry.yarnpkg.com/@box/languages/-/languages-1.1.2.tgz#cd4266b3da62da18560d881e10b429653186be29" integrity sha512-d64TGosx+KRmrLZj4CIyLp42LUiEbgBJ8n8cviMQwTJmfU0g+UwZqLjmQZR1j+Q9D64yV4xHzY9K1t5nInWWeQ== -"@box/metadata-editor@^0.54.0": - version "0.54.0" - resolved "https://registry.yarnpkg.com/@box/metadata-editor/-/metadata-editor-0.54.0.tgz#699266252fca2b776eb9df814b158d988e719f01" - integrity sha512-Og7w6DsoDoXpuujDcnrlTWiTmVONuuWDwDitcrXflNkxsx9ZVvgTFwKD9SVj2yW5HjMM5X46X1qHYDe40qDXkg== +"@box/metadata-editor@^0.61.1": + version "0.61.2" + resolved "https://registry.yarnpkg.com/@box/metadata-editor/-/metadata-editor-0.61.2.tgz#b8e66f3e822531ae57463cfdd8be89dd9cabdb21" + integrity sha512-Ot+cGU+benO0ChRrKWahvJmHMgxKTp4nuNr+VypnQtTt63hGCn1F8TbZ2aQNRtPikhoGCAiOwYspG8fRKJ/Fmw== "@box/react-virtualized@9.22.3-rc-box.9": version "9.22.3-rc-box.9"