Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
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
6 changes: 3 additions & 3 deletions extension/jest.config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,9 @@ module.exports = {
coverageThreshold: {
global: {
statements: 69,
branches: 64,
functions: 67,
lines: 68,
branches: 65,
functions: 70,
lines: 69,
},
"./src/rules/": {
statements: 91,
Expand Down
168 changes: 163 additions & 5 deletions extension/src/lib/__tests__/storage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,15 @@
// the freshly-derived defaults instead of any stale stored value from a
// previous case.

import { RULE_DEFAULTS } from "../../rules/rule-defaults.generated";
import type { getRuleStates as GetRuleStates } from "../storage";
import { RULE_DEFAULTS as RAW_RULE_DEFAULTS } from "../../rules/rule-defaults.generated";
import type * as Storage from "../storage";

interface StorageModule {
getRuleStates: typeof GetRuleStates;
}
type StorageModule = typeof Storage;

// `RULE_DEFAULTS` is exported as a deeply-typed `as const` object so callers
// see per-key literal types. For these tests we want plain string indexing —
// pick a rule id off Object.keys, look up its default. Widen via this alias.
const RULE_DEFAULTS = RAW_RULE_DEFAULTS as Readonly<Record<string, boolean>>;

async function loadStorage(): Promise<StorageModule> {
let module!: StorageModule;
Expand Down Expand Up @@ -114,4 +117,159 @@ describe("storage default states", () => {
const states = await getRuleStates();
expect(states).toEqual(RULE_DEFAULTS);
});

// parseOverrides rejects anything that isn't a plain object: null,
// arrays, and primitives all decode but should be ignored. Without this
// case the `Array.isArray(parsed)` / typeof guard at the top of
// parseOverrides goes uncovered.
it.each([
["null", "null"],
["an array", JSON.stringify([true, false])],
["a primitive number", "42"],
])("ignores EXTENSION_DEFAULT_OVERRIDES that decodes to %s", async (_label, raw) => {
process.env.EXTENSION_DEFAULT_OVERRIDES = raw;
const { getRuleStates } = await loadStorage();
await expect(getRuleStates()).resolves.toEqual(RULE_DEFAULTS);
});
});

describe("normalize via getRuleStates", () => {
// Pick stable rule ids off the live catalog so the test doesn't tie itself
// to the iteration order of RULE_DEFAULTS.
function pickRuleIds(): { first: string; second: string } {
const ids = Object.keys(RULE_DEFAULTS);
const first = ids[0];
const second = ids[1];
if (!first || !second) {
throw new Error("RULE_DEFAULTS must have at least two entries");
}
return { first, second };
}

it("substitutes the codegen default for any non-boolean stored value", async () => {
const { getRuleStates, ruleStatesStorage } = await loadStorage();
const { first, second } = pickRuleIds();
// Cast to bypass the typed shape — the whole point is that the storage
// layer's normalize() drops the corrupt string value.
await ruleStatesStorage.set({
[first]: !RULE_DEFAULTS[first],
[second]: "yes",
} as unknown as Storage.RuleStates);

const states = await getRuleStates();
expect(states[first]).toBe(!RULE_DEFAULTS[first]);
expect(states[second]).toBe(RULE_DEFAULTS[second]);
});

it("fills in missing rule ids from defaults when stored state is partial", async () => {
const { getRuleStates, ruleStatesStorage } = await loadStorage();
const { first } = pickRuleIds();
await ruleStatesStorage.set({ [first]: !RULE_DEFAULTS[first] });

const states = await getRuleStates();
expect(states[first]).toBe(!RULE_DEFAULTS[first]);
// Every other rule still resolves to its codegen default.
for (const [id, value] of Object.entries(RULE_DEFAULTS)) {
if (id === first) {
continue;
}
expect(states[id]).toBe(value);
}
});
});

describe("setRuleEnabled", () => {
it("flips one rule while preserving the others", async () => {
const { getRuleStates, setRuleEnabled } = await loadStorage();
const target = Object.keys(RULE_DEFAULTS)[0];
if (!target) {
throw new Error("RULE_DEFAULTS must have at least one entry");
}
const next = !RULE_DEFAULTS[target];

await setRuleEnabled(target, next);

const states = await getRuleStates();
expect(states[target]).toBe(next);
for (const [id, value] of Object.entries(RULE_DEFAULTS)) {
if (id === target) {
continue;
}
expect(states[id]).toBe(value);
}
});

it("notifies subscribers when state changes", async () => {
const { setRuleEnabled, subscribe } = await loadStorage();
const target = Object.keys(RULE_DEFAULTS)[0];
if (!target) {
throw new Error("RULE_DEFAULTS must have at least one entry");
}
const listener = jest.fn();
subscribe(listener);

await setRuleEnabled(target, !RULE_DEFAULTS[target]);

expect(listener).toHaveBeenCalledTimes(1);
expect(listener).toHaveBeenCalledWith(
expect.objectContaining({ [target]: !RULE_DEFAULTS[target] }),
);
});
});

describe("setAllRuleStates", () => {
it("normalizes partial input before writing — missing ids fill from defaults", async () => {
const { getRuleStates, setAllRuleStates } = await loadStorage();
const target = Object.keys(RULE_DEFAULTS)[0];
if (!target) {
throw new Error("RULE_DEFAULTS must have at least one entry");
}

await setAllRuleStates({ [target]: !RULE_DEFAULTS[target] });

const states = await getRuleStates();
expect(states[target]).toBe(!RULE_DEFAULTS[target]);
for (const [id, value] of Object.entries(RULE_DEFAULTS)) {
if (id === target) {
continue;
}
expect(states[id]).toBe(value);
}
});

it("drops non-boolean values via normalize before writing", async () => {
const { getRuleStates, setAllRuleStates } = await loadStorage();
const target = Object.keys(RULE_DEFAULTS)[0];
if (!target) {
throw new Error("RULE_DEFAULTS must have at least one entry");
}

// Cast to bypass the typed shape — normalize() inside setAllRuleStates
// is responsible for dropping the corrupt string value.
await setAllRuleStates({
[target]: "junk",
} as unknown as Partial<Storage.RuleStates>);

// junk replaced with default → stored map equals defaults.
await expect(getRuleStates()).resolves.toEqual(RULE_DEFAULTS);
});
});

describe("subscribe", () => {
it("stops invoking the listener after the returned cleanup runs", async () => {
const { setRuleEnabled, subscribe } = await loadStorage();
const target = Object.keys(RULE_DEFAULTS)[0];
if (!target) {
throw new Error("RULE_DEFAULTS must have at least one entry");
}
const listener = jest.fn();
const cleanup = subscribe(listener);

await setRuleEnabled(target, !RULE_DEFAULTS[target]);
expect(listener).toHaveBeenCalledTimes(1);

cleanup();
await setRuleEnabled(target, Boolean(RULE_DEFAULTS[target]));
expect(listener).toHaveBeenCalledTimes(1);
});
});
Loading