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/theme-preference.test.js"
},
"license": "MIT"
}
63 changes: 63 additions & 0 deletions src/theme-preference.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
const THEME_KEY = 'theme';
const LIGHT_THEME = 'light';
const DARK_THEME = 'dark';

function normalizeTheme(theme) {
return theme === DARK_THEME ? DARK_THEME : LIGHT_THEME;
}

function createThemeToggleController({
storage,
applyTheme,
updateToggle,
initialTheme,
storageKey = THEME_KEY,
}) {
if (!storage || typeof storage.getItem !== 'function' || typeof storage.setItem !== 'function') {
throw new TypeError('storage must provide getItem and setItem');
}
if (typeof applyTheme !== 'function') {
throw new TypeError('applyTheme must be a function');
}
if (typeof updateToggle !== 'function') {
throw new TypeError('updateToggle must be a function');
}

let currentTheme = normalizeTheme(initialTheme || storage.getItem(storageKey));

function sync() {
applyTheme(currentTheme);
updateToggle({
theme: currentTheme,
label: currentTheme === DARK_THEME ? 'Switch to light mode' : 'Switch to dark mode',
pressed: currentTheme === DARK_THEME,
});
}

function setTheme(theme) {
currentTheme = normalizeTheme(theme);
storage.setItem(storageKey, currentTheme);
sync();
return currentTheme;
}

function toggleTheme() {
return setTheme(currentTheme === DARK_THEME ? LIGHT_THEME : DARK_THEME);
}

sync();

return {
getTheme: () => currentTheme,
setTheme,
toggleTheme,
};
}

module.exports = {
DARK_THEME,
LIGHT_THEME,
THEME_KEY,
createThemeToggleController,
normalizeTheme,
};
96 changes: 96 additions & 0 deletions test/theme-preference.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
const {
DARK_THEME,
LIGHT_THEME,
createThemeToggleController,
normalizeTheme,
} = require('../src/theme-preference');

let passed = 0;
let failed = 0;

function assert(name, condition) {
if (condition) {
console.log(` OK ${name}`);
passed++;
} else {
console.log(` FAIL ${name}`);
failed++;
}
}

function createStorage(initial = {}) {
const values = { ...initial };
return {
getItem: (key) => values[key] || null,
setItem: (key, value) => {
values[key] = value;
},
values,
};
}

function testNormalizesUnknownThemes() {
assert('dark is preserved', normalizeTheme('dark') === DARK_THEME);
assert('unknown theme falls back to light', normalizeTheme('sepia') === LIGHT_THEME);
}

function testRestoresStoredPreference() {
const storage = createStorage({ theme: 'dark' });
const applied = [];
const toggleStates = [];

const controller = createThemeToggleController({
storage,
applyTheme: (theme) => applied.push(theme),
updateToggle: (state) => toggleStates.push(state),
});

assert('restores stored dark theme', controller.getTheme() === DARK_THEME);
assert('applies restored theme on start', applied[0] === DARK_THEME);
assert('marks toggle pressed in dark mode', toggleStates[0].pressed === true);
}

function testTogglesAndPersistsTheme() {
const storage = createStorage();
const applied = [];

const controller = createThemeToggleController({
storage,
applyTheme: (theme) => applied.push(theme),
updateToggle: () => {},
});

const nextTheme = controller.toggleTheme();

assert('toggles light to dark', nextTheme === DARK_THEME);
assert('persists dark preference', storage.values.theme === DARK_THEME);
assert('applies toggled theme', applied[applied.length - 1] === DARK_THEME);
}

function testCanSetThemeDirectly() {
const storage = createStorage();
const toggleStates = [];

const controller = createThemeToggleController({
storage,
applyTheme: () => {},
updateToggle: (state) => toggleStates.push(state),
});

controller.setTheme('dark');
controller.setTheme('light');

assert('sets light theme directly', controller.getTheme() === LIGHT_THEME);
assert('updates toggle label for light mode', toggleStates[toggleStates.length - 1].label === 'Switch to dark mode');
assert('marks toggle unpressed in light mode', toggleStates[toggleStates.length - 1].pressed === false);
}

console.log('\nTheme preference tests\n');

testNormalizesUnknownThemes();
testRestoresStoredPreference();
testTogglesAndPersistsTheme();
testCanSetThemeDirectly();

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