Clarify restart-safe batches and scripted UI handler tests

Address Nikola's batching and UI-handler feedback on PR #132. Preserve existing false-positive guards and defer the unconfirmed IsHandled policy.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: f841a18b-535a-498e-96f2-f279b5378da5
This commit is contained in:
wenjiefan 2026-09-14 09:29:35 +02:00
parent 35d0966a8d
commit 0561b12c96
7 changed files with 155 additions and 46 deletions

View file

@ -13,7 +13,6 @@ codeunit 50401 "Test UI Handler Proof Bad"
Page.RunModal(Page::"Customer Card", Customer);
// This only proves a value assigned before the action stayed true.
Assert.IsTrue(ActionSucceeded, 'The customer card action failed.');
end;
@ -51,6 +50,32 @@ codeunit 50401 "Test UI Handler Proof Bad"
Page.RunModal(Page::"Customer Card", Customer);
end;
[Test]
[HandlerFunctions('ConfirmHandler,PostMessageHandler')]
procedure ConfirmPostingAndShowMessage()
begin
Assert.IsTrue(RunPostingThatConfirmsAndMessages(), 'The posting confirmation was declined.');
end;
local procedure RunPostingThatConfirmsAndMessages(): Boolean
begin
if not Confirm('Post this document?', false) then
exit(false);
Message('Posting completed.');
exit(true);
end;
[ConfirmHandler]
procedure ConfirmHandler(Question: Text[1024]; var Reply: Boolean)
begin
Reply := true;
end;
[MessageHandler]
procedure PostMessageHandler(MessageText: Text[1024])
begin
end;
[ModalPageHandler]
procedure CustomerCardHandler(var CustomerCard: TestPage "Customer Card")
begin

View file

@ -30,6 +30,41 @@ codeunit 50400 "Test UI Handler Capture Good"
Assert.AreEqual(Customer."No.", CapturedCustomerNo, 'The customer card opened for the wrong customer.');
end;
[Test]
[HandlerFunctions('ConfirmHandler,PostMessageHandler')]
procedure ConfirmPostingAndShowMessage()
begin
LibraryVariableStorage.Clear();
LibraryVariableStorage.Enqueue('Post this document?');
LibraryVariableStorage.Enqueue(true);
LibraryVariableStorage.Enqueue('Posting completed.');
Assert.IsTrue(RunPostingThatConfirmsAndMessages(), 'The posting confirmation was declined.');
LibraryVariableStorage.AssertEmpty();
end;
local procedure RunPostingThatConfirmsAndMessages(): Boolean
begin
if not Confirm('Post this document?', false) then
exit(false);
Message('Posting completed.');
exit(true);
end;
[ConfirmHandler]
procedure ConfirmHandler(Question: Text[1024]; var Reply: Boolean)
begin
Assert.ExpectedConfirm(LibraryVariableStorage.DequeueText(), Question);
Reply := LibraryVariableStorage.DequeueBoolean();
end;
[MessageHandler]
procedure PostMessageHandler(MessageText: Text[1024])
begin
Assert.ExpectedMessage(LibraryVariableStorage.DequeueText(), MessageText);
end;
[ModalPageHandler]
procedure CustomerCardHandler(var CustomerCard: TestPage "Customer Card")
begin
@ -45,5 +80,6 @@ codeunit 50400 "Test UI Handler Capture Good"
var
Assert: Codeunit "Library Assert";
LibrarySales: Codeunit "Library - Sales";
LibraryVariableStorage: Codeunit "Library - Variable Storage";
CapturedCustomerNo: Code[20];
}

View file

@ -19,12 +19,16 @@ Beyond that wiring guarantee, the test must verify the behavior it cares about.
## Best Practice
List the handlers the scenario triggers, keep an optional notification handler listed for a notification the scenario may conditionally raise, and make each executed handler contribute meaningful evidence. For a single modal page, reset a capture variable before the action, capture a concrete value from the page in the handler, and assert the expected value after `RunModal`. For ordered or repeated interactions, let the test enqueue expectations, let handlers dequeue and verify them, clear storage during initialization, and finish with `AssertEmpty`.
List the handlers the scenario triggers and keep an optional notification handler listed for a notification the scenario may conditionally raise. Make the test and its handlers prove the scenario's contract. For a single modal page, reset a capture variable before the action, capture a concrete value from the page in the handler, and assert the expected value after `RunModal`. This proves the captured result, not an exact call count or sequence: repeated calls can overwrite earlier captures.
When interaction count, text, order, or replies are part of the contract, drive the handlers with the `Library - Variable Storage` codeunit. Clear `LibraryVariableStorage` during test initialization, then enqueue expected text and replies in interaction order. A shared `ConfirmHandler` dequeues the expected question, verifies it with `Assert.ExpectedConfirm`, and dequeues the Boolean reply to return; a message handler can use `Assert.ExpectedMessage` with the next expected text. These asserts match a stable text fragment. Finish with `LibraryVariableStorage.AssertEmpty` to catch unconsumed expectations; each handler must also dequeue and verify its interaction so unexpected calls cannot pass silently. Equivalent explicit assertions are valid; absence of this library alone is not a finding.
Prefer one reusable handler of each type within a test codeunit where practical, with individual tests supplying their expectations and replies. This is a maintainability recommendation, not a platform requirement; specialized handlers remain valid when their contracts differ. The paired confirmation-and-message samples run the same flow and check its Boolean result, but only the good sample verifies the expected interactions. The modal capture and optional notification scenarios illustrate contracts that do not require a scripted queue.
See sample: [`ui-handlers-in-tests.good.al`](ui-handlers-in-tests.good.al).
## Anti Pattern
Omitting a handler for a UI call, listing a nonoptional handler the path never reaches, or claiming action success from a Boolean set before the action runs. A handler that only closes a page can also leave the test without a semantic assertion. Do not flag the absence of queue storage by itself; require it only when the test needs to prove interaction order, count, text, replies, or a scripted sequence. Do not flag a listed `[SendNotificationHandler(true)]` or `[RecallNotificationHandler(true)]` that the run does not reach, and never propose removing one: the entry is what keeps the test passing on the runs where the notification does fire.
Omitting a handler for a UI call, listing a nonoptional handler the path never reaches, or claiming action success from a Boolean set before the action runs. A handler that only closes a page can also leave the test without a semantic assertion. Blindly confirming or dismissing messages does not verify a contract that requires specific interactions. Do not flag the absence of queue storage by itself; require interaction verification only when the test needs to prove order, count, text, replies, or a scripted sequence. Do not flag a listed `[SendNotificationHandler(true)]` or `[RecallNotificationHandler(true)]` that the run does not reach, and never propose removing one: the entry is what keeps the test passing on the runs where the notification does fire.
See sample: [`ui-handlers-in-tests.bad.al`](ui-handlers-in-tests.bad.al).