From b4e4b01fefea753d21a4aa75dccde92fce05af21 Mon Sep 17 00:00:00 2001 From: Dawid Jankowiak Date: Wed, 18 Sep 2024 23:38:29 +0200 Subject: [PATCH] feat(metadata-sidebar): Add metadata instance list (#3664) * feat(metadata-sidebar): add metadata empty state to metadata sidebar redesign (#3605) * chore(content-sidebar): Temporarily remove files while we be working with not yet publish internal library. Will remove this commit after the library will become publicly available on NPM and added to BUIE. feat(metadata-sidebar): MetadataEmptyState feat(metadata-sidebar): comment api to pass test feat(metadata-sidebar): uncomment comments feat(metadata-sidebar): add states feat(metadata-sidebar): tests update feat(metadata-sidebar): PR comments feat(metadata-sidebar): delete git add . in showEditor feat(metadata-sidebar): PR comments feat(metadata-sidebar): storybook tests feat(metadata-sidebar): update unit tests feat(metadata-sidebar): pr comments feat(metadata-sidebar): variables name changes feat(metadata-sidebar): PR comments feat(metadata-sidebar): PR comments feat(metadata-sidebar): global token change and status enum feat(metadata-sidebar): global token update feat(metadata-sidebar): global variables and enum upper case change feat(metadata-sidebar): PR comments feat(metadata-sidebar): styles import and title deletion feat(metadata-sidebar): convert type to interface * feat(metadata-sidebar): missing tests after rebase --------- Co-authored-by: Dawid Jankowiak * feat(metadata-sidebar): Display MetadataInstanceList * feat(metadata-sidebar): Add stories for AddTemplateDropdownMenu Plus fix syncing when some metadata template instances are already loaded * feat(metadata-sidebar): Move TooltipProvider to ContentSidebar * feat(metadata-sidebar): Add unit test for showing MetadataInstanceList * feat(metadata-sidebar): Update stories * feat(metadata-sidebar): Reorganize stories * feat(metadata-sidebar): Handle edition cancelation * feat(metadata-sidebar): bump metadata-editor version * feat(metadata-sidebar): Don't show list when metadata is being edited * feat(metadata-sidebar): Bump @box/metadata-editor version * feat(metadata-sidebar): Fix tests * feat(metadata-sidebar): Update story name * feat(metadata-sidebar): Remove data-testid attribute And timeout * feat(metadata-sidebar): Use `getByRole` for test expect Co-authored-by: greg-in-a-box <103291617+greg-in-a-box@users.noreply.github.com> * feat(metadata-editor): Reorganize showing editor views + move AutofillContextProvider where it's needed --------- Co-authored-by: karolinaru <91914885+karolinaru@users.noreply.github.com> Co-authored-by: greg-in-a-box <103291617+greg-in-a-box@users.noreply.github.com> --- package.json | 14 +- .../content-sidebar/ContentSidebar.js | 61 +++--- .../MetadataInstanceEditor.tsx | 25 +-- .../MetadataSidebarRedesign.tsx | 65 ++++--- .../__tests__/MetadataInstanceEditor.test.tsx | 15 +- .../MetadataSidebarRedesign.test.tsx | 44 ++++- .../hooks/useSidebarMetadataFetcher.ts | 2 +- .../MetadataSidebarRedesign.stories.tsx | 39 +--- ...MetadataSidebarRedesign-visual.stories.tsx | 183 ++++++++++++++---- src/test-utils/testing-library.tsx | 9 +- yarn.lock | 8 +- 11 files changed, 298 insertions(+), 167 deletions(-) diff --git a/package.json b/package.json index 253350f1b5..09055c2748 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.46.1", + "@box/metadata-editor": "^0.50.5", "@box/react-virtualized": "9.22.3-rc-box.9", "@cfaester/enzyme-adapter-react-18": "^0.8.0", "@chromatic-com/storybook": "^1.6.1", @@ -304,7 +308,7 @@ "@box/blueprint-web-assets": "^4.21.0", "@box/box-ai-content-answers": "^0.49.1", "@box/cldr-data": ">=34.2.0", - "@box/metadata-editor": "^0.46.1", + "@box/metadata-editor": "^0.50.5", "@box/react-virtualized": "9.22.3-rc-box.9", "@hapi/address": "^2.1.4", "axios": "^0.25.0", @@ -362,6 +366,8 @@ } }, "msw": { - "workerDirectory": [".storybook/public"] + "workerDirectory": [ + ".storybook/public" + ] } } diff --git a/src/elements/content-sidebar/ContentSidebar.js b/src/elements/content-sidebar/ContentSidebar.js index 327e74ca24..1288b8cd03 100644 --- a/src/elements/content-sidebar/ContentSidebar.js +++ b/src/elements/content-sidebar/ContentSidebar.js @@ -8,6 +8,7 @@ import 'regenerator-runtime/runtime'; import * as React from 'react'; import noop from 'lodash/noop'; import flow from 'lodash/flow'; +import { TooltipProvider } from '@box/blueprint-web'; import type { RouterHistory } from 'react-router-dom'; import API from '../../api'; import APIContext from '../common/api-context'; @@ -359,35 +360,37 @@ class ContentSidebar extends React.Component { - { - this.sidebarRef = ref; - }} - /> + + { + this.sidebarRef = ref; + }} + /> + diff --git a/src/elements/content-sidebar/MetadataInstanceEditor.tsx b/src/elements/content-sidebar/MetadataInstanceEditor.tsx index 709e9a47fb..bda6322443 100644 --- a/src/elements/content-sidebar/MetadataInstanceEditor.tsx +++ b/src/elements/content-sidebar/MetadataInstanceEditor.tsx @@ -1,34 +1,32 @@ -import { - MetadataInstanceForm, - type MetadataTemplateInstance, - UnsavedChangesModal, - type MetadataTemplate, -} from '@box/metadata-editor'; +import { AutofillContextProvider, MetadataInstanceForm, type MetadataTemplateInstance } from '@box/metadata-editor'; +import noop from 'lodash/noop'; import React from 'react'; export interface MetadataInstanceEditorProps { isBoxAiSuggestionsEnabled: boolean; isUnsavedChangesModalOpen: boolean; - template: MetadataTemplateInstance | MetadataTemplate; + template: MetadataTemplateInstance; + onCancel: () => void; } const MetadataInstanceEditor: React.FC = ({ isBoxAiSuggestionsEnabled, isUnsavedChangesModalOpen, template, + onCancel, }) => { const handleSubmit = () => { // TODO in a future PR }; const handleCancel = () => { - // TODO in a future PR + onCancel(); }; const handleDelete = () => { // TODO in a future PR }; return ( - <> + = ({ onCancel={handleCancel} onDelete={handleDelete} onSubmit={handleSubmit} + isUnsavedChangesModalOpen={isUnsavedChangesModalOpen} + setIsUnsavedChangesModalOpen={noop} /> - - + ); }; diff --git a/src/elements/content-sidebar/MetadataSidebarRedesign.tsx b/src/elements/content-sidebar/MetadataSidebarRedesign.tsx index 48e817338f..10f6372b85 100644 --- a/src/elements/content-sidebar/MetadataSidebarRedesign.tsx +++ b/src/elements/content-sidebar/MetadataSidebarRedesign.tsx @@ -9,9 +9,11 @@ import { InlineError, LoadingIndicator } from '@box/blueprint-web'; import { AddMetadataTemplateDropdown, MetadataEmptyState, + MetadataInstanceList, type MetadataTemplateInstance, type MetadataTemplate, } from '@box/metadata-editor'; +import noop from 'lodash/noop'; import API from '../../api'; import SidebarContent from './SidebarContent'; @@ -67,26 +69,27 @@ function MetadataSidebarRedesign({ onError, isFeatureEnabled, }: MetadataSidebarRedesignProps) { - const { formatMessage } = useIntl(); - - const [selectedTemplates, setSelectedTemplates] = React.useState>([]); - const [editingTemplate, setEditingTemplate] = React.useState( - null, - ); - const [isUnsavedChangesModalOpen, setIsUnsavedChangesModalOpen] = React.useState(false); - const { file, templates, errorMessage, status, templateInstances } = useSidebarMetadataFetcher( api, fileId, onError, isFeatureEnabled, ); + const { formatMessage } = useIntl(); + const [editingTemplate, setEditingTemplate] = React.useState(null); + const [isUnsavedChangesModalOpen, setIsUnsavedChangesModalOpen] = React.useState(false); + const [selectedTemplates, setSelectedTemplates] = + React.useState>(templateInstances); + + React.useEffect(() => { + setSelectedTemplates(templateInstances); + }, [templateInstances]); const handleUnsavedChanges = () => { setIsUnsavedChangesModalOpen(true); }; - const handleTemplateSelect = (selectedTemplate: MetadataTemplate) => { + const handleTemplateSelect = (selectedTemplate: MetadataTemplateInstance) => { setSelectedTemplates([...selectedTemplates, selectedTemplate]); setEditingTemplate(selectedTemplate); }; @@ -94,9 +97,11 @@ function MetadataSidebarRedesign({ const metadataDropdown = status === STATUS.SUCCESS && templates && ( { - editingTemplate ? handleUnsavedChanges() : handleTemplateSelect(selectedTemplate); + editingTemplate + ? handleUnsavedChanges() + : handleTemplateSelect(selectedTemplate as MetadataTemplateInstance); }} /> ); @@ -108,7 +113,11 @@ function MetadataSidebarRedesign({ ); const showTemplateInstances = file && templates && templateInstances; - const showEmptyState = showTemplateInstances && templateInstances.length === 0 && !editingTemplate; + + const showLoading = status === STATUS.LOADING; + const showEmptyState = !showLoading && showTemplateInstances && templateInstances.length === 0 && !editingTemplate; + const showEditor = !showEmptyState && editingTemplate; + const showList = !showEditor && templateInstances.length > 0 && !editingTemplate; return (
{errorMessageDisplay} - {status === STATUS.LOADING && ( - - )} - {showEmptyState ? ( + {showLoading && } + {showEmptyState && ( - ) : ( - editingTemplate && ( - - ) + )} + {editingTemplate && ( + setEditingTemplate(null)} + /> + )} + {showList && ( + { + setEditingTemplate(templateInstance); + }} + onEditWithAutofill={noop} + templateInstances={templateInstances} + /> )}
diff --git a/src/elements/content-sidebar/__tests__/MetadataInstanceEditor.test.tsx b/src/elements/content-sidebar/__tests__/MetadataInstanceEditor.test.tsx index 04cd139b7c..b9b7789031 100644 --- a/src/elements/content-sidebar/__tests__/MetadataInstanceEditor.test.tsx +++ b/src/elements/content-sidebar/__tests__/MetadataInstanceEditor.test.tsx @@ -1,30 +1,35 @@ import React from 'react'; -import { type MetadataTemplate } from '@box/metadata-editor'; +import { type MetadataTemplateInstance } from '@box/metadata-editor'; import { screen, render } from '../../../test-utils/testing-library'; import MetadataInstanceEditor, { MetadataInstanceEditorProps } from '../MetadataInstanceEditor'; describe('MetadataInstanceEditor', () => { - const mockCustomMetadataTemplate: MetadataTemplate = { + const mockCustomMetadataTemplate: MetadataTemplateInstance = { id: 'template-id', fields: [], scope: 'global', templateKey: 'properties', type: 'template-id', hidden: false, + canEdit: true, }; - const mockMetadataTemplate: MetadataTemplate = { ...mockCustomMetadataTemplate, displayName: 'Template Name' }; + const mockMetadataTemplateInstance: MetadataTemplateInstance = { + ...mockCustomMetadataTemplate, + displayName: 'Template Name', + }; const defaultProps: MetadataInstanceEditorProps = { isBoxAiSuggestionsEnabled: true, isUnsavedChangesModalOpen: false, - template: mockMetadataTemplate, + template: mockMetadataTemplateInstance, + onCancel: jest.fn(), }; test('should render MetadataInstanceForm with correct props', () => { render(); - const templateHeader = screen.getByText(mockMetadataTemplate.displayName); + const templateHeader = screen.getByText(mockMetadataTemplateInstance.displayName); expect(templateHeader).toBeInTheDocument(); }); diff --git a/src/elements/content-sidebar/__tests__/MetadataSidebarRedesign.test.tsx b/src/elements/content-sidebar/__tests__/MetadataSidebarRedesign.test.tsx index a58ef62770..8c53c2f199 100644 --- a/src/elements/content-sidebar/__tests__/MetadataSidebarRedesign.test.tsx +++ b/src/elements/content-sidebar/__tests__/MetadataSidebarRedesign.test.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { userEvent } from '@testing-library/user-event'; -import { type MetadataTemplate } from '@box/metadata-editor'; +import { type MetadataTemplate, type MetadataTemplateInstance } from '@box/metadata-editor'; import { FIELD_PERMISSIONS_CAN_UPLOAD } from '../../../constants'; import { screen, render } from '../../../test-utils/testing-library'; import { @@ -26,6 +26,29 @@ describe('elements/content-sidebar/Metadata/MetadataSidebarRedesign', () => { }, ]; + const mockCustomTemplateInstance = { + canEdit: true, + hidden: false, + id: 'metadata_template_42', + fields: [ + { + key: 'Another testing key', + type: 'string', + value: '42', + hidden: false, + }, + { + key: 'Test key', + type: 'string', + value: 'Some test value', + hidden: false, + }, + ], + scope: 'global', + templateKey: 'properties', + type: 'properties', + } satisfies MetadataTemplateInstance; + const mockFile = { id: '123', permissions: { [FIELD_PERMISSIONS_CAN_UPLOAD]: true }, @@ -117,7 +140,7 @@ describe('elements/content-sidebar/Metadata/MetadataSidebarRedesign', () => { renderComponent(); expect(screen.getByRole('heading', { level: 3, name: 'Metadata' })).toBeInTheDocument(); - expect(screen.getByTestId('loading')).toBeInTheDocument(); + expect(screen.getByRole('status', { name: 'Loading' })).toBeInTheDocument(); expect(screen.getByRole('status', { name: 'Loading' })).toBeInTheDocument(); }); @@ -138,4 +161,21 @@ describe('elements/content-sidebar/Metadata/MetadataSidebarRedesign', () => { screen.getByText('Add Metadata to your file to support business operations, workflows, and more!'), ).toBeInTheDocument(); }); + + test('should render metadata instance list when templates are present', () => { + mockUseSidebarMetadataFetcher.mockReturnValue({ + templateInstances: [mockCustomTemplateInstance], + templates: mockTemplates, + errorMessage: null, + status: STATUS.SUCCESS, + file: mockFile, + }); + + renderComponent(); + + expect(screen.getByRole('heading', { level: 3, name: 'Metadata' })).toBeInTheDocument(); + expect(screen.getByRole('heading', { level: 1, name: 'Custom Metadata' })).toBeInTheDocument(); + expect(screen.getByText(mockCustomTemplateInstance.fields[0].key)).toBeInTheDocument(); + expect(screen.getByText(mockCustomTemplateInstance.fields[1].key)).toBeInTheDocument(); + }); }); diff --git a/src/elements/content-sidebar/hooks/useSidebarMetadataFetcher.ts b/src/elements/content-sidebar/hooks/useSidebarMetadataFetcher.ts index 182ec33281..a8513a43e6 100644 --- a/src/elements/content-sidebar/hooks/useSidebarMetadataFetcher.ts +++ b/src/elements/content-sidebar/hooks/useSidebarMetadataFetcher.ts @@ -5,7 +5,7 @@ import { type MetadataTemplate, type MetadataTemplateInstance } from '@box/metad import API from '../../../api'; import { type ElementsXhrError } from '../../../common/types/api'; import { isUserCorrectableError } from '../../../utils/error'; -import { FIELD_IS_EXTERNALLY_OWNED, FIELD_PERMISSIONS, FIELD_PERMISSIONS_CAN_UPLOAD } from '../../../constants'; +import { FIELD_IS_EXTERNALLY_OWNED, FIELD_PERMISSIONS, FIELD_PERMISSIONS_CAN_UPLOAD} from '../../../constants'; import messages from '../../common/messages'; diff --git a/src/elements/content-sidebar/stories/MetadataSidebarRedesign.stories.tsx b/src/elements/content-sidebar/stories/MetadataSidebarRedesign.stories.tsx index e569d3ea89..246e89be8b 100644 --- a/src/elements/content-sidebar/stories/MetadataSidebarRedesign.stories.tsx +++ b/src/elements/content-sidebar/stories/MetadataSidebarRedesign.stories.tsx @@ -5,7 +5,6 @@ import MetadataSidebarRedesign from '../MetadataSidebarRedesign'; import ContentSidebar from '../ContentSidebar'; const fileIdWithMetadata = global.FILE_ID; -const fileIdWithNoMetadata = '416047501580'; const mockFeatures = { 'metadata.redesign.enabled': true, }; @@ -38,47 +37,11 @@ export default { }, }; -export const AddTemplateDropdownMenu: StoryObj = {}; - -export const EmptyStateWithBoxAiEnabled: StoryObj = { - args: { - fileId: fileIdWithNoMetadata, - metadataSidebarProps: { - ...defaultMetadataSidebarProps, - }, - }, -}; - -export const EmptyStateWithBoxAiDisabled: StoryObj = { - args: { - fileId: fileIdWithNoMetadata, - metadataSidebarProps: { - ...defaultMetadataSidebarProps, - isBoxAiSuggestionsEnabled: false, - }, - }, -}; - -export const MetadataInstanceEditorWithDefinedTemplate: StoryObj = { +export const Basic: StoryObj = { play: async ({ canvasElement }) => { const canvas = within(canvasElement); const addTemplateButton = await canvas.findByRole('button', { name: 'Add template' }, { timeout: 5000 }); await userEvent.click(addTemplateButton); - - const customMetadataOption = canvas.getByRole('option', { name: 'Select Dropdowns' }); - await userEvent.click(customMetadataOption); - }, -}; - -export const MetadataInstanceEditorWithCustomTemplate: StoryObj = { - play: async ({ canvasElement }) => { - const canvas = within(canvasElement); - - const addTemplateButton = await canvas.findByRole('button', { name: 'Add template' }, { timeout: 5000 }); - await userEvent.click(addTemplateButton); - - const customMetadataOption = canvas.getByRole('option', { name: 'Custom Metadata' }); - await userEvent.click(customMetadataOption); }, }; 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 e0057f4064..20308a884f 100644 --- a/src/elements/content-sidebar/stories/tests/MetadataSidebarRedesign-visual.stories.tsx +++ b/src/elements/content-sidebar/stories/tests/MetadataSidebarRedesign-visual.stories.tsx @@ -1,39 +1,71 @@ +import { type ComponentProps } from 'react'; import { expect, userEvent, within, fn, screen } from '@storybook/test'; import { type StoryObj } from '@storybook/react'; import { defaultVisualConfig } from '../../../../utils/storybook'; import ContentSidebar from '../../ContentSidebar'; import MetadataSidebarRedesign from '../../MetadataSidebarRedesign'; -export const Basic = { +const fileIdWithMetadata = global.FILE_ID; +const fileWithoutMetadata = '416047501580'; +const token = global.TOKEN; + +const defaultMetadataArgs = { + fileId: fileIdWithMetadata, + isFeatureEnabled: true, + onError: fn, +}; +const defaultMetadataSidebarProps: ComponentProps = { + isBoxAiSuggestionsEnabled: true, + isFeatureEnabled: true, + onError: fn, +}; +const mockFeatures = { + 'metadata.redesign.enabled': true, +}; +const mockLogger = { + onReadyMetric: ({ endMarkName }) => { + // eslint-disable-next-line no-console + console.log(`Logger: onReadyMetric called with endMarkName: ${endMarkName}`); + }, +}; + +export const AddTemplateDropdownMenuOn = { play: async ({ canvasElement }) => { const canvas = within(canvasElement); - await canvas.findByRole('heading', { name: 'Metadata' }); - const addTemplateButton = await canvas.findByRole('button', { name: 'Add template' }); + + const addTemplateButton = await canvas.findByRole('button', { name: 'Add template' }, { timeout: 5000 }); expect(addTemplateButton).toBeInTheDocument(); await userEvent.click(addTemplateButton); const customMetadataOption = canvas.getByRole('option', { name: 'Custom Metadata' }); expect(customMetadataOption).toBeInTheDocument(); + expect(customMetadataOption).toHaveAttribute('aria-disabled'); }, }; -const fileIdWithMetadata = global.FILE_ID; -const token = global.TOKEN; -const mockFeatures = { - 'metadata.redesign.enabled': true, -}; -const mockLogger = { - onReadyMetric: ({ endMarkName }) => { - // eslint-disable-next-line no-console - console.log(`Logger: onReadyMetric called with endMarkName: ${endMarkName}`); +export const AddTemplateDropdownMenuOnEmpty = { + args: { + fileId: '416047501580', + metadataSidebarProps: { + isBoxAiSuggestionsEnabled: true, + isFeatureEnabled: true, + onError: fn, + }, }, -}; + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + const addTemplateButton = await canvas.findByRole('button', { name: 'Add template' }, { timeout: 5000 }); -const defaultMetadataArgs = { - fileId: fileIdWithMetadata, - isFeatureEnabled: true, - onError: fn, + expect(addTemplateButton).toBeInTheDocument(); + await userEvent.click(addTemplateButton); + + const options = canvas.getAllByRole('option'); + expect(options).toHaveLength(4); + options.forEach(option => { + expect(option).not.toHaveAttribute('disabled'); + }); + }, }; export default { @@ -54,64 +86,131 @@ export default { }, }; -export const MetadataInstanceEditorWithCustomMetadata: StoryObj = { +export const AddingNewMetadataTemplate: StoryObj = { play: async ({ canvasElement }) => { const canvas = within(canvasElement); - const heading = await canvas.findByRole('heading', { name: 'Metadata' }); - expect(heading).toBeInTheDocument(); - const addTemplateButton = await canvas.findByRole('button', { name: 'Add template' }); + const addTemplateButton = await canvas.findByRole('button', { name: 'Add template' }, { timeout: 5000 }); expect(addTemplateButton).toBeInTheDocument(); await userEvent.click(addTemplateButton); - const customMetadataOption = canvas.getByRole('option', { name: 'Custom Metadata' }); + const customMetadataOption = canvas.getByRole('option', { name: 'Virus Scan' }); expect(customMetadataOption).toBeInTheDocument(); await userEvent.click(customMetadataOption); - const templateHeader = await canvas.findByRole('heading', { name: 'Custom Metadata' }); + const templateHeader = await canvas.findByRole('heading', { name: 'Virus Scan' }); expect(templateHeader).toBeInTheDocument(); }, }; -export const MetadataInstanceEditorWithTemplate: StoryObj = { +export const UnsavedChangesModalWhenChoosingDifferentTemplate: StoryObj = { + args: { + fileId: '416047501580', + metadataSidebarProps: defaultMetadataSidebarProps, + }, play: async ({ canvasElement }) => { const canvas = within(canvasElement); - const heading = await canvas.findByRole('heading', { name: 'Metadata' }); - expect(heading).toBeInTheDocument(); - const addTemplateButton = await canvas.findByRole('button', { name: 'Add template' }); + const addTemplateButton = await canvas.findByRole('button', { name: 'Add template' }, { timeout: 5000 }); expect(addTemplateButton).toBeInTheDocument(); await userEvent.click(addTemplateButton); - const customMetadataOption = canvas.getByRole('option', { name: 'My Template' }); + const customMetadataOption = canvas.getByRole('option', { name: 'Virus Scan' }); expect(customMetadataOption).toBeInTheDocument(); await userEvent.click(customMetadataOption); - const templateHeader = await canvas.findByRole('heading', { name: 'My Template' }); - expect(templateHeader).toBeInTheDocument(); + await userEvent.click(addTemplateButton); + const myTemplateOption = canvas.getByRole('option', { name: 'My Template' }); + expect(myTemplateOption).toBeInTheDocument(); + await userEvent.click(myTemplateOption); + + const unsavedChangesModal = await screen.findByRole( + 'heading', + { level: 2, name: 'Unsaved Changes' }, + { timeout: 5000 }, + ); + expect(unsavedChangesModal).toBeInTheDocument(); }, }; -export const UnsavedChangesModalWhenChoosingDifferentTemplate: StoryObj = { +export const EmptyStateWithBoxAiEnabled: StoryObj = { + args: { + fileId: fileWithoutMetadata, + metadataSidebarProps: { + ...defaultMetadataSidebarProps, + }, + }, +}; + +export const EmptyStateWithBoxAiDisabled: StoryObj = { + args: { + fileId: fileWithoutMetadata, + metadataSidebarProps: { + ...defaultMetadataSidebarProps, + isBoxAiSuggestionsEnabled: false, + }, + }, +}; + +export const MetadataInstanceEditorWithDefinedTemplate: StoryObj = { + args: { + fileId: '416047501580', + metadataSidebarProps: defaultMetadataSidebarProps, + }, play: async ({ canvasElement }) => { const canvas = within(canvasElement); - const heading = await canvas.findByRole('heading', { name: 'Metadata' }); - expect(heading).toBeInTheDocument(); - const addTemplateButton = await canvas.findByRole('button', { name: 'Add template' }); - expect(addTemplateButton).toBeInTheDocument(); + const addTemplateButton = await canvas.findByRole('button', { name: 'Add template' }, { timeout: 5000 }); await userEvent.click(addTemplateButton); - const customMetadataOption = canvas.getByRole('option', { name: 'Custom Metadata' }); - expect(customMetadataOption).toBeInTheDocument(); + const customMetadataOption = canvas.getByRole('option', { name: 'My Template' }); await userEvent.click(customMetadataOption); + }, +}; +export const MetadataInstanceEditorWithCustomTemplate: StoryObj = { + args: { + fileId: '416047501580', + metadataSidebarProps: defaultMetadataSidebarProps, + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + + const addTemplateButton = await canvas.findByRole('button', { name: 'Add template' }, { timeout: 5000 }); await userEvent.click(addTemplateButton); - const myTemplateOption = canvas.getByRole('option', { name: 'My Template' }); - expect(myTemplateOption).toBeInTheDocument(); - await userEvent.click(myTemplateOption); - const unsavedChangesModal = screen.getByText('Unsaved Changes'); - expect(unsavedChangesModal).toBeInTheDocument(); + const customMetadataOption = canvas.getByRole('option', { name: 'Custom Metadata' }); + await userEvent.click(customMetadataOption); + }, +}; + +export const MetadataInstanceEditorCancelChanges: StoryObj = { + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + + const editButtons = await canvas.findAllByRole('button', { name: 'Edit' }, { timeout: 5000 }); + + let headlines = await canvas.findAllByRole('heading', { level: 1 }); + expect(headlines).toHaveLength(3); + expect(headlines.map(heading => heading.textContent)).toEqual( + expect.arrayContaining(['My Template', 'Select Dropdowns', 'Custom Metadata']), + ); + + // go to edit mode - only edited template is visible + await userEvent.click(editButtons[0]); + + headlines = await canvas.findAllByRole('heading', { level: 1 }); + expect(headlines).toHaveLength(1); + expect(headlines.map(heading => heading.textContent)).toEqual(expect.arrayContaining(['My Template'])); + + // cancel editing - back to list view + const cancelButton = await canvas.findByRole('button', { name: 'Cancel' }); + await userEvent.click(cancelButton); + + headlines = await canvas.findAllByRole('heading', { level: 1 }); + expect(headlines).toHaveLength(3); + expect(headlines.map(heading => heading.textContent)).toEqual( + expect.arrayContaining(['My Template', 'Select Dropdowns', 'Custom Metadata']), + ); }, }; diff --git a/src/test-utils/testing-library.tsx b/src/test-utils/testing-library.tsx index 3835699eb4..d42dc1a266 100644 --- a/src/test-utils/testing-library.tsx +++ b/src/test-utils/testing-library.tsx @@ -4,13 +4,16 @@ import { render } from '@testing-library/react'; // Data Providers import { TooltipProvider } from '@box/blueprint-web'; import { IntlProvider } from 'react-intl'; +import { AutofillContextProvider } from '@box/metadata-editor'; jest.unmock('react-intl'); const Wrapper = ({ children }) => ( - - {children} - + + + {children} + + ); const renderConnected = (element, options = {}) => render(element, { wrapper: Wrapper, ...options }); diff --git a/yarn.lock b/yarn.lock index fa1df02fba..2567cc54f1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1564,10 +1564,10 @@ resolved "https://registry.yarnpkg.com/@box/languages/-/languages-1.1.2.tgz#cd4266b3da62da18560d881e10b429653186be29" integrity sha512-d64TGosx+KRmrLZj4CIyLp42LUiEbgBJ8n8cviMQwTJmfU0g+UwZqLjmQZR1j+Q9D64yV4xHzY9K1t5nInWWeQ== -"@box/metadata-editor@^0.46.1": - version "0.46.1" - resolved "https://registry.yarnpkg.com/@box/metadata-editor/-/metadata-editor-0.46.1.tgz#ea37ca31dbcbb163c2018be30755098e6a0542c9" - integrity sha512-Jw2Lp+wS1iBxt1ZNH7RP4oWfDdrPU37rr/+W0XceG1Vqqtt/kk0pDcHJoy0aoX641pQGvkYuHKu9jAH3FedVtA== +"@box/metadata-editor@^0.50.5": + version "0.50.5" + resolved "https://registry.yarnpkg.com/@box/metadata-editor/-/metadata-editor-0.50.5.tgz#7036c6027dbc8369aa7a6bfaf8a285826c8aae2c" + integrity sha512-aTfzND4yQJTuszX/LioXX1EoUHVAZFaZNKP/60bZdkQP/QAZydO0W+HyHfoDK+0P6zsqIoLpIyoZgnSuGIRDFg== "@box/react-virtualized@9.22.3-rc-box.9": version "9.22.3-rc-box.9"