From c3d5c34536af8437a16f23480ce4f818ce2a1326 Mon Sep 17 00:00:00 2001 From: Stell Hub Date: Tue, 12 May 2026 14:37:01 +0800 Subject: [PATCH] fix: normalize blank spaces in actor search results --- src/utils/actor_card.ts | 16 ++++++++++---- tests/unit/utils.actor_card.test.ts | 34 ++++++++++++++++++++++++++++- 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/src/utils/actor_card.ts b/src/utils/actor_card.ts index b8c45f93..ebd52e26 100644 --- a/src/utils/actor_card.ts +++ b/src/utils/actor_card.ts @@ -45,6 +45,10 @@ export const DEFAULT_CARD_OPTIONS: ActorCardOptions = { includeMetadata: true, }; +function normalizeBlankSpaces(value?: string | null): string { + return value?.replaceAll(/\s+/g, ' ').trim() ?? ''; +} + /** * Private intermediate representation holding all extracted actor data. * Preserves raw PricingInfo so both markdown (pricingInfoToString) and @@ -87,14 +91,16 @@ function extractActorData( ): ExtractedActorData { const actorFullName = `${actor.username}/${actor.name}`; const actorUrl = `${APIFY_STORE_URL}/${actorFullName}`; + const normalizedTitle = normalizeBlankSpaces(actor.title); + const normalizedDescription = normalizeBlankSpaces(actor.description); const data: ExtractedActorData = { actorFullName, actorUrl, - title: actor.title, + title: normalizedTitle || undefined, pictureUrl: actor.pictureUrl || undefined, description: options.includeDescription - ? (actor.description || 'No description provided.') + ? (normalizedDescription || 'No description provided.') : '', pricingInfo: options.includePricing ? getActorPricingInfo(actor) : null, developer: { username: '', isOfficialApify: false, url: '' }, @@ -285,13 +291,15 @@ export function formatActorForWidget( userTier: PricingTier, ): WidgetActor { const fullName = `${actor.username}/${actor.name}`; + const normalizedTitle = normalizeBlankSpaces(actor.title); + const normalizedDescription = normalizeBlankSpaces(actor.description); return { id: actor.id, name: actor.name, username: actor.username, fullName, - title: actor.title || actor.name, - description: actor.description || 'No description available', + title: normalizedTitle || actor.name, + description: normalizedDescription || 'No description available', pictureUrl: actor.pictureUrl || '', stats: { actorReviewRating: ('actorReviewRating' in actor && actor.actorReviewRating) diff --git a/tests/unit/utils.actor_card.test.ts b/tests/unit/utils.actor_card.test.ts index f3a2b79e..0c77b3bf 100644 --- a/tests/unit/utils.actor_card.test.ts +++ b/tests/unit/utils.actor_card.test.ts @@ -2,7 +2,7 @@ import type { Actor } from 'apify-client'; import { describe, expect, it } from 'vitest'; import type { ActorStoreList } from '../../src/types.js'; -import { formatActorToActorCard, formatActorToStructuredCard } from '../../src/utils/actor_card.js'; +import { formatActorForWidget, formatActorToActorCard, formatActorToStructuredCard } from '../../src/utils/actor_card.js'; // Mock Actor data for testing (based on real apify/rag-web-browser Actor) const mockActor: Actor = { @@ -83,6 +83,13 @@ const mockDeprecatedActor: Actor = { isDeprecated: true, } as unknown as Actor; +const mockActorWithIrregularSpaces: ActorStoreList = { + ...mockActorStoreList, + id: 'actor-spaces', + title: ' Store Actor \n Deluxe ', + description: 'A store listing actor\nfor\t testing ', +} as unknown as ActorStoreList; + describe('formatActorToActorCard', () => { describe('backwards compatibility (no options)', () => { it('should include all sections when no options are provided', () => { @@ -126,6 +133,15 @@ describe('formatActorToActorCard', () => { const result = formatActorToActorCard(mockActorStoreList); expect(result).toContain('- **Categories:** Web Scraping, AI'); }); + + it('should normalize irregular whitespace in titles and descriptions', () => { + const result = formatActorToActorCard(mockActorWithIrregularSpaces); + + expect(result).toContain('## [Store Actor Deluxe](https://apify.com/community/store-actor)'); + expect(result).toContain('- **Description:** A store listing actor for testing'); + expect(result).not.toContain(' '); + expect(result).not.toContain('\t'); + }); }); describe('granular options - includeDescription', () => { @@ -375,6 +391,13 @@ describe('formatActorToStructuredCard', () => { const result = formatActorToStructuredCard(mockActorStoreList); expect(result.rating).toBeDefined(); }); + + it('should normalize irregular whitespace in structured output', () => { + const result = formatActorToStructuredCard(mockActorWithIrregularSpaces); + + expect(result.title).toBe('Store Actor Deluxe'); + expect(result.description).toBe('A store listing actor for testing'); + }); }); describe('granular options - includeDescription', () => { @@ -574,3 +597,12 @@ describe('formatActorToStructuredCard', () => { }); }); }); + +describe('formatActorForWidget', () => { + it('should normalize irregular whitespace in widget output', () => { + const result = formatActorForWidget(mockActorWithIrregularSpaces, 'FREE'); + + expect(result.title).toBe('Store Actor Deluxe'); + expect(result.description).toBe('A store listing actor for testing'); + }); +});