Test Case Editor Modernized - sebiboga/testlink-upgraded GitHub Wiki

Test Case Editor — Modernized Screen

Modernization of the legacy "test case version editor" screen (lib/testcases/tcEdit.php, gui/templates/dashio/testcases/tcEdit.tpl) — GitHub issue #812.

The legacy Smarty editor is replaced by a standalone Dashio page (gui/templates/testcases/tcEdit.html) backed by a plain-PHP REST BFF API (api/testcasesedit/index.php). The test case viewer toolbar Edit button (openTcEdit) now opens the modern screen instead of the legacy tcEdit.php redirect — this was the last remaining HIGH-priority legacy redirect from issue #753 (tcExport.php, tcCompareVersions.php, tcAssign2Tplan.php were already converted in #803/#809/#810).

Path: Test Spec → Test Case Viewer → toolbar Edit URL: gui/templates/testcases/tcEdit.html?tcase_id=..&tcversion_id=..&tproject_id=.. BFF API: GET /api/testcasesedit/?action=edit + POST /api/testcasesedit/?action=update + POST /api/testcasesedit/?action=create_version Rights: mgt_modify_tc for edit/update; editing an executed version is gated by testproject_edit_executed_testcases (parity with the legacy controller's edit-permission logic). Tracking issue: #812

Test Case Editor — editing Login Test v2 with summary, preconditions and three steps

Unsaved-changes guard (parity with legacy checkNotSaved)

Issue #1317 — the legacy editor (tcEdit.tpl:93-98) wires checkmodified.js when $tlCfg->gui->checkNotSaved is enabled, so leaving the page with unsaved edits triggers a "You have unsaved changes" prompt. The modern screen mirrors that:

  • ctx.content_modified / ctx.show_modified_warning flags plus markModified() / resetModified() helpers (tcEdit.html).
  • doBeforeUnload() installed on window.onbeforeunload, returning the localised tcedit.unsavedWarning message only when the form is dirty and the warning is armed.
  • Dirty tracking is event-delegated on the editor card: input/change on .form-control, input[type=text], select, textarea, plus click on keyword pills and step-delete buttons; Add step marks the form modified too.
  • renderEditor() calls resetModified() so prefilling never flags a clean form; a successful Save resets the flag; Cancel clears show_modified_warning (mirrors the legacy "suppress on Save/Cancel").
  • i18n key tcedit.unsavedWarning added to all 10 locale bundles.

Because the modern editor uses plain inputs/textareas (no CKEditor), the legacy editorChanged()/IsDirty() detection is replaced by the delegated event tracking described above.

Test Case Editor — dirty form armed with the unsaved-changes guard on reload

Status dropdown — legacy testCaseStatus domain (gap vs legacy)

Issue #1316 — the initial modernization built the Status select (tcEdit.html) from hardcoded i18n keys tcedit.statusN, whose labels diverged from the legacy domain: 4 "Reviewed" (legacy Rework), 5 "Rework" (legacy Obsolete), 6 "Obsolete" (legacy Future), 7 "Future" (legacy Final). Every stored tcversions.status >= 4 was displayed with the wrong meaning, and saving e.g. "Future" silently converted a Future into a Final.

Fix (config-sourced, same helper as the already-aligned tcBulkOp/suiteView):

  • api/testcasesedit/index.php adds statusLabels to the edit payload — getConfigAndLabels('testCaseStatus','code') (legacy cfg/const.inc.php testCaseStatus + locale testCaseStatus_* strings), keyed by numeric code.
  • tcEdit.html builds the dropdown from statusLabels (codes sorted ascending) with a defensive fallback map; the hardcoded 1..7 loop and the tcedit.statusN i18n lookup are gone.
  • The obsolete tcedit.status1..7 keys were removed from all 10 locale bundles; the labels are now a single server-side source of truth.

Legacy domain served: 1 Draft, 2 Ready for review, 3 Review in progress, 4 Rework, 5 Obsolete, 6 Future, 7 Final.

tcEdit Status dropdown with a status=4 version now showing Rework (legacy domain)

Behavioral parity with the legacy controller

The BFF mirrors lib/testcases/tcEdit.php + testcaseCommands:

  • Load (action=edit) resolves the test case version through its position in the version chain, validates it belongs to the project, and returns its full content: name, summary, preconditions, ordered steps (actions + expected results), importance, status, execution type, estimated execution duration, and the project's keywords. It also computes keywords (assigned) and all_keywords.
  • Rights (mgt_modify_tc) are enforced on every route; a user lacking the right receives HTTP 403 No permission.
  • Executed-version gate: when the current version has already been executed, editing is only permitted for users carrying testproject_edit_executed_testcases; otherwise the BFF refuses the edit.
  • Save (action=update) persists via testcase::update() (name on the nodes_hierarchy node, summary/preconditions/steps/expected_results on the tcversions row) plus $attr for status and estimated_exec_duration, and syncs the keywords. Empty titles are rejected (HTTP 400 Title must not be empty); bad durations rejected with a client-side guard.
  • Create New Version (action=create_version) clones the current version as a new Draft version (version = max+1) with steps copied, then returns the new version identity so the screen can switch to editing it.

Screen layout

Section Description
Heading "Edit Test Case" + test-case identity (PREFIX-<id>:<name> (#<id> v<version>))
Toolbar Name/title input, Importance / Status / Execution Type / Estimated duration selects, locale switcher
Description Summary textarea
Preconditions Preconditions textarea
Steps Data-list of (actions, expected results) rows with Add step / remove buttons
Keywords Assigned-project-keyword pills
Actions Save, Cancel, Create New Version
Overlay/toast Loading overlay + Bootstrap-toast messages for save/errors/confirms

Create New Version — clones the EDITED version and freezes the source

Issue #1315 — the initial modernization called create_new_version($tcaseId, $userId) without the submitted tcversion_id, so testcase::create_new_version() (testcase.class.php:2445-2448) fell back to the latest version as clone source, and the BFF never froze the source version. Parity with the legacy controller (lib/testcases/tcEdit.php createNewVersion():838-852) restored:

  • Source of the clone = the version being edited. The front-end already POSTed tcversion_id: ctx.tcversionId (doNewVersion():512-517); the BFF now passes that $tcverId as the third $source_version_id argument of create_new_version(). A v1 → "Create New Version" now clones v1, not the latest v2.
  • Source freeze (legacy switch). After a successful creation the BFF reads config_get('testcase_cfg')->freezeTCVersionOnNewTCVersion (default TRUE, config.inc.php:1352) and applies setIsOpen($tcaseId, $sourceTCVID, !freeze) — the edited version is closed (is_open=0) so it can no longer be modified; the new draft version is open.
  • Configurable like legacy. With freezeTCVersionOnNewTCVersion=FALSE the source version stays open after the clone; the clone source is still the edited version.
  • Fallback path. When tcversion_id is omitted, resolveContext() defaults to the latest active version — matching the legacy do_create_new_version_from_latest (tcEdit.php:280-282), which also goes through the same freeze logic.
  • Rights check unchanged: mgt_modify_tc on the owning project (403).

Create New Version from v1 — new v3 cloned from the edited v1 (summary V1 SUMMARY, no smoke keyword), v1 frozen

Regression Suite 1315 in tmp/TLU_Test_Cases.md — 6/6 PASS (default-config clone/freeze, keyword no-leak, configurable freeze=FALSE, from-latest fallback, editor reload, clean Event Viewer).

Flow

  1. On load the page calls GET /api/testcasesedit/?action=edit with tcase_id, tcversion_id, tproject_id. The BFF resolves and validates the version, checks rights, and returns the version content.
  2. The screen renders the form with all fields prefilled.
  3. Save POSTs ?action=update with the full payload (name, summary, preconditions, steps, importance, status, exec type, duration, keyword ids). On success a toast shows "Test case saved." and the form reloads the saved version.
  4. Add step appends a new (actions, expected results) row client-side; remove deletes a row; at least one step may be kept.
  5. Create New Version confirms, then POSTs ?action=create_version; on success the screen switches to editing the freshly created Draft version.
  6. Cancel closes the editor.

API surface (api/testcasesedit/index.php)

  • GET ?action=edit&tcase_id=..&tcversion_id=..&tproject_id=.. → 200 with the version content (tcase.name, summary, preconditions, steps[], importance, status, exec_type, estimated_duration, keywords, all_keywords, grants, has_been_executed, identity). 403 when the user lacks mgt_modify_tc; 404 when context invalid.
  • POST ?action=update (JSON) → 200 {status:ok}; 400 on empty title; 403 no right.
  • POST ?action=create_version (JSON {tcase_id, tcversion_id, tproject_id}) → 200 with new version identity; clones the edited tcversion_id (the POSTed one; falls back to the latest active version when absent) and, when testcase_cfg.freezeTCVersionOnNewTCVersion is on (default), freezes the source version (is_open=0); 403 no right.
  • Any other action → 404; no session → 401.

i18n

All strings use the TLi18n module (gui/templates/i18n/i18n.js) with the tcedit.* key prefix, present in all 10 locale bundles (en/ro/de/fr/es/it/pt/ja/ru/zh.json).

Verification

Executed as regression Suite 812 — 17/17 PASS (tmp/TLU_Test_Cases.md), covering load, render, save (fields/name/attrs), add-step, create-version, empty-title/bad-duration guards, the three rights-deny paths (edit/update/create_version HTTP 403), unknown-action 404, unauthenticated 401, bad-context 400, i18n in all bundles, the tcView link switch, and a clean Event Viewer (0 new Error/Warning rows).

⚠️ **GitHub.com Fallback** ⚠️