Skip to content

Instantly share code, notes, and snippets.

@JohnPhamous
Created November 21, 2025 22:32
Show Gist options
  • Select an option

  • Save JohnPhamous/2c23ad308d76fde49586e9b678b18559 to your computer and use it in GitHub Desktop.

Select an option

Save JohnPhamous/2c23ad308d76fde49586e9b678b18559 to your computer and use it in GitHub Desktop.
import React from 'react';
import { describe, it, expect } from 'vitest';
import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom';
import {
calculateSegments,
TruncateMiddleOfString,
} from '@/lib/string-components';
describe('calculateSegments', () => {
it('handles user/feature pattern', () => {
const input = 'john/feature-branch-name';
const { prefix, middle, suffix } = calculateSegments(input);
expect(prefix).toBe('john/');
expect(middle).toBe('feature-branch');
expect(suffix).toBe('-name');
});
it('handles ticket id pattern', () => {
const input =
'vade-183-fix-image-aspect-ratios-and-sizing-in-markdown-renderer';
const { prefix, middle, suffix } = calculateSegments(input);
// "vade-183" is 8 chars. Suffix should be last 8 chars "renderer".
expect(prefix).toBe('vade-183');
expect(suffix).toBe('renderer');
expect(middle).toBe('-fix-image-aspect-ratios-and-sizing-in-markdown-');
});
it('handles ticket id pattern with different structure', () => {
const input = 'PROJ-1234-some-description';
const { prefix, middle, suffix } = calculateSegments(input);
// "PROJ-1234" is 9 chars. Suffix should be last 9 chars: "scription".
expect(prefix).toBe('PROJ-1234');
expect(suffix).toBe('scription');
expect(middle).toBe('-some-de');
});
it('handles fallback for plain long strings', () => {
// Default length is 4
const input = 'abcdefghijklmnopqrstuvwxyz';
const { prefix, middle, suffix } = calculateSegments(input);
expect(prefix).toBe('abcd');
expect(suffix).toBe('wxyz');
expect(middle).toBe('efghijklmnopqrstuv');
});
it('handles short strings without splitting', () => {
const input = 'short';
const { prefix, middle, suffix } = calculateSegments(input);
expect(prefix).toBe('short');
expect(middle).toBe('');
expect(suffix).toBe('');
});
it('handles empty string', () => {
const { prefix, middle, suffix } = calculateSegments('');
expect(prefix).toBe('');
expect(middle).toBe('');
expect(suffix).toBe('');
});
it('handles edge case where string length is close to 2x prefix', () => {
// prefix "user/" is 5 chars. 2x = 10.
// Input length 10. Should not split.
const input = 'user/12345';
const { prefix, middle, suffix } = calculateSegments(input);
expect(prefix).toBe('user/12345');
expect(middle).toBe('');
expect(suffix).toBe('');
});
it('handles edge case where string length is > 2x prefix', () => {
// prefix "user/" is 5 chars. 2x = 10.
// Input length 11. Should split.
const input = 'user/123456';
const { prefix, middle, suffix } = calculateSegments(input);
expect(prefix).toBe('user/');
expect(middle).toBe('1');
expect(suffix).toBe('23456');
});
});
describe('TruncateMiddleOfString', () => {
it('renders full string when short', () => {
render(<TruncateMiddleOfString>short</TruncateMiddleOfString>);
expect(screen.getByText('short')).toBeInTheDocument();
});
it('renders split parts for long string', () => {
const input = 'john/feature-branch-name';
const { container } = render(
<TruncateMiddleOfString>{input}</TruncateMiddleOfString>,
);
// We expect 3 spans usually, but if the logic splits it.
// prefix: john/
// middle: feature-branch
// suffix: -name
expect(screen.getByText('john/')).toBeInTheDocument();
expect(screen.getByText('feature-branch')).toBeInTheDocument();
expect(screen.getByText('-name')).toBeInTheDocument();
const spans = container.querySelectorAll('span');
// Root span + 3 children spans = 4 spans
expect(spans.length).toBeGreaterThanOrEqual(3);
});
});
import React from 'react';
import { clsx } from 'clsx';
/**
* Calculates the prefix, middle, and suffix parts of a string for smart truncation.
*/
export const calculateSegments = (
text: string,
): { prefix: string; middle: string; suffix: string } => {
if (!text) {
return { prefix: '', middle: '', suffix: '' };
}
let prefixLength = 0;
// 1. Check for path/branch pattern: user/feature -> prefix is "user/"
const slashMatch = text.match(/^([^\/]+\/)/);
if (slashMatch) {
prefixLength = slashMatch[1].length;
} else {
// 2. Check for issue ID pattern: TICKET-123 -> prefix is "TICKET-123"
// Only matching if it starts with letters, followed by hyphen, then numbers
// And typically followed by another hyphen or end of string
const issueMatch = text.match(/^([a-zA-Z]+-\d+)/);
if (issueMatch) {
prefixLength = issueMatch[1].length;
} else {
// 3. Fallback: Use a default length (e.g., 4 chars)
// If the string is very short, we'll handle it by checking total length
prefixLength = 4;
}
}
// If the string is short enough that splitting doesn't make sense, return it as prefix
// "Short enough" could be defined as <= prefixLength * 2 + some buffer
// But for simplicity, let's just follow the "prefix and suffix lengths should be equal" rule strictly
// unless the string is shorter than 2 * prefixLength.
if (text.length <= prefixLength * 2) {
// If it's too short to split nicely, we can just return the whole thing as prefix
// or handle it gracefully.
// Let's return it as prefix so it shows up fully.
return { prefix: text, middle: '', suffix: '' };
}
const prefix = text.slice(0, prefixLength);
const suffix = text.slice(-prefixLength);
const middle = text.slice(prefixLength, -prefixLength);
return { prefix, middle, suffix };
};
/**
* This component should return a <span> with content. We'll split the string into 3 parts: prefix, middle, suffix.
* Prefix and suffix should never be truncated. The middle can be truncated based on the available width.
* Prefix and suffix lengths should be equal.
* The prefix should be smart and try to parse for semantics.
*
* The truncation should happen with CSS so it's responsive.
*/
export const TruncateMiddleOfString = ({
children,
className,
}: {
children: string;
className?: string;
}) => {
const { prefix, middle, suffix } = calculateSegments(children);
if (!middle && !suffix) {
return <span className={className}>{prefix}</span>;
}
return (
<span className={clsx('inline-flex min-w-0 max-w-full', className)}>
<span>{prefix}</span>
<span className="truncate min-w-0 shrink-100">{middle}</span>
<span className="whitespace-nowrap">{suffix}</span>
</span>
);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment