style(auth-files): use neutral colors for credential action buttons
- Remove color accents from refresh, settings, models, delete, details, and import buttons - Unify borders, text, and hover styles across light and dark themes - Preserve status colors for badges, usage metrics, and toggles
This commit is contained in:
1 parent
fab5159cd4
commit
8eab980d72
9 files changed
+267
-23
No files matched your search
@@ -1,3 +1,36 @@
|
||||
#[derive(Clone, Copy, Debug, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub(crate) enum ThemePreference {
|
||||
Light,
|
||||
Dark,
|
||||
System,
|
||||
}
|
||||
|
||||
fn read_preference(path: &std::path::Path) -> Result<Option<ThemePreference>, String> {
|
||||
match std::fs::read(path) {
|
||||
Ok(content) => serde_json::from_slice(&content)
|
||||
.map(Some)
|
||||
.map_err(|_| "Failed to parse theme preference".to_string()),
|
||||
Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(None),
|
||||
Err(_) => Err("Failed to read theme preference".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
fn write_preference(path: &std::path::Path, preference: ThemePreference) -> Result<(), String> {
|
||||
let content = serde_json::to_vec(&preference).map_err(|error| error.to_string())?;
|
||||
super::write_bytes_atomically(path, &content)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub(crate) fn get_theme_preference() -> Result<Option<ThemePreference>, String> {
|
||||
read_preference(&super::core_base_dir()?.join("theme-preference.json"))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub(crate) fn save_theme_preference(preference: ThemePreference) -> Result<(), String> {
|
||||
write_preference(&super::core_base_dir()?.join("theme-preference.json"), preference)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub(crate) async fn get_linux_system_theme() -> Option<tauri::Theme> {
|
||||
#[cfg(target_os = "linux")]
|
||||
@@ -47,6 +80,25 @@ fn portal_color_scheme(value: u32) -> Option<tauri::Theme> {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn theme_preference_survives_restart_and_rejects_invalid_values() {
|
||||
let directory = std::env::temp_dir().join(format!(
|
||||
"ezcpa-theme-{}-{}",
|
||||
std::process::id(),
|
||||
std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_nanos()
|
||||
));
|
||||
std::fs::create_dir_all(&directory).unwrap();
|
||||
let path = directory.join("theme-preference.json");
|
||||
assert_eq!(read_preference(&path).unwrap(), None);
|
||||
for preference in [ThemePreference::Light, ThemePreference::Dark, ThemePreference::System] {
|
||||
write_preference(&path, preference).unwrap();
|
||||
assert_eq!(read_preference(&path).unwrap(), Some(preference));
|
||||
}
|
||||
std::fs::write(&path, b"\"invalid\"").unwrap();
|
||||
assert!(read_preference(&path).is_err());
|
||||
std::fs::remove_dir_all(directory).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn follows_the_freedesktop_color_scheme_values() {
|
||||
assert_eq!(portal_color_scheme(1), Some(tauri::Theme::Dark));
|
||||
|
||||
@@ -2663,6 +2663,8 @@ fn main() {
|
||||
})
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
desktop_theme::get_linux_system_theme,
|
||||
desktop_theme::get_theme_preference,
|
||||
desktop_theme::save_theme_preference,
|
||||
health_check,
|
||||
detect_core_platform,
|
||||
get_core_status,
|
||||
|
||||
+2
-2
@@ -4,12 +4,12 @@ import { isTauri } from '@tauri-apps/api/core';
|
||||
import { AppErrorBoundary } from './AppErrorBoundary';
|
||||
import App from './App';
|
||||
import { I18nProvider } from './i18n';
|
||||
import { initializeTheme } from './theme';
|
||||
import { initializeTheme, initializeThemePreferences } from './theme';
|
||||
import { initializeUsagePreferences } from './services/usagePreferences';
|
||||
import './styles/index.css';
|
||||
|
||||
async function bootstrap() {
|
||||
await initializeUsagePreferences();
|
||||
await Promise.all([initializeUsagePreferences(), initializeThemePreferences()]);
|
||||
if (import.meta.env.DEV && !isTauri()) {
|
||||
const { installBrowserMock } = await import('./mocks/browserMock');
|
||||
installBrowserMock();
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
.auth-files-page {
|
||||
--auth-muted: #64748b;
|
||||
--auth-text: #334155;
|
||||
--auth-muted: #475569;
|
||||
--auth-text: #1e293b;
|
||||
--auth-accent: #3b82f6;
|
||||
--auth-border: #e9edf2;
|
||||
--auth-control: #c5d0dc;
|
||||
--success-color: #16b653;
|
||||
gap: 12px;
|
||||
}
|
||||
[data-theme="dark"] .auth-files-page {
|
||||
--auth-muted: #94a3b8;
|
||||
--auth-text: #e2e8f0;
|
||||
--auth-muted: #cbd5e1;
|
||||
--auth-text: #f1f5f9;
|
||||
--auth-accent: #70a7ff;
|
||||
--auth-border: #293241;
|
||||
--auth-control: #3d4c60;
|
||||
--success-color: #34d399;
|
||||
}
|
||||
.auth-files-heading { display: flex; align-items: flex-start; justify-content: space-between; flex-wrap: wrap; gap: 8px 12px; min-height: 36px; }
|
||||
@@ -30,14 +32,18 @@
|
||||
box-shadow: none;
|
||||
}
|
||||
.auth-files-page .auth-files-toolbar { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; min-height: 36px; margin-bottom: 4px; padding: 0; }
|
||||
.auth-files-search { display: flex; align-items: center; gap: 8px; flex: 1 1 220px; min-width: 0; max-width: 380px; color: var(--auth-muted); background: var(--bg-card); border: 1px solid var(--auth-border); border-radius: 7px; padding: 0 10px; }
|
||||
.auth-files-search { display: flex; align-items: center; gap: 8px; flex: 1 1 220px; min-width: 0; max-width: 380px; color: var(--auth-text); background: var(--bg-card); border: 1px solid var(--auth-control); border-radius: 8px; padding: 0 10px; }
|
||||
.auth-files-search:focus-within { border-color: var(--auth-accent); }
|
||||
.auth-files-search > svg { flex: none; }
|
||||
.auth-files-page .auth-files-search input { width: 100%; min-width: 0; height: 32px; padding: 0; border: 0; outline: 0; box-shadow: none; background: transparent; color: var(--auth-text); font: inherit; font-size: 12px; }
|
||||
.auth-files-search input::placeholder { color: var(--auth-muted); }
|
||||
.auth-files-filters { display: flex; align-items: center; gap: 8px; }
|
||||
.auth-files-page .auth-files-toolbar .auth-files-filters select { width: auto; min-width: 0; max-width: 100%; height: 32px; padding: 0 8px; border: 1px solid var(--auth-border); border-radius: 7px; background: var(--bg-card); color: var(--auth-muted); font: inherit; font-size: 12px; }
|
||||
.auth-files-page .auth-files-toolbar select:focus-visible { outline: 2px solid var(--auth-accent); outline-offset: 1px; }
|
||||
.auth-files-page .auth-files-toolbar .auth-files-filters select { appearance: none; -webkit-appearance: none; width: auto; min-width: 0; max-width: 100%; height: 32px; padding: 0 30px 0 11px; border: 1px solid var(--theme-d5d2cb); border-radius: 6px; outline: 0; box-shadow: none; background-color: var(--theme-ffffff); background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%23f8fafc' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 9px center; background-size: 14px; color: var(--theme-2d2a26); color-scheme: dark; font: inherit; font-size: 12px; font-weight: 650; line-height: 30px; cursor: pointer; }
|
||||
:root:not([data-theme='dark']) .auth-files-page .auth-files-toolbar .auth-files-filters select { color-scheme: light; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%232d2a26' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E"); }
|
||||
.auth-files-page .auth-files-toolbar .auth-files-filters select::-ms-expand { display: none; }
|
||||
.auth-files-page .auth-files-toolbar .auth-files-filters select:hover { border-color: var(--border-hover); }
|
||||
.auth-files-page .auth-files-toolbar .auth-files-filters select:focus,
|
||||
.auth-files-page .auth-files-toolbar .auth-files-filters select:focus-visible { border-color: var(--ui-accent); outline: 0; box-shadow: none; }
|
||||
.auth-file-table-scroll { min-width: 0; overflow: visible; padding: 0 0 2px; }
|
||||
.auth-file-list-head { display: none; }
|
||||
.auth-file-card-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(420px, 100%), 1fr)); gap: 12px; align-items: stretch; min-width: 0; }
|
||||
@@ -70,10 +76,10 @@
|
||||
.auth-status-badge.warning { color: var(--amber-text); background: color-mix(in srgb, var(--amber-text) 8%, var(--bg-card)); }
|
||||
.auth-status-badge.error { color: var(--failure-badge-text); background: color-mix(in srgb, var(--failure-badge-text) 7%, var(--bg-card)); }
|
||||
.auth-status-badge.info { color: var(--auth-accent); background: color-mix(in srgb, var(--auth-accent) 8%, var(--bg-card)); }
|
||||
.auth-status-badge.neutral { color: var(--auth-muted); background: color-mix(in srgb, var(--auth-muted) 10%, var(--bg-card)); }
|
||||
.auth-status-badge.neutral { color: var(--auth-text); background: color-mix(in srgb, var(--auth-text) 8%, var(--bg-card)); }
|
||||
.auth-list-muted { color: var(--auth-muted); font-size: 12px; }
|
||||
.auth-list-recent, .auth-list-usage, .auth-list-quota { display: grid; gap: 4px; }
|
||||
.auth-list-recent::before, .auth-list-usage::before, .auth-list-quota::before { content: attr(data-label); color: var(--auth-muted); font-size: 11px; font-weight: 650; line-height: 1.2; }
|
||||
.auth-list-recent::before, .auth-list-usage::before, .auth-list-quota::before { content: attr(data-label); color: var(--auth-text); font-size: 12px; font-weight: 700; line-height: 1.2; }
|
||||
.auth-list-recent .auth-file-requests { display: grid; grid-template-columns: max-content minmax(0, 1fr); grid-template-areas: "time bars" "empty empty"; align-items: center; gap: 4px 10px; }
|
||||
.auth-list-recent .auth-file-requests-latest { grid-area: time; color: var(--auth-muted); font-size: 12px; font-weight: 650; text-align: left; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.auth-list-recent .auth-file-requests-timeline { display: contents; }
|
||||
@@ -82,7 +88,7 @@
|
||||
.auth-list-recent .auth-file-requests-blocks { gap: 2px; padding: 0; }
|
||||
.auth-list-recent .auth-file-request-block { height: 12px; border-radius: 2px; }
|
||||
.auth-list-recent .auth-file-requests-timeline { gap: 5px; font-size: 11px; }
|
||||
.auth-list-recent .auth-file-requests-unavailable { grid-area: empty; justify-self: start; font-size: 12px; line-height: 1.4; text-align: left; color: var(--auth-muted); }
|
||||
.auth-list-recent .auth-file-requests-unavailable { grid-area: empty; justify-self: start; font-size: 12px; font-weight: 600; line-height: 1.4; text-align: left; color: var(--auth-text); }
|
||||
.auth-list-usage .auth-file-usage { color: var(--auth-text); }
|
||||
.auth-list-usage .auth-file-usage-metrics { grid-template-columns: repeat(auto-fit, minmax(92px, max-content)); grid-template-rows: none; gap: 4px 14px; justify-content: start; align-content: center; }
|
||||
.auth-list-usage .auth-file-usage-metric { gap: 5px; }
|
||||
@@ -156,6 +162,7 @@
|
||||
.credential-quota-row.unknown .credential-quota-track { background: repeating-linear-gradient(115deg, var(--border-color) 0 4px, transparent 4px 8px); }
|
||||
.credential-quota small, .credential-quota-empty, .credential-quota-loading { font-size: var(--font-size-caption); line-height: 1.6; color: var(--text-tertiary); overflow-wrap: anywhere; }
|
||||
.credential-quota-empty { margin: 0; }
|
||||
.auth-files-page .credential-quota-dense .credential-quota-empty { color: var(--auth-text); font-size: 12px; font-weight: 600; }
|
||||
.credential-quota-footnotes { display: grid; gap: 3px; }
|
||||
.credential-quota-footnotes:empty { display: none; }
|
||||
.credential-quota-error { display: grid; gap: 4px; color: var(--failure-badge-text); }
|
||||
@@ -229,7 +236,7 @@
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 3;
|
||||
}
|
||||
.auth-files-page .auth-file-card {
|
||||
:root:not([data-theme='dark']) .auth-files-page .auth-file-card {
|
||||
border-color: #edf0f4;
|
||||
box-shadow: 0 1px 2px rgb(35 48 65 / 3%);
|
||||
}
|
||||
@@ -243,7 +250,36 @@
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.auth-files-page .auth-file-card:hover {
|
||||
/* Keep credential actions visually quiet; color is reserved for status and metrics. */
|
||||
.auth-files-page .auth-files-heading-actions > .primary-button,
|
||||
.auth-files-page .auth-card-actions .auth-list-action,
|
||||
.auth-files-page .auth-list-details-button {
|
||||
border-color: var(--auth-control);
|
||||
background: var(--bg-card);
|
||||
color: var(--auth-text);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.auth-files-page .auth-files-heading-actions > .primary-button:hover:not(:disabled),
|
||||
.auth-files-page .auth-card-actions .auth-list-action:not(:disabled):hover,
|
||||
.auth-files-page .auth-list-details-button:hover {
|
||||
border-color: var(--border-hover);
|
||||
background: var(--bg-hover);
|
||||
color: var(--auth-text);
|
||||
}
|
||||
|
||||
.auth-files-page .auth-card-actions .auth-list-action.danger,
|
||||
.auth-files-page .auth-card-actions .auth-list-action.muted {
|
||||
border-color: var(--auth-control);
|
||||
background: var(--bg-card);
|
||||
color: var(--auth-text);
|
||||
}
|
||||
|
||||
.auth-files-page .credential-quota-refresh:not(:disabled) {
|
||||
color: var(--auth-text);
|
||||
}
|
||||
|
||||
:root:not([data-theme='dark']) .auth-files-page .auth-file-card:hover {
|
||||
border-color: color-mix(in srgb, var(--auth-accent) 26%, #edf0f4);
|
||||
box-shadow: 0 4px 14px rgb(35 83 160 / 7%);
|
||||
}
|
||||
@@ -128,6 +128,31 @@
|
||||
.home-dashboard .home-runtime-refresh { width: 32px; height: 32px; min-width: 32px; border-color: transparent; background: transparent; }
|
||||
.home-dashboard .home-runtime-refresh svg { width: 18px; height: 18px; }
|
||||
|
||||
.home-dashboard .home-runtime-stop {
|
||||
border-color: #a33b32;
|
||||
background: #a33b32;
|
||||
color: #fff;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.home-dashboard .home-runtime-stop:hover:not(:disabled) {
|
||||
border-color: #872f28;
|
||||
background: #872f28;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .home-dashboard .home-runtime-stop {
|
||||
border-color: #8d403b;
|
||||
background: #6b302c;
|
||||
color: #f6e4e2;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .home-dashboard .home-runtime-stop:hover:not(:disabled) {
|
||||
border-color: #b86a64;
|
||||
background: #81403b;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
|
||||
.home-panel-heading,
|
||||
.home-overview-heading {
|
||||
display: flex;
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import type { ThemePreference } from '../themeController';
|
||||
|
||||
const STORAGE_KEY = 'easy-cli-proxy-api.theme';
|
||||
type Backend = {
|
||||
load(): Promise<ThemePreference | null>;
|
||||
save(preference: ThemePreference): Promise<unknown>;
|
||||
};
|
||||
|
||||
export class ThemePreferences {
|
||||
private value?: ThemePreference;
|
||||
private backend?: Backend;
|
||||
private pending = Promise.resolve();
|
||||
|
||||
constructor(private storage: () => Storage) {}
|
||||
|
||||
private readLegacy(): ThemePreference | null {
|
||||
try {
|
||||
const saved = this.storage().getItem(STORAGE_KEY);
|
||||
if (saved === 'light' || saved === 'dark' || saved === 'system') return saved;
|
||||
} catch {}
|
||||
return null;
|
||||
}
|
||||
|
||||
get(): ThemePreference {
|
||||
return this.value ?? this.readLegacy() ?? 'system';
|
||||
}
|
||||
|
||||
async initialize(backend: Backend) {
|
||||
// Do not overwrite a file with fallback defaults if reading it fails.
|
||||
const saved = await backend.load();
|
||||
const legacy = this.readLegacy();
|
||||
this.backend = backend;
|
||||
this.value = saved ?? legacy ?? 'system';
|
||||
if (saved !== null) this.cache(saved);
|
||||
else if (legacy !== null) {
|
||||
this.set(legacy);
|
||||
await this.flush();
|
||||
}
|
||||
}
|
||||
|
||||
private cache(preference: ThemePreference) {
|
||||
try { this.storage().setItem(STORAGE_KEY, preference); } catch {}
|
||||
}
|
||||
|
||||
set(preference: ThemePreference) {
|
||||
this.value = preference;
|
||||
this.cache(preference);
|
||||
if (this.backend) {
|
||||
this.pending = this.pending.then(() => this.backend!.save(preference)).then(() => {}, () => {
|
||||
console.error('Failed to save theme preference');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
flush() { return this.pending; }
|
||||
}
|
||||
+10
-1
@@ -432,6 +432,10 @@ code, pre, kbd, samp {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
|
||||
html,
|
||||
body,
|
||||
#root {
|
||||
@@ -487,7 +491,9 @@ textarea:focus-visible {
|
||||
.app-shell {
|
||||
display: grid;
|
||||
grid-template-columns: 264px minmax(0, 1fr);
|
||||
min-height: 100vh;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
background: var(--theme-faf9f5);
|
||||
}
|
||||
|
||||
@@ -867,6 +873,9 @@ textarea:focus-visible {
|
||||
|
||||
.workspace {
|
||||
min-width: 0;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
|
||||
.state-pill {
|
||||
|
||||
+13
-8
@@ -3,22 +3,30 @@ import { invoke, isTauri } from '@tauri-apps/api/core';
|
||||
import { getCurrentWebview } from '@tauri-apps/api/webview';
|
||||
import { getCurrentWindow } from '@tauri-apps/api/window';
|
||||
import { createThemeController, type AppTheme, type ThemePreference } from './themeController';
|
||||
import { ThemePreferences } from './services/themePreferences';
|
||||
|
||||
export type { AppTheme, ThemePreference } from './themeController';
|
||||
|
||||
const STORAGE_KEY = 'easy-cli-proxy-api.theme';
|
||||
const preferences = new ThemePreferences(() => window.localStorage);
|
||||
const WINDOW_BACKGROUND: Record<AppTheme, string> = {
|
||||
light: '#ffffff',
|
||||
dark: '#0b0d11',
|
||||
};
|
||||
|
||||
export function detectThemePreference(): ThemePreference {
|
||||
return preferences.get();
|
||||
}
|
||||
|
||||
export async function initializeThemePreferences(): Promise<void> {
|
||||
if (!isTauri()) return;
|
||||
try {
|
||||
const saved = window.localStorage.getItem(STORAGE_KEY);
|
||||
if (saved === 'light' || saved === 'dark' || saved === 'system') return saved;
|
||||
await preferences.initialize({
|
||||
load: () => invoke<ThemePreference | null>('get_theme_preference'),
|
||||
save: (preference) => invoke('save_theme_preference', { preference }),
|
||||
});
|
||||
} catch {
|
||||
console.error('Failed to load theme preference; using browser preference');
|
||||
}
|
||||
return 'system';
|
||||
}
|
||||
|
||||
function applyTheme(theme: AppTheme): void {
|
||||
@@ -38,10 +46,7 @@ export function initializeTheme() {
|
||||
controller = createThemeController({
|
||||
readPreference: detectThemePreference,
|
||||
savePreference(preference) {
|
||||
try {
|
||||
window.localStorage.setItem(STORAGE_KEY, preference);
|
||||
} catch {
|
||||
}
|
||||
preferences.set(preference);
|
||||
},
|
||||
readMediaTheme: () => media?.matches ? 'dark' : 'light',
|
||||
listenMedia(listener) {
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import { expect, test } from 'bun:test';
|
||||
import { ThemePreferences } from '../src/services/themePreferences';
|
||||
import type { ThemePreference } from '../src/themeController';
|
||||
|
||||
function storage(initial?: string) {
|
||||
let value = initial ?? null;
|
||||
return { getItem: () => value, setItem: (_key: string, next: string) => { value = next; } } as Storage;
|
||||
}
|
||||
|
||||
for (const preference of ['light', 'dark', 'system'] as const) {
|
||||
test(`migrates ${preference} and restores it after browser storage is lost`, async () => {
|
||||
let disk: ThemePreference | null = null;
|
||||
const backend = { load: async () => disk, save: async (next: ThemePreference) => { disk = next; } };
|
||||
const first = new ThemePreferences(() => storage(preference));
|
||||
await first.initialize(backend);
|
||||
expect(disk).toBe(preference);
|
||||
const cache = storage();
|
||||
const restarted = new ThemePreferences(() => cache);
|
||||
await restarted.initialize(backend);
|
||||
expect(restarted.get()).toBe(preference);
|
||||
expect(cache.getItem('easy-cli-proxy-api.theme')).toBe(preference);
|
||||
});
|
||||
}
|
||||
|
||||
test('native preference wins over a stale browser preference', async () => {
|
||||
const preferences = new ThemePreferences(() => storage('light'));
|
||||
await preferences.initialize({ load: async () => 'dark', save: async () => { throw new Error('unexpected write'); } });
|
||||
expect(preferences.get()).toBe('dark');
|
||||
});
|
||||
|
||||
test('saves with blocked browser storage and serializes rapid changes', async () => {
|
||||
const preferences = new ThemePreferences(() => { throw new Error('blocked'); });
|
||||
const writes: ThemePreference[] = [];
|
||||
await preferences.initialize({ load: async () => null, save: async (next) => { await Promise.resolve(); writes.push(next); } });
|
||||
preferences.set('dark');
|
||||
preferences.set('light');
|
||||
preferences.set('system');
|
||||
await preferences.flush();
|
||||
expect(writes).toEqual(['dark', 'light', 'system']);
|
||||
expect(preferences.get()).toBe('system');
|
||||
});
|
||||
|
||||
test('a failed native read does not overwrite the saved file', async () => {
|
||||
const preferences = new ThemePreferences(() => storage('dark'));
|
||||
const writes: ThemePreference[] = [];
|
||||
await expect(preferences.initialize({ load: async () => { throw new Error('unreadable'); }, save: async (next) => { writes.push(next); } })).rejects.toThrow('unreadable');
|
||||
expect(preferences.get()).toBe('dark');
|
||||
preferences.set('light');
|
||||
await preferences.flush();
|
||||
expect(writes).toEqual([]);
|
||||
});
|
||||
|
||||
test('missing preferences use system without writing a default', async () => {
|
||||
const preferences = new ThemePreferences(() => storage('invalid'));
|
||||
const writes: ThemePreference[] = [];
|
||||
await preferences.initialize({ load: async () => null, save: async (next) => { writes.push(next); } });
|
||||
expect(preferences.get()).toBe('system');
|
||||
expect(writes).toEqual([]);
|
||||
});
|
||||
Reference in new issue
Block a user