Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions docs/plans/2026-03-08-utils-coverage-implementation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Utils Coverage Implementation Plan

> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.

**Goal:** Add missing unit tests so the current `vitest` coverage scope for `src/**` reaches 100% without changing production code.

**Architecture:** Keep all changes in the existing `test/Truncate.spec.tsx` suite. Target uncovered branches in `src/Truncate/utils.tsx`, especially the expansion, proportional truncation, and final fine-tuning paths inside `getMiddleTruncateFragments`.

**Tech Stack:** React, Vitest, Testing Library, Sinon

---

### Task 1: Add failing coverage tests for `getMiddleTruncateFragments`

**Files:**
- Modify: `test/Truncate.spec.tsx`
- Test: `test/Truncate.spec.tsx`

**Step 1: Write the failing test**

Add focused test cases for:
- expanding `startFragment` before hitting `targetWidth`
- expanding `endFragment` when more width remains
- truncating `endFragment` when it is proportionally wider
- truncating only one side after the opposite side becomes empty
- fine-tuning with one extra character added to `startFragment`
- fine-tuning with one extra character added to `endFragment`

**Step 2: Run test to verify expected behavior**

Run: `pnpm test test/Truncate.spec.tsx`

Expected: the new cases either pass immediately because behavior already exists, or fail with assertion output that identifies the remaining uncovered path.

### Task 2: Verify full coverage

**Files:**
- Test: `test/Truncate.spec.tsx`

**Step 1: Run the targeted suite**

Run: `pnpm test test/Truncate.spec.tsx`

Expected: all tests pass.

**Step 2: Run coverage**

Run: `pnpm coverage`

Expected: `src/**` coverage reaches 100% for statements, branches, functions, and lines. If any path remains uncovered, add one more focused test and re-run coverage.
26 changes: 17 additions & 9 deletions src/Truncate/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,13 +85,13 @@ export const getMiddleTruncateFragments = ({
// If current width is less than target width, attempt to expand fragments
if (fullWidth < targetWidth) {
// Try to expand startFragment to utilize available space
// Only expand if startSliceIndex > 0 (i.e., when end is not greater than text length)
// Only expand while there is still uncovered text between the two fragments.
while (
startSliceIndex > 0 &&
startSliceIndex + startFragment.length < length &&
startFragment.length < length &&
startFragment.length + endFragment.length < fullText.length &&
fullWidth < targetWidth
) {
const nextChar = lastLineText[startSliceIndex + startFragment.length]
const nextChar = lastLineText[startFragment.length]
const testStartFragment = startFragment + nextChar
const testWidth = getFragmentsTotalWidth(testStartFragment, endFragment)

Expand All @@ -104,7 +104,11 @@ export const getMiddleTruncateFragments = ({
}

// If there's still space available, try to expand endFragment
while (endFragment.length < fullText.length && fullWidth < targetWidth) {
while (
endFragment.length < fullText.length &&
startFragment.length + endFragment.length < fullText.length &&
fullWidth < targetWidth
) {
const nextChar = fullText[fullText.length - endFragment.length - 1]
const testEndFragment = nextChar + endFragment
const testWidth = getFragmentsTotalWidth(startFragment, testEndFragment)
Expand Down Expand Up @@ -145,8 +149,6 @@ export const getMiddleTruncateFragments = ({
startFragment = startFragment.slice(0, startFragment.length - 1)
} else if (endFragment.length > 0) {
endFragment = endFragment.slice(1)
} else {
break
}

fullWidth = getFragmentsTotalWidth(startFragment, endFragment)
Expand All @@ -156,12 +158,18 @@ export const getMiddleTruncateFragments = ({
// This step ensures to use every available pixel efficiently
if (fullWidth < targetWidth) {
const remainingWidth = targetWidth - fullWidth
const hasHiddenText =
startFragment.length + endFragment.length < fullText.length

// Try to add characters to both ends while maintaining visual balance
// Only add to startFragment if startSliceIndex > 0
const startChar =
startSliceIndex > 0 ? lastLineText[startFragment.length] : null
const endChar = fullText[fullText.length - endFragment.length - 1]
hasHiddenText && startSliceIndex > 0
? lastLineText[startFragment.length]
: null
const endChar = hasHiddenText
? fullText[fullText.length - endFragment.length - 1]
: null

if (startChar && measureWidth(startChar) <= remainingWidth) {
startFragment += startChar
Expand Down
157 changes: 157 additions & 0 deletions test/Truncate.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -566,6 +566,15 @@ describe('<Truncate />', () => {
describe('getMiddleTruncateFragments', () => {
const targetWidth = width
const ellipsisWidth = measureWidth(ellipsis)
const createVariableMeasureWidth = (
widths: Record<string, number>,
fallback = 1,
) => {
return (text: string) =>
text.split('').reduce((total, char) => {
return total + (widths[char] ?? fallback)
}, 0)
}

it('should return correct fragments when text fits within target width', () => {
const options = {
Expand Down Expand Up @@ -639,6 +648,154 @@ describe('<Truncate />', () => {
endFragment: 'This is a long text',
})
})

it('should expand startFragment with the next uncovered character only', () => {
const measureCompactWidth = (text: string) => text.length
const result = getMiddleTruncateFragments({
end: -2,
lastLineText: 'abcdef',
fullText: 'abcdefghij',
targetWidth: 8,
ellipsisWidth: 1,
measureWidth: measureCompactWidth,
})

expect(result).toEqual({
startFragment: 'abcde',
endFragment: 'ij',
})
})

it('should stop expanding when fragments already cover the full text', () => {
const measureCompactWidth = (text: string) => text.length
const result = getMiddleTruncateFragments({
end: -4,
lastLineText: 'abcdef',
fullText: 'abcdef',
targetWidth: 8,
ellipsisWidth: 1,
measureWidth: measureCompactWidth,
})

expect(result).toEqual({
startFragment: 'ab',
endFragment: 'cdef',
})
})

it('should break start expansion when the next character exceeds the target width', () => {
const result = getMiddleTruncateFragments({
end: -1,
lastLineText: 'abc',
fullText: 'abcdef',
targetWidth: 5,
ellipsisWidth: 1,
measureWidth: createVariableMeasureWidth({ c: 2 }),
})

expect(result).toEqual({
startFragment: 'ab',
endFragment: 'ef',
})
})

it('should expand endFragment when start expansion cannot use the remaining width', () => {
const measureCompactWidth = (text: string) => text.length
const result = getMiddleTruncateFragments({
end: -1,
lastLineText: 'abc',
fullText: 'abcdef',
targetWidth: 6,
ellipsisWidth: 1,
measureWidth: measureCompactWidth,
})

expect(result).toEqual({
startFragment: 'abc',
endFragment: 'ef',
})
})

it('should break end expansion when prepending one more character exceeds the target width', () => {
const result = getMiddleTruncateFragments({
end: -1,
lastLineText: 'abc',
fullText: 'abcdef',
targetWidth: 6,
ellipsisWidth: 1,
measureWidth: createVariableMeasureWidth({ e: 2 }),
})

expect(result).toEqual({
startFragment: 'abc',
endFragment: 'f',
})
})

it('should truncate only the end fragment when the start fragment is empty', () => {
const measureCompactWidth = (text: string) => text.length
const result = getMiddleTruncateFragments({
end: -3,
lastLineText: 'abc',
fullText: 'abcdef',
targetWidth: 2,
ellipsisWidth: 1,
measureWidth: measureCompactWidth,
})

expect(result).toEqual({
startFragment: '',
endFragment: 'f',
})
})

it('should truncate the wider end fragment first and then trim the start fragment if needed', () => {
const result = getMiddleTruncateFragments({
end: -1,
lastLineText: 'abc',
fullText: 'abcdef',
targetWidth: 2,
ellipsisWidth: 1,
measureWidth: createVariableMeasureWidth({ f: 3 }),
})

expect(result).toEqual({
startFragment: 'a',
endFragment: '',
})
})

it('should fine-tune by adding one start character when there is remaining width', () => {
const result = getMiddleTruncateFragments({
end: -1,
lastLineText: 'abc',
fullText: 'abcdef',
targetWidth: 3,
ellipsisWidth: 1,
measureWidth: createVariableMeasureWidth({ f: 2 }),
})

expect(result).toEqual({
startFragment: 'ab',
endFragment: '',
})
})

it('should fine-tune by adding one end character when start fine-tuning does not fit', () => {
const result = getMiddleTruncateFragments({
end: -1,
lastLineText: 'abc',
fullText: 'abcdef',
targetWidth: 4,
ellipsisWidth: 1,
measureWidth: createVariableMeasureWidth({ b: 2 }),
})

expect(result).toEqual({
startFragment: 'a',
endFragment: 'ef',
})
})
})

describe('Testing side effects of other props values', () => {
Expand Down