--- /dev/null
+/**
+ * Whether the application runs in a real DEV environment (`pnpm dev` /
+ * Vite serve), excluding production builds and Vitest runs. Use to gate
+ * dev-only diagnostics that would otherwise emit noise in tests or race
+ * environment teardown via `requestAnimationFrame` / async callbacks.
+ * @returns Whether the runtime is DEV and not a Vitest test run
+ */
+export const isDev = (): boolean => import.meta.env.DEV && import.meta.env.MODE !== 'test'
UI_SERVER_CONFIGURATION_INDEX_KEY,
WH_PER_KWH,
} from './Constants.js'
+export { isDev } from './env.js'
export {
chargingStationsKey,
configurationKey,
import { inject } from 'vue'
+import { isDev } from './env.js'
import { UIClient } from './UIClient.js'
export const configurationKey: InjectionKey<Ref<ConfigurationData>> = Symbol('configuration')
export const useUIClient = (): UIClient => {
const injected = inject(uiClientKey, undefined)
if (injected != null) return injected
- if (import.meta.env.DEV) {
+ if (isDev()) {
console.debug('[useUIClient] Accessed outside provide scope — using singleton fallback')
}
return UIClient.getInstance()
import { SHARED_TOGGLE_BUTTON_KEY_PREFIX, TOGGLE_BUTTON_KEY_PREFIX } from './Constants.js'
+import { isDev } from './env.js'
export const getFromLocalStorage = <T>(key: string, defaultValue: T): T => {
try {
const item = localStorage.getItem(key)
return item != null ? (JSON.parse(item) as T) : defaultValue
} catch {
- if (import.meta.env.DEV) {
+ if (isDev()) {
console.debug(`[localStorage] Failed to read key '${key}', using default`)
}
return defaultValue
// - QuotaExceededError when the origin's storage quota is genuinely exceeded
// - SecurityError when storage is blocked by user settings or browser policies
// (e.g., "Block All Cookies" in Safari, third-party iframe in Chrome, file: URLs)
- if (import.meta.env.DEV) {
+ if (isDev()) {
console.debug(`[localStorage] Failed to write key '${key}'`)
}
}
try {
localStorage.removeItem(key)
} catch {
- if (import.meta.env.DEV) {
+ if (isDev()) {
console.debug(`[localStorage] Failed to delete key '${key}'`)
}
}
deleteFromLocalStorage(key)
}
} catch {
- if (import.meta.env.DEV) {
+ if (isDev()) {
console.debug(`[localStorage] Failed to delete keys matching '${pattern}'`)
}
}
configurationKey,
DEFAULT_SKIN,
getFromLocalStorage,
+ isDev,
LEGACY_UI_SERVER_CONFIG_KEY,
setToLocalStorage,
templatesKey,
const initializeApp = async (app: AppType, config: ConfigurationData): Promise<void> => {
app.config.errorHandler = (error, instance, info) => {
console.error('Error:', error)
- if (import.meta.env.DEV) {
+ if (isDev()) {
console.info('Vue instance:', instance)
console.info('Error info:', info)
}
*
* Every theme file MUST define a value for each token (as `--{token-name}`).
*/
+import { isDev } from '@/core/index.js'
+
export const TOKEN_CONTRACT = [
'color-accent',
'color-bg',
* @param contextId - The skin/theme id that was just applied
*/
export function validateTokenContract (source: string, contextId: string): void {
- if (!import.meta.env.DEV || typeof document === 'undefined') return
+ if (!isDev() || typeof document === 'undefined') {
+ return
+ }
requestAnimationFrame(() => {
const style = getComputedStyle(document.documentElement)
for (const token of TOKEN_CONTRACT) {
import {
getFromLocalStorage,
getLocalStorage,
+ isDev,
setToLocalStorage,
SHARED_TOGGLE_BUTTON_KEY_PREFIX,
TOGGLE_BUTTON_KEY_PREFIX,
setToLocalStorage<boolean>(key, false)
}
} catch {
- if (import.meta.env.DEV) {
+ if (isDev()) {
console.debug('[ToggleButton] Failed to clear shared toggle buttons')
}
}
/**
- * @file Tests for TOKEN_CONTRACT theme compliance
- * @description Ensures every theme CSS file defines all CSS custom properties declared in TOKEN_CONTRACT.
+ * @file Tests for TOKEN_CONTRACT theme compliance and validateTokenContract guard
+ * @description Ensures every theme CSS file defines all CSS custom properties declared in
+ * TOKEN_CONTRACT, and that validateTokenContract is a no-op under Vitest.
*/
import { readFileSync } from 'node:fs'
import { resolve } from 'node:path'
-import { describe, expect, it } from 'vitest'
+import { describe, expect, it, vi } from 'vitest'
-import { TOKEN_CONTRACT } from '@/shared/tokens/contract.js'
+import { TOKEN_CONTRACT, validateTokenContract } from '@/shared/tokens/contract.js'
const themesDir = resolve(__dirname, '../../../../src/assets/themes')
const themeFiles = ['tokyo-night-storm.css', 'catppuccin-latte.css', 'sap-horizon.css']
}
})
})
+
+describe('validateTokenContract', () => {
+ it('should be a no-op under Vitest', () => {
+ const rafSpy = vi.spyOn(globalThis, 'requestAnimationFrame')
+ validateTokenContract('useTheme', 'tokyo-night-storm')
+ expect(rafSpy).not.toHaveBeenCalled()
+ })
+})