mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-07-07 13:08:54 -07:00
feat(stats): Trends dashboard overhaul — title visibility, ranking modes, calendar-accurate windows, tooltips (#140)
This commit is contained in:
@@ -5,9 +5,40 @@ import type { PerAnimeDataPoint } from './StackedTrendChart';
|
||||
import {
|
||||
buildAnimeVisibilityOptions,
|
||||
filterHiddenAnimeData,
|
||||
loadHiddenTitles,
|
||||
loadMaxTitles,
|
||||
loadMaxTitlesMode,
|
||||
loadShowEmptyDays,
|
||||
pruneHiddenAnime,
|
||||
saveHiddenTitles,
|
||||
saveMaxTitles,
|
||||
saveMaxTitlesMode,
|
||||
saveShowEmptyDays,
|
||||
} from './anime-visibility';
|
||||
|
||||
function installLocalStorage(initial: Record<string, string> = {}) {
|
||||
const previous = Object.getOwnPropertyDescriptor(globalThis, 'localStorage');
|
||||
const values = new Map(Object.entries(initial));
|
||||
Object.defineProperty(globalThis, 'localStorage', {
|
||||
configurable: true,
|
||||
value: {
|
||||
getItem: (key: string) => values.get(key) ?? null,
|
||||
setItem: (key: string, value: string) => values.set(key, value),
|
||||
removeItem: (key: string) => values.delete(key),
|
||||
},
|
||||
});
|
||||
return {
|
||||
values,
|
||||
restore: () => {
|
||||
if (previous) {
|
||||
Object.defineProperty(globalThis, 'localStorage', previous);
|
||||
} else {
|
||||
delete (globalThis as { localStorage?: unknown }).localStorage;
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const SAMPLE_POINTS: PerAnimeDataPoint[] = [
|
||||
{ epochDay: 1, animeTitle: 'KonoSuba', value: 5 },
|
||||
{ epochDay: 2, animeTitle: 'KonoSuba', value: 10 },
|
||||
@@ -45,3 +76,86 @@ test('pruneHiddenAnime drops titles that are no longer available', () => {
|
||||
|
||||
assert.deepEqual([...hidden], ['KonoSuba']);
|
||||
});
|
||||
|
||||
test('hidden titles round-trip through localStorage', () => {
|
||||
const { restore } = installLocalStorage();
|
||||
try {
|
||||
saveHiddenTitles(new Set(['KonoSuba', 'One Piece']));
|
||||
assert.deepEqual([...loadHiddenTitles()], ['KonoSuba', 'One Piece']);
|
||||
} finally {
|
||||
restore();
|
||||
}
|
||||
});
|
||||
|
||||
test('loadHiddenTitles tolerates missing or malformed stored values', () => {
|
||||
const { restore } = installLocalStorage({
|
||||
'subminer-stats-trends-hidden-titles': '{"not":"an array"}',
|
||||
});
|
||||
try {
|
||||
assert.deepEqual([...loadHiddenTitles()], []);
|
||||
} finally {
|
||||
restore();
|
||||
}
|
||||
});
|
||||
|
||||
test('max titles preference defaults to 7 and round-trips including explicit All', () => {
|
||||
const { values, restore } = installLocalStorage();
|
||||
try {
|
||||
// First run (nothing stored) defaults to the 7-title cap, not "All".
|
||||
assert.equal(loadMaxTitles(), 7);
|
||||
saveMaxTitles(5);
|
||||
assert.equal(loadMaxTitles(), 5);
|
||||
// "All" persists explicitly instead of collapsing back to the default.
|
||||
saveMaxTitles(null);
|
||||
assert.equal(loadMaxTitles(), null);
|
||||
assert.equal(values.get('subminer-stats-trends-max-titles'), 'all');
|
||||
} finally {
|
||||
restore();
|
||||
}
|
||||
});
|
||||
|
||||
test('loadMaxTitles falls back to the default for unsupported stored values', () => {
|
||||
for (const storedValue of ['8', '0', 'banana']) {
|
||||
const { restore } = installLocalStorage({ 'subminer-stats-trends-max-titles': storedValue });
|
||||
try {
|
||||
assert.equal(loadMaxTitles(), 7);
|
||||
} finally {
|
||||
restore();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('max titles mode defaults to recent and round-trips', () => {
|
||||
const { restore } = installLocalStorage();
|
||||
try {
|
||||
assert.equal(loadMaxTitlesMode(), 'recent');
|
||||
saveMaxTitlesMode('total');
|
||||
assert.equal(loadMaxTitlesMode(), 'total');
|
||||
saveMaxTitlesMode('recent');
|
||||
assert.equal(loadMaxTitlesMode(), 'recent');
|
||||
} finally {
|
||||
restore();
|
||||
}
|
||||
});
|
||||
|
||||
test('loadMaxTitlesMode falls back to recent for unknown stored values', () => {
|
||||
const { restore } = installLocalStorage({ 'subminer-stats-trends-max-titles-mode': 'sideways' });
|
||||
try {
|
||||
assert.equal(loadMaxTitlesMode(), 'recent');
|
||||
} finally {
|
||||
restore();
|
||||
}
|
||||
});
|
||||
|
||||
test('show empty days preference defaults to true and round-trips', () => {
|
||||
const { restore } = installLocalStorage();
|
||||
try {
|
||||
assert.equal(loadShowEmptyDays(), true);
|
||||
saveShowEmptyDays(false);
|
||||
assert.equal(loadShowEmptyDays(), false);
|
||||
saveShowEmptyDays(true);
|
||||
assert.equal(loadShowEmptyDays(), true);
|
||||
} finally {
|
||||
restore();
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user