fix(ComparisonView): fix character morphing thresholds and add tracking support
This commit is contained in:
+35
-39
@@ -109,56 +109,52 @@ describe('CharacterComparisonEngine', () => {
|
||||
expect(r2).not.toBe(r1);
|
||||
});
|
||||
|
||||
it('getCharState returns proximity 1 when slider is exactly over char center', () => {
|
||||
// 'A' only: FontA width=10. Container=500px. Line centered.
|
||||
// lineXOffset = (500 - maxWidth) / 2. maxWidth = max(10, 15) = 15 (FontB is wider).
|
||||
// charCenterX = lineXOffset + xA + widthA/2.
|
||||
// Using xA=0, widthA=10: charCenterX = (500-15)/2 + 0 + 5 = 247.5 + 5 = 252.5
|
||||
// charGlobalPercent = (252.5 / 500) * 100 = 50.5
|
||||
// distance = |50.5 - 50.5| = 0 => proximity = 1
|
||||
it('getLineCharStates returns proximity 1 when slider is exactly over char center', () => {
|
||||
const containerWidth = 500;
|
||||
engine.layout('A', '400 16px "FontA"', '400 16px "FontB"', containerWidth, 20);
|
||||
// Recalculate expected percent manually:
|
||||
const lineWidth = Math.max(FONT_A_WIDTH, FONT_B_WIDTH); // 15 (unified worst-case)
|
||||
const lineXOffset = (containerWidth - lineWidth) / 2;
|
||||
const charCenterX = lineXOffset + 0 + FONT_A_WIDTH / 2;
|
||||
const charPercent = (charCenterX / containerWidth) * 100;
|
||||
const result = engine.layout('A', '400 16px "FontA"', '400 16px "FontB"', containerWidth, 20);
|
||||
// Single char: no neighbors → totalWidth = widthA, threshold = containerWidth/2.
|
||||
// When isPast=false, visual center = (containerWidth - widthB)/2 + widthB/2 = containerWidth/2.
|
||||
// So proximity=1 at exactly 50%.
|
||||
const charPercent = 50;
|
||||
|
||||
const state = engine.getCharState(0, 0, charPercent, containerWidth);
|
||||
expect(state.proximity).toBe(1);
|
||||
expect(state.isPast).toBe(false);
|
||||
const states = engine.getLineCharStates(result.lines[0], charPercent, containerWidth);
|
||||
expect(states[0]?.proximity).toBe(1);
|
||||
expect(states[0]?.isPast).toBe(false);
|
||||
});
|
||||
|
||||
it('getCharState returns proximity 0 when slider is far from char', () => {
|
||||
engine.layout('A', '400 16px "FontA"', '400 16px "FontB"', 500, 20);
|
||||
// Slider at 0%, char is near 50% — distance > 5 range => proximity = 0
|
||||
const state = engine.getCharState(0, 0, 0, 500);
|
||||
expect(state.proximity).toBe(0);
|
||||
it('getLineCharStates returns proximity 0 when slider is far from char', () => {
|
||||
const result = engine.layout('A', '400 16px "FontA"', '400 16px "FontB"', 500, 20);
|
||||
const states = engine.getLineCharStates(result.lines[0], 0, 500);
|
||||
expect(states[0]?.proximity).toBe(0);
|
||||
});
|
||||
|
||||
it('getCharState isPast is true when slider has passed char center', () => {
|
||||
engine.layout('A', '400 16px "FontA"', '400 16px "FontB"', 500, 20);
|
||||
const state = engine.getCharState(0, 0, 100, 500);
|
||||
expect(state.isPast).toBe(true);
|
||||
it('getLineCharStates isPast is true when slider has passed char center', () => {
|
||||
const result = engine.layout('A', '400 16px "FontA"', '400 16px "FontB"', 500, 20);
|
||||
const states = engine.getLineCharStates(result.lines[0], 100, 500);
|
||||
expect(states[0]?.isPast).toBe(true);
|
||||
});
|
||||
|
||||
it('getCharState returns safe default for out-of-range lineIndex', () => {
|
||||
engine.layout('A', '400 16px "FontA"', '400 16px "FontB"', 500, 20);
|
||||
const state = engine.getCharState(99, 0, 50, 500);
|
||||
expect(state.proximity).toBe(0);
|
||||
expect(state.isPast).toBe(false);
|
||||
it('getLineCharStates returns empty array for out-of-range lineIndex', () => {
|
||||
const result = engine.layout('A', '400 16px "FontA"', '400 16px "FontB"', 500, 20);
|
||||
// Passing an undefined object because the index doesn't exist.
|
||||
const states = engine.getLineCharStates(result.lines[99], 50, 500);
|
||||
expect(states).toEqual([]);
|
||||
});
|
||||
|
||||
it('getCharState returns safe default for out-of-range charIndex', () => {
|
||||
engine.layout('A', '400 16px "FontA"', '400 16px "FontB"', 500, 20);
|
||||
const state = engine.getCharState(0, 99, 50, 500);
|
||||
expect(state.proximity).toBe(0);
|
||||
expect(state.isPast).toBe(false);
|
||||
it('getLineCharStates returns empty array before layout() has been called', () => {
|
||||
// Passing an undefined object because layout() hasn't been called.
|
||||
const states = engine.getLineCharStates(undefined as any, 50, 500);
|
||||
expect(states).toEqual([]);
|
||||
});
|
||||
|
||||
it('getCharState returns safe default before layout() has been called', () => {
|
||||
const state = engine.getCharState(0, 0, 50, 500);
|
||||
expect(state.proximity).toBe(0);
|
||||
expect(state.isPast).toBe(false);
|
||||
it('getLineCharStates returns safe defaults for all chars', () => {
|
||||
const result = engine.layout('A', '400 16px "FontA"', '400 16px "FontB"', 500, 20);
|
||||
const states = engine.getLineCharStates(result.lines[0], 50, 500);
|
||||
expect(states.length).toBeGreaterThan(0);
|
||||
for (const s of states) {
|
||||
expect(s.proximity).toBeGreaterThanOrEqual(0);
|
||||
expect(s.proximity).toBeLessThanOrEqual(1);
|
||||
expect(typeof s.isPast).toBe('boolean');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user