mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-10-18 20:30:48 +00:00
* fix(#11530): markdown viewer enableSeeMoreVariant is not working as expected * add unit test * remove unused component * update the comment
This commit is contained in:
parent
f22d604c54
commit
1da5dd9b9b
@ -40,10 +40,6 @@ jest.mock('../common/ServiceDocPanel/ServiceDocPanel', () => {
|
|||||||
return jest.fn().mockReturnValue(<div>ServiceDocPanel</div>);
|
return jest.fn().mockReturnValue(<div>ServiceDocPanel</div>);
|
||||||
});
|
});
|
||||||
|
|
||||||
jest.mock('../common/ServiceRightPanel/ServiceRightPanel', () => {
|
|
||||||
return jest.fn().mockReturnValue(<div>Right Panel</div>);
|
|
||||||
});
|
|
||||||
|
|
||||||
jest.mock('components/common/ResizablePanels/ResizablePanels', () =>
|
jest.mock('components/common/ResizablePanels/ResizablePanels', () =>
|
||||||
jest.fn().mockImplementation(({ firstPanel, secondPanel }) => (
|
jest.fn().mockImplementation(({ firstPanel, secondPanel }) => (
|
||||||
<>
|
<>
|
||||||
|
@ -33,7 +33,6 @@ const ChangeLogs = ({ data }: Props) => {
|
|||||||
<RichTextEditorPreviewer
|
<RichTextEditorPreviewer
|
||||||
enableSeeMoreVariant={false}
|
enableSeeMoreVariant={false}
|
||||||
markdown={data[log]}
|
markdown={data[log]}
|
||||||
maxLength={data[log].length}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
@ -132,7 +132,6 @@ const ServiceDocPanel: FC<ServiceDocPanelProp> = ({
|
|||||||
<RichTextEditorPreviewer
|
<RichTextEditorPreviewer
|
||||||
enableSeeMoreVariant={false}
|
enableSeeMoreVariant={false}
|
||||||
markdown={markdownContent}
|
markdown={markdownContent}
|
||||||
maxLength={markdownContent.length}
|
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
@ -1,34 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2023 Collate.
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
import { ServiceCategory } from 'enums/service.enum';
|
|
||||||
import { PipelineType } from 'generated/api/services/ingestionPipelines/createIngestionPipeline';
|
|
||||||
|
|
||||||
export type ExcludedPipelineType = Exclude<
|
|
||||||
PipelineType,
|
|
||||||
| PipelineType.DataInsight
|
|
||||||
| PipelineType.ElasticSearchReindex
|
|
||||||
| PipelineType.TestSuite
|
|
||||||
>;
|
|
||||||
|
|
||||||
export interface RightPanelProps {
|
|
||||||
activeStep: number;
|
|
||||||
isIngestion: boolean;
|
|
||||||
serviceName: string;
|
|
||||||
isUpdating: boolean;
|
|
||||||
selectedService: string;
|
|
||||||
selectedServiceCategory: ServiceCategory;
|
|
||||||
showDeployedTitle?: boolean;
|
|
||||||
pipelineType?: ExcludedPipelineType;
|
|
||||||
ingestionName?: string;
|
|
||||||
activeField?: string;
|
|
||||||
}
|
|
@ -1,84 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2023 Collate.
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
import { act, render, screen } from '@testing-library/react';
|
|
||||||
import { ServiceCategory } from 'enums/service.enum';
|
|
||||||
import { PipelineType } from 'generated/api/services/ingestionPipelines/createIngestionPipeline';
|
|
||||||
import React from 'react';
|
|
||||||
import RightPanel from './ServiceRightPanel';
|
|
||||||
import { ExcludedPipelineType } from './ServiceRightPanel.interface';
|
|
||||||
|
|
||||||
jest.mock('components/common/rich-text-editor/RichTextEditorPreviewer', () =>
|
|
||||||
jest
|
|
||||||
.fn()
|
|
||||||
.mockImplementation(({ markdown }) => (
|
|
||||||
<div data-testid="requirement-text">{markdown}</div>
|
|
||||||
))
|
|
||||||
);
|
|
||||||
|
|
||||||
jest.mock('rest/miscAPI', () => ({
|
|
||||||
fetchMarkdownFile: jest
|
|
||||||
.fn()
|
|
||||||
.mockImplementation(() => Promise.resolve('markdown text')),
|
|
||||||
}));
|
|
||||||
|
|
||||||
const mockProps = {
|
|
||||||
isIngestion: false,
|
|
||||||
pipelineType: PipelineType.Metadata as ExcludedPipelineType,
|
|
||||||
activeStep: 1,
|
|
||||||
isAirflowRunning: true,
|
|
||||||
showDeployedTitle: true,
|
|
||||||
isUpdating: false,
|
|
||||||
ingestionName: 'service_ingestion',
|
|
||||||
serviceName: 'service',
|
|
||||||
activeField: 'root_username',
|
|
||||||
selectedServiceCategory: ServiceCategory.DATABASE_SERVICES,
|
|
||||||
selectedService: 'Mysql',
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('Right Panel Component', () => {
|
|
||||||
it('Should render the active field doc', async () => {
|
|
||||||
await act(async () => {
|
|
||||||
render(<RightPanel {...mockProps} />);
|
|
||||||
});
|
|
||||||
|
|
||||||
const activeFieldName = screen.getByTestId('active-field-name');
|
|
||||||
|
|
||||||
expect(activeFieldName).toBeInTheDocument();
|
|
||||||
|
|
||||||
expect(activeFieldName).toHaveTextContent('Username');
|
|
||||||
|
|
||||||
const activeFieldDocumentElement = screen.getByTestId('requirement-text');
|
|
||||||
|
|
||||||
expect(activeFieldDocumentElement).toBeInTheDocument();
|
|
||||||
|
|
||||||
expect(activeFieldDocumentElement).toHaveTextContent('markdown text');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should render the current step guide if active field is empty', async () => {
|
|
||||||
await act(async () => {
|
|
||||||
render(<RightPanel {...mockProps} activeField={undefined} />);
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(screen.queryByTestId('active-field-name')).not.toBeInTheDocument();
|
|
||||||
|
|
||||||
const activeFieldDocumentElement = screen.queryByTestId('requirement-text');
|
|
||||||
|
|
||||||
expect(activeFieldDocumentElement).not.toBeInTheDocument();
|
|
||||||
|
|
||||||
expect(screen.getByText('label.add-a-new-service')).toBeInTheDocument();
|
|
||||||
|
|
||||||
expect(
|
|
||||||
screen.getByText('message.add-new-service-description')
|
|
||||||
).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,219 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2023 Collate.
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
import { Affix, Card } from 'antd';
|
|
||||||
import RichTextEditorPreviewer from 'components/common/rich-text-editor/RichTextEditorPreviewer';
|
|
||||||
import Loader from 'components/Loader/Loader';
|
|
||||||
import { oneofOrEndsWithNumberRegex } from 'constants/regex.constants';
|
|
||||||
import {
|
|
||||||
addServiceGuide,
|
|
||||||
addServiceGuideWOAirflow,
|
|
||||||
EMAIL_CONFIG_SERVICE_CATEGORY,
|
|
||||||
} from 'constants/service-guide.constant';
|
|
||||||
import { INGESTION_GUIDE_MAP } from 'constants/Services.constant';
|
|
||||||
import { useAirflowStatus } from 'hooks/useAirflowStatus';
|
|
||||||
import { first, last, startCase } from 'lodash';
|
|
||||||
import React, { FC, useEffect, useMemo, useRef, useState } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { fetchMarkdownFile } from 'rest/miscAPI';
|
|
||||||
import { SupportedLocales } from 'utils/i18next/i18nextUtil';
|
|
||||||
import { getEmailConfigStepGuide } from 'utils/ServiceRightPanelUtils';
|
|
||||||
import { getFormattedGuideText, getServiceType } from 'utils/ServiceUtils';
|
|
||||||
import { RightPanelProps } from './ServiceRightPanel.interface';
|
|
||||||
|
|
||||||
const RightPanel: FC<RightPanelProps> = ({
|
|
||||||
isIngestion,
|
|
||||||
pipelineType,
|
|
||||||
activeStep,
|
|
||||||
isUpdating,
|
|
||||||
ingestionName,
|
|
||||||
serviceName,
|
|
||||||
activeField,
|
|
||||||
selectedServiceCategory,
|
|
||||||
selectedService,
|
|
||||||
showDeployedTitle = false,
|
|
||||||
}) => {
|
|
||||||
const panelContainerRef = useRef<HTMLDivElement>(null);
|
|
||||||
|
|
||||||
const { isAirflowAvailable } = useAirflowStatus();
|
|
||||||
const { t, i18n } = useTranslation();
|
|
||||||
|
|
||||||
const [activeFieldDocument, setActiveFieldDocument] = useState<string>('');
|
|
||||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
|
||||||
|
|
||||||
const isEmailConfigPage = useMemo(
|
|
||||||
() => selectedService === EMAIL_CONFIG_SERVICE_CATEGORY,
|
|
||||||
[selectedService]
|
|
||||||
);
|
|
||||||
|
|
||||||
const activeStepGuide = useMemo(() => {
|
|
||||||
let guideTemp;
|
|
||||||
|
|
||||||
if (isIngestion && pipelineType) {
|
|
||||||
guideTemp = INGESTION_GUIDE_MAP[pipelineType]?.find(
|
|
||||||
(item) => item.step === activeStep
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
guideTemp =
|
|
||||||
!isAirflowAvailable && activeStep === 4
|
|
||||||
? addServiceGuideWOAirflow
|
|
||||||
: addServiceGuide.find((item) => item.step === activeStep);
|
|
||||||
}
|
|
||||||
|
|
||||||
return guideTemp;
|
|
||||||
}, [isIngestion, pipelineType, isAirflowAvailable, activeStep]);
|
|
||||||
|
|
||||||
const activeFieldName = useMemo(() => {
|
|
||||||
/**
|
|
||||||
* active field is like root_fieldName
|
|
||||||
* so we need to split and get the fieldName
|
|
||||||
*/
|
|
||||||
const fieldNameArr = activeField?.split('/');
|
|
||||||
|
|
||||||
const fieldName = last(fieldNameArr);
|
|
||||||
|
|
||||||
// check if activeField is select or list field
|
|
||||||
if (oneofOrEndsWithNumberRegex.test(fieldName ?? '')) {
|
|
||||||
return first(fieldName?.split('_'));
|
|
||||||
} else {
|
|
||||||
return fieldName;
|
|
||||||
}
|
|
||||||
}, [activeField]);
|
|
||||||
|
|
||||||
const showActiveFieldElement = Boolean(
|
|
||||||
activeFieldName && activeFieldDocument
|
|
||||||
);
|
|
||||||
|
|
||||||
const getActiveStepTitle = (title: string) => {
|
|
||||||
const deployMessage = showDeployedTitle ? ` & ${t('label.deployed')}` : '';
|
|
||||||
const updateTitle = title.replace(
|
|
||||||
t('label.added'),
|
|
||||||
`${t('label.updated')}${deployMessage}`
|
|
||||||
);
|
|
||||||
const newTitle = showDeployedTitle
|
|
||||||
? title.replace(t('label.added'), `${t('label.added')}${deployMessage}`)
|
|
||||||
: title;
|
|
||||||
|
|
||||||
return isUpdating ? updateTitle : newTitle;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getActiveStepDescription = (description: string) => {
|
|
||||||
const replaceText = isIngestion
|
|
||||||
? `<${t('label.ingestion-pipeline-name')}>`
|
|
||||||
: `<${t('label.service-name')}>`;
|
|
||||||
|
|
||||||
const replacement = isIngestion ? ingestionName || '' : serviceName;
|
|
||||||
|
|
||||||
return getFormattedGuideText(description, replaceText, replacement);
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetchFieldDocument = async () => {
|
|
||||||
const serviceType = isEmailConfigPage
|
|
||||||
? selectedServiceCategory
|
|
||||||
: getServiceType(selectedServiceCategory);
|
|
||||||
setIsLoading(true);
|
|
||||||
try {
|
|
||||||
let response = '';
|
|
||||||
const isEnglishLanguage = i18n.language === SupportedLocales.English;
|
|
||||||
const filePath = `${i18n.language}/${serviceType}/${selectedService}/fields/${activeFieldName}.md`;
|
|
||||||
const fallbackFilePath = `${SupportedLocales.English}/${serviceType}/${selectedService}/fields/${activeFieldName}.md`;
|
|
||||||
|
|
||||||
const [translation, fallbackTranslation] = await Promise.allSettled([
|
|
||||||
fetchMarkdownFile(filePath),
|
|
||||||
isEnglishLanguage
|
|
||||||
? Promise.reject('')
|
|
||||||
: fetchMarkdownFile(fallbackFilePath),
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (translation.status === 'fulfilled') {
|
|
||||||
response = translation.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isEnglishLanguage && fallbackTranslation.status === 'fulfilled') {
|
|
||||||
response = fallbackTranslation.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
setActiveFieldDocument(response);
|
|
||||||
} catch (error) {
|
|
||||||
setActiveFieldDocument('');
|
|
||||||
} finally {
|
|
||||||
setIsLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleAffixTarget = () => document.getElementById('page-container-v1');
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const shouldFetchFieldDoc = Boolean(
|
|
||||||
selectedService && selectedServiceCategory && activeFieldName
|
|
||||||
);
|
|
||||||
// only fetch file when required fields are present
|
|
||||||
if (shouldFetchFieldDoc) {
|
|
||||||
fetchFieldDocument();
|
|
||||||
}
|
|
||||||
}, [
|
|
||||||
selectedService,
|
|
||||||
selectedServiceCategory,
|
|
||||||
activeFieldName,
|
|
||||||
isEmailConfigPage,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const activeStepGuideElement = activeStepGuide ? (
|
|
||||||
<>
|
|
||||||
<h6 className="tw-heading tw-text-base">
|
|
||||||
{getActiveStepTitle(activeStepGuide.title)}
|
|
||||||
</h6>
|
|
||||||
<div className="tw-mb-5" data-test="current-step-guide">
|
|
||||||
{getActiveStepDescription(activeStepGuide.description)}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
) : null;
|
|
||||||
|
|
||||||
const activeFieldDocumentElement = activeFieldName ? (
|
|
||||||
<>
|
|
||||||
<h6 className="tw-heading tw-text-base" data-testid="active-field-name">
|
|
||||||
{startCase(activeFieldName)}
|
|
||||||
</h6>
|
|
||||||
<RichTextEditorPreviewer
|
|
||||||
enableSeeMoreVariant={false}
|
|
||||||
markdown={activeFieldDocument}
|
|
||||||
maxLength={activeFieldDocument.length}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
) : null;
|
|
||||||
|
|
||||||
const renderElement = useMemo(() => {
|
|
||||||
if (showActiveFieldElement) {
|
|
||||||
return activeFieldDocumentElement;
|
|
||||||
} else {
|
|
||||||
return isEmailConfigPage
|
|
||||||
? getEmailConfigStepGuide()
|
|
||||||
: activeStepGuideElement;
|
|
||||||
}
|
|
||||||
}, [
|
|
||||||
showActiveFieldElement,
|
|
||||||
activeFieldDocumentElement,
|
|
||||||
activeStepGuideElement,
|
|
||||||
selectedService,
|
|
||||||
isEmailConfigPage,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div id="service-right-panel" ref={panelContainerRef}>
|
|
||||||
<Affix offsetTop={5} target={handleAffixTarget}>
|
|
||||||
<Card>{isLoading ? <Loader /> : renderElement}</Card>
|
|
||||||
</Affix>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default RightPanel;
|
|
@ -11,22 +11,28 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { findByTestId, fireEvent, render } from '@testing-library/react';
|
import {
|
||||||
|
act,
|
||||||
|
findByTestId,
|
||||||
|
fireEvent,
|
||||||
|
render,
|
||||||
|
screen,
|
||||||
|
} from '@testing-library/react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { MemoryRouter } from 'react-router-dom';
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
import { act } from 'react-test-renderer';
|
|
||||||
|
import userEvent from '@testing-library/user-event';
|
||||||
|
import { PreviewerProp } from './RichTextEditor.interface';
|
||||||
import RichTextEditorPreviewer from './RichTextEditorPreviewer';
|
import RichTextEditorPreviewer from './RichTextEditorPreviewer';
|
||||||
|
|
||||||
const mockDescription =
|
const mockDescription =
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
'**Headings**\n\n# H1\n## H2\n### H3\n\n***\n**Bold**\n\n**bold text**\n\n\n***\n**Italic**\n\n*italic*\n\n***\n**BlockQuote**\n\n> blockquote\n\n***\n**Ordered List**\n\n1. First item\n2. Second item\n3. Third item\n\n\n***\n**Unordered List**\n\n- First item\n- Second item\n- Third item\n\n\n***\n**Code**\n\n`code`\n\n\n***\n**Horizontal Rule**\n\n---\n\n\n***\n**Link**\n[title](https://www.example.com)\n\n\n***\n**Image**\n\n\n\n\n***\n**Table**\n\n| Syntax | Description |\n| ----------- | ----------- |\n| Header | Title |\n| Paragraph | Text |\n***\n\n**Fenced Code Block**\n\n```\n{\n "firstName": "John",\n "lastName": "Smith",\n "age": 25\n}\n```\n\n\n***\n**Strikethrough**\n~~The world is flat.~~\n';
|
'**Headings**\n\n# H1\n## H2\n### H3\n\n***\n**Bold**\n\n**bold text**\n\n\n***\n**Italic**\n\n*italic*\n\n***\n**BlockQuote**\n\n> blockquote\n\n***\n**Ordered List**\n\n1. First item\n2. Second item\n3. Third item\n\n\n***\n**Unordered List**\n\n- First item\n- Second item\n- Third item\n\n\n***\n**Code**\n\n`code`\n\n\n***\n**Horizontal Rule**\n\n---\n\n\n***\n**Link**\n[title](https://www.example.com)\n\n\n***\n**Image**\n\n\n\n\n***\n**Table**\n\n| Syntax | Description |\n| ----------- | ----------- |\n| Header | Title |\n| Paragraph | Text |\n***\n\n**Fenced Code Block**\n\n```\n{\n "firstName": "John",\n "lastName": "Smith",\n "age": 25\n}\n```\n\n\n***\n**Strikethrough**\n~~The world is flat.~~\n';
|
||||||
|
|
||||||
const mockProp = {
|
const mockProp: PreviewerProp = {
|
||||||
markdown: mockDescription,
|
markdown: mockDescription,
|
||||||
className: '',
|
className: '',
|
||||||
blurClasses: 'see-more-blur',
|
maxLength: 300,
|
||||||
maxHtClass: 'tw-h-24',
|
|
||||||
maxLen: 300,
|
|
||||||
enableSeeMoreVariant: true,
|
enableSeeMoreVariant: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -344,4 +350,89 @@ describe('Test RichTextEditor Previewer Component', () => {
|
|||||||
|
|
||||||
expect(markdownParser).toBeInTheDocument();
|
expect(markdownParser).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Should render read more button if enableSeeMoreVariant is true and max length is less than content length', () => {
|
||||||
|
render(<RichTextEditorPreviewer {...mockProp} />, {
|
||||||
|
wrapper: MemoryRouter,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(screen.getByTestId('read-more-button')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Read more toggling should work', async () => {
|
||||||
|
render(<RichTextEditorPreviewer {...mockProp} />, {
|
||||||
|
wrapper: MemoryRouter,
|
||||||
|
});
|
||||||
|
|
||||||
|
const readMoreButton = screen.getByTestId('read-more-button');
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
userEvent.click(readMoreButton);
|
||||||
|
});
|
||||||
|
|
||||||
|
const readLessButton = screen.getByTestId('read-less-button');
|
||||||
|
|
||||||
|
expect(readLessButton).toBeInTheDocument();
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
userEvent.click(readLessButton);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(screen.getByTestId('read-more-button')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should render the whole content if enableSeeMoreVariant is false', () => {
|
||||||
|
const markdown = 'This is a simple paragraph text';
|
||||||
|
|
||||||
|
render(
|
||||||
|
<RichTextEditorPreviewer
|
||||||
|
{...mockProp}
|
||||||
|
enableSeeMoreVariant={false}
|
||||||
|
markdown={markdown}
|
||||||
|
/>,
|
||||||
|
{
|
||||||
|
wrapper: MemoryRouter,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText(markdown)).toBeInTheDocument();
|
||||||
|
expect(screen.queryByTestId('read-more-button')).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should render the clipped content if enableSeeMoreVariant is true', () => {
|
||||||
|
const markdown = 'This is a simple paragraph text';
|
||||||
|
|
||||||
|
render(
|
||||||
|
<RichTextEditorPreviewer
|
||||||
|
{...mockProp}
|
||||||
|
enableSeeMoreVariant
|
||||||
|
markdown={markdown}
|
||||||
|
maxLength={20}
|
||||||
|
/>,
|
||||||
|
{
|
||||||
|
wrapper: MemoryRouter,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText('This is a simple...')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByTestId('read-more-button')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should not clipped content if enableSeeMoreVariant is true and markdown length is less than max length', () => {
|
||||||
|
const markdown = 'This is a simple paragraph text';
|
||||||
|
|
||||||
|
render(
|
||||||
|
<RichTextEditorPreviewer
|
||||||
|
{...mockProp}
|
||||||
|
enableSeeMoreVariant
|
||||||
|
markdown={markdown}
|
||||||
|
/>,
|
||||||
|
{
|
||||||
|
wrapper: MemoryRouter,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText(markdown)).toBeInTheDocument();
|
||||||
|
expect(screen.queryByTestId('read-more-button')).toBeNull();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -15,7 +15,7 @@ import { Viewer } from '@toast-ui/react-editor';
|
|||||||
import { Button } from 'antd';
|
import { Button } from 'antd';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { uniqueId } from 'lodash';
|
import { uniqueId } from 'lodash';
|
||||||
import React, { useCallback, useEffect, useState } from 'react';
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { getTrimmedContent } from 'utils/CommonUtils';
|
import { getTrimmedContent } from 'utils/CommonUtils';
|
||||||
import { DESCRIPTION_MAX_PREVIEW_CHARACTERS } from '../../../constants/constants';
|
import { DESCRIPTION_MAX_PREVIEW_CHARACTERS } from '../../../constants/constants';
|
||||||
@ -32,13 +32,30 @@ const RichTextEditorPreviewer = ({
|
|||||||
}: PreviewerProp) => {
|
}: PreviewerProp) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [content, setContent] = useState<string>('');
|
const [content, setContent] = useState<string>('');
|
||||||
const [hideReadMoreText, setHideReadMoreText] = useState<boolean>(
|
|
||||||
markdown.length <= maxLength
|
// initially read more will be false
|
||||||
|
const [readMore, setReadMore] = useState<boolean>(false);
|
||||||
|
|
||||||
|
// read more toggle handler
|
||||||
|
const handleReadMoreToggle = () => setReadMore((pre) => !pre);
|
||||||
|
|
||||||
|
// whether has read more content or not
|
||||||
|
const hasReadMore = useMemo(
|
||||||
|
() => enableSeeMoreVariant && markdown.length > maxLength,
|
||||||
|
[enableSeeMoreVariant, markdown, maxLength]
|
||||||
);
|
);
|
||||||
|
|
||||||
const displayMoreHandler = () => {
|
/**
|
||||||
setHideReadMoreText((pre) => !pre);
|
* if hasReadMore is true then value will be based on read more state
|
||||||
};
|
* else value will be content
|
||||||
|
*/
|
||||||
|
const viewerValue = useMemo(() => {
|
||||||
|
if (hasReadMore) {
|
||||||
|
return readMore ? content : `${getTrimmedContent(content, maxLength)}...`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return content;
|
||||||
|
}, [hasReadMore, readMore, maxLength, content]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setContent(markdown);
|
setContent(markdown);
|
||||||
@ -83,22 +100,18 @@ const RichTextEditorPreviewer = ({
|
|||||||
<Viewer
|
<Viewer
|
||||||
extendedAutolinks
|
extendedAutolinks
|
||||||
customHTMLRenderer={customHTMLRenderer}
|
customHTMLRenderer={customHTMLRenderer}
|
||||||
initialValue={
|
initialValue={viewerValue}
|
||||||
hideReadMoreText
|
|
||||||
? content
|
|
||||||
: `${getTrimmedContent(content, maxLength)}...`
|
|
||||||
}
|
|
||||||
key={uniqueId()}
|
key={uniqueId()}
|
||||||
linkAttributes={{ target: '_blank' }}
|
linkAttributes={{ target: '_blank' }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{enableSeeMoreVariant && markdown.length > maxLength && (
|
{hasReadMore && (
|
||||||
<Button
|
<Button
|
||||||
className="leading-0"
|
className="leading-0"
|
||||||
data-testid="read-more-button"
|
data-testid={`read-${readMore ? 'less' : 'more'}-button`}
|
||||||
type="link"
|
type="link"
|
||||||
onClick={displayMoreHandler}>
|
onClick={handleReadMoreToggle}>
|
||||||
{hideReadMoreText
|
{readMore
|
||||||
? t('label.read-type-lowercase', {
|
? t('label.read-type-lowercase', {
|
||||||
type: t('label.less-lowercase'),
|
type: t('label.less-lowercase'),
|
||||||
})
|
})
|
||||||
|
Loading…
x
Reference in New Issue
Block a user