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 },