fix(web): выбор спринтов при недопустимых id и восстановление испорченного хранилища (#112)
parseSprintIds: при отсутствии допустимых id — все активные (T2.1); токены только десятичные (T2.3). saveView перезаписывает испорченный JSON (T2.2). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
a29df27da9
commit
7fbc6bd574
@ -37,6 +37,12 @@ describe("agilePref", () => {
|
||||
expect(readView("AWPCA", "backlog")).toEqual(DEFAULT_VIEW);
|
||||
});
|
||||
|
||||
it("испорченный JSON: saveView перезаписывает мусор, readView возвращает сохранённое", () => {
|
||||
localStorage.setItem(AGILE_KEY, "oops");
|
||||
saveView("AWPCA", "backlog", view({ hideDone: true, epic: "AURORA-10" }));
|
||||
expect(readView("AWPCA", "backlog")).toEqual(view({ hideDone: true, epic: "AURORA-10" }));
|
||||
});
|
||||
|
||||
it("мусор: «[1]» → DEFAULT_VIEW", () => {
|
||||
localStorage.setItem(AGILE_KEY, "[1]");
|
||||
expect(readView("AWPCA", "backlog")).toEqual(DEFAULT_VIEW);
|
||||
|
||||
@ -41,7 +41,12 @@ export function readView(label: string, page: AgilePage): AgileView {
|
||||
|
||||
export function saveView(label: string, page: AgilePage, v: AgileView): void {
|
||||
try {
|
||||
const root = readRoot();
|
||||
let root: Obj;
|
||||
try {
|
||||
root = readRoot();
|
||||
} catch {
|
||||
root = {}; // испорченный JSON перезаписывается, иначе вид не сохранится никогда
|
||||
}
|
||||
const project = isObj(root[label]) ? root[label] : {};
|
||||
const view: AgileView = {
|
||||
assignee: v.assignee, epic: v.epic, hideDone: v.hideDone,
|
||||
|
||||
@ -202,7 +202,15 @@ describe("parseSprintIds", () => {
|
||||
];
|
||||
it("id из строки, только среди selectableSprints", () => {
|
||||
expect(parseSprintIds("102,999", sprints)).toEqual([102]);
|
||||
expect(parseSprintIds("101", sprints)).toEqual([]);
|
||||
expect(parseSprintIds("999,103", sprints)).toEqual([103]);
|
||||
});
|
||||
it("если не осталось ни одного допустимого id — все активные", () => {
|
||||
expect(parseSprintIds("999", sprints)).toEqual([102]);
|
||||
expect(parseSprintIds("101", sprints)).toEqual([102]);
|
||||
});
|
||||
it("токены — только десятичные цифры (1e2, 0x10, 1.0 отбрасываются)", () => {
|
||||
expect(parseSprintIds("1e2,0x66,102.0", sprints)).toEqual([102]);
|
||||
expect(parseSprintIds("1e2,103", sprints)).toEqual([103]);
|
||||
});
|
||||
it("порядок — как в selectableSprints, не как в строке", () => {
|
||||
expect(parseSprintIds("103,102", sprints)).toEqual([102, 103]);
|
||||
|
||||
@ -77,14 +77,15 @@ export function selectableSprints(sprints: AgileSprint[]): AgileSprint[] {
|
||||
return [...active, ...future.sort((a, b) => startMs(a) - startMs(b))];
|
||||
}
|
||||
|
||||
// id из «101,102» среди selectableSprints, в их порядке; пусто или без годных чисел — все активные
|
||||
// id из «101,102» среди selectableSprints, в их порядке; недопустимые отбрасываются; если не осталось ни одного — все активные
|
||||
export function parseSprintIds(param: string | null, sprints: AgileSprint[]): number[] {
|
||||
const selectable = selectableSprints(sprints);
|
||||
const ids = new Set(
|
||||
(param ?? "").split(",").map((x) => x.trim()).filter((x) => x !== "").map(Number).filter((n) => Number.isInteger(n)),
|
||||
(param ?? "").split(",").map((x) => x.trim()).filter((x) => /^\d+$/.test(x)).map(Number),
|
||||
);
|
||||
if (ids.size === 0) return selectable.filter((s) => s.state === "active").map((s) => s.id);
|
||||
return selectable.filter((s) => ids.has(s.id)).map((s) => s.id);
|
||||
const picked = selectable.filter((s) => ids.has(s.id)).map((s) => s.id);
|
||||
if (picked.length > 0) return picked;
|
||||
return selectable.filter((s) => s.state === "active").map((s) => s.id);
|
||||
}
|
||||
|
||||
// строки-эпики из всех спринтов и бэклога (по rank, без повторов), затем ключи из epic_key без строки (по ключу);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user