dotnet / maui

.NET MAUI is the .NET Multi-platform App UI, a framework for building native device applications spanning mobile, tablet, and desktop.
https://dot.net/maui
MIT License
22.04k stars 1.73k forks source link

SystemResources depends on DependencyService #24216

Open dansiegel opened 1 month ago

dansiegel commented 1 month ago

Description

When trying to run unit tests and using the MauiAppBuilder after resolving the Application and while creating the Window a NullReferenceException is encountered.

  Message: 
System.NullReferenceException : Object reference not set to an instance of an object.

  Stack Trace: 
<.ctor>b__7_0()
Lazy`1.ViaFactory(LazyThreadSafetyMode mode)
Lazy`1.ExecutionAndPublication(LazyHelper executionAndPublication, Boolean useDefaultConstructor)
Lazy`1.CreateValue()
Application.get_SystemResources()
ResourcesExtensions.GetMergedResources(IElementDefinition element)
Element.SetParent(Element value)
Element.OnChildAdded(Element child)
Element.AddLogicalChild(Element element)
Application.AddWindow(Window window)
IApplication.CreateWindow(IActivationState activationState)

This error is a result of the following lines of code:

https://github.com/dotnet/maui/blob/210bc3581a8033cbbcbb14da931833c68c85583e/src/Controls/src/Core/Application/Application.cs#L42-L49

This isn't initially obvious as to either the problem or the solution as it requires implementing "Obsolete" code in order for a very basic scenario to work in a Unit Test.

Steps to Reproduce

var mauiBuilder = MauiApp.CreateBuilder()
    .UseMauiApp<Application>();
mauiBuilder.Services.AddTransient<IWindowCreator, WindowCreator()
    .AddTransient<TestPage>();

var mauiApp = mauiBuilder.Build();
var app = mauiApp.Services.GetRequiredService<IApplication>();
var activationState = new ActivationState(new MauiContext(mauiApp.Services));
var window = app.CreateWindow(activationState);

private record WindowCreator(TestPage Page) : IWindowCreator
{
    public Window CreateWindow(Application app, IActivationState? activationState) => new Window(Page);
}

public class TestPage : ContentPage
{
    public TestPage()
    {
        Title = nameof(TestPage);
        Content = new Label { Text = "Unit Tests" };
    }
}

Link to public reproduction project repository

No response

Version with bug

8.0.80 SR8

Is this a regression from previous behavior?

Yes, this used to work in Xamarin.Forms

Last version that worked well

Unknown/Other

Affected platforms

Other (Tizen, Linux, etc. not supported by Microsoft directly)

Affected platform versions

No response

Did you find any workaround?

While this technically does work... it shouldn't be something that is needed just to run Unit Tests.

#pragma warning disable CS0612 // Type or member is obsolete
using Microsoft.Maui.Controls;
using Microsoft.Maui.Controls.Internals;
using Prism.Ioc.Tests.Mocks;

[assembly: Dependency(typeof(MockResourcesProvider))]
[assembly: Dependency(typeof(MockFontNamedSizeService))]

namespace Prism.Ioc.Tests.Mocks;

[Obsolete]
internal class MockResourcesProvider : ISystemResourcesProvider
{
    public IResourceDictionary GetSystemResources()
    {
        var dictionary = new ResourceDictionary();
        Style style;
        style = new Style(typeof(Label));
        dictionary[Device.Styles.BodyStyleKey] = style;

        style = new Style(typeof(Label));
        style.Setters.Add(Label.FontSizeProperty, 50);
        dictionary[Device.Styles.TitleStyleKey] = style;

        style = new Style(typeof(Label));
        style.Setters.Add(Label.FontSizeProperty, 40);
        dictionary[Device.Styles.SubtitleStyleKey] = style;

        style = new Style(typeof(Label));
        style.Setters.Add(Label.FontSizeProperty, 30);
        dictionary[Device.Styles.CaptionStyleKey] = style;

        style = new Style(typeof(Label));
        style.Setters.Add(Label.FontSizeProperty, 20);
        dictionary[Device.Styles.ListItemTextStyleKey] = style;

        style = new Style(typeof(Label));
        style.Setters.Add(Label.FontSizeProperty, 10);
        dictionary[Device.Styles.ListItemDetailTextStyleKey] = style;

        return dictionary;
    }
}

[Obsolete]
public class MockFontNamedSizeService : IFontNamedSizeService
{
    public double GetNamedSize(NamedSize size, Type targetElement, bool useOldSizes)
    {
        return size switch
        {
            NamedSize.Default => 12,// new MockFontManager().DefaultFontSize,
            NamedSize.Micro => (double)4,
            NamedSize.Small => (double)8,
            NamedSize.Medium => (double)12,
            NamedSize.Large => (double)16,
            _ => throw new ArgumentOutOfRangeException(nameof(size)),
        };
    }
}
#pragma warning restore CS0612 // Type or member is obsolete

Relevant log output

No response

github-actions[bot] commented 1 month ago

Hi I'm an AI powered bot that finds similar issues based off the issue title.

Please view the issues below to see if they solve your problem, and if the issue describes your problem please consider closing this one and thumbs upping the other issue to help us prioritize it. Thank you!

Closed similar issues:

Note: You can give me feedback by thumbs upping or thumbs downing this comment.