mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-23 06:03:05 +00:00
refactor(glow): move small-player glow strength into graphics overrides (#4634)
Moves the small-player glow strength from a standalone UserSettings key (`settings.highlightGlowStrength`) into the graphics overrides blob (`smallPlayerGlow.strength`), and moves its slider from the basic settings modal into the **Effects** section of the graphics settings modal. The pass now reads strength from its live `RenderSettings` slice like every other graphics knob, flowing through `applyGraphicsOverrides`. This removes the dedicated push chain (ClientGameRunner listener → MapRenderer → Renderer → pass setter) and MapRenderer's context-restore reapply — strength survives WebGL context restores automatically now, participates in the graphics modal's "Reset to defaults", and shows up in the render debug GUI. The old slider had no visible effect below ~115%: strength only drove the blur-iteration count (`round(strength ** 2.74)`, floored at 1 pass), and the composite intensity (`alpha * pulse * sqrt(passes)`) never factored strength directly — so 10% rendered identical to 100%. Strength is now a linear opacity fade on a fixed-width aura: - 100% = the aura's full brightness, 10% = barely visible, 0% = off - Slider capped at 100% (the >100% aura-widening mechanism became unreachable and is removed: `passesFor`, `lastPasses`, and the blur-iteration loop) - Default is 25% - The pass clamps persisted values above 1 so stale settings from the old 500% range can't over-brighten - Reuses the existing `user_setting.highlight_glow_strength_label` / `highlight_small_players_desc` translation keys — no en.json change - The legacy `settings.highlightGlowStrength` localStorage key is not migrated (the slider only shipped in #4588 a few days ago); users reset to the 25% default - [x] `tests/GraphicsOverrides.test.ts` — schema validation + apply coverage for the new override group - [x] Full suite (171 tests), tsc, eslint all pass 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
committed by
evanpelle
co-authored by
Claude Fable 5
parent
e3676439d2
commit
11c5db8b6f
@@ -527,17 +527,6 @@ async function createClientGame(
|
||||
{ signal: graphicsListenerAbort.signal },
|
||||
);
|
||||
|
||||
// Push the small-player glow Strength to the pass (which is a pure consumer)
|
||||
// on the settings-changed event, so moving the slider updates the glow live
|
||||
// even while the settings modal has the sim paused.
|
||||
view.setSmallPlayerGlowStrength(userSettings.highlightGlowStrength());
|
||||
globalThis.addEventListener(
|
||||
`${USER_SETTINGS_CHANGED_EVENT}:settings.highlightGlowStrength`,
|
||||
() =>
|
||||
view.setSmallPlayerGlowStrength(userSettings.highlightGlowStrength()),
|
||||
{ signal: graphicsListenerAbort.signal },
|
||||
);
|
||||
|
||||
// Re-resolve names drawn on the map when the anonymous-names setting toggles
|
||||
// so they switch live, like the leaderboard.
|
||||
globalThis.addEventListener(
|
||||
|
||||
@@ -70,6 +70,12 @@ const RAIL_THICKNESS_MIN = 0.5;
|
||||
const RAIL_THICKNESS_MAX = 3;
|
||||
const RAIL_THICKNESS_STEP = 0.1;
|
||||
|
||||
// Small-player glow strength is shown as a percentage: 0% = off, 100% = the
|
||||
// glow's full brightness.
|
||||
const GLOW_STRENGTH_MIN = 0;
|
||||
const GLOW_STRENGTH_MAX = 100;
|
||||
const GLOW_STRENGTH_STEP = 5;
|
||||
|
||||
// "Ambient light" level shown to the player: 0 = no darkening (lighting off),
|
||||
// 10 = darkest with the strongest glow. Mapped linearly onto the renderer's
|
||||
// ambient value (1 = identity, AMBIENT_MIN = darkest).
|
||||
@@ -541,6 +547,29 @@ export class GraphicsSettingsModal extends LitElement implements Controller {
|
||||
this.patchPassEnabled({ fallout: !this.currentFallout() });
|
||||
}
|
||||
|
||||
private patchSmallPlayerGlow(
|
||||
patch: Partial<GraphicsOverrides["smallPlayerGlow"]>,
|
||||
) {
|
||||
const current = this.userSettings.graphicsOverrides();
|
||||
this.userSettings.setGraphicsOverrides({
|
||||
...current,
|
||||
smallPlayerGlow: { ...current.smallPlayerGlow, ...patch },
|
||||
});
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
private currentGlowStrength(): number {
|
||||
return (
|
||||
this.userSettings.graphicsOverrides().smallPlayerGlow?.strength ??
|
||||
renderDefaults.smallPlayerGlow.strength
|
||||
);
|
||||
}
|
||||
|
||||
private onGlowStrengthChange(event: Event) {
|
||||
const value = parseFloat((event.target as HTMLInputElement).value) / 100;
|
||||
this.patchSmallPlayerGlow({ strength: value });
|
||||
}
|
||||
|
||||
/** Whether colorblind mode is currently enabled. */
|
||||
private currentColorblind(): boolean {
|
||||
return (
|
||||
@@ -619,6 +648,7 @@ export class GraphicsSettingsModal extends LitElement implements Controller {
|
||||
const nukeColor = this.currentNukeColor();
|
||||
const ambientLevel = this.currentAmbientLevel();
|
||||
const unitGlow = this.currentUnitGlow();
|
||||
const glowStrength = this.currentGlowStrength();
|
||||
const colorblind = this.currentColorblind();
|
||||
|
||||
return html`
|
||||
@@ -1258,6 +1288,31 @@ export class GraphicsSettingsModal extends LitElement implements Controller {
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<div
|
||||
class="flex gap-3 items-center w-full text-left p-3 hover:bg-slate-700 rounded-sm text-white transition-colors"
|
||||
>
|
||||
<div class="flex-1">
|
||||
<div class="font-medium">
|
||||
${translateText("user_setting.highlight_glow_strength_label")}
|
||||
</div>
|
||||
<div class="text-sm text-slate-400">
|
||||
${translateText("user_setting.highlight_small_players_desc")}
|
||||
</div>
|
||||
<input
|
||||
type="range"
|
||||
min=${GLOW_STRENGTH_MIN}
|
||||
max=${GLOW_STRENGTH_MAX}
|
||||
step=${GLOW_STRENGTH_STEP}
|
||||
.value=${String(glowStrength * 100)}
|
||||
@input=${this.onGlowStrengthChange}
|
||||
class="w-full border border-slate-500 rounded-lg"
|
||||
/>
|
||||
</div>
|
||||
<div class="text-sm text-slate-400 w-12 text-right">
|
||||
${Math.round(glowStrength * 100)}%
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="px-3 py-1 text-xs font-semibold text-slate-400 uppercase tracking-wider mt-2"
|
||||
>
|
||||
|
||||
@@ -19,7 +19,6 @@ import { ShowGraphicsSettingsModalEvent } from "./GraphicsSettingsModal";
|
||||
const cursorPriceIcon = assetUrl("images/CursorPriceIconWhite.svg");
|
||||
const emojiIcon = assetUrl("images/EmojiIconWhite.svg");
|
||||
const exitIcon = assetUrl("images/ExitIconWhite.svg");
|
||||
const highlightIcon = assetUrl("images/HighlightIconWhite.svg");
|
||||
const mouseIcon = assetUrl("images/MouseIconWhite.svg");
|
||||
const ninjaIcon = assetUrl("images/NinjaIconWhite.svg");
|
||||
const settingsIcon = assetUrl("images/SettingIconWhite.svg");
|
||||
@@ -205,12 +204,6 @@ export class SettingsModal extends LitElement implements Controller {
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
private onHighlightGlowStrengthChange(event: Event) {
|
||||
const strength = parseFloat((event.target as HTMLInputElement).value) / 100;
|
||||
this.userSettings.setHighlightGlowStrength(strength);
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.isVisible) {
|
||||
return null;
|
||||
@@ -357,36 +350,6 @@ export class SettingsModal extends LitElement implements Controller {
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<div
|
||||
class="flex gap-3 items-center w-full text-left p-3 hover:bg-slate-700 rounded-sm text-white transition-colors"
|
||||
>
|
||||
<img
|
||||
src=${highlightIcon}
|
||||
alt="highlightGlowStrength"
|
||||
width="20"
|
||||
height="20"
|
||||
/>
|
||||
<div class="flex-1">
|
||||
<div class="font-medium">
|
||||
${translateText("user_setting.highlight_glow_strength_label")}
|
||||
</div>
|
||||
<div class="text-sm text-slate-400">
|
||||
${translateText("user_setting.highlight_small_players_desc")}
|
||||
</div>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="500"
|
||||
.value=${this.userSettings.highlightGlowStrength() * 100}
|
||||
@input=${this.onHighlightGlowStrengthChange}
|
||||
class="w-full border border-slate-500 rounded-lg"
|
||||
/>
|
||||
</div>
|
||||
<div class="text-sm text-slate-400">
|
||||
${Math.round(this.userSettings.highlightGlowStrength() * 100)}%
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="flex gap-3 items-center w-full text-left p-3 hover:bg-slate-700 rounded-sm text-white transition-colors"
|
||||
@click="${this.onToggleAlertFrameButtonClick}"
|
||||
|
||||
@@ -41,6 +41,12 @@ export const GraphicsOverridesSchema = z
|
||||
railThickness: z.number(),
|
||||
})
|
||||
.partial(),
|
||||
smallPlayerGlow: z
|
||||
.object({
|
||||
// Aura around small players' territory: 0 = off, 1 = full brightness.
|
||||
strength: z.number(),
|
||||
})
|
||||
.partial(),
|
||||
passEnabled: z
|
||||
.object({
|
||||
fx: z.boolean(),
|
||||
|
||||
@@ -36,10 +36,6 @@ import type { RenderSettings } from "./RenderSettings";
|
||||
export class MapRenderer {
|
||||
private renderer: GPURenderer | null = null;
|
||||
private resizeObs: ResizeObserver | null = null;
|
||||
// Persisted so a WebGL context restore (which recreates GPURenderer via
|
||||
// initRenderer) reapplies the user's chosen glow strength instead of
|
||||
// resetting it to the pass default until the next settings change.
|
||||
private smallPlayerGlowStrength = 1;
|
||||
|
||||
/**
|
||||
* Called after a lost WebGL context is restored and the renderer has been
|
||||
@@ -93,8 +89,6 @@ export class MapRenderer {
|
||||
|
||||
const rect = this.canvas.getBoundingClientRect();
|
||||
if (rect.width > 0) this.renderer.resize(rect.width, rect.height);
|
||||
// Reapply state that lives outside RenderSettings so it survives a restore.
|
||||
this.renderer.setSmallPlayerGlowStrength(this.smallPlayerGlowStrength);
|
||||
};
|
||||
|
||||
private handleContextLost = (e: Event) => {
|
||||
@@ -233,12 +227,6 @@ export class MapRenderer {
|
||||
this.renderer?.updateSmallPlayerGlow(set);
|
||||
}
|
||||
|
||||
/** Set the small-player glow Strength (0 = off, 1 = default, capped at 5). */
|
||||
setSmallPlayerGlowStrength(strength: number): void {
|
||||
this.smallPlayerGlowStrength = strength;
|
||||
this.renderer?.setSmallPlayerGlowStrength(strength);
|
||||
}
|
||||
|
||||
// ---- Selection box ----
|
||||
|
||||
/** Set multiple selected units (multi-select). Pass [] to clear. */
|
||||
|
||||
@@ -85,6 +85,9 @@ export function applyGraphicsOverrides(
|
||||
if (overrides.railroad?.railThickness !== undefined) {
|
||||
settings.railroad.railThickness = overrides.railroad.railThickness;
|
||||
}
|
||||
if (overrides.smallPlayerGlow?.strength !== undefined) {
|
||||
settings.smallPlayerGlow.strength = overrides.smallPlayerGlow.strength;
|
||||
}
|
||||
if (overrides.passEnabled?.fx !== undefined) {
|
||||
settings.passEnabled.fx = overrides.passEnabled.fx;
|
||||
}
|
||||
|
||||
@@ -371,6 +371,7 @@ export interface RenderSettings {
|
||||
color: number[]; // RGB, each 0–1
|
||||
alpha: number; // peak opacity (0–1)
|
||||
pulseSpeed: number; // breath animation speed
|
||||
strength: number; // opacity fade: 0 = off, 1 = full brightness (default 0.25)
|
||||
};
|
||||
altView: {
|
||||
gridFontSize: number;
|
||||
|
||||
@@ -917,10 +917,6 @@ export class GPURenderer {
|
||||
this.smallPlayerGlowPass.update(set);
|
||||
}
|
||||
|
||||
setSmallPlayerGlowStrength(strength: number): void {
|
||||
this.smallPlayerGlowPass.setGlowStrength(strength);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Queries
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -57,8 +57,6 @@ export class SmallPlayerGlowPass {
|
||||
private dirty = false; // aura needs rebuilding (set changed)
|
||||
private animTime = 0;
|
||||
private lastTime = 0;
|
||||
private lastPasses = 1; // last width the aura was blurred at (to detect change)
|
||||
private glowStrength = 1; // pushed via setGlowStrength; 0 = off, 1 = default, capped at 5
|
||||
|
||||
constructor(
|
||||
gl: WebGL2RenderingContext,
|
||||
@@ -123,16 +121,6 @@ export class SmallPlayerGlowPass {
|
||||
this.quadVao = createFullscreenQuad(gl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Push the glow Strength (0 = off, 1 = default, capped at 5). The client reads
|
||||
* it from UserSettings and pushes it here so the pass stays a pure consumer
|
||||
* (passes never read game/DOM state directly); pushing on the settings-changed
|
||||
* event keeps it live even while the settings modal has the sim paused.
|
||||
*/
|
||||
setGlowStrength(strength: number): void {
|
||||
this.glowStrength = strength;
|
||||
}
|
||||
|
||||
/** Push the highlight set (1 byte per owner smallID), or null to disable. */
|
||||
update(set: Uint8Array | null): void {
|
||||
if (set === null) {
|
||||
@@ -156,13 +144,6 @@ export class SmallPlayerGlowPass {
|
||||
this.dirty = true;
|
||||
}
|
||||
|
||||
// Strength (0 = off, 1 = default width) -> blur iterations; each iteration
|
||||
// widens the Gaussian aura. Non-linear so the slider ramps gently low and
|
||||
// grows fast toward the top: 500% ≈ 82 passes (very wide). Clamped for perf.
|
||||
private passesFor(strength: number): number {
|
||||
return Math.min(96, Math.max(1, Math.round(strength ** 2.74)));
|
||||
}
|
||||
|
||||
// One separable-blur axis: sample `src`, write the blurred result into `dst`.
|
||||
private blurAxis(
|
||||
src: RenderTarget,
|
||||
@@ -180,9 +161,11 @@ export class SmallPlayerGlowPass {
|
||||
}
|
||||
|
||||
draw(cameraMatrix: Float32Array): void {
|
||||
// Strength is pushed by the client (not tick-gated), so a slider change is
|
||||
// live even while the sim is paused (e.g. the settings modal is open).
|
||||
const strength = this.glowStrength;
|
||||
// Strength is a graphics override read from the live settings slice each
|
||||
// frame (not tick-gated), so a slider change is live even while the sim is
|
||||
// paused (e.g. the graphics settings modal is open). Clamped to 1 so a
|
||||
// value persisted from the old 500% range can't over-brighten.
|
||||
const strength = Math.min(1, this.settings.strength);
|
||||
if (!this.active || strength <= 0) return;
|
||||
|
||||
const gl = this.gl;
|
||||
@@ -199,11 +182,6 @@ export class SmallPlayerGlowPass {
|
||||
}
|
||||
this.lastTime = now;
|
||||
const pulse = 0.5 + 0.5 * Math.sin(this.animTime);
|
||||
const passes = this.passesFor(strength);
|
||||
if (passes !== this.lastPasses) {
|
||||
this.dirty = true; // width changed -> rebuild the aura this frame
|
||||
this.lastPasses = passes;
|
||||
}
|
||||
|
||||
// Rebuild the blurred aura only when the set changed (~1/s); its inputs
|
||||
// don't move faster than that. The composite below still runs every frame.
|
||||
@@ -222,14 +200,11 @@ export class SmallPlayerGlowPass {
|
||||
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
||||
});
|
||||
|
||||
// Separable blur, iterated `passes` times to widen the aura (more
|
||||
// iterations = wider Gaussian). Each pass: horizontal A->B, vertical B->A.
|
||||
// Separable blur: horizontal A->B, then vertical B->A.
|
||||
gl.useProgram(this.blurProg);
|
||||
gl.activeTexture(gl.TEXTURE0);
|
||||
for (let k = 0; k < passes; k++) {
|
||||
this.blurAxis(a, b, 1 / a.w, 0); // horizontal A->B
|
||||
this.blurAxis(b, a, 0, 1 / b.h); // vertical B->A
|
||||
}
|
||||
this.blurAxis(a, b, 1 / a.w, 0); // horizontal A->B
|
||||
this.blurAxis(b, a, 0, 1 / b.h); // vertical B->A
|
||||
this.dirty = false;
|
||||
}
|
||||
|
||||
@@ -243,9 +218,9 @@ export class SmallPlayerGlowPass {
|
||||
gl.uniformMatrix3fv(this.uCompositeCam, false, cameraMatrix);
|
||||
gl.uniform2f(this.uCompositeMapSize, this.mapW, this.mapH);
|
||||
gl.uniform3fv(this.uGlowColor, s.color);
|
||||
// Widening spreads the mask thinner, so scale intensity up to keep the
|
||||
// glow about as bright (wider, not darker), capped by the shader's alpha.
|
||||
gl.uniform1f(this.uIntensity, s.alpha * pulse * Math.sqrt(passes));
|
||||
// Strength fades the glow linearly: 1 = the aura's full alpha, 0.1 =
|
||||
// barely visible.
|
||||
gl.uniform1f(this.uIntensity, s.alpha * pulse * strength);
|
||||
gl.activeTexture(gl.TEXTURE0);
|
||||
gl.bindTexture(gl.TEXTURE_2D, a.tex);
|
||||
gl.bindVertexArray(this.mapVao);
|
||||
|
||||
@@ -322,7 +322,8 @@
|
||||
"smallPlayerGlow": {
|
||||
"color": [1.0, 0.12, 0.1],
|
||||
"alpha": 0.9,
|
||||
"pulseSpeed": 0.003
|
||||
"pulseSpeed": 0.003,
|
||||
"strength": 0.25
|
||||
},
|
||||
"altView": {
|
||||
"gridFontSize": 24,
|
||||
|
||||
@@ -137,13 +137,6 @@ export class UserSettings {
|
||||
return this.getBool("settings.emojis", true);
|
||||
}
|
||||
|
||||
highlightGlowStrength() {
|
||||
// 0 = off, 1 = default; capped at 5 (the 500% slider max) so a value
|
||||
// persisted from an older, larger range can't display/apply above it.
|
||||
const v = this.getFloat("settings.highlightGlowStrength", 1);
|
||||
return Math.min(5, Math.max(0, v));
|
||||
}
|
||||
|
||||
performanceOverlay() {
|
||||
return this.getBool(PERFORMANCE_OVERLAY_KEY, false);
|
||||
}
|
||||
@@ -196,10 +189,6 @@ export class UserSettings {
|
||||
this.setBool("settings.emojis", !this.emojis());
|
||||
}
|
||||
|
||||
setHighlightGlowStrength(value: number) {
|
||||
this.setFloat("settings.highlightGlowStrength", value);
|
||||
}
|
||||
|
||||
// Performance overlay specifically needs a direct setter for Shift-D
|
||||
setPerformanceOverlay(value: boolean) {
|
||||
this.setBool(PERFORMANCE_OVERLAY_KEY, value);
|
||||
|
||||
@@ -74,6 +74,17 @@ describe("GraphicsOverridesSchema", () => {
|
||||
}
|
||||
});
|
||||
|
||||
test("accepts partial smallPlayerGlow overrides", () => {
|
||||
const cases = [
|
||||
{ smallPlayerGlow: {} },
|
||||
{ smallPlayerGlow: { strength: 0 } },
|
||||
{ smallPlayerGlow: { strength: 0.5 } },
|
||||
];
|
||||
for (const c of cases) {
|
||||
expect(GraphicsOverridesSchema.safeParse(c).success).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
test("accepts partial lighting overrides", () => {
|
||||
const cases = [
|
||||
{ lighting: {} },
|
||||
@@ -128,6 +139,11 @@ describe("GraphicsOverridesSchema", () => {
|
||||
railroad: { railThickness: "wide" },
|
||||
}).success,
|
||||
).toBe(false);
|
||||
expect(
|
||||
GraphicsOverridesSchema.safeParse({
|
||||
smallPlayerGlow: { strength: "strong" },
|
||||
}).success,
|
||||
).toBe(false);
|
||||
expect(
|
||||
GraphicsOverridesSchema.safeParse({
|
||||
lighting: { ambient: "dark" },
|
||||
@@ -374,6 +390,28 @@ describe("applyGraphicsOverrides", () => {
|
||||
expect(z.railThickness).toBe(defaults.railThickness);
|
||||
});
|
||||
|
||||
test("applies smallPlayerGlow strength override (including 0 = off)", () => {
|
||||
expect(
|
||||
gen({ smallPlayerGlow: { strength: 0.5 } }).smallPlayerGlow.strength,
|
||||
).toBe(0.5);
|
||||
expect(
|
||||
gen({ smallPlayerGlow: { strength: 0 } }).smallPlayerGlow.strength,
|
||||
).toBe(0);
|
||||
});
|
||||
|
||||
test("smallPlayerGlow strength absent → keeps default of 0.25", () => {
|
||||
expect(gen({}).smallPlayerGlow.strength).toBe(0.25);
|
||||
expect(gen({ smallPlayerGlow: {} }).smallPlayerGlow.strength).toBe(0.25);
|
||||
});
|
||||
|
||||
test("smallPlayerGlow override leaves other glow fields at defaults", () => {
|
||||
const defaults = createRenderSettings().smallPlayerGlow;
|
||||
const g = gen({ smallPlayerGlow: { strength: 0.25 } }).smallPlayerGlow;
|
||||
expect(g.alpha).toBe(defaults.alpha);
|
||||
expect(g.pulseSpeed).toBe(defaults.pulseSpeed);
|
||||
expect(g.color).toEqual(defaults.color);
|
||||
});
|
||||
|
||||
test("ambient < 1 sets ambient and enables the lighting pass", () => {
|
||||
const l = gen({ lighting: { ambient: 0.5 } }).lighting;
|
||||
expect(l.ambient).toBe(0.5);
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
import { UserSettings } from "../src/core/game/UserSettings";
|
||||
|
||||
describe("UserSettings highlight glow strength", () => {
|
||||
beforeEach(() => {
|
||||
localStorage.clear();
|
||||
// UserSettings keeps a static in-memory cache; reset it too so each test
|
||||
// reads fresh from the (cleared) localStorage.
|
||||
(
|
||||
UserSettings as unknown as { cache: Map<string, string | null> }
|
||||
).cache.clear();
|
||||
});
|
||||
|
||||
it("defaults to 1 (100%, on)", () => {
|
||||
expect(new UserSettings().highlightGlowStrength()).toBe(1);
|
||||
});
|
||||
|
||||
it("persists a set value, including 0 (off)", () => {
|
||||
const s = new UserSettings();
|
||||
s.setHighlightGlowStrength(2.5);
|
||||
expect(s.highlightGlowStrength()).toBe(2.5);
|
||||
s.setHighlightGlowStrength(0);
|
||||
expect(s.highlightGlowStrength()).toBe(0);
|
||||
});
|
||||
|
||||
it("shares state across instances via the static cache", () => {
|
||||
// The settings modal and the renderer's frame builder each hold their own
|
||||
// UserSettings; a change in one must be visible to the other.
|
||||
new UserSettings().setHighlightGlowStrength(3);
|
||||
expect(new UserSettings().highlightGlowStrength()).toBe(3);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user