Test Suite Create Edit Delete Refs 1852 - sebiboga/testlink-upgraded GitHub Wiki
Test Suite Create / Edit / Delete (modernized) — Refs #1852
One Dashio page replaces the three legacy modes of
lib/testcases/containerEdit.php (new_testsuite, add_testsuite,
edit_testsuite, update_testsuite, delete_testsuite).
- Screen:
gui/templates/testcases/suiteEdit.html - BFF:
api/suiteedit/index.phpGET ?action=init&mode=create|edit|deletePOST ?action=create|update|delete
Why this screen had to be rebuilt
The api/suiteview inline "create/rename suite" modal was a bare name +
details form. Against 1.9.20 that meant:
| Legacy 1.9.20 | the inline modal | suiteEdit |
|---|---|---|
html_table_of_custom_field_inputs() |
never rendered | all types rendered from API data |
design_values_to_db() on write |
never called | called on create and update |
| keyword assignment | absent | dual picker, persisted |
delete_executed_testcases gate (build_del_testsuite_warning_msg(), system_blocks_tsuite_delete_due_to_exec_tc) |
delete_deep() on a plain mgt_modify_tc |
re-checked at write time |
So renaming a suite silently destroyed every design custom field of that suite, and a suite holding a linked and executed test case could be deleted without the right that legacy refused without.
Edit mode

- Context meta grid: test project, prefix, container path, mode.
- Name (pipe rejected, 100-char cap), details.
- Custom fields: every type — string, numeric, email, checkbox, list,
multiselection, date, textarea — with label, type name and possible values
coming from the API as data (never
innerHTML). - Keywords: available / assigned dual picker; an untouched save is a no-op.
- The URL switches to the persisted identity without a reload, so the success message survives (the #1724 lesson).
- The legacy actions 302 here, so a bookmarked/legacy link cannot reach the CF-dropping form any more.
Delete mode — the executed-case gate

- Blast radius first: how many nested suites and how many deep test cases go.
- Per-case table with the link / execution status of every case
(
linked_and_executed,linked_but_not_executed,no_links). - With an executed case and no
delete_executed_testcasesright: the block warning is shown, the confirm dialog explains that the executions go too, and the Delete button stays disabled. - The gate is enforced server-side on the POST as well — the disabled button is
a courtesy, the
409 suite_has_executedis the rule. - Only navigates away after the server confirms the write.
i18n
58 sued.* keys + footers.suiteEdit in all ten bundles, with a TLi18n
locale switcher. Custom-field labels and type names stay in the language they
were typed in: they are user data, not UI strings.
Bugs found while testing this screen
| Issue | What |
|---|---|
| #1853 (filed) | cfield_mgr::_build_cfield() classifies a hash key as a DATE part by its _input suffix instead of the type, so a non-date field keeps an ARRAY cf_value and design_values_to_db() fatals (tlStringLen(): Argument #1 must be of type string, array given). Affects every CF write path, not only this screen. |
| #1854 (fixed) | The BFF called check_string() with the wrong polarity (if (check_string(...)) -> bad_chars): the helper returns 1 = check ok, so no suite name was saveable at all while a name with a ` |
The CF silent-drop and the ungated delete in api/suiteview are deliberately
not separate issues: they are exactly what Refs #1852 fixes.
Defects the browser pass caught (fixed in the same run)
- The CF block died on a multiselection field —
cfInput()read avardeclared in the checkbox branch, and sincerenderCfs()aborts, the keyword picker vanished too. - Every save wiped the suite's keywords — the Assigned options were never
selected, and only:selectedoptions are posted (1 row -> 0 in the DB). - The form could be saved exactly once per page load —
BUSYwas never released on the success path, so later clicks were swallowed silently. - 33
E_WARNINGper page load:$m[4..6]of a DATE-only value's optional time group. - The legacy redirect dropped the suite/container identity when reached with
suite_id/container_idinstead oftestsuiteID/objectID. - Keyword writes were unvalidated:
addKeywords()inserts raw ids, so a crafted payload could attach another project's keywords. Now400 keyword_mismatch.
Class-API traps recorded for the next screen
testsuite::addKeywords()writesfk_table='nodes_hierarchy'(the node id is the suite id), not'testsuite'.testcase::get_exec_status()INNER JOINstestplan_tcversions— an "executed" case only exists throughtest plan -> testplan_tcversions -> tcversion -> executions; anexecutionsrow alone reads backno_links.- A stored DATE custom field is a UNIX timestamp, not a localized string.
nodes_hierarchyhas nodetailscolumn (2.0.1 schema drift).
Tests
tmp/TLU_Test_Cases.md → ## Issue #1852 — 51 PASS / 1 SKIPPED / 0 FAIL,
including a 15-case real-browser pass. Fixture: tmp/fixtures_1852.php
(re-runnable, prints the ids it created).