From c9896a8fe5ce6f31ad5fcaf2364ff4c49c7b6462 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Chris=20Ch=C3=A1vez?= Date: Tue, 17 Jun 2025 19:58:57 -0500 Subject: [PATCH] [Teak] fix: published name in unit sidebar in container picker & Issues on Inplace Editor (#2140) Backport of fix: show unit published name in sidebar on content picker [FC-0090] #2100 Backport of fix: Issue on the Inplace editor [FC-0090] #2101 --- .../InplaceTextEditor.test.tsx | 21 ++++++++ src/generic/inplace-text-editor/index.tsx | 22 +++++---- .../LibraryAuthoringPage.tsx | 2 +- .../__mocks__/library-search.json | 12 +++-- .../component-picker/ComponentPicker.test.tsx | 20 ++++++++ .../containers/ContainerEditableTitle.tsx | 48 +++++++++++++++++++ .../containers/ContainerInfoHeader.tsx | 39 ++------------- src/library-authoring/data/api.mocks.ts | 1 + src/library-authoring/data/api.ts | 1 + src/library-authoring/data/apiHooks.ts | 2 +- 10 files changed, 117 insertions(+), 51 deletions(-) create mode 100644 src/library-authoring/containers/ContainerEditableTitle.tsx diff --git a/src/generic/inplace-text-editor/InplaceTextEditor.test.tsx b/src/generic/inplace-text-editor/InplaceTextEditor.test.tsx index c4dbc4619..9b7a55c28 100644 --- a/src/generic/inplace-text-editor/InplaceTextEditor.test.tsx +++ b/src/generic/inplace-text-editor/InplaceTextEditor.test.tsx @@ -110,4 +110,25 @@ describe('', () => { // Show original text expect(screen.getByText('Test text')).toBeInTheDocument(); }); + + it('should disappear edit button while editing', async () => { + render(); + + const title = screen.getByText('Test text'); + expect(title).toBeInTheDocument(); + + const editButton = screen.getByRole('button', { name: /edit/i }); + expect(editButton).toBeInTheDocument(); + fireEvent.click(editButton); + + const textBox = screen.getByRole('textbox'); + expect(editButton).not.toBeInTheDocument(); + + fireEvent.change(textBox, { target: { value: 'New text' } }); + fireEvent.keyDown(textBox, { key: 'Enter', code: 'Enter', charCode: 13 }); + + expect(textBox).not.toBeInTheDocument(); + expect(mockOnSave).toHaveBeenCalledWith('New text'); + expect(await screen.findByRole('button', { name: /edit/i })).toBeInTheDocument(); + }); }); diff --git a/src/generic/inplace-text-editor/index.tsx b/src/generic/inplace-text-editor/index.tsx index 89c97a2a0..cb07d2ffa 100644 --- a/src/generic/inplace-text-editor/index.tsx +++ b/src/generic/inplace-text-editor/index.tsx @@ -92,17 +92,19 @@ export const InplaceTextEditor: React.FC = ({ /> ) : ( - - {text} - + <> + + {text} + + + )} - ); }; diff --git a/src/library-authoring/LibraryAuthoringPage.tsx b/src/library-authoring/LibraryAuthoringPage.tsx index 61e33df09..b49ae07e7 100644 --- a/src/library-authoring/LibraryAuthoringPage.tsx +++ b/src/library-authoring/LibraryAuthoringPage.tsx @@ -113,7 +113,7 @@ export const SubHeaderTitle = ({ title }: { title: ReactNode }) => { const showReadOnlyBadge = readOnly && !componentPickerMode; return ( - + {title} {showReadOnlyBadge && (
diff --git a/src/library-authoring/__mocks__/library-search.json b/src/library-authoring/__mocks__/library-search.json index 9eee97031..ba27af218 100644 --- a/src/library-authoring/__mocks__/library-search.json +++ b/src/library-authoring/__mocks__/library-search.json @@ -494,7 +494,7 @@ ], "created": 1742221203.895054, "modified": 1742221203.895054, - "usage_key": "lct:Axim:TEST:unit:test-unit-9284e2", + "usage_key": "lct:org:lib:unit:test-unit-9a207", "block_type": "unit", "context_key": "lib:Axim:TEST", "org": "Axim", @@ -512,12 +512,18 @@ ], "created": "1742221203.895054", "modified": "1742221203.895054", - "usage_key": "lct:Axim:TEST:unit:test-unit-9284e2", + "usage_key": "lct:org:lib:unit:test-unit-9a207", "block_type": "unit", "context_key": "lib:Axim:TEST", "org": "Axim", "access_id": "15", - "num_children": "0" + "num_children": "0", + "published": { + "display_name": "Published Test Unit" + } + }, + "published": { + "display_name": "Published Test Unit" } } ], diff --git a/src/library-authoring/component-picker/ComponentPicker.test.tsx b/src/library-authoring/component-picker/ComponentPicker.test.tsx index 2d492adf6..cd7f497a3 100644 --- a/src/library-authoring/component-picker/ComponentPicker.test.tsx +++ b/src/library-authoring/component-picker/ComponentPicker.test.tsx @@ -14,6 +14,7 @@ import { mockGetCollectionMetadata, mockGetContentLibraryV2List, mockLibraryBlockMetadata, + mockGetContainerMetadata, } from '../data/api.mocks'; import { ComponentPicker } from './ComponentPicker'; @@ -40,6 +41,7 @@ mockContentSearchConfig.applyMock(); mockGetCollectionMetadata.applyMock(); mockGetContentLibraryV2List.applyMock(); mockLibraryBlockMetadata.applyMock(); +mockGetContainerMetadata.applyMock(); let postMessageSpy: jest.SpyInstance; @@ -99,6 +101,24 @@ describe('', () => { }, '*'); }); + it('should open the unit sidebar', async () => { + render(); + + expect(await screen.findByText('Test Library 1')).toBeInTheDocument(); + fireEvent.click(screen.getByDisplayValue(/lib:sampletaxonomyorg1:tl1/i)); + + // Wait for the content library to load + await screen.findByText(/Change Library/i); + expect(await screen.findByText('Test Library 1')).toBeInTheDocument(); + + // Click on the unit card to open the sidebar + fireEvent.click((await screen.findByText('Published Test Unit'))); + + const sidebar = await screen.findByTestId('library-sidebar'); + expect(sidebar).toBeInTheDocument(); + await waitFor(() => expect(within(sidebar).getByText('Published Test Unit')).toBeInTheDocument()); + }); + it('should pick component inside a collection using the card', async () => { render(); diff --git a/src/library-authoring/containers/ContainerEditableTitle.tsx b/src/library-authoring/containers/ContainerEditableTitle.tsx new file mode 100644 index 000000000..5a1ea0f6d --- /dev/null +++ b/src/library-authoring/containers/ContainerEditableTitle.tsx @@ -0,0 +1,48 @@ +import { useIntl } from '@edx/frontend-platform/i18n'; +import { useContext } from 'react'; +import { InplaceTextEditor } from '../../generic/inplace-text-editor'; +import { ToastContext } from '../../generic/toast-context'; +import { useLibraryContext } from '../common/context/LibraryContext'; +import { useContainer, useUpdateContainer } from '../data/apiHooks'; +import messages from './messages'; + +interface EditableTitleProps { + containerId: string; + textClassName?: string; +} + +export const ContainerEditableTitle = ({ containerId, textClassName }: EditableTitleProps) => { + const intl = useIntl(); + + const { readOnly, showOnlyPublished } = useLibraryContext(); + + const { data: container } = useContainer(containerId); + + const updateMutation = useUpdateContainer(containerId); + const { showToast } = useContext(ToastContext); + + const handleSaveDisplayName = async (newDisplayName: string) => { + try { + await updateMutation.mutateAsync({ + displayName: newDisplayName, + }); + showToast(intl.formatMessage(messages.updateContainerSuccessMsg)); + } catch (err) { + showToast(intl.formatMessage(messages.updateContainerErrorMsg)); + } + }; + + // istanbul ignore if: this should never happen + if (!container) { + return null; + } + + return ( + + ); +}; diff --git a/src/library-authoring/containers/ContainerInfoHeader.tsx b/src/library-authoring/containers/ContainerInfoHeader.tsx index 65357c0f1..c47a4ee6b 100644 --- a/src/library-authoring/containers/ContainerInfoHeader.tsx +++ b/src/library-authoring/containers/ContainerInfoHeader.tsx @@ -1,17 +1,7 @@ -import { useContext } from 'react'; -import { useIntl } from '@edx/frontend-platform/i18n'; - -import { InplaceTextEditor } from '../../generic/inplace-text-editor'; -import { ToastContext } from '../../generic/toast-context'; -import { useLibraryContext } from '../common/context/LibraryContext'; import { useSidebarContext } from '../common/context/SidebarContext'; -import { useContainer, useUpdateContainer } from '../data/apiHooks'; -import messages from './messages'; +import { ContainerEditableTitle } from './ContainerEditableTitle'; const ContainerInfoHeader = () => { - const intl = useIntl(); - - const { readOnly } = useLibraryContext(); const { sidebarComponentInfo } = useSidebarContext(); const containerId = sidebarComponentInfo?.id; @@ -20,32 +10,9 @@ const ContainerInfoHeader = () => { throw new Error('containerId is required'); } - const { data: container } = useContainer(containerId); - - const updateMutation = useUpdateContainer(containerId); - const { showToast } = useContext(ToastContext); - - const handleSaveDisplayName = async (newDisplayName: string) => { - try { - await updateMutation.mutateAsync({ - displayName: newDisplayName, - }); - showToast(intl.formatMessage(messages.updateContainerSuccessMsg)); - } catch (err) { - showToast(intl.formatMessage(messages.updateContainerErrorMsg)); - throw err; - } - }; - - if (!container) { - return null; - } - return ( - ); diff --git a/src/library-authoring/data/api.mocks.ts b/src/library-authoring/data/api.mocks.ts index 3ff35e624..92220b140 100644 --- a/src/library-authoring/data/api.mocks.ts +++ b/src/library-authoring/data/api.mocks.ts @@ -495,6 +495,7 @@ mockGetContainerMetadata.containerData = { id: 'lct:org:lib:unit:test-unit-9a2072', containerType: 'unit', displayName: 'Test Unit', + publishedDisplayName: 'Published Test Unit', created: '2024-09-19T10:00:00Z', createdBy: 'test_author', lastPublished: '2024-09-20T10:00:00Z', diff --git a/src/library-authoring/data/api.ts b/src/library-authoring/data/api.ts index 92f5d3f40..dd8a8f743 100644 --- a/src/library-authoring/data/api.ts +++ b/src/library-authoring/data/api.ts @@ -600,6 +600,7 @@ export interface Container { id: string; containerType: 'unit'; displayName: string; + publishedDisplayName: string; lastPublished: string | null; publishedBy: string | null; createdBy: string | null; diff --git a/src/library-authoring/data/apiHooks.ts b/src/library-authoring/data/apiHooks.ts index 65b9445ad..3fd1f0633 100644 --- a/src/library-authoring/data/apiHooks.ts +++ b/src/library-authoring/data/apiHooks.ts @@ -615,7 +615,7 @@ export const useUpdateContainer = (containerId: string) => { return useMutation({ mutationFn: (data: api.UpdateContainerDataRequest) => api.updateContainerMetadata(containerId, data), onMutate: (data) => { - const previousData = queryClient.getQueryData(containerQueryKey) as api.CollectionMetadata; + const previousData = queryClient.getQueryData(containerQueryKey) as api.Container; queryClient.setQueryData(containerQueryKey, { ...previousData, ...data,