Repository navigation
Bugfix Issue 1596 reqLinkButtons testSpec
Screen: gui/templates/testcases/testSpec.html (modern Test Specification, Dashio)
Fixed by: d270ea7dd — 2 lines, no backend change, no new endpoint, no new i18n key
Verified: 2026-09-26 — browser (headless Chrome 154) on a fresh database, signed in as admin; issue closed after that independent re-verification
Two requirement-linking entry points on the Test Specification screen were never rendered, with no error, no console message and no log entry:
- the "Assign Requirements" button in the test case view (single test case → requirement picker), and
- the "Requirements Bulk Assignment" button in the test suite view (added with #1595).
They stayed invisible even for an Administrator in a test project with
option_reqs (requirements) enabled, i.e. exactly the user who should see them.
ctx is built in init() (gui/templates/testcases/testSpec.html:542-554) and enriched
from the context API response:
ctx = { tproject_id: … };
ctx.options = r.options || {}; // -> { requirementsEnabled, automationEnabled, testPriorityEnabled }
ctx.hasTestPlans = !!r.hasTestPlans;Both gates read a property that is not assigned in the current tree:
// testSpec.html:747 (showSuiteView) and testSpec.html:890 (renderTcView)
if (ctx.reqEnabled && !!grants['req_tcase_link_management']) { … }ctx.reqEnabled is undefined → undefined && … is always falsy → the button HTML is
never appended. The API never sends such a key either; the flag the screen actually
receives is options.requirementsEnabled (api/testcases/index.php:588,
tprojectOpt($opt, 'requirementsEnabled')).
The property was assigned when the feature was introduced. d327ece3a
(2026-09-15, “feat(testspec): assign requirements button+modal in Test Specification
editor and Test Case Viewer”) had both halves:
// gui/templates/testcases/testSpec.html:257 in d327ece3a
ctx.reqEnabled = !!(r.options && r.options.requirementsEnabled);
…
// gui/templates/testcases/testSpec.html:620 in d327ece3a
if (ctx.reqEnabled && !!grants['req_tcase_link_management']) { … }d327ece3a is an ancestor of 4673dd4f5 (parent 2 of the merge 8038f9670,
2026-09-15 “Merge branch 'origin/sebiboga' into sebiboga”). Measured on the three trees:
| tree | ctx.reqEnabled = |
if (ctx.reqEnabled |
|---|---|---|
parent 1 492b8280c
|
0 | 0 |
parent 2 4673dd4f5 (has d327ece3a) |
1 | 1 |
merge result 8038f9670 |
0 | 1 |
The merge resolved the conflict in favour of the side without the feature, so it kept
the gate and dropped the assignment: the button was therefore working between d327ece3a
and 8038f9670, and has been silently missing ever since. #1595 then added the second
gate (testSpec.html:890, suite view) copying the already-broken pattern, so the new
screen inherited the dead gate on its first day.
The failure was silent by construction: no undefined-property warning (reading a missing key of an existing object is legal JS), no failed request, so nothing landed in the Event Viewer.
--- a/gui/templates/testcases/testSpec.html
+++ b/gui/templates/testcases/testSpec.html
@@ -744,7 +744,7 @@ function showSuiteView() {
- if (ctx.reqEnabled && !!grants['req_tcase_link_management']) {
+ if (ctx.options && ctx.options.requirementsEnabled && !!grants['req_tcase_link_management']) {
h += '<button class="btn-dark btn-sm" onclick="openReqBulkAssign()">… ' + t('rtcb.title') + '</button>';
}
@@ -887,7 +887,7 @@ function renderTcView(r) {
- if (ctx.reqEnabled && !!grants['req_tcase_link_management']) {
+ if (ctx.options && ctx.options.requirementsEnabled && !!grants['req_tcase_link_management']) {
h += '<div style="margin-top:16px;display:flex;gap:10px;flex-wrap:wrap;">' +
'<button class="btn-dark btn-sm" onclick="openAssignReqs(' + tc.id + ')">… ' + t('tspec.assignRequirements') + '</button>' +Why this shape
- Minimal — two tokens per line, no refactor, no behavioural change beyond the gate.
-
Null-safe —
ctx.options &&is evaluated first, so the screen still renders ifcontextever answers withoutoptions(the same defensive shape used byctx.options.automationEnabledattestSpec.html:811,:1155,:1395). -
Consistent with the file's own conventions — the surrounding code already reads the
sibling flags as
ctx.options && ctx.options.<flag>; the tworeqEnabledreads were the only outliers. -
Right-hand side untouched — the
grants['req_tcase_link_management']right check (and the surrounding Dashiobtn-darkmarkup / i18n keysrtcb.title,tspec.assignRequirements) is preserved, so the button still disappears for users without the right and for projects with requirements disabled.
Rejected alternatives: hard-coding the button (would bypass both the project option and
the role right — the very parity #1595 restored); adding ctx.reqEnabled = … next to
init()'s other assignments (fixes only this screen's symptom while leaving the wrong
property name in place for the next reader); making the context API emit a reqEnabled
alias (an API change for a purely cosmetic reason, and the screen already had the right
flag under its own name).
All ids, event ids and counters below come from one ephemeral run on a freshly imported database; they are evidence of that run, not stable values.
tmp/fixtures_1596.php → test project REQ1596 (id 13, requirements ENABLED),
specification RS1596 (id 14) with 2 requirements, test suite Suite A (id 20),
test case REQ1596 TC 01 (id 21). Signed in as admin / admin
(Administrator → req_tcase_link_management granted).
The before state was obtained by reverting the two gated lines in the working copy only (never committed) and hard-reloading the screen; the working copy was then restored from git and confirmed clean.
| Probe in the page console | Before (bug) | After (fixed) |
|---|---|---|
ctx.reqEnabled |
undefined |
undefined (the property is simply unused now) |
ctx.options.requirementsEnabled |
true |
true |
grants['req_tcase_link_management'] |
true |
true |
#suiteView buttons |
6 buttons, no Requirements Bulk Assignment | 7 buttons, with Requirements Bulk Assignment (onclick="openReqBulkAssign()") |
#tcView buttons |
7 buttons, no Assign Requirements | 8 buttons, with Assign Requirements (onclick="openAssignReqs(21)") |
window.open target of the suite button |
– | /gui/templates/requirements/reqTcBulkAssign.html?tproject_id=13&tsuite_id=20 |
GET /api/requirements/index.php/assign-reqspecs |
– | 200 {"status":"ok","items":[{"id":14,"name":"[RS1596] - RS1596 Specification"}]} |
The suite-level restored entry point is functional, not merely visible (the per-test-case modal opens but is dead inside — see §6 / #1598):
-
Suite → Requirements Bulk Assignment — the grid lists both requirements with
not linked to any test case of this suite (1); check-uncheck-all + Assign to all test cases of the suite + confirm (Assign 2 selected requirement(s) to 1 test case(s) of Suite A (#20)?) writes the links; both rows then read1/1 test cases of this suite already linkedand the audit eventevents.id=9 · log_level=16 · object_type=testsuites · object_id=20is written. - Test case → Assign Requirements — the modal opens with the project's specification combo, FREE / ASSIGNED lists and the Assign / Unassign / Cancel / Close actions.
-
Event Viewer —
select log_level, count(*) from events group by log_level→16 → 9rows, i.e. 0 ERROR and 0 WARNING (levels 1 and 2); console clean on both screens.
tprojectOpt() returns false when a project's serialized options blob lacks the
requirementsEnabled key (api/testcases/index.php:52-60, with a missing blob becoming
new stdClass() at :480-481), whereas the write route's projReqsEnabled()
(api/requirements/index.php:1813-1824) falls back to default-true. A legacy project
with a missing/partial options blob therefore still sees no button, even though the BFF
would accept the write. No false positive is possible (the BFF re-checks both the right
at api/requirements/index.php:2181 and the project option at
api/reqtcbassign/index.php:141), and the fix strictly improves on "always hidden" —
recorded for the next reader, not fixed here.
tmp/TLU_Test_Cases.md, section “Regression — Issue #1596”, 6/6 PASS:
pre-fix reproduction of both missing buttons, post-fix rendering + handler arguments of
both buttons, end-to-end bulk assignment with DB/audit evidence, Event Viewer + console
hygiene.
Screenshots: docs/screenshots/issue-1596-prefix-tcview.png (before),
docs/screenshots/issue-1596-postfix-assign-requirements.png (after),
docs/screenshots/issue-1596-postfix-bulk-assign-button.png (suite toolbar).
The restored "Assign Requirements" modal always shows empty FREE / ASSIGNED lists.
Cause: openAssignReqs(tcaseId) (testSpec.html:357) never stores the id in the
module-level arqTcaseId (testSpec.html:433 var arqTcaseId = 0;), so arqLoadReqs()
returns at testSpec.html:383 before its GET /assign-reqs call and the Assign /
Unassign actions would post tcase_id=0. The backend is healthy —
GET /assign-reqs?req_spec_id=14&tcase_id=21 answers 200 with
{"status":"ok","all":[…],"assigned":[],"unassigned":[{"id":16,"doc_id":"REQ-001",…},{"id":18,"doc_id":"REQ-002",…}]}
(each element is an object from arReqRowToJSON(), api/requirements/index.php:2291-2298;
the route also returns all, which the quote above omits for brevity).
That is a different property, a different code path and a different symptom (button
present but dead vs button missing), so it was filed as its own bug issue #1598
instead of being folded into this one; the one-line remedy is recorded there. Consequence
to keep in mind: until #1598 is fixed, the single-test-case linking has no working path
in 2.0.1 — the suite-level grid of #1595 remains the working alternative.
Before the fix — test case view of REQ1596 TC 01 in a requirements-enabled project, as admin: the toolbar ends at Delete this version, there is no Assign Requirements (nor Requirements Bulk Assignment in the suite toolbar).

After the fix — same screen, same session: Assign Requirements is rendered at the bottom of the toolbar.

After the fix, suite view — Requirements Bulk Assignment is rendered in the suite toolbar next to Rename/Delete.

| File | Role in this bugfix |
|---|---|
gui/templates/testcases/testSpec.html |
the only changed file: lines 747 (suite view) and 890 (test case view) |
api/testcases/index.php |
untouched — read only, to confirm options.requirementsEnabled (:588) is the flag the screen receives |
tmp/fixtures_1596.php |
re-runnable fixture for the before/after verification (project, spec, 2 requirements, suite, test case) |
tmp/TLU_Test_Cases.md |
regression suite “Regression — Issue #1596”, 6/6 PASS |
docs/screenshots/issue-1596-*.png |
before / after evidence |
CHANGELOG |
the Key bugfix #1596 line under the 2.0.1 section |
Refs #1596 · follow-up #1598 · related #1595