connecton satus mobil profile

This commit is contained in:
larssand
2026-09-08 19:27:37 +02:00
parent 5559d74846
commit b3bec531de
11 changed files with 169 additions and 7 deletions

View File

@@ -13,8 +13,8 @@ function profileHarness() {
set innerHTML(value) { ui.html = value; },
};
const context = vm.createContext({
URL, URLSearchParams,
document: { getElementById: () => root, activeElement: { matches: () => true } },
URL, URLSearchParams, AbortController, setTimeout, clearTimeout,
document: { getElementById: (id) => id === "publicTrainingRoot" ? root : null, activeElement: { matches: () => true } },
window: { location: { pathname: "/public-training/test", search: "?lang=sv", origin: "http://localhost" } },
fetch: async () => { ui.requests++; return { ok: true, json: async () => ({}) }; },
onRender: () => { ui.renders++; },
@@ -60,3 +60,68 @@ test("an initial mobile load failure still displays the error", async () => {
await refresh();
assert.match(ui.html, /public-training-error/);
});
test("mobile status detects stale data and recovery after failure", async () => {
const { context, refresh } = profileHarness();
await refresh();
assert.equal(vm.runInContext('getProfileConnectionState()', context), "current");
assert.equal(vm.runInContext('getProfileConnectionState(profileConnection.lastSuccessAt + 30000)', context), "stale");
context.fetch = async () => { throw new Error("offline"); };
await refresh();
assert.equal(vm.runInContext('getProfileConnectionState()', context), "failed");
context.fetch = async () => ({ ok: true, json: async () => ({}) });
await refresh();
assert.equal(vm.runInContext('getProfileConnectionState()', context), "current");
});
test("mobile requests cannot overlap and a skipped edit never refreshes the timestamp", async () => {
const { context, ui, refresh } = profileHarness();
let resolve;
context.fetch = () => { ui.requests++; return new Promise(done => { resolve = done; }); };
const pending = refresh();
await refresh();
assert.equal(ui.requests, 1);
ui.editing = true;
resolve({ ok: true, json: async () => ({}) });
await pending;
assert.equal(vm.runInContext('profileConnection.lastSuccessAt', context), null);
assert.equal(vm.runInContext('profileConnection.loading', context), false);
});
test("mobile status updates its own nodes without replacing loaded profile", async () => {
const { context, ui, refresh } = profileHarness();
const nodes = {
publicTrainingConnection: { dataset: {} },
publicTrainingConnectionText: { textContent: "" },
publicTrainingRetry: {},
};
context.document.getElementById = id => nodes[id] || null;
await refresh();
assert.equal(nodes.publicTrainingConnection.dataset.state, "current");
assert.equal(nodes.publicTrainingRetry.hidden, true);
context.fetch = async () => { throw new Error("offline"); };
await refresh();
assert.equal(nodes.publicTrainingConnection.dataset.state, "failed");
assert.equal(nodes.publicTrainingRetry.hidden, false);
assert.equal(nodes.publicTrainingRetry.disabled, false);
assert.equal(ui.html, "existing profile");
assert.equal(ui.renders, 1);
});
test("a timed-out mobile fetch releases its lock and allows retry", async () => {
const { context, refresh } = profileHarness();
let expire;
context.setTimeout = callback => { expire = callback; return 1; };
context.clearTimeout = () => {};
context.fetch = (_, { signal }) => new Promise((resolve, reject) => {
signal.addEventListener("abort", () => reject(new Error("aborted")));
});
const pending = refresh();
expire();
await pending;
assert.equal(vm.runInContext('profileConnection.loading', context), false);
assert.equal(vm.runInContext('getProfileConnectionState()', context), "failed");
context.fetch = async () => ({ ok: true, json: async () => ({}) });
await refresh();
assert.equal(vm.runInContext('getProfileConnectionState()', context), "current");
});