microsoft / AL

Home of the Dynamics 365 Business Central AL Language extension for Visual Studio Code. Used to track issues regarding the latest version of the AL compiler and developer tools available in the Visual Studio Code Marketplace or as part of the AL Developer Preview builds for Dynamics 365 Business Central.
MIT License
747 stars 245 forks source link

Incorrect Behavior of Table Relation Filters from FlowFields in AL TestPages #7460

Closed Drakonian closed 1 year ago

Drakonian commented 1 year ago

I have encountered a bug that's related to the use of TestPage type variables in AL Language within the Business Central Test Framework. Specifically, this issue occurs when using a TestPage type variable to simulate UI behavior.

In my project, I have created a flow field and used it in a table relation filter. I also added CalcFields of that flow field on AfterGetCurrRecord of a page. This implementation works as expected in the Business Central UI, for all cases - when a record is already inserted or not.

However, when trying to simulate this behaviour using a TestPage type variable in a test codeunit, the flow field is always returning an empty value. This inconsistency breaks all possible tests of that filtering within the Business Central test framework.

This behavior is not expected as the TestPage variable in test codeunits should simulate the UI behavior in the same way the page works in Business Central.

Attached is a project with example code demonstrating this issue.

Steps to Reproduce:

  1. Create a flow field and use it in a table relation filter.
  2. Add CalcFields of that flow field on AfterGetCurrRecord of a page.
  3. Verify that the flow field works as expected in the Business Central UI, for all cases - when a record is already inserted or not.
  4. Try to simulate this behaviour using a TestPage type variable in a test codeunit.
  5. Note that the flow field returns an empty value when accessed from a TestPage type variable in the test codeunit.

Expected Result:

The flow field should return the correct value, even when accessed from a TestPage type variable in a test codeunit, replicating the same behavior as in the Business Central UI.

Actual Result:

The flow field always returns an empty value when accessed from a TestPage type variable in the test codeunit.

Please consider investigating this matter. The consistent behavior between TestPage variables and actual UI pages is crucial for accurate testing within the Business Central Test Framework.

AL Code which works as expected:

tableextension 50100 "TST Purchase Line" extends "Purchase Line"
{
    fields
    {
        field(50100; "TST Buy From Vendor No."; Code[20])
        {
            Caption = 'Buy From Vendor No.';
            Editable = false;
            FieldClass = FlowField;
            CalcFormula = lookup("Purchase Header"."Buy-from Vendor No." where("Document Type" = field("Document Type"), "No." = field("Document No.")));
        }

        field(50101; "TST Item No."; Code[20])
        {
            DataClassification = CustomerContent;
            Caption = 'Item No.';
            TableRelation = Item."No." where(Blocked = const(false), "Vendor No." = field("TST Buy From Vendor No."));
        }
    }
}

pageextension 50100 "TST Purchase Order Subform" extends "Purchase Order Subform"
{
    layout
    {
        addafter(Description)
        {
            field("TST Item No."; Rec."TST Item No.")
            {
                ApplicationArea = All;
                ToolTip = 'Specifies the value of the Item No. field.';
            }
        }
    }

    trigger OnAfterGetCurrRecord()
    begin
        Rec.CalcFields("TST Buy From Vendor No.");
    end;
}

AL Test which is always fail, I can't find any way to write this test using test pages.

codeunit 50100 "TST Lookup Filter Test"
{
    Subtype = Test;

    [Test]
    [HandlerFunctions('ItemLookupHandler')]
    procedure POLineLookupFilter()
    var
        PurchaseHeader: Record "Purchase Header";
        Item: Record Item;
        PurchaseOrderCard: TestPage "Purchase Order";
    begin

        //[GIVEN] Create purchase order
        LibraryPurchase.CreatePurchHeader(PurchaseHeader, PurchaseHeader."Document Type"::Order, '');

        //[GIVEN] Create Item
        LibraryInventory.CreateItem(Item);

        //[GIVEN] Set Vendor for current item from Purchase Header Vendor
        Item.Validate("Vendor No.", PurchaseHeader."Buy-from Vendor No.");
        Item.Modify(true);

        //[GIVEN] Save Item No. for item lookup handler
        ItemNo := Item."No.";

        //[GIVEN] Purchase Order with subform is opened
        PurchaseOrderCard.OpenEdit();
        PurchaseOrderCard.GoToRecord(PurchaseHeader);

        //[GIVEN] PO Line is created
        PurchaseOrderCard.PurchLines.New();
        PurchaseOrderCard.PurchLines.Type.Value('Item');

        //[WHEN] Item Lookup page is opened
        PurchaseOrderCard.PurchLines."TST Item No.".Lookup();

        //[THEN] Vendor No filter must have value as in UI....
    end;

    [ModalPageHandler]
    procedure ItemLookupHandler(var ItemLookup: TestPage "Item Lookup")
    begin
        VendorNoFilter := ItemLookup.Filter.GetFilter("Vendor No."); // Wrong, filter return '' = filter to empty vendor, flowField is not evaluated
        BlockedFilter := ItemLookup.Filter.GetFilter(Blocked); // Correct, filter from real field
        ItemLookup.GoToKey(ItemNo); //Wrong, system can't find created item because of empty filter which is must contain value
    end;

    var
        LibraryPurchase: Codeunit "Library - Purchase";
        LibraryInventory: Codeunit "Library - Inventory";
        ItemNo: Code[20];
        VendorNoFilter: Text;
        BlockedFilter: Text;
}

Correct:

image

Wrong:

image

Drakonian commented 1 year ago

TestPageBug.zip

NKarolak commented 1 year ago

Unfortunately, this is the wrong place to submit your issue. If you are a partner with access to Yammer, then here's the audience to be addressed: https://www.yammer.com/dynamicsnavdev/#/threads/inGroup?type=in_group&feedId=13879726&view=all

If you have no access, let me know, and I will link your issue there.

Drakonian commented 1 year ago

@NKarolak I don't have access at the moment, but isn't that AL Language's problem?

NKarolak commented 1 year ago

I believe it is more for the platform team. I might be wrong about this. However, the expert on Microsoft's side to decide (or at least: to communicate) whether this will be ever implemented, is active on Yammer only. Then let me inform him on Yammer about this. This will at least speed up matters.

NKarolak commented 1 year ago

For your reference, I've mentioned your issue here: https://www.yammer.com/dynamicsnavdev/threads/2359402983677952

pri-kise commented 1 year ago

@Drakonian you should be able to request access to the Yammer via the following shortlink: https://aka.ms/bcyammer There should be some option to request access. Normally this should be approved within a week, but keep in mind that summer breaks have started and a significant number of people are on vacation and might take a little longer right now.

Drakonian commented 1 year ago

@pri-kise Thanks for you suggestion. I did it and hope will get access :)

NKarolak commented 1 year ago

From Yammer:

Screenshot_20230724_190805_Viva Engage

BazookaMusic commented 1 year ago

As mentioned before, this issue is not related to the AL language itself but concerns the behavior of the runtime. I have created a bug on the team responsible for it and they will investigate it. If it is by design, there will be a response with the explanation to this issue, else it will be fixed in one of the next releases.

omnisip commented 1 year ago

Can you provide a link?

On Wed, Aug 2, 2023, 08:13 Sotiris Dragonas @.***> wrote:

As mentioned before, this issue is not related to the AL language itself but concerns the behavior of the runtime. I have created a bug on the relevant team and they will investigate it. If it is by design, there will be a response with the explanation to this issue, else it will be fixed in one of the next releases.

— Reply to this email directly, view it on GitHub https://github.com/microsoft/AL/issues/7460#issuecomment-1662289351, or unsubscribe https://github.com/notifications/unsubscribe-auth/AACKQJUSYBJW4GN7RQE6SL3XTJOAFANCNFSM6AAAAAA2KWC3TM . You are receiving this because you are subscribed to this thread.Message ID: @.***>

BazookaMusic commented 1 year ago

@omnisip It is in our internal systems, thus I cannot provide a link for it.

omnisip commented 1 year ago

Okay. Then what's the way for us to receive updates on the status of this bug? Closing this here and providing no tracking means that it's lost in the black hole.

On Wed, Aug 2, 2023, 08:55 Sotiris Dragonas @.***> wrote:

@omnisip https://github.com/omnisip It is in our internal systems, thus I cannot provide a link for it.

— Reply to this email directly, view it on GitHub https://github.com/microsoft/AL/issues/7460#issuecomment-1662363696, or unsubscribe https://github.com/notifications/unsubscribe-auth/AACKQJTP4WFJZAIHC3AFQQLXTJS5ZANCNFSM6AAAAAA2KWC3TM . You are receiving this because you were mentioned.Message ID: @.***>

BazookaMusic commented 1 year ago

Again, this is an issue of the runtime and not an issue in the AL language, thus this is not the correct repository for this issue. The responsible team does not use this repository or follow the issues here.

It is possible to make a support request to have the issue tracked through a support case, if the issue is blocking. See below for how:

https://cloudblogs.microsoft.com/dynamics365/it/2019/09/25/new-process-to-submit-support-requests-for-dynamics-365-business-central/

To be clear, the bug was not closed here to be ignored. I forwarded it to the correct team to speed up the process. Issues on the runtime are currently handled through either support cases or internal tickets.

navdotnetreqs commented 7 months ago

So you are never going to fix this?

jehelles commented 1 month ago

I am looking at this bug, and it does not look like a general issue with CalcField/Filters/TestPages. The following stripped down scenario works just fine - I will try and see if I can find the difference between this working case below and the 'real' non-working case.

table 50101 MyPurchaseHeader
{
    fields
    {
        field(1; "Document Type"; Enum "Purchase Document Type")
        {
        }
        field(2; "Buy-from Vendor No."; Code[20])
        {
            TableRelation = Vendor;
        }
        field(3; "No."; Code[20])
        {
        }
    }

    keys
    {
        key(Key1; "Document Type", "No.")
        {
            Clustered = true;
        }
    }
}

table 50100 MyPurchaseLine
{
    fields
    {
        field(1; "Document Type"; Enum "Purchase Document Type")
        {
        }
        field(3; "Document No."; Code[20])
        {
            TableRelation = "MyPurchaseHeader"."No." where("Document Type" = field("Document Type"));
        }
        field(4; "Line No."; Integer)
        {
        }
        field(5; Type; Enum "Purchase Line Type")
        {
        }
        field(6; "No."; Code[20])
        {
            TableRelation = if (Type = const(" ")) "Standard Text"
            else if (Type = const(Item), "Document Type" = filter(<> "Credit Memo" & <> "Return Order")) Item where(Blocked = const(false), "Purchasing Blocked" = const(false))
            else if (Type = const(Item), "Document Type" = filter("Credit Memo" | "Return Order")) Item where(Blocked = const(false));
            ValidateTableRelation = false;
        }
        field(50100; "TST Buy From Vendor No."; Code[20])
        {
            Editable = false;
            FieldClass = FlowField;
            CalcFormula = lookup("MyPurchaseHeader"."Buy-from Vendor No." where("Document Type" = field("Document Type"), "No." = field("Document No.")));
        }
        field(50101; "TST Item No."; Code[20])
        {
            TableRelation = Item."No." where(Blocked = const(false), "Vendor No." = field("TST Buy From Vendor No."));
        }
    }

    keys
    {
        key(Key1; "Document Type", "Document No.", "Line No.")
        {
            Clustered = true;
        }
    }
}

page 50101 MyPurchaseOrder
{
    PageType = Document;
    RefreshOnActivate = true;
    SourceTable = MyPurchaseHeader;
    SourceTableView = where("Document Type" = filter(Order));

    layout
    {
        area(content)
        {
            group(General)
            {
                field("No."; Rec."No.")
                {
                    ApplicationArea = All;
                }
                field("Buy-from Vendor No."; Rec."Buy-from Vendor No.")
                {
                    ApplicationArea = All;
                    NotBlank = true;
                }
            }
            part(PurchLines; MyPurchaseLineSubform)
            {
                ApplicationArea = Suite;
                SubPageLink = "Document No." = field("No.");
                UpdatePropagation = Both;
            }
        }
    }
}

page 50100 MyPurchaseLineSubform
{
    AutoSplitKey = true;
    DelayedInsert = true;
    LinksAllowed = false;
    MultipleNewLines = true;
    PageType = ListPart;
    SourceTable = "MyPurchaseLine";
    SourceTableView = where("Document Type" = filter(Order));

    layout
    {
        area(Content)
        {
            repeater(GroupName)
            {
                field(Type; Rec.Type)
                {
                }
                field("No."; Rec."No.")
                {
                }
                field("TST Item No."; Rec."TST Item No.")
                {
                }
            }
        }
    }

    trigger OnAfterGetCurrRecord()
    begin
        Rec.CalcFields("TST Buy From Vendor No.");
    end;
}

codeunit 50100 MyRepro
{
    Subtype = Test;

    [Test]
    [HandlerFunctions('ItemLookupHandler')]
    procedure POLineLookupFilter()
    var
        MyPurchaseHeader: Record "MyPurchaseHeader";
        Item: Record Item;
        Vendor: Record Vendor;
        MyPurchaseOrder: TestPage "MyPurchaseOrder";
    begin
        Vendor.FindFirst();

        Item.FindFirst();
        Item.Validate("Vendor No.", Vendor."No.");
        Item.Modify(true);

        ItemNo := Item."No.";

        MyPurchaseHeader."Document Type" := MyPurchaseHeader."Document Type"::Order;
        MyPurchaseHeader."No." := 'JHH100';
        MyPurchaseHeader."Buy-from Vendor No." := vendor."No.";

        if (MyPurchaseHeader.Find('=')) then
            MyPurchaseHeader.Delete();

        MyPurchaseHeader.Insert();

        MyPurchaseOrder.OpenEdit();
        MyPurchaseOrder.GoToRecord(MyPurchaseHeader);

        MyPurchaseOrder.PurchLines.New();
        MyPurchaseOrder.PurchLines.Type.Value('Item');

        MyPurchaseOrder.PurchLines."TST Item No.".Lookup();
    end;

    [ModalPageHandler]
    procedure ItemLookupHandler(var ItemLookup: TestPage "Item Lookup")
    var
        VendorNoFilter: Text;
        BlockedFilter: Text;
    begin
        VendorNoFilter := ItemLookup.Filter.GetFilter("Vendor No.");
        Message('VendorNoFilter: %1', VendorNoFilter);
        BlockedFilter := ItemLookup.Filter.GetFilter(Blocked);
        Message('BlockedFilter: %1', BlockedFilter);
        ItemLookup.GoToKey(ItemNo);
    end;

    var
        ItemNo: Code[20];
}
jehelles commented 1 month ago

This is unfortunately not a platform bug, but just the platform doing as requested by the AL code.

In the Validate function for Type in Purchase Line, the Init function is called which clears the calculated fields. In the test code, there is nothing re-calculating this field if you go and invoke the lookup immediately afterwards, leading to blank filters. The same thing can also be reproduced in the web client if the first thing you do after changing the type is to invoke the lookup.

I suggest you subscribed to one of the events in the Type validate function and re-calculate the field there, as that will make the experience better both in the web client and in tests.

Drakonian commented 1 month ago

Thanks, it's really not a bug, but rather expected behavior given the original conditions. I was able to reproduce this behavior the same way in the UI.

Drakonian commented 1 month ago

As you mention one of possible solution in this specific scenario will be:

[EventSubscriber(ObjectType::Table, Database::"Purchase Line", 'OnAfterValidateType', '', false, false)]
local procedure PurchaseLine_OnAfterValidateType(var PurchaseLine: Record "Purchase Line")
begin
    PurchaseLine.CalcFields("TST Buy From Vendor No.");
end;

Works perfectly.