feat: Add nesting of tags in a tree
- levels are joined by forward slashes in the keywords - behaviour is opt-in for now - long paths are shortened if there is not enough display room Closes #687.
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { describe, it, expect, afterEach, vi } from 'vitest';
|
||||
import { useShortenedText } from '../use-shortened-text';
|
||||
|
||||
const CANDIDATES = ['Work/Clients/Acme/Sales', 'Work/../Acme/Sales', 'Work/.../Sales'];
|
||||
|
||||
/**
|
||||
* Reports `width` for the observed element and measures text at 10px per
|
||||
* character, so a width of N*10 fits any candidate of N characters or fewer.
|
||||
*/
|
||||
function stubMeasurement(width: number) {
|
||||
// Implementing the interface rather than passing an anonymous class keeps the
|
||||
// members the hook never calls from reading as dead code.
|
||||
class StubResizeObserver implements ResizeObserver {
|
||||
constructor(private readonly callback: ResizeObserverCallback) {}
|
||||
|
||||
/** The hook observes once on mount; hand it `width` straight back. */
|
||||
observe(target: Element) {
|
||||
this.callback([{ target, contentRect: { width } } as unknown as ResizeObserverEntry], this);
|
||||
}
|
||||
|
||||
unobserve() {}
|
||||
|
||||
disconnect() {}
|
||||
}
|
||||
|
||||
vi.stubGlobal('ResizeObserver', StubResizeObserver);
|
||||
vi.spyOn(HTMLCanvasElement.prototype, 'getContext').mockReturnValue({
|
||||
font: '',
|
||||
measureText: (text: string) => ({ width: text.length * 10 }),
|
||||
} as unknown as CanvasRenderingContext2D);
|
||||
}
|
||||
|
||||
function Probe({ candidates }: { candidates: string[] }) {
|
||||
const [ref, text] = useShortenedText(candidates);
|
||||
return <span ref={ref} data-testid="probe">{text}</span>;
|
||||
}
|
||||
|
||||
function renderProbe(candidates: string[]): string {
|
||||
render(<Probe candidates={candidates} />);
|
||||
return screen.getByTestId('probe').textContent ?? '';
|
||||
}
|
||||
|
||||
describe('useShortenedText', () => {
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals();
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('returns the longest candidate where the DOM cannot be measured', () => {
|
||||
// No ResizeObserver: server rendering, and jsdom by default. Showing the
|
||||
// whole path beats shortening it on a guess.
|
||||
expect(renderProbe(CANDIDATES)).toBe('Work/Clients/Acme/Sales');
|
||||
});
|
||||
|
||||
it('keeps the full path when the element is wide enough', () => {
|
||||
stubMeasurement(230);
|
||||
expect(renderProbe(CANDIDATES)).toBe('Work/Clients/Acme/Sales');
|
||||
});
|
||||
|
||||
it('steps down only as far as the width requires', () => {
|
||||
stubMeasurement(200);
|
||||
expect(renderProbe(CANDIDATES)).toBe('Work/../Acme/Sales');
|
||||
});
|
||||
|
||||
it('falls back to the shortest candidate when none of them fit', () => {
|
||||
stubMeasurement(40);
|
||||
expect(renderProbe(CANDIDATES)).toBe('Work/.../Sales');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user