Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"description": "A simple CSV parser - BountyPay workflow demo",
"main": "src/parser.js",
"scripts": {
"test": "node test/parser.test.js"
"test": "node test/parser.test.js && node test/settings-theme-preference.test.js"
},
"license": "MIT"
}
85 changes: 85 additions & 0 deletions src/settings-theme-preference.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
const SETTINGS_THEME_KEY = 'ai-agent-pay-demo.settings.theme';
const VALID_THEMES = ['light', 'dark'];

function isValidTheme(theme) {
return VALID_THEMES.includes(theme);
}

function getStoredTheme(storage) {
try {
const value = storage && typeof storage.getItem === 'function'
? storage.getItem(SETTINGS_THEME_KEY)
: null;
return isValidTheme(value) ? value : null;
} catch (_error) {
return null;
}
}

function saveTheme(storage, theme) {
if (!isValidTheme(theme)) {
throw new Error(`Unsupported theme: ${theme}`);
}

try {
if (storage && typeof storage.setItem === 'function') {
storage.setItem(SETTINGS_THEME_KEY, theme);
}
} catch (_error) {
// Storage can fail in private browsing; the in-memory state still updates.
}

return theme;
}

function applySettingsTheme(root, theme) {
const normalized = isValidTheme(theme) ? theme : 'light';
if (root) {
root.dataset.settingsTheme = normalized;
root.classList.remove('settings-theme-light', 'settings-theme-dark');
root.classList.add(`settings-theme-${normalized}`);
}
return normalized;
}

function createSettingsThemePreference({ storage, root, defaultTheme = 'light' } = {}) {
let currentTheme = getStoredTheme(storage) || (isValidTheme(defaultTheme) ? defaultTheme : 'light');

function setTheme(theme) {
currentTheme = saveTheme(storage, theme);
applySettingsTheme(root, currentTheme);
return getSettingsState();
}

function getSettingsState() {
return {
field: 'theme',
value: currentTheme,
options: VALID_THEMES.map((theme) => ({
value: theme,
label: theme === 'dark' ? 'Dark mode' : 'Light mode',
selected: theme === currentTheme,
})),
};
}

return {
initialize() {
applySettingsTheme(root, currentTheme);
return getSettingsState();
},
getTheme() {
return currentTheme;
},
getSettingsState,
setTheme,
};
}

module.exports = {
SETTINGS_THEME_KEY,
applySettingsTheme,
createSettingsThemePreference,
getStoredTheme,
saveTheme,
};
105 changes: 105 additions & 0 deletions test/settings-theme-preference.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
const {
SETTINGS_THEME_KEY,
applySettingsTheme,
createSettingsThemePreference,
getStoredTheme,
saveTheme,
} = require('../src/settings-theme-preference');

let passed = 0;
let failed = 0;

function assert(name, actual, expected) {
const a = JSON.stringify(actual);
const e = JSON.stringify(expected);
if (a === e) {
console.log(` ✅ ${name}`);
passed++;
} else {
console.log(` ❌ ${name}`);
console.log(` Expected: ${e}`);
console.log(` Actual: ${a}`);
failed++;
}
}

function createStorage(initial = {}) {
const data = { ...initial };
return {
getItem(key) {
return Object.prototype.hasOwnProperty.call(data, key) ? data[key] : null;
},
setItem(key, value) {
data[key] = value;
},
snapshot() {
return { ...data };
},
};
}

function createRoot() {
const classes = new Set();
return {
dataset: {},
classList: {
add(name) {
classes.add(name);
},
remove(...names) {
names.forEach((name) => classes.delete(name));
},
snapshot() {
return Array.from(classes).sort();
},
},
};
}

console.log('\n⚙️ Settings Theme Preference Tests\n');

const storage = createStorage({ [SETTINGS_THEME_KEY]: 'dark' });
assert('reads stored dark theme', getStoredTheme(storage), 'dark');
assert('ignores invalid stored theme', getStoredTheme(createStorage({ [SETTINGS_THEME_KEY]: 'sepia' })), null);

const root = createRoot();
assert('applies settings theme', applySettingsTheme(root, 'dark'), 'dark');
assert('sets data attribute', root.dataset.settingsTheme, 'dark');
assert('sets settings theme class', root.classList.snapshot(), ['settings-theme-dark']);

const preferenceStorage = createStorage();
const preference = createSettingsThemePreference({
storage: preferenceStorage,
root: createRoot(),
});

assert('initializes light settings state', preference.initialize(), {
field: 'theme',
value: 'light',
options: [
{ value: 'light', label: 'Light mode', selected: true },
{ value: 'dark', label: 'Dark mode', selected: false },
],
});

assert('sets dark settings state', preference.setTheme('dark'), {
field: 'theme',
value: 'dark',
options: [
{ value: 'light', label: 'Light mode', selected: false },
{ value: 'dark', label: 'Dark mode', selected: true },
],
});

assert('persists settings theme', preferenceStorage.snapshot()[SETTINGS_THEME_KEY], 'dark');

let unsupportedThemeRejected = false;
try {
saveTheme(createStorage(), 'blue');
} catch (_error) {
unsupportedThemeRejected = true;
}
assert('rejects unsupported theme values', unsupportedThemeRejected, true);

console.log(`\n📊 Results: ${passed} passed, ${failed} failed\n`);
process.exit(failed > 0 ? 1 : 0);