- test_anime_endpoints.py: Minor updates - test_download_retry.py: Refinements - test_i18n.js: Updates - test_tmdb_client.py: Improvements - test_tmdb_rate_limiting.py: Test enhancements - test_user_preferences.js: Updates
591 lines
20 KiB
JavaScript
591 lines
20 KiB
JavaScript
/**
|
|
* Unit tests for user preferences functionality
|
|
* Tests preference storage, loading, and application
|
|
*/
|
|
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
describe('UserPreferences', () => {
|
|
let originalLocalStorage;
|
|
let originalDocument;
|
|
let consoleErrorSpy;
|
|
|
|
const STORAGE_KEY = 'aniworld_preferences';
|
|
|
|
// User Preferences module implementation
|
|
const createUserPreferences = () => {
|
|
const UserPrefs = {
|
|
loadPreferences() {
|
|
try {
|
|
const stored = localStorage.getItem(STORAGE_KEY);
|
|
if (stored) {
|
|
const preferences = JSON.parse(stored);
|
|
this.applyPreferences(preferences);
|
|
return preferences;
|
|
}
|
|
return {};
|
|
} catch (error) {
|
|
console.error('[User Preferences] Error loading:', error);
|
|
return {};
|
|
}
|
|
},
|
|
|
|
savePreferences(preferences) {
|
|
try {
|
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(preferences));
|
|
return true;
|
|
} catch (error) {
|
|
console.error('[User Preferences] Error saving:', error);
|
|
return false;
|
|
}
|
|
},
|
|
|
|
applyPreferences(preferences) {
|
|
if (preferences.theme) {
|
|
document.documentElement.setAttribute('data-theme', preferences.theme);
|
|
}
|
|
if (preferences.language) {
|
|
// Language preference would be applied here
|
|
document.documentElement.setAttribute('lang', preferences.language);
|
|
}
|
|
},
|
|
|
|
getPreferences() {
|
|
try {
|
|
const stored = localStorage.getItem(STORAGE_KEY);
|
|
return stored ? JSON.parse(stored) : {};
|
|
} catch (error) {
|
|
console.error('[User Preferences] Error getting preferences:', error);
|
|
return {};
|
|
}
|
|
},
|
|
|
|
updatePreference(key, value) {
|
|
const preferences = this.getPreferences();
|
|
preferences[key] = value;
|
|
this.savePreferences(preferences);
|
|
this.applyPreferences(preferences);
|
|
},
|
|
|
|
resetPreferences() {
|
|
try {
|
|
localStorage.removeItem(STORAGE_KEY);
|
|
return true;
|
|
} catch (error) {
|
|
console.error('[User Preferences] Error resetting:', error);
|
|
return false;
|
|
}
|
|
}
|
|
};
|
|
|
|
return UserPrefs;
|
|
};
|
|
|
|
beforeEach(() => {
|
|
// Mock localStorage
|
|
originalLocalStorage = global.localStorage;
|
|
global.localStorage = {
|
|
data: {},
|
|
getItem(key) {
|
|
return this.data[key] || null;
|
|
},
|
|
setItem(key, value) {
|
|
this.data[key] = value;
|
|
},
|
|
removeItem(key) {
|
|
delete this.data[key];
|
|
},
|
|
clear() {
|
|
this.data = {};
|
|
}
|
|
};
|
|
|
|
// Mock document
|
|
originalDocument = global.document;
|
|
global.document = {
|
|
documentElement: {
|
|
attributes: {},
|
|
setAttribute(key, value) {
|
|
this.attributes[key] = value;
|
|
},
|
|
getAttribute(key) {
|
|
return this.attributes[key] || null;
|
|
},
|
|
removeAttribute(key) {
|
|
delete this.attributes[key];
|
|
}
|
|
},
|
|
readyState: 'complete',
|
|
addEventListener: vi.fn()
|
|
};
|
|
|
|
// Spy on console.error
|
|
consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
});
|
|
|
|
afterEach(() => {
|
|
global.localStorage = originalLocalStorage;
|
|
global.document = originalDocument;
|
|
consoleErrorSpy.mockRestore();
|
|
});
|
|
|
|
describe('Loading Preferences', () => {
|
|
it('should load preferences from localStorage', () => {
|
|
const UserPrefs = createUserPreferences();
|
|
const testPrefs = { theme: 'dark', language: 'de' };
|
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(testPrefs));
|
|
|
|
const loaded = UserPrefs.loadPreferences();
|
|
|
|
expect(loaded).toEqual(testPrefs);
|
|
});
|
|
|
|
it('should return empty object when no preferences exist', () => {
|
|
const UserPrefs = createUserPreferences();
|
|
const loaded = UserPrefs.loadPreferences();
|
|
|
|
expect(loaded).toEqual({});
|
|
});
|
|
|
|
it('should handle invalid JSON gracefully', () => {
|
|
const UserPrefs = createUserPreferences();
|
|
localStorage.setItem(STORAGE_KEY, 'invalid-json{');
|
|
|
|
const loaded = UserPrefs.loadPreferences();
|
|
|
|
expect(loaded).toEqual({});
|
|
expect(consoleErrorSpy).toHaveBeenCalled();
|
|
});
|
|
|
|
it('should handle localStorage errors', () => {
|
|
const UserPrefs = createUserPreferences();
|
|
const originalGetItem = localStorage.getItem;
|
|
localStorage.getItem = () => {
|
|
throw new Error('localStorage error');
|
|
};
|
|
|
|
const loaded = UserPrefs.loadPreferences();
|
|
|
|
expect(loaded).toEqual({});
|
|
expect(consoleErrorSpy).toHaveBeenCalled();
|
|
|
|
localStorage.getItem = originalGetItem;
|
|
});
|
|
|
|
it('should apply preferences after loading', () => {
|
|
const UserPrefs = createUserPreferences();
|
|
const testPrefs = { theme: 'dark' };
|
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(testPrefs));
|
|
|
|
UserPrefs.loadPreferences();
|
|
|
|
expect(document.documentElement.getAttribute('data-theme')).toBe('dark');
|
|
});
|
|
});
|
|
|
|
describe('Saving Preferences', () => {
|
|
it('should save preferences to localStorage', () => {
|
|
const UserPrefs = createUserPreferences();
|
|
const testPrefs = { theme: 'light', language: 'en' };
|
|
|
|
const result = UserPrefs.savePreferences(testPrefs);
|
|
|
|
expect(result).toBe(true);
|
|
expect(localStorage.getItem(STORAGE_KEY)).toBe(JSON.stringify(testPrefs));
|
|
});
|
|
|
|
it('should overwrite existing preferences', () => {
|
|
const UserPrefs = createUserPreferences();
|
|
localStorage.setItem(STORAGE_KEY, JSON.stringify({ theme: 'dark' }));
|
|
|
|
UserPrefs.savePreferences({ theme: 'light' });
|
|
|
|
const stored = JSON.parse(localStorage.getItem(STORAGE_KEY));
|
|
expect(stored.theme).toBe('light');
|
|
});
|
|
|
|
it('should handle localStorage errors', () => {
|
|
const UserPrefs = createUserPreferences();
|
|
const originalSetItem = localStorage.setItem;
|
|
localStorage.setItem = () => {
|
|
throw new Error('localStorage full');
|
|
};
|
|
|
|
const result = UserPrefs.savePreferences({ theme: 'dark' });
|
|
|
|
expect(result).toBe(false);
|
|
expect(consoleErrorSpy).toHaveBeenCalled();
|
|
|
|
localStorage.setItem = originalSetItem;
|
|
});
|
|
|
|
it('should handle null preferences', () => {
|
|
const UserPrefs = createUserPreferences();
|
|
|
|
expect(() => UserPrefs.savePreferences(null)).not.toThrow();
|
|
});
|
|
|
|
it('should handle undefined preferences', () => {
|
|
const UserPrefs = createUserPreferences();
|
|
|
|
expect(() => UserPrefs.savePreferences(undefined)).not.toThrow();
|
|
});
|
|
});
|
|
|
|
describe('Getting Preferences', () => {
|
|
it('should get stored preferences', () => {
|
|
const UserPrefs = createUserPreferences();
|
|
const testPrefs = { theme: 'dark', language: 'de' };
|
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(testPrefs));
|
|
|
|
const prefs = UserPrefs.getPreferences();
|
|
|
|
expect(prefs).toEqual(testPrefs);
|
|
});
|
|
|
|
it('should return empty object when none exist', () => {
|
|
const UserPrefs = createUserPreferences();
|
|
|
|
const prefs = UserPrefs.getPreferences();
|
|
|
|
expect(prefs).toEqual({});
|
|
});
|
|
|
|
it('should handle parse errors gracefully', () => {
|
|
const UserPrefs = createUserPreferences();
|
|
localStorage.setItem(STORAGE_KEY, '{invalid}');
|
|
|
|
const prefs = UserPrefs.getPreferences();
|
|
|
|
expect(prefs).toEqual({});
|
|
expect(consoleErrorSpy).toHaveBeenCalled();
|
|
});
|
|
|
|
it('should not modify original stored data', () => {
|
|
const UserPrefs = createUserPreferences();
|
|
const testPrefs = { theme: 'dark' };
|
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(testPrefs));
|
|
|
|
const prefs = UserPrefs.getPreferences();
|
|
prefs.theme = 'light';
|
|
|
|
const storedPrefs = UserPrefs.getPreferences();
|
|
expect(storedPrefs.theme).toBe('dark');
|
|
});
|
|
});
|
|
|
|
describe('Applying Preferences', () => {
|
|
it('should apply theme preference to document', () => {
|
|
const UserPrefs = createUserPreferences();
|
|
const preferences = { theme: 'dark' };
|
|
|
|
UserPrefs.applyPreferences(preferences);
|
|
|
|
expect(document.documentElement.getAttribute('data-theme')).toBe('dark');
|
|
});
|
|
|
|
it('should apply language preference to document', () => {
|
|
const UserPrefs = createUserPreferences();
|
|
const preferences = { language: 'de' };
|
|
|
|
UserPrefs.applyPreferences(preferences);
|
|
|
|
expect(document.documentElement.getAttribute('lang')).toBe('de');
|
|
});
|
|
|
|
it('should apply multiple preferences', () => {
|
|
const UserPrefs = createUserPreferences();
|
|
const preferences = { theme: 'dark', language: 'de' };
|
|
|
|
UserPrefs.applyPreferences(preferences);
|
|
|
|
expect(document.documentElement.getAttribute('data-theme')).toBe('dark');
|
|
expect(document.documentElement.getAttribute('lang')).toBe('de');
|
|
});
|
|
|
|
it('should handle empty preferences object', () => {
|
|
const UserPrefs = createUserPreferences();
|
|
|
|
expect(() => UserPrefs.applyPreferences({})).not.toThrow();
|
|
});
|
|
|
|
it('should handle undefined preferences', () => {
|
|
const UserPrefs = createUserPreferences();
|
|
|
|
expect(() => UserPrefs.applyPreferences(undefined)).not.toThrow();
|
|
});
|
|
|
|
it('should only apply defined preferences', () => {
|
|
const UserPrefs = createUserPreferences();
|
|
const preferences = { theme: 'dark' };
|
|
|
|
UserPrefs.applyPreferences(preferences);
|
|
|
|
expect(document.documentElement.getAttribute('data-theme')).toBe('dark');
|
|
expect(document.documentElement.getAttribute('lang')).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('Updating Specific Preference', () => {
|
|
it('should update single preference', () => {
|
|
const UserPrefs = createUserPreferences();
|
|
|
|
UserPrefs.updatePreference('theme', 'dark');
|
|
|
|
const prefs = UserPrefs.getPreferences();
|
|
expect(prefs.theme).toBe('dark');
|
|
});
|
|
|
|
it('should update existing preference', () => {
|
|
const UserPrefs = createUserPreferences();
|
|
UserPrefs.savePreferences({ theme: 'light', language: 'en' });
|
|
|
|
UserPrefs.updatePreference('theme', 'dark');
|
|
|
|
const prefs = UserPrefs.getPreferences();
|
|
expect(prefs.theme).toBe('dark');
|
|
expect(prefs.language).toBe('en');
|
|
});
|
|
|
|
it('should add new preference to existing ones', () => {
|
|
const UserPrefs = createUserPreferences();
|
|
UserPrefs.savePreferences({ theme: 'light' });
|
|
|
|
UserPrefs.updatePreference('language', 'de');
|
|
|
|
const prefs = UserPrefs.getPreferences();
|
|
expect(prefs.theme).toBe('light');
|
|
expect(prefs.language).toBe('de');
|
|
});
|
|
|
|
it('should apply preference after updating', () => {
|
|
const UserPrefs = createUserPreferences();
|
|
|
|
UserPrefs.updatePreference('theme', 'dark');
|
|
|
|
expect(document.documentElement.getAttribute('data-theme')).toBe('dark');
|
|
});
|
|
|
|
it('should persist updated preference', () => {
|
|
const UserPrefs = createUserPreferences();
|
|
|
|
UserPrefs.updatePreference('theme', 'dark');
|
|
|
|
const stored = JSON.parse(localStorage.getItem(STORAGE_KEY));
|
|
expect(stored.theme).toBe('dark');
|
|
});
|
|
});
|
|
|
|
describe('Resetting Preferences', () => {
|
|
it('should remove preferences from localStorage', () => {
|
|
const UserPrefs = createUserPreferences();
|
|
UserPrefs.savePreferences({ theme: 'dark' });
|
|
|
|
const result = UserPrefs.resetPreferences();
|
|
|
|
expect(result).toBe(true);
|
|
expect(localStorage.getItem(STORAGE_KEY)).toBeNull();
|
|
});
|
|
|
|
it('should handle missing preferences gracefully', () => {
|
|
const UserPrefs = createUserPreferences();
|
|
|
|
expect(() => UserPrefs.resetPreferences()).not.toThrow();
|
|
});
|
|
|
|
it('should handle localStorage errors', () => {
|
|
const UserPrefs = createUserPreferences();
|
|
const originalRemoveItem = localStorage.removeItem;
|
|
localStorage.removeItem = () => {
|
|
throw new Error('localStorage error');
|
|
};
|
|
|
|
const result = UserPrefs.resetPreferences();
|
|
|
|
expect(result).toBe(false);
|
|
expect(consoleErrorSpy).toHaveBeenCalled();
|
|
|
|
localStorage.removeItem = originalRemoveItem;
|
|
});
|
|
});
|
|
|
|
describe('Persistence Across Sessions', () => {
|
|
it('should persist theme across sessions', () => {
|
|
const UserPrefs1 = createUserPreferences();
|
|
UserPrefs1.updatePreference('theme', 'dark');
|
|
|
|
// Simulate new session
|
|
const UserPrefs2 = createUserPreferences();
|
|
const prefs = UserPrefs2.getPreferences();
|
|
|
|
expect(prefs.theme).toBe('dark');
|
|
});
|
|
|
|
it('should persist language across sessions', () => {
|
|
const UserPrefs1 = createUserPreferences();
|
|
UserPrefs1.updatePreference('language', 'de');
|
|
|
|
// Simulate new session
|
|
const UserPrefs2 = createUserPreferences();
|
|
const prefs = UserPrefs2.getPreferences();
|
|
|
|
expect(prefs.language).toBe('de');
|
|
});
|
|
|
|
it('should persist multiple preferences', () => {
|
|
const UserPrefs1 = createUserPreferences();
|
|
UserPrefs1.savePreferences({
|
|
theme: 'dark',
|
|
language: 'de',
|
|
autoUpdate: true
|
|
});
|
|
|
|
// Simulate new session
|
|
const UserPrefs2 = createUserPreferences();
|
|
const prefs = UserPrefs2.getPreferences();
|
|
|
|
expect(prefs.theme).toBe('dark');
|
|
expect(prefs.language).toBe('de');
|
|
expect(prefs.autoUpdate).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('Edge Cases', () => {
|
|
it('should handle very large preferences object', () => {
|
|
const UserPrefs = createUserPreferences();
|
|
const largePrefs = {};
|
|
for (let i = 0; i < 100; i++) {
|
|
largePrefs[`key${i}`] = `value${i}`;
|
|
}
|
|
|
|
const result = UserPrefs.savePreferences(largePrefs);
|
|
|
|
expect(result).toBe(true);
|
|
const loaded = UserPrefs.getPreferences();
|
|
expect(loaded).toEqual(largePrefs);
|
|
});
|
|
|
|
it('should handle special characters in values', () => {
|
|
const UserPrefs = createUserPreferences();
|
|
const prefs = {
|
|
theme: 'dark-<script>alert("xss")</script>',
|
|
language: '日本語'
|
|
};
|
|
|
|
UserPrefs.savePreferences(prefs);
|
|
const loaded = UserPrefs.getPreferences();
|
|
|
|
expect(loaded).toEqual(prefs);
|
|
});
|
|
|
|
it('should handle numeric values', () => {
|
|
const UserPrefs = createUserPreferences();
|
|
const prefs = { fontSize: 16, volume: 0.5 };
|
|
|
|
UserPrefs.savePreferences(prefs);
|
|
const loaded = UserPrefs.getPreferences();
|
|
|
|
expect(loaded.fontSize).toBe(16);
|
|
expect(loaded.volume).toBe(0.5);
|
|
});
|
|
|
|
it('should handle boolean values', () => {
|
|
const UserPrefs = createUserPreferences();
|
|
const prefs = { autoUpdate: true, notifications: false };
|
|
|
|
UserPrefs.savePreferences(prefs);
|
|
const loaded = UserPrefs.getPreferences();
|
|
|
|
expect(loaded.autoUpdate).toBe(true);
|
|
expect(loaded.notifications).toBe(false);
|
|
});
|
|
|
|
it('should handle nested objects', () => {
|
|
const UserPrefs = createUserPreferences();
|
|
const prefs = {
|
|
display: {
|
|
theme: 'dark',
|
|
fontSize: 16
|
|
}
|
|
};
|
|
|
|
UserPrefs.savePreferences(prefs);
|
|
const loaded = UserPrefs.getPreferences();
|
|
|
|
expect(loaded.display.theme).toBe('dark');
|
|
expect(loaded.display.fontSize).toBe(16);
|
|
});
|
|
|
|
it('should handle arrays in preferences', () => {
|
|
const UserPrefs = createUserPreferences();
|
|
const prefs = {
|
|
recentSearches: ['naruto', 'one piece', 'bleach']
|
|
};
|
|
|
|
UserPrefs.savePreferences(prefs);
|
|
const loaded = UserPrefs.getPreferences();
|
|
|
|
expect(loaded.recentSearches).toEqual(['naruto', 'one piece', 'bleach']);
|
|
});
|
|
|
|
it('should handle rapid updates', () => {
|
|
const UserPrefs = createUserPreferences();
|
|
|
|
UserPrefs.updatePreference('theme', 'dark');
|
|
UserPrefs.updatePreference('theme', 'light');
|
|
UserPrefs.updatePreference('theme', 'dark');
|
|
|
|
const prefs = UserPrefs.getPreferences();
|
|
expect(prefs.theme).toBe('dark');
|
|
});
|
|
|
|
it('should handle empty string values', () => {
|
|
const UserPrefs = createUserPreferences();
|
|
const prefs = { theme: '' };
|
|
|
|
UserPrefs.savePreferences(prefs);
|
|
const loaded = UserPrefs.getPreferences();
|
|
|
|
expect(loaded.theme).toBe('');
|
|
});
|
|
});
|
|
|
|
describe('Default Preferences', () => {
|
|
it('should return empty object as default', () => {
|
|
const UserPrefs = createUserPreferences();
|
|
|
|
const prefs = UserPrefs.getPreferences();
|
|
|
|
expect(prefs).toEqual({});
|
|
});
|
|
|
|
it('should not apply anything when no preferences exist', () => {
|
|
const UserPrefs = createUserPreferences();
|
|
|
|
UserPrefs.loadPreferences();
|
|
|
|
expect(document.documentElement.getAttribute('data-theme')).toBeNull();
|
|
expect(document.documentElement.getAttribute('lang')).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('Storage Key', () => {
|
|
it('should use correct storage key', () => {
|
|
const UserPrefs = createUserPreferences();
|
|
UserPrefs.savePreferences({ theme: 'dark' });
|
|
|
|
const stored = localStorage.getItem(STORAGE_KEY);
|
|
expect(stored).toBeTruthy();
|
|
});
|
|
|
|
it('should not interfere with other localStorage keys', () => {
|
|
const UserPrefs = createUserPreferences();
|
|
localStorage.setItem('other_key', 'other_value');
|
|
|
|
UserPrefs.savePreferences({ theme: 'dark' });
|
|
|
|
expect(localStorage.getItem('other_key')).toBe('other_value');
|
|
});
|
|
});
|
|
});
|