From 93e66d012281e3a2d79c54eceefea908b8a8e09c Mon Sep 17 00:00:00 2001 From: Han Sen Date: Tue, 3 Mar 2026 14:31:59 +0900 Subject: [PATCH] fix: Step case overwriting prevention by hypothetical ID (#396) --- .../[folderId]/cases/[caseId]/CaseEditor.tsx | 137 ++++++++++-------- 1 file changed, 74 insertions(+), 63 deletions(-) diff --git a/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/[caseId]/CaseEditor.tsx b/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/[caseId]/CaseEditor.tsx index 20d607b..911561c 100644 --- a/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/[caseId]/CaseEditor.tsx +++ b/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/[caseId]/CaseEditor.tsx @@ -60,7 +60,7 @@ export default function CaseEditor({ const [testCase, setTestCase] = useState(defaultTestCase); const [isTitleInvalid] = useState(false); const [isUpdating, setIsUpdating] = useState(false); - const [plusCount, setPlusCount] = useState(0); + const [idCounter, setIdCounter] = useState(0); const [isDirty, setIsDirty] = useState(false); const [selectedTags, setSelectedTags] = useState<{ id: number; name: string }[]>([]); @@ -68,9 +68,14 @@ export default function CaseEditor({ useFormGuard(isDirty, messages.areYouSureLeave); const onPlusClick = async (newStepNo: number) => { + if (!testCase.Steps) { + return; + } setIsDirty(true); + const nextId = idCounter + 1; const newStep: StepType = { - id: plusCount, + // hypothetical ID + id: nextId, step: '', result: '', createdAt: new Date(), @@ -78,66 +83,65 @@ export default function CaseEditor({ caseSteps: { stepNo: newStepNo, }, - uid: `uid${plusCount}`, + uid: `uid${nextId}`, editState: 'new', }; - setPlusCount(plusCount + 1); - if (testCase.Steps) { - const updatedSteps = testCase.Steps.map((step) => { - if (step.caseSteps.stepNo >= newStepNo) { - return { - ...step, - editState: step.editState === 'notChanged' ? 'changed' : step.editState, - caseSteps: { - ...step.caseSteps, - stepNo: step.caseSteps.stepNo + 1, - }, - }; - } - return step; - }); + const updatedSteps = testCase.Steps.map((step) => { + if (step.caseSteps.stepNo >= newStepNo) { + return { + ...step, + editState: step.editState === 'notChanged' ? 'changed' : step.editState, + caseSteps: { + ...step.caseSteps, + stepNo: step.caseSteps.stepNo + 1, + }, + }; + } + return step; + }); - updatedSteps.push(newStep); + updatedSteps.push(newStep); - setTestCase({ - ...testCase, - Steps: updatedSteps, - }); - } + setTestCase({ + ...testCase, + Steps: updatedSteps, + }); + setIdCounter(nextId); }; const onDeleteClick = async (stepId: number) => { setIsDirty(true); - - // find deletedStep's stepNo - if (testCase.Steps) { - const deletedStep = testCase.Steps.find((step) => step.id === stepId); - if (!deletedStep) { - return; - } - const deletedStepNo = deletedStep.caseSteps.stepNo; - deletedStep.editState = 'deleted'; - - const updatedSteps = testCase.Steps.map((step) => { - if (step.caseSteps.stepNo > deletedStepNo) { - return { - ...step, - editState: step.editState === 'notChanged' ? 'changed' : step.editState, - caseSteps: { - ...step.caseSteps, - stepNo: step.caseSteps.stepNo - 1, - }, - }; - } - return step; - }); - - setTestCase({ - ...testCase, - Steps: updatedSteps, - }); + if (!testCase.Steps) { + return; } + // find deletedStep's stepNo + + const deletedStep = testCase.Steps.find((step) => step.id === stepId); + if (!deletedStep) { + return; + } + const deletedStepNo = deletedStep.caseSteps.stepNo; + deletedStep.editState = 'deleted'; + + const updatedSteps = testCase.Steps.map((step) => { + if (step.caseSteps.stepNo > deletedStepNo) { + return { + ...step, + editState: step.editState === 'notChanged' ? 'changed' : step.editState, + caseSteps: { + ...step.caseSteps, + stepNo: step.caseSteps.stepNo - 1, + }, + }; + } + return step; + }); + + setTestCase({ + ...testCase, + Steps: updatedSteps, + }); }; const handleDrop = (event: DragEvent) => { @@ -201,18 +205,20 @@ export default function CaseEditor({ changeStep.editState = 'changed'; } - if (testCase.Steps) { - setTestCase({ - ...testCase, - Steps: testCase.Steps.map((step) => { - if (step.id === stepId) { - return changeStep; - } else { - return step; - } - }), - }); + if (!testCase.Steps) { + return; } + + setTestCase({ + ...testCase, + Steps: testCase.Steps.map((step) => { + if (step.id === stepId) { + return changeStep; + } else { + return step; + } + }), + }); }; useEffect(() => { @@ -223,6 +229,11 @@ export default function CaseEditor({ data.Steps.forEach((step: StepType) => { step.editState = 'notChanged'; }); + + // set idCounter to the max step id to avoid id conflict for new steps + // id is not reflected on database + const maxStepId = data.Steps.reduce((maxId: number, step: StepType) => Math.max(maxId, step.id), 0); + setIdCounter(maxStepId); setTestCase(data); if (data.Tags) { setSelectedTags(Array.isArray(data.Tags) ? data.Tags : []);