mirror of
https://github.com/hansjone/netx.git
synced 2026-10-15 02:33:23 +08:00
Structure
- pages/ -> features/<domain>/{Page, components, hooks, lib}; 95 files moved,
333 imports rewritten, verified with tsc.
- services/api.ts -> services/api/<domain>.ts (250 exports preserved),
types.ts -> types/<domain>.ts, i18n zh/en -> locales/<lang>/<namespace>.ts
(2696 messages deep-equal before/after).
- ESLint rule: shared layers must not import features/.
Styles
- index.css (16k lines) split losslessly into styles/{base,layout,components,
pages,legacy,theme}; compiled CSS was byte-identical before tokenizing.
- ~930 hard-coded colors replaced by --nx-* tokens (theme/tokens.css); WebCRT
off-palette colors unified; fixed dark-on-dark text introduced by mapping.
- theme/theme.css: modern blue design language (rounded shapes, gradients,
unified buttons/fields/tables/modals), form-grid alignment fix, flat nav list
without tree indent, soft selected state instead of left accent bar.
Docs: WEB.md documents code structure and style rules.
Not touched: the large Biz*/Webcrt/Topology page components (to be split later).
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
72 lines
2.9 KiB
JavaScript
72 lines
2.9 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { beforeEach, test } from "node:test";
|
|
import { cutoverCachedGet, cutoverCachedGetSWR, invalidateCutoverCache } from "../src/features/cutover/lib/cutoverDataCache.ts";
|
|
|
|
const deferred = () => {
|
|
let resolve, reject;
|
|
const promise = new Promise((ok, fail) => { resolve = ok; reject = fail; });
|
|
return { promise, resolve, reject };
|
|
};
|
|
|
|
beforeEach(() => invalidateCutoverCache());
|
|
|
|
test("concurrent readers share one fetch", async () => {
|
|
const pending = deferred();
|
|
let calls = 0;
|
|
const fetcher = () => { calls++; return pending.promise; };
|
|
const first = cutoverCachedGet("batches", fetcher);
|
|
const second = cutoverCachedGet("batches", fetcher);
|
|
pending.resolve("fresh");
|
|
assert.deepEqual(await Promise.all([first, second]), ["fresh", "fresh"]);
|
|
assert.equal(calls, 1);
|
|
});
|
|
|
|
test("invalidated pending requests cannot repopulate the cache", async () => {
|
|
const pending = deferred();
|
|
const old = cutoverCachedGet("batches", () => pending.promise);
|
|
invalidateCutoverCache("batches");
|
|
pending.resolve("stale");
|
|
await old;
|
|
assert.equal(await cutoverCachedGet("batches", async () => "fresh"), "fresh");
|
|
});
|
|
|
|
test("forced refresh supersedes a slow older request", async () => {
|
|
const pending = deferred();
|
|
const old = cutoverCachedGet("lists", () => pending.promise);
|
|
assert.equal(await cutoverCachedGet("lists", async () => "fresh", { force: true }), "fresh");
|
|
pending.resolve("old");
|
|
await old;
|
|
assert.equal(await cutoverCachedGet("lists", async () => "unexpected"), "fresh");
|
|
});
|
|
|
|
test("a failed fetch can be retried", async () => {
|
|
await assert.rejects(cutoverCachedGet("lists", async () => { throw new Error("offline"); }));
|
|
assert.equal(await cutoverCachedGet("lists", async () => "recovered"), "recovered");
|
|
});
|
|
|
|
test("SWR readers share background refresh and each receives the fresh value", async () => {
|
|
await cutoverCachedGet("lists", async () => "cached");
|
|
const pending = deferred();
|
|
let calls = 0;
|
|
const notifications = [];
|
|
const fetcher = () => { calls++; return pending.promise; };
|
|
const first = await cutoverCachedGetSWR("lists", fetcher, (x) => notifications.push(x));
|
|
const second = await cutoverCachedGetSWR("lists", fetcher, (x) => notifications.push(x));
|
|
assert.equal(first, "cached");
|
|
assert.equal(second, "cached");
|
|
pending.resolve("fresh");
|
|
await new Promise((resolve) => setImmediate(resolve));
|
|
assert.equal(calls, 1);
|
|
assert.deepEqual(notifications, ["fresh", "fresh"]);
|
|
});
|
|
|
|
test("invalidated SWR requests do not notify with stale data", async () => {
|
|
await cutoverCachedGet("lists", async () => "cached");
|
|
const pending = deferred();
|
|
const notifications = [];
|
|
await cutoverCachedGetSWR("lists", () => pending.promise, (x) => notifications.push(x));
|
|
invalidateCutoverCache("lists");
|
|
pending.resolve("stale");
|
|
await new Promise((resolve) => setImmediate(resolve));
|
|
assert.deepEqual(notifications, []);
|
|
});
|