[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
This commit is contained in:
@@ -110,4 +110,25 @@ describe('<InplaceTextEditor />', () => {
|
||||
// Show original text
|
||||
expect(screen.getByText('Test text')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should disappear edit button while editing', async () => {
|
||||
render(<InplaceTextEditor text="Test text" onSave={mockOnSave} />);
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -92,17 +92,19 @@ export const InplaceTextEditor: React.FC<InplaceTextEditorProps> = ({
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<Truncate className={textClassName}>
|
||||
{text}
|
||||
</Truncate>
|
||||
<>
|
||||
<Truncate className={textClassName}>
|
||||
{text}
|
||||
</Truncate>
|
||||
<IconButton
|
||||
src={Edit}
|
||||
iconAs={Icon}
|
||||
alt={intl.formatMessage(messages.editTextButtonAlt)}
|
||||
onClick={handleEdit}
|
||||
size="sm"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<IconButton
|
||||
src={Edit}
|
||||
iconAs={Icon}
|
||||
alt={intl.formatMessage(messages.editTextButtonAlt)}
|
||||
onClick={handleEdit}
|
||||
size="inline"
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -113,7 +113,7 @@ export const SubHeaderTitle = ({ title }: { title: ReactNode }) => {
|
||||
const showReadOnlyBadge = readOnly && !componentPickerMode;
|
||||
|
||||
return (
|
||||
<Stack direction="vertical">
|
||||
<Stack direction="vertical" className="mt-1.5">
|
||||
{title}
|
||||
{showReadOnlyBadge && (
|
||||
<div>
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
@@ -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('<ComponentPicker />', () => {
|
||||
}, '*');
|
||||
});
|
||||
|
||||
it('should open the unit sidebar', async () => {
|
||||
render(<ComponentPicker />);
|
||||
|
||||
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(<ComponentPicker />);
|
||||
|
||||
|
||||
48
src/library-authoring/containers/ContainerEditableTitle.tsx
Normal file
48
src/library-authoring/containers/ContainerEditableTitle.tsx
Normal file
@@ -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 (
|
||||
<InplaceTextEditor
|
||||
onSave={handleSaveDisplayName}
|
||||
text={showOnlyPublished ? (container.publishedDisplayName ?? container.displayName) : container.displayName}
|
||||
readOnly={readOnly}
|
||||
textClassName={textClassName}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -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 (
|
||||
<InplaceTextEditor
|
||||
onSave={handleSaveDisplayName}
|
||||
text={container.displayName}
|
||||
readOnly={readOnly}
|
||||
<ContainerEditableTitle
|
||||
containerId={containerId}
|
||||
textClassName="font-weight-bold m-1.5"
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -600,6 +600,7 @@ export interface Container {
|
||||
id: string;
|
||||
containerType: 'unit';
|
||||
displayName: string;
|
||||
publishedDisplayName: string;
|
||||
lastPublished: string | null;
|
||||
publishedBy: string | null;
|
||||
createdBy: string | null;
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user