diff --git a/app/assets/javascripts/ci/catalog/components/list/ci_resources_list_item.vue b/app/assets/javascripts/ci/catalog/components/list/ci_resources_list_item.vue index 2a8877a6be17d6fd27f6c1eb79857a0356abaeac..f778e619327d90b6ac6b1780b38a3d426e1b9306 100644 --- a/app/assets/javascripts/ci/catalog/components/list/ci_resources_list_item.vue +++ b/app/assets/javascripts/ci/catalog/components/list/ci_resources_list_item.vue @@ -14,6 +14,7 @@ import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import { formatDate, getTimeago } from '~/lib/utils/datetime_utility'; import { toNounSeriesText } from '~/lib/utils/grammar'; import { cleanLeadingSeparator } from '~/lib/utils/url_utility'; +import { truncate } from '~/lib/utils/text_utility'; import Markdown from '~/vue_shared/components/markdown/non_gfm_markdown.vue'; import TopicBadges from '~/vue_shared/components/topic_badges.vue'; import { CI_RESOURCE_DETAILS_PAGE_NAME } from '../../router/constants'; @@ -27,6 +28,7 @@ export default { releasedMessage: s__('CiCatalog|Released %{timeAgo} by %{author}'), unreleased: s__('CiCatalog|Unreleased'), }, + descriptionTruncateWidth: 260, components: { CiVerificationBadge, GlAvatar, @@ -133,6 +135,9 @@ export default { starsHref() { return this.resource.starrersPath; }, + truncatedDescription() { + return truncate(this.resource.description, this.$options.descriptionTruncateWidth); + }, }, methods: { getComponent(index) { @@ -212,7 +217,11 @@ export default { </div> <div class="gl-flex gl-flex-col gl-justify-between gl-gap-2 gl-text-sm md:gl-flex-row"> <div class="gl-flex gl-basis-2/3 gl-flex-col"> - <markdown v-if="resource.description" :markdown="resource.description" /> + <markdown + v-if="resource.description" + class="gl-hidden md:gl-block" + :markdown="truncatedDescription" + /> <div v-if="hasComponents" data-testid="ci-resource-component-names" diff --git a/spec/frontend/ci/catalog/components/list/ci_resources_list_item_spec.js b/spec/frontend/ci/catalog/components/list/ci_resources_list_item_spec.js index 76dfb55341bce78f7f68cdad33e6c672e20e7416..cd90d2a2869dd8c10a8c347c90164c12ce77b284 100644 --- a/spec/frontend/ci/catalog/components/list/ci_resources_list_item_spec.js +++ b/spec/frontend/ci/catalog/components/list/ci_resources_list_item_spec.js @@ -8,7 +8,7 @@ import CiVerificationBadge from '~/ci/catalog/components/shared/ci_verification_ import ProjectVisibilityIcon from '~/ci/catalog/components/shared/project_visibility_icon.vue'; import Markdown from '~/vue_shared/components/markdown/non_gfm_markdown.vue'; import TopicBadges from '~/vue_shared/components/topic_badges.vue'; -import { catalogSinglePageResponse } from '../../mock'; +import { catalogSinglePageResponse, longResourceDescription } from '../../mock'; const defaultEvent = { preventDefault: jest.fn, ctrlKey: false, metaKey: false }; const baseRoute = '/'; @@ -95,6 +95,19 @@ describe('CiResourcesListItem', () => { expect(markdown.exists()).toBe(true); expect(markdown.props().markdown).toBe(defaultProps.resource.description); }); + + it('renders a truncated resource description', () => { + defaultProps.resource.description = longResourceDescription; + createComponent(); + + const markdown = findMarkdown(); + expect(markdown.props().markdown.length).toBe(260); + }); + + it('hides the resource description on mobile devices', () => { + const markdown = findMarkdown(); + expect(markdown.classes()).toEqual(expect.arrayContaining(['gl-hidden', 'md:gl-block'])); + }); }); describe('components', () => { diff --git a/spec/frontend/ci/catalog/mock.js b/spec/frontend/ci/catalog/mock.js index 864b1dfb04289a01ee71601775941a86b1e332c1..5bfb49338b06c6ff0cb49b6a99b737906fe9df25 100644 --- a/spec/frontend/ci/catalog/mock.js +++ b/spec/frontend/ci/catalog/mock.js @@ -716,3 +716,6 @@ export const mockComponentsEmpty = { }, }, }; + +export const longResourceDescription = + 'This innovative project leverages cutting-edge microservices architecture to deliver scalable cloud-native solutions. With comprehensive CI/CD pipelines, automated testing frameworks, and robust monitoring capabilities, it ensures reliable deployments and optimal performance. The modular design incorporates industry best practices for security, maintainability and extensibility. Advanced caching mechanisms and efficient database optimization techniques provide lightning-fast response times. Built using modern development frameworks and tools, it seamlessly integrates with existing enterprise systems while maintaining flexibility for future enhancements.';