diff --git a/client/src/components/ui/SplitText.spec.tsx b/client/src/components/ui/SplitText.spec.tsx new file mode 100644 index 0000000000..3f8a8e49ae --- /dev/null +++ b/client/src/components/ui/SplitText.spec.tsx @@ -0,0 +1,38 @@ +import { render } from '@testing-library/react'; +import SplitText from './SplitText'; + +// Mock IntersectionObserver +class MockIntersectionObserver { + observe = jest.fn(); + unobserve = jest.fn(); + disconnect = jest.fn(); +} + +Object.defineProperty(window, 'IntersectionObserver', { + writable: true, + configurable: true, + value: MockIntersectionObserver, +}); + +describe('SplitText', () => { + it('renders emojis correctly', () => { + const emojis = ['🚧', '❤️‍🔥', '💜', '🦎', '❌', '✅', '⚠️']; + const originalText = emojis.join(''); + + const { container } = render(); + const textSpans = container.querySelectorAll('p > span > span.inline-block'); + + // Reconstruct the text by joining all span contents + const reconstructedText = Array.from(textSpans) + .map((span) => span.textContent) + .join('') + .trim(); + // Compare the reconstructed text with the original + expect(reconstructedText).toBe(originalText); + + // Check the first character specifically as the reconstructed text could hide issues + for (let i = 0; i < emojis.length; i++) { + expect(Array.from(textSpans)[i].textContent).toBe(emojis[i]); + } + }); +}); diff --git a/client/src/components/ui/SplitText.tsx b/client/src/components/ui/SplitText.tsx index 8a37def906..cde04eb8a1 100644 --- a/client/src/components/ui/SplitText.tsx +++ b/client/src/components/ui/SplitText.tsx @@ -15,6 +15,17 @@ interface SplitTextProps { onLineCountChange?: (lineCount: number) => void; } +const splitGraphemes = (text: string): string[] => { + if (typeof Intl !== 'undefined' && Intl.Segmenter) { + const segmenter = new Intl.Segmenter('en', { granularity: 'grapheme' }); + const segments = segmenter.segment(text); + return Array.from(segments).map((s) => s.segment); + } else { + // Fallback for browsers without Intl.Segmenter + return [...text]; + } +}; + const SplitText: React.FC = ({ text = '', className = '', @@ -28,7 +39,7 @@ const SplitText: React.FC = ({ onLetterAnimationComplete, onLineCountChange, }) => { - const words = text.split(' ').map((word) => word.split('')); + const words = text.split(' ').map(splitGraphemes); const letters = words.flat(); const [inView, setInView] = useState(false); const ref = useRef(null); @@ -40,12 +51,12 @@ const SplitText: React.FC = ({ from: animationFrom, to: inView ? async (next: (props: any) => Promise) => { - await next(animationTo); - animatedCount.current += 1; - if (animatedCount.current === letters.length && onLetterAnimationComplete) { - onLetterAnimationComplete(); + await next(animationTo); + animatedCount.current += 1; + if (animatedCount.current === letters.length && onLetterAnimationComplete) { + onLetterAnimationComplete(); + } } - } : animationFrom, delay: i * delay, config: { easing },