fix: resolve download client filter element ID mismatch and bind Select All/Deselect All (closes #38)
Build and Push Docker Image / build (push) Successful in 40s
Licence Check / Licence compatibility and copyright header verification (push) Successful in 1m34s
CI / Security audit (push) Successful in 1m52s
CI / Swagger Validation & Coverage (push) Successful in 2m19s
CI / Tests & coverage (push) Successful in 2m31s

This commit is contained in:
2026-05-22 22:49:36 +01:00
parent 4ddd3036d9
commit a006cb4a37
3 changed files with 220 additions and 25 deletions
+171
View File
@@ -0,0 +1,171 @@
// Copyright (c) 2026 Gordon Bolton. MIT License.
/**
* @vitest-environment jsdom
* Tests for client/src/ui/filters.js
*/
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { state } from '../../../client/src/state.js';
import { initDownloadClientFilter, updateDownloadClientFilter, toggleClientSelection, toggleAllClients, updateSelectedCountDisplay } from '../../../client/src/ui/filters.js';
import { renderDownloads } from '../../../client/src/ui/downloads.js';
// Mock renderDownloads to verify re-render triggers
vi.mock('../../../client/src/ui/downloads.js', () => ({
renderDownloads: vi.fn()
}));
// Mock localStorage
const localStorageMock = (() => {
let store = {};
return {
getItem: (key) => store[key] || null,
setItem: (key, value) => { store[key] = value; },
removeItem: (key) => { delete store[key]; },
clear: () => { store = {}; }
};
})();
Object.defineProperty(window, 'localStorage', {
value: localStorageMock
});
function setupDOM() {
document.body.innerHTML = `
<div class="downloads-controls">
<div class="download-client-filter" id="download-client-filter">
<button class="download-client-dropdown-btn" id="download-client-dropdown-btn" type="button">
<span id="download-client-selected-text">All clients</span>
<span class="dropdown-arrow">▼</span>
</button>
<div class="download-client-dropdown" id="download-client-dropdown">
<div class="download-client-dropdown-header">
<button id="download-client-select-all" type="button">Select All</button>
<button id="download-client-deselect-all" type="button">Deselect All</button>
</div>
<div class="download-client-options" id="download-client-options">
<!-- Options will be populated by JavaScript -->
</div>
</div>
</div>
</div>
`;
}
describe('initDownloadClientFilter', () => {
beforeEach(() => {
localStorageMock.clear();
state.downloadClients = [
{ id: 1, type: 'sabnzbd', name: 'SABnzbd' },
{ id: 2, type: 'qbittorrent', name: 'qBittorrent' }
];
state.selectedDownloadClients = [];
vi.clearAllMocks();
setupDOM();
initDownloadClientFilter();
});
afterEach(() => {
document.body.innerHTML = '';
});
it('populates options list with checkboxes matching download clients', () => {
const optionsList = document.getElementById('download-client-options');
expect(optionsList.children.length).toBe(2);
const firstItem = optionsList.children[0];
const checkbox = firstItem.querySelector('input');
const label = firstItem.querySelector('label');
expect(checkbox.type).toBe('checkbox');
expect(checkbox.checked).toBe(false);
expect(label.textContent).toBe('SABnzbd');
});
it('restores checked state based on state.selectedDownloadClients', () => {
state.selectedDownloadClients = [0];
updateDownloadClientFilter();
const optionsList = document.getElementById('download-client-options');
const firstCheckbox = optionsList.children[0].querySelector('input');
const secondCheckbox = optionsList.children[1].querySelector('input');
expect(firstCheckbox.checked).toBe(true);
expect(secondCheckbox.checked).toBe(false);
});
it('clicking a checkbox updates selected state and triggers re-render', () => {
const optionsList = document.getElementById('download-client-options');
const firstCheckbox = optionsList.children[0].querySelector('input');
firstCheckbox.click();
expect(state.selectedDownloadClients).toEqual([0]);
expect(localStorageMock.getItem('sofarr-download-clients')).toBe(JSON.stringify([0]));
expect(renderDownloads).toHaveBeenCalled();
});
it('select all selects all clients and saves to storage', () => {
document.getElementById('download-client-select-all').click();
expect(state.selectedDownloadClients).toEqual([0, 1]);
expect(localStorageMock.getItem('sofarr-download-clients')).toBe(JSON.stringify([0, 1]));
expect(renderDownloads).toHaveBeenCalled();
const optionsList = document.getElementById('download-client-options');
expect(optionsList.children[0].querySelector('input').checked).toBe(true);
expect(optionsList.children[1].querySelector('input').checked).toBe(true);
});
it('deselect all clears all clients and saves empty list to storage', () => {
state.selectedDownloadClients = [0, 1];
updateDownloadClientFilter();
document.getElementById('download-client-deselect-all').click();
expect(state.selectedDownloadClients).toEqual([]);
expect(localStorageMock.getItem('sofarr-download-clients')).toBe(JSON.stringify([]));
expect(renderDownloads).toHaveBeenCalled();
const optionsList = document.getElementById('download-client-options');
expect(optionsList.children[0].querySelector('input').checked).toBe(false);
expect(optionsList.children[1].querySelector('input').checked).toBe(false);
});
it('toggles dropdown when dropdown button is clicked', () => {
const dropdown = document.getElementById('download-client-dropdown');
const btn = document.getElementById('download-client-dropdown-btn');
btn.click();
expect(dropdown.classList.contains('open')).toBe(true);
btn.click();
expect(dropdown.classList.contains('open')).toBe(false);
});
it('closes dropdown when clicking outside', () => {
const dropdown = document.getElementById('download-client-dropdown');
const btn = document.getElementById('download-client-dropdown-btn');
btn.click();
expect(dropdown.classList.contains('open')).toBe(true);
document.body.click();
expect(dropdown.classList.contains('open')).toBe(false);
});
it('updates selected text display correctly based on count', () => {
const selectedText = document.getElementById('download-client-selected-text');
state.selectedDownloadClients = [];
updateSelectedCountDisplay();
expect(selectedText.textContent).toBe('All clients');
state.selectedDownloadClients = [0];
updateSelectedCountDisplay();
expect(selectedText.textContent).toBe('SABnzbd');
state.selectedDownloadClients = [0, 1];
updateSelectedCountDisplay();
expect(selectedText.textContent).toBe('All clients'); // Since it's all of them
});
});