Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
35 changes: 35 additions & 0 deletions photomap/frontend/static/javascript/seek-slider.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class SeekSlider {

this.addEventListeners();
this.updateSliderPosition();
this.updateHoverStripProgress();

// Update position when window resizes (debounced to avoid performance issues)
this.debouncedUpdatePosition = debounce(() => this.updateSliderPosition(), 100);
Expand All @@ -66,6 +67,32 @@ class SeekSlider {
this.hoverStrip.style.left = `${leftPosition}px`;
}

/**
* Update the hover strip gradient to show current slide position
* Everything to the left of the current position is yellow, right is white
* @param {number|null} sliderValue - Optional slider value (1-indexed). If not provided, uses slideState.
*/
updateHoverStripProgress(sliderValue = null) {
if (!this.hoverStrip) return;

let max = 1;

if (state.searchResults?.length > 0) {
max = state.searchResults.length;
} else {
const [, totalSlides] = getCurrentSlideIndex();
max = totalSlides || 1;
}

// Calculate percentage using same formula as slider thumb positioning
// Slider uses 1-indexed values with min=1
const value = sliderValue !== null ? sliderValue : slideState.getCurrentIndex() + 1;
const percent = max > 1 ? ((value - 1) / (max - 1)) * 100 : 0;

// Apply gradient: yellow up to current position, white after
this.hoverStrip.style.background = `linear-gradient(to right, #ffc107 ${percent}%, #ffffff ${percent}%)`;
}

addEventListeners() {
if (this.scoreDisplayElement) {
// Only toggle slider on click, not on hover
Expand Down Expand Up @@ -124,16 +151,21 @@ class SeekSlider {
);
window.addEventListener("searchResultsChanged", () => {
this.searchResultsChanged = true;
this.updateHoverStripProgress();
});
window.addEventListener("albumChanged", () => {
this.searchResultsChanged = true;
this.updateHoverStripProgress();
});
}

async onSliderInput(e) {
const now = Date.now();
const value = parseInt(this.slider.value, 10);

// Update hover strip progress immediately as user drags
this.updateHoverStripProgress(value);

this.infoPanel.style.display = "block";

if (now - this.lastFetchTime >= this.FETCH_THROTTLE_MS) {
Expand Down Expand Up @@ -245,6 +277,8 @@ class SeekSlider {
async onSliderChange() {
this.infoPanel.textContent = "";
const targetIndex = parseInt(this.slider.value, 10) - 1;
// Update hover strip with final slider position
this.updateHoverStripProgress(targetIndex + 1);
this.isUserSeeking = true;
slideState.navigateToIndex(targetIndex, slideState.isSearchMode);
setTimeout(() => {
Expand All @@ -257,6 +291,7 @@ class SeekSlider {
if (this.isUserSeeking) return;
const currentIndex = slideState.getCurrentIndex();
if (this.slider) this.slider.value = currentIndex + 1;
this.updateHoverStripProgress();
this.resetFadeOutTimer();
}

Expand Down
191 changes: 191 additions & 0 deletions tests/frontend/seek-slider.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
// Test file for seek-slider.js - updateHoverStripProgress logic
import { jest, describe, it, expect, beforeEach, afterEach } from '@jest/globals';
import '@testing-library/jest-dom';

describe('seek-slider.js', () => {
describe('updateHoverStripProgress logic', () => {
let hoverStrip;
let mockSlideState;
let backgroundSetter;

// This is the updateHoverStripProgress method extracted for unit testing
const updateHoverStripProgress = (hoverStrip, getCurrentIndex, searchResults, getCurrentSlideIndex, sliderValue = null) => {
if (!hoverStrip) return;

let max = 1;

if (searchResults?.length > 0) {
max = searchResults.length;
} else {
const [, totalSlides] = getCurrentSlideIndex();
max = totalSlides || 1;
}

// Calculate percentage using same formula as slider thumb positioning
// Slider uses 1-indexed values with min=1
const value = sliderValue !== null ? sliderValue : getCurrentIndex() + 1;
const percent = max > 1 ? ((value - 1) / (max - 1)) * 100 : 0;

// Apply gradient: yellow up to current position, white after
hoverStrip.style.background = `linear-gradient(to right, #ffc107 ${percent}%, #ffffff ${percent}%)`;
};

beforeEach(() => {
// Create DOM elements
document.body.innerHTML = `
<div id="sliderHoverStrip" class="slider-hover-strip"></div>
`;
hoverStrip = document.getElementById('sliderHoverStrip');

// Mock the style.background setter since jsdom doesn't support linear-gradient
backgroundSetter = jest.fn();
Object.defineProperty(hoverStrip.style, 'background', {
set: backgroundSetter,
get: () => '',
configurable: true
});

// Mock slideState
mockSlideState = {
getCurrentIndex: jest.fn().mockReturnValue(0)
};
});

afterEach(() => {
document.body.innerHTML = '';
jest.clearAllMocks();
});

it('should set gradient with 0% yellow when at first slide', () => {
mockSlideState.getCurrentIndex.mockReturnValue(0);
const getCurrentSlideIndex = () => [0, 100, null];

updateHoverStripProgress(
hoverStrip,
mockSlideState.getCurrentIndex,
null,
getCurrentSlideIndex
);

// At position 0 of 100 (value=1, min=1, max=100), percent = 0%
expect(backgroundSetter).toHaveBeenCalledWith('linear-gradient(to right, #ffc107 0%, #ffffff 0%)');
});

it('should set gradient with 100% yellow when at last slide', () => {
mockSlideState.getCurrentIndex.mockReturnValue(99);
const getCurrentSlideIndex = () => [99, 100, null];

updateHoverStripProgress(
hoverStrip,
mockSlideState.getCurrentIndex,
null,
getCurrentSlideIndex
);

// At position 99 of 100 (value=100, min=1, max=100), percent = 100%
expect(backgroundSetter).toHaveBeenCalledWith('linear-gradient(to right, #ffc107 100%, #ffffff 100%)');
});

it('should set gradient at approximately 50% when at middle slide', () => {
mockSlideState.getCurrentIndex.mockReturnValue(49);
const getCurrentSlideIndex = () => [49, 100, null];

updateHoverStripProgress(
hoverStrip,
mockSlideState.getCurrentIndex,
null,
getCurrentSlideIndex
);

// At position 49 of 100 (value=50, min=1, max=100), percent = (50-1)/(100-1) * 100 ≈ 49.49%
const expectedPercent = ((50 - 1) / (100 - 1)) * 100;
expect(backgroundSetter).toHaveBeenCalledWith(
expect.stringContaining(`linear-gradient(to right, #ffc107 ${expectedPercent}%`)
);
});

it('should handle search results mode', () => {
mockSlideState.getCurrentIndex.mockReturnValue(4);
const searchResults = [
{ index: 0 }, { index: 1 }, { index: 2 }, { index: 3 }, { index: 4 },
{ index: 5 }, { index: 6 }, { index: 7 }, { index: 8 }, { index: 9 }
];
const getCurrentSlideIndex = () => [4, 100, 4];

updateHoverStripProgress(
hoverStrip,
mockSlideState.getCurrentIndex,
searchResults,
getCurrentSlideIndex
);

// At position 4 of 10 (value=5, min=1, max=10), percent = (5-1)/(10-1) * 100 ≈ 44.44%
const expectedPercent = ((5 - 1) / (10 - 1)) * 100;
expect(backgroundSetter).toHaveBeenCalledWith(
expect.stringContaining(`linear-gradient(to right, #ffc107 ${expectedPercent}%`)
);
});

it('should not throw when hoverStrip is null', () => {
const getCurrentSlideIndex = () => [0, 100, null];

expect(() => updateHoverStripProgress(
null,
mockSlideState.getCurrentIndex,
null,
getCurrentSlideIndex
)).not.toThrow();
});

it('should handle single slide (max equals min)', () => {
mockSlideState.getCurrentIndex.mockReturnValue(0);
const getCurrentSlideIndex = () => [0, 1, null];

updateHoverStripProgress(
hoverStrip,
mockSlideState.getCurrentIndex,
null,
getCurrentSlideIndex
);

// Single slide case: percent should be 0 (not NaN or error)
expect(backgroundSetter).toHaveBeenCalledWith('linear-gradient(to right, #ffc107 0%, #ffffff 0%)');
});

it('should use white and yellow colors for the gradient', () => {
mockSlideState.getCurrentIndex.mockReturnValue(50);
const getCurrentSlideIndex = () => [50, 100, null];

updateHoverStripProgress(
hoverStrip,
mockSlideState.getCurrentIndex,
null,
getCurrentSlideIndex
);

const calledWith = backgroundSetter.mock.calls[0][0];
expect(calledWith).toContain('#ffc107'); // Yellow
expect(calledWith).toContain('#ffffff'); // White
});

it('should use slider value when provided instead of getCurrentIndex', () => {
mockSlideState.getCurrentIndex.mockReturnValue(0); // Would be 0% if used
const getCurrentSlideIndex = () => [0, 100, null];

// Provide slider value of 50 (50% progress)
updateHoverStripProgress(
hoverStrip,
mockSlideState.getCurrentIndex,
null,
getCurrentSlideIndex,
50 // slider value (1-indexed)
);

// At position 49 of 100 (value=50, min=1, max=100), percent = (50-1)/(100-1) * 100 ≈ 49.49%
const expectedPercent = ((50 - 1) / (100 - 1)) * 100;
expect(backgroundSetter).toHaveBeenCalledWith(
expect.stringContaining(`linear-gradient(to right, #ffc107 ${expectedPercent}%`)
);
});
});
});