Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions InertiaCore/Inertia.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using InertiaCore.Props;
using InertiaCore.Utils;
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Mvc;

[assembly: InternalsVisibleTo("InertiaCoreTests")]

Expand Down Expand Up @@ -31,6 +32,8 @@ public static class Inertia

public static void Share(IDictionary<string, object?> data) => _factory.Share(data);

public static void ResolveUrlUsing(Func<ActionContext, string> urlResolver) => _factory.ResolveUrlUsing(urlResolver);

public static AlwaysProp Always(string value) => _factory.Always(value);

public static AlwaysProp Always(Func<string> callback) => _factory.Always(callback);
Expand Down
7 changes: 4 additions & 3 deletions InertiaCore/Response.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,14 @@ public class Response : IActionResult
private readonly string? _version;
private readonly bool _encryptHistory;
private readonly bool _clearHistory;
private readonly Func<ActionContext, string>? _urlResolver;

private ActionContext? _context;
private Page? _page;
private IDictionary<string, object>? _viewData;

internal Response(string component, Dictionary<string, object?> props, string rootView, string? version, bool encryptHistory, bool clearHistory)
=> (_component, _props, _rootView, _version, _encryptHistory, _clearHistory) = (component, props, rootView, version, encryptHistory, clearHistory);
internal Response(string component, Dictionary<string, object?> props, string rootView, string? version, bool encryptHistory, bool clearHistory, Func<ActionContext, string>? urlResolver = null)
=> (_component, _props, _rootView, _version, _encryptHistory, _clearHistory, _urlResolver) = (component, props, rootView, version, encryptHistory, clearHistory, urlResolver);

public async Task ExecuteResultAsync(ActionContext context)
{
Expand All @@ -41,7 +42,7 @@ protected internal async Task ProcessResponse()
{
Component = _component,
Version = _version,
Url = _context!.RequestedUri(),
Url = _urlResolver?.Invoke(_context!) ?? _context!.RequestedUri(),
Props = props,
EncryptHistory = _encryptHistory,
ClearHistory = _clearHistory,
Expand Down
7 changes: 6 additions & 1 deletion InertiaCore/ResponseFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using InertiaCore.Utils;
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;

namespace InertiaCore;
Expand All @@ -24,6 +25,7 @@ internal interface IResponseFactory
public void Share(IDictionary<string, object?> data);
public void ClearHistory(bool clear = true);
public void EncryptHistory(bool encrypt = true);
public void ResolveUrlUsing(Func<ActionContext, string> urlResolver);
public AlwaysProp Always(object? value);
public AlwaysProp Always(Func<object?> callback);
public AlwaysProp Always(Func<Task<object?>> callback);
Expand All @@ -40,6 +42,7 @@ internal class ResponseFactory : IResponseFactory
private object? _version;
private bool _clearHistory;
private bool? _encryptHistory;
private Func<ActionContext, string>? _urlResolver;

public ResponseFactory(IHttpContextAccessor contextAccessor, IGateway gateway, IOptions<InertiaOptions> options) =>
(_contextAccessor, _gateway, _options) = (contextAccessor, gateway, options);
Expand All @@ -54,7 +57,7 @@ public Response Render(string component, object? props = null)
.ToDictionary(o => o.Name, o => o.GetValue(props))
};

return new Response(component, dictProps, _options.Value.RootView, GetVersion(), _encryptHistory ?? _options.Value.EncryptHistory, _clearHistory);
return new Response(component, dictProps, _options.Value.RootView, GetVersion(), _encryptHistory ?? _options.Value.EncryptHistory, _clearHistory, _urlResolver);
}

public async Task<IHtmlContent> Head(dynamic model)
Expand Down Expand Up @@ -139,6 +142,8 @@ public void Share(IDictionary<string, object?> data)

public void EncryptHistory(bool encrypt = true) => _encryptHistory = encrypt;

public void ResolveUrlUsing(Func<ActionContext, string> urlResolver) => _urlResolver = urlResolver;

public LazyProp Lazy(Func<object?> callback) => new(callback);
public LazyProp Lazy(Func<Task<object?>> callback) => new(callback);
public AlwaysProp Always(object? value) => new(value);
Expand Down
147 changes: 147 additions & 0 deletions InertiaCoreTests/UnitTestUrlResolver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
using InertiaCore.Models;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Moq;

namespace InertiaCoreTests;

public partial class Tests
{
[Test]
[Description("Test if custom URL resolver is used when provided.")]
public async Task TestCustomUrlResolver()
{
// Set up a custom URL resolver
_factory.ResolveUrlUsing(context => "/custom/url");

var response = _factory.Render("Test/Page", new
{
Test = "Test"
});

var context = PrepareContext();

response.SetContext(context);
await response.ProcessResponse();

var page = response.GetJson().Value as Page;

Assert.That(page?.Url, Is.EqualTo("/custom/url"));
Assert.That(page?.Props, Is.EqualTo(new Dictionary<string, object?>
{
{ "test", "Test" },
{ "errors", new Dictionary<string, string>(0) }
}));
}

[Test]
[Description("Test if default URL resolver is used when no custom resolver is provided.")]
public async Task TestDefaultUrlResolver()
{
var response = _factory.Render("Test/Page", new
{
Test = "Test"
});

var context = PrepareContext();

response.SetContext(context);
await response.ProcessResponse();

var page = response.GetJson().Value as Page;

// Should use the default RequestedUri() method
Assert.That(page?.Url, Is.Not.Null);
Assert.That(page?.Props, Is.EqualTo(new Dictionary<string, object?>
{
{ "test", "Test" },
{ "errors", new Dictionary<string, string>(0) }
}));
}

[Test]
[Description("Test if custom URL resolver receives correct ActionContext.")]
public async Task TestUrlResolverReceivesContext()
{
ActionContext? receivedContext = null;

// Set up a custom URL resolver that captures the context
_factory.ResolveUrlUsing(context =>
{
receivedContext = context;
return "/captured/context/url";
});

var response = _factory.Render("Test/Page", new
{
Test = "Test"
});

var context = PrepareContext();

response.SetContext(context);
await response.ProcessResponse();

var page = response.GetJson().Value as Page;

Assert.That(page?.Url, Is.EqualTo("/captured/context/url"));
Assert.That(receivedContext, Is.Not.Null);
Assert.That(receivedContext, Is.EqualTo(context));
}

[Test]
[Description("Test if custom URL resolver can access request information.")]
public async Task TestUrlResolverAccessesRequest()
{
// Set up a custom URL resolver that uses request path
_factory.ResolveUrlUsing(context =>
{
var path = context.HttpContext.Request.Path;
return $"/custom{path}";
});

var response = _factory.Render("Test/Page", new
{
Test = "Test"
});

var context = PrepareContext();

response.SetContext(context);
await response.ProcessResponse();

var page = response.GetJson().Value as Page;

Assert.That(page?.Url, Is.Not.Null);
Assert.That(page?.Url, Does.StartWith("/custom"));
}

[Test]
[Description("Test if URL resolver can be changed between requests.")]
public async Task TestUrlResolverCanBeChanged()
{
// First resolver
_factory.ResolveUrlUsing(context => "/first/url");

var response1 = _factory.Render("Test/Page", new { Test = "Test1" });
var context1 = PrepareContext();

response1.SetContext(context1);
await response1.ProcessResponse();

var page1 = response1.GetJson().Value as Page;
Assert.That(page1?.Url, Is.EqualTo("/first/url"));

// Change resolver
_factory.ResolveUrlUsing(context => "/second/url");

var response2 = _factory.Render("Test/Page", new { Test = "Test2" });
var context2 = PrepareContext();

response2.SetContext(context2);
await response2.ProcessResponse();

var page2 = response2.GetJson().Value as Page;
Assert.That(page2?.Url, Is.EqualTo("/second/url"));
}
}