From 1dc8b6b637888582177a41296290558f4f5ff7ed Mon Sep 17 00:00:00 2001 From: Austin Drummond Date: Sat, 14 Dec 2024 15:24:15 -0500 Subject: [PATCH 01/31] added header constants --- InertiaCore/Extensions/Configure.cs | 4 ++-- InertiaCore/Extensions/InertiaExtensions.cs | 7 ++++--- InertiaCore/Response.cs | 2 +- InertiaCore/Utils/Header.cs | 18 ++++++++++++++++++ InertiaCore/Utils/LocationResult.cs | 2 +- 5 files changed, 26 insertions(+), 7 deletions(-) create mode 100644 InertiaCore/Utils/Header.cs diff --git a/InertiaCore/Extensions/Configure.cs b/InertiaCore/Extensions/Configure.cs index 6be799c..d0d12b5 100644 --- a/InertiaCore/Extensions/Configure.cs +++ b/InertiaCore/Extensions/Configure.cs @@ -25,7 +25,7 @@ public static IApplicationBuilder UseInertia(this IApplicationBuilder app) { if (context.IsInertiaRequest() && context.Request.Method == "GET" - && context.Request.Headers["X-Inertia-Version"] != Inertia.GetVersion()) + && context.Request.Headers[Header.Version] != Inertia.GetVersion()) { await OnVersionChange(context, app); return; @@ -69,7 +69,7 @@ private static async Task OnVersionChange(HttpContext context, IApplicationBuild if (tempData.Any()) tempData.Keep(); - context.Response.Headers.Add("X-Inertia-Location", context.RequestedUri()); + context.Response.Headers.Add(Header.Location, context.RequestedUri()); context.Response.StatusCode = (int)HttpStatusCode.Conflict; await context.Response.CompleteAsync(); diff --git a/InertiaCore/Extensions/InertiaExtensions.cs b/InertiaCore/Extensions/InertiaExtensions.cs index dbcfae3..10844ea 100644 --- a/InertiaCore/Extensions/InertiaExtensions.cs +++ b/InertiaCore/Extensions/InertiaExtensions.cs @@ -1,4 +1,5 @@ using System.Text.Json; +using InertiaCore.Utils; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Mvc; @@ -12,13 +13,13 @@ internal static IEnumerable Only(this object obj, IEnumerable on .Intersect(only, StringComparer.OrdinalIgnoreCase).ToList(); internal static List GetPartialData(this ActionContext context) => - context.HttpContext.Request.Headers["X-Inertia-Partial-Data"] + context.HttpContext.Request.Headers[Header.PartialOnly] .FirstOrDefault()?.Split(",") .Where(s => !string.IsNullOrEmpty(s)) .ToList() ?? new List(); internal static bool IsInertiaPartialComponent(this ActionContext context, string component) => - context.HttpContext.Request.Headers["X-Inertia-Partial-Component"] == component; + context.HttpContext.Request.Headers[Header.PartialComponent] == component; internal static string RequestedUri(this HttpContext context) => Uri.UnescapeDataString(context.Request.GetEncodedPathAndQuery()); @@ -26,7 +27,7 @@ internal static string RequestedUri(this HttpContext context) => internal static string RequestedUri(this ActionContext context) => context.HttpContext.RequestedUri(); internal static bool IsInertiaRequest(this HttpContext context) => - bool.TryParse(context.Request.Headers["X-Inertia"], out _); + bool.TryParse(context.Request.Headers[Header.Inertia], out _); internal static bool IsInertiaRequest(this ActionContext context) => context.HttpContext.IsInertiaRequest(); diff --git a/InertiaCore/Response.cs b/InertiaCore/Response.cs index 865ff45..f9bd802 100644 --- a/InertiaCore/Response.cs +++ b/InertiaCore/Response.cs @@ -81,7 +81,7 @@ protected internal void ProcessResponse() protected internal JsonResult GetJson() { - _context!.HttpContext.Response.Headers.Add("X-Inertia", "true"); + _context!.HttpContext.Response.Headers.Add(Header.Inertia, "true"); _context!.HttpContext.Response.Headers.Add("Vary", "Accept"); _context!.HttpContext.Response.StatusCode = 200; diff --git a/InertiaCore/Utils/Header.cs b/InertiaCore/Utils/Header.cs new file mode 100644 index 0000000..b729dbf --- /dev/null +++ b/InertiaCore/Utils/Header.cs @@ -0,0 +1,18 @@ +namespace InertiaCore.Utils; + +public static class Header +{ + public const string Inertia = "X-Inertia"; + + public const string ErrorBag = "X-Inertia-Error-Bag"; + + public const string Location = "X-Inertia-Location"; + + public const string Version = "X-Inertia-Version"; + + public const string PartialComponent = "X-Inertia-Partial-Component"; + + public const string PartialOnly = "X-Inertia-Partial-Data"; + + public const string PartialExcept = "X-Inertia-Partial-Except"; +} diff --git a/InertiaCore/Utils/LocationResult.cs b/InertiaCore/Utils/LocationResult.cs index 6e358ed..3ddcedd 100644 --- a/InertiaCore/Utils/LocationResult.cs +++ b/InertiaCore/Utils/LocationResult.cs @@ -14,7 +14,7 @@ public async Task ExecuteResultAsync(ActionContext context) { if (context.IsInertiaRequest()) { - context.HttpContext.Response.Headers.Add("X-Inertia-Location", _url); + context.HttpContext.Response.Headers.Add(Header.Location, _url); await new StatusCodeResult((int)HttpStatusCode.Conflict).ExecuteResultAsync(context); return; } From 3a334498c0dc4ffc96860bc2df7ce3aa0c435cb2 Mon Sep 17 00:00:00 2001 From: Austin Drummond Date: Sat, 14 Dec 2024 17:07:47 -0500 Subject: [PATCH 02/31] refactor resolve props --- InertiaCore/Extensions/InertiaExtensions.cs | 18 +++++-- InertiaCore/Response.cs | 59 +++++++++++++-------- 2 files changed, 53 insertions(+), 24 deletions(-) diff --git a/InertiaCore/Extensions/InertiaExtensions.cs b/InertiaCore/Extensions/InertiaExtensions.cs index 10844ea..ba6cc0c 100644 --- a/InertiaCore/Extensions/InertiaExtensions.cs +++ b/InertiaCore/Extensions/InertiaExtensions.cs @@ -8,9 +8,21 @@ namespace InertiaCore.Extensions; internal static class InertiaExtensions { - internal static IEnumerable Only(this object obj, IEnumerable only) => - obj.GetType().GetProperties().Select(c => c.Name) - .Intersect(only, StringComparer.OrdinalIgnoreCase).ToList(); + internal static Dictionary OnlyProps(this ActionContext context, Dictionary props) + { + var onlyKeys = context.HttpContext.Request.Headers[Header.PartialOnly].ToString().Split(',').Select(k => k.Trim()).ToList(); + + return props.Where(kv => onlyKeys.Contains(kv.Key, StringComparer.OrdinalIgnoreCase)) + .ToDictionary(kv => kv.Key, kv => kv.Value); + } + + internal static Dictionary ExceptProps(this ActionContext context, Dictionary props) + { + var exceptKeys = context.HttpContext.Request.Headers[Header.PartialExcept].ToString().Split(',').Select(k => k.Trim()).ToList(); + + return props.Where(kv => exceptKeys.Contains(kv.Key, StringComparer.OrdinalIgnoreCase) == false) + .ToDictionary(kv => kv.Key, kv => kv.Value); + } internal static List GetPartialData(this ActionContext context) => context.HttpContext.Request.Headers[Header.PartialOnly] diff --git a/InertiaCore/Response.cs b/InertiaCore/Response.cs index f9bd802..13348c1 100644 --- a/InertiaCore/Response.cs +++ b/InertiaCore/Response.cs @@ -37,29 +37,10 @@ protected internal void ProcessResponse() { Component = _component, Version = _version, - Url = _context!.RequestedUri() + Url = _context!.RequestedUri(), + Props = ResolveProperties(_props.GetType().GetProperties().ToDictionary(o => o.Name.ToCamelCase(), o => o.GetValue(_props))) }; - var partial = _context!.GetPartialData(); - if (partial.Any() && _context!.IsInertiaPartialComponent(_component)) - { - var only = _props.Only(partial); - var partialProps = only.ToDictionary(o => o.ToCamelCase(), o => - _props.GetType().GetProperty(o)?.GetValue(_props)); - - page.Props = partialProps; - } - else - { - var props = _props.GetType().GetProperties() - .Where(o => o.PropertyType != typeof(LazyProp)) - .ToDictionary(o => o.Name.ToCamelCase(), o => o.GetValue(_props)); - - page.Props = props; - } - - page.Props = PrepareProps(page.Props); - var shared = _context!.HttpContext.Features.Get(); if (shared != null) page.Props = shared.GetMerged(page.Props); @@ -127,4 +108,40 @@ public Response WithViewData(IDictionary viewData) _viewData = viewData; return this; } + + private Dictionary ResolveProperties(Dictionary props) + { + bool isPartial = _context!.IsInertiaPartialComponent(_component); + + if (!isPartial) + { + props = props + .Where(kv => kv.Value is not LazyProp) + .ToDictionary(kv => kv.Key, kv => kv.Value); + } + + if (isPartial && _context!.HttpContext.Request.Headers.ContainsKey(Header.PartialOnly)) + { + props = ResolveOnly(props); + } + + if (isPartial && _context!.HttpContext.Request.Headers.ContainsKey(Header.PartialExcept)) + { + props = ResolveExcept(props); + } + + props = PrepareProps(props); + + return props; + } + + private Dictionary ResolveOnly(Dictionary props) + { + return _context!.OnlyProps(props); + } + + private Dictionary ResolveExcept(Dictionary props) + { + return _context!.ExceptProps(props); + } } From c456148b3e0f3b7603ac12dc480f8e0be33d956e Mon Sep 17 00:00:00 2001 From: Austin Drummond Date: Sat, 14 Dec 2024 17:08:57 -0500 Subject: [PATCH 03/31] added always prop --- InertiaCore/Inertia.cs | 3 +++ InertiaCore/Response.cs | 14 ++++++++++++++ InertiaCore/ResponseFactory.cs | 6 ++++++ InertiaCore/Utils/AlwaysProp.cs | 25 +++++++++++++++++++++++++ 4 files changed, 48 insertions(+) create mode 100644 InertiaCore/Utils/AlwaysProp.cs diff --git a/InertiaCore/Inertia.cs b/InertiaCore/Inertia.cs index 729e52f..dec6eda 100644 --- a/InertiaCore/Inertia.cs +++ b/InertiaCore/Inertia.cs @@ -28,5 +28,8 @@ public static class Inertia public static void Share(IDictionary data) => _factory.Share(data); + public static AlwaysProp Always(object? value) => _factory.Always(value); + + public static AlwaysProp Always(Func callback) => _factory.Always(callback); public static LazyProp Lazy(Func callback) => _factory.Lazy(callback); } diff --git a/InertiaCore/Response.cs b/InertiaCore/Response.cs index 13348c1..acab917 100644 --- a/InertiaCore/Response.cs +++ b/InertiaCore/Response.cs @@ -56,6 +56,7 @@ protected internal void ProcessResponse() { Func f => f.Invoke(), LazyProp l => l.Invoke(), + AlwaysProp l => l.Invoke(), _ => pair.Value }); } @@ -130,6 +131,8 @@ public Response WithViewData(IDictionary viewData) props = ResolveExcept(props); } + props = ResolveAlways(props); + props = PrepareProps(props); return props; @@ -144,4 +147,15 @@ public Response WithViewData(IDictionary viewData) { return _context!.ExceptProps(props); } + + private Dictionary ResolveAlways(Dictionary props) + { + var alwaysProps = _props.GetType().GetProperties() + .Where(o => o.PropertyType == typeof(AlwaysProp)) + .ToDictionary(o => o.Name.ToCamelCase(), o => o.GetValue(_props)); ; + + return props + .Where(kv => kv.Value is not AlwaysProp) + .Concat(alwaysProps).ToDictionary(kv => kv.Key, kv => kv.Value); + } } diff --git a/InertiaCore/ResponseFactory.cs b/InertiaCore/ResponseFactory.cs index 0692b40..288721e 100644 --- a/InertiaCore/ResponseFactory.cs +++ b/InertiaCore/ResponseFactory.cs @@ -21,6 +21,9 @@ internal interface IResponseFactory public LocationResult Location(string url); public void Share(string key, object? value); public void Share(IDictionary data); + public AlwaysProp Always(object? value); + public AlwaysProp Always(Func callback); + public AlwaysProp Always(Func> callback); public LazyProp Lazy(Func callback); public LazyProp Lazy(Func> callback); } @@ -121,4 +124,7 @@ public void Share(IDictionary data) public LazyProp Lazy(Func callback) => new LazyProp(callback); public LazyProp Lazy(Func> callback) => new LazyProp(callback); + public AlwaysProp Always(object? value) => new AlwaysProp(value); + public AlwaysProp Always(Func callback) => new AlwaysProp(callback); + public AlwaysProp Always(Func> callback) => new AlwaysProp(callback); } diff --git a/InertiaCore/Utils/AlwaysProp.cs b/InertiaCore/Utils/AlwaysProp.cs new file mode 100644 index 0000000..12182de --- /dev/null +++ b/InertiaCore/Utils/AlwaysProp.cs @@ -0,0 +1,25 @@ +namespace InertiaCore.Utils; + +public class AlwaysProp +{ + private readonly object? _value; + + public AlwaysProp(object? value) + { + _value = value; + } + + public object? Invoke() + { + // Check if the value is a callable delegate + return Task.Run(() => + { + if (_value is Delegate callable) + { + return callable.DynamicInvoke(); + } + + return _value; + }).GetAwaiter().GetResult(); + } +} From 7d0d3bfa8d8414435de76a67e7e1487507030ff9 Mon Sep 17 00:00:00 2001 From: Austin Drummond Date: Sat, 14 Dec 2024 17:15:46 -0500 Subject: [PATCH 04/31] fix some compile time warnings --- InertiaCore/Extensions/Configure.cs | 2 +- InertiaCore/Extensions/InertiaExtensions.cs | 12 ++++++++++++ InertiaCore/Response.cs | 4 ++-- InertiaCore/Utils/InertiaActionFilter.cs | 2 +- InertiaCore/Utils/LocationResult.cs | 2 +- 5 files changed, 17 insertions(+), 5 deletions(-) diff --git a/InertiaCore/Extensions/Configure.cs b/InertiaCore/Extensions/Configure.cs index 6be799c..2e9ce05 100644 --- a/InertiaCore/Extensions/Configure.cs +++ b/InertiaCore/Extensions/Configure.cs @@ -69,7 +69,7 @@ private static async Task OnVersionChange(HttpContext context, IApplicationBuild if (tempData.Any()) tempData.Keep(); - context.Response.Headers.Add("X-Inertia-Location", context.RequestedUri()); + context.Response.Headers.Override(Header.Location, context.RequestedUri()); context.Response.StatusCode = (int)HttpStatusCode.Conflict; await context.Response.CompleteAsync(); diff --git a/InertiaCore/Extensions/InertiaExtensions.cs b/InertiaCore/Extensions/InertiaExtensions.cs index dbcfae3..e92dbd9 100644 --- a/InertiaCore/Extensions/InertiaExtensions.cs +++ b/InertiaCore/Extensions/InertiaExtensions.cs @@ -31,4 +31,16 @@ internal static bool IsInertiaRequest(this HttpContext context) => internal static bool IsInertiaRequest(this ActionContext context) => context.HttpContext.IsInertiaRequest(); internal static string ToCamelCase(this string s) => JsonNamingPolicy.CamelCase.ConvertName(s); + + internal static bool Override(this IDictionary dictionary, TKey key, TValue value) + { + if (dictionary.ContainsKey(key)) + { + dictionary[key] = value; + return true; + } + + dictionary.Add(key, value); + return false; + } } diff --git a/InertiaCore/Response.cs b/InertiaCore/Response.cs index 865ff45..966a95f 100644 --- a/InertiaCore/Response.cs +++ b/InertiaCore/Response.cs @@ -81,8 +81,8 @@ protected internal void ProcessResponse() protected internal JsonResult GetJson() { - _context!.HttpContext.Response.Headers.Add("X-Inertia", "true"); - _context!.HttpContext.Response.Headers.Add("Vary", "Accept"); + _context!.HttpContext.Response.Headers.Override(Header.Inertia, "true"); + _context!.HttpContext.Response.Headers.Override("Vary", "Accept"); _context!.HttpContext.Response.StatusCode = 200; return new JsonResult(_page, new JsonSerializerOptions diff --git a/InertiaCore/Utils/InertiaActionFilter.cs b/InertiaCore/Utils/InertiaActionFilter.cs index f3c5b75..c2ff160 100644 --- a/InertiaCore/Utils/InertiaActionFilter.cs +++ b/InertiaCore/Utils/InertiaActionFilter.cs @@ -32,7 +32,7 @@ public void OnActionExecuted(ActionExecutedContext context) }; if (destinationUrl == null) return; - context.HttpContext.Response.Headers.Add("Location", destinationUrl); + context.HttpContext.Response.Headers.Override("Location", destinationUrl); context.Result = new StatusCodeResult((int)HttpStatusCode.RedirectMethod); } diff --git a/InertiaCore/Utils/LocationResult.cs b/InertiaCore/Utils/LocationResult.cs index 6e358ed..436f372 100644 --- a/InertiaCore/Utils/LocationResult.cs +++ b/InertiaCore/Utils/LocationResult.cs @@ -14,7 +14,7 @@ public async Task ExecuteResultAsync(ActionContext context) { if (context.IsInertiaRequest()) { - context.HttpContext.Response.Headers.Add("X-Inertia-Location", _url); + context.HttpContext.Response.Headers.Override(Header.Location, _url); await new StatusCodeResult((int)HttpStatusCode.Conflict).ExecuteResultAsync(context); return; } From f9ad84b836c0fd459a2e88fbd1fa4656a79693fa Mon Sep 17 00:00:00 2001 From: Austin Drummond Date: Sat, 14 Dec 2024 22:26:54 -0500 Subject: [PATCH 05/31] added always prop test --- InertiaCore/Utils/AlwaysProp.cs | 11 +- InertiaCoreTests/UnitTestAlwaysData.cs | 175 +++++++++++++++++++++++++ 2 files changed, 183 insertions(+), 3 deletions(-) create mode 100644 InertiaCoreTests/UnitTestAlwaysData.cs diff --git a/InertiaCore/Utils/AlwaysProp.cs b/InertiaCore/Utils/AlwaysProp.cs index 12182de..1ccc9b8 100644 --- a/InertiaCore/Utils/AlwaysProp.cs +++ b/InertiaCore/Utils/AlwaysProp.cs @@ -12,11 +12,16 @@ public AlwaysProp(object? value) public object? Invoke() { // Check if the value is a callable delegate - return Task.Run(() => + return Task.Run(async () => { - if (_value is Delegate callable) + if (_value is Func> asyncCallable) { - return callable.DynamicInvoke(); + return await asyncCallable.Invoke(); + } + + if (_value is Func callable) + { + return callable.Invoke(); } return _value; diff --git a/InertiaCoreTests/UnitTestAlwaysData.cs b/InertiaCoreTests/UnitTestAlwaysData.cs new file mode 100644 index 0000000..f3d7bcb --- /dev/null +++ b/InertiaCoreTests/UnitTestAlwaysData.cs @@ -0,0 +1,175 @@ +using InertiaCore.Models; +using Microsoft.AspNetCore.Http; + +namespace InertiaCoreTests; + +public partial class Tests +{ + [Test] + [Description("Test if the always data is fetched properly.")] + public void TestAlwaysData() + { + var response = _factory.Render("Test/Page", new + { + Test = "Test", + TestFunc = new Func(() => "Func"), + TestAlways = _factory.Always(() => + { + return "Always"; + }) + }); + + var context = PrepareContext(); + + response.SetContext(context); + response.ProcessResponse(); + + var page = response.GetJson().Value as Page; + + Assert.That(page?.Props, Is.EqualTo(new Dictionary + { + { "test", "Test" }, + { "testFunc", "Func" }, + { "testAlways", "Always" }, + { "errors", new Dictionary(0) } + })); + } + + [Test] + [Description("Test if the always data is fetched properly with specified partial props.")] + public void TestAlwaysPartialData() + { + var response = _factory.Render("Test/Page", new + { + TestFunc = new Func(() => "Func"), + TestAlways = _factory.Always(() => "Always") + }); + + var headers = new HeaderDictionary + { + { "X-Inertia-Partial-Data", "testFunc,testAlways" }, + { "X-Inertia-Partial-Component", "Test/Page" } + }; + + var context = PrepareContext(headers); + + response.SetContext(context); + response.ProcessResponse(); + + var page = response.GetJson().Value as Page; + + Assert.That(page?.Props, Is.EqualTo(new Dictionary + { + { "testFunc", "Func" }, + { "testAlways", "Always" }, + { "errors", new Dictionary(0) } + })); + } + + [Test] + [Description("Test if the always async data is fetched properly.")] + public void TestAlwaysAsyncData() + { + var testFunction = new Func>(async () => + { + await Task.Delay(100); + return "Always Async"; + }); + + var response = _factory.Render("Test/Page", new + { + Test = "Test", + TestFunc = new Func(() => "Func"), + TestAlways = _factory.Always(testFunction) + }); + + var context = PrepareContext(); + + response.SetContext(context); + response.ProcessResponse(); + + var page = response.GetJson().Value as Page; + + Assert.That(page?.Props, Is.EqualTo(new Dictionary + { + { "test", "Test" }, + { "testFunc", "Func" }, + { "testAlways", "Always Async" }, + { "errors", new Dictionary(0) } + })); + } + + [Test] + [Description("Test if the always async data is fetched properly with specified partial props.")] + public void TestAlwaysAsyncPartialData() + { + var testFunction = new Func>(async () => + { + await Task.Delay(100); + return "Always Async"; + }); + + var response = _factory.Render("Test/Page", new + { + TestFunc = new Func(() => "Func"), + TestAlways = _factory.Always(async () => await testFunction()) + }); + + var headers = new HeaderDictionary + { + { "X-Inertia-Partial-Data", "testFunc,testAlways" }, + { "X-Inertia-Partial-Component", "Test/Page" } + }; + + var context = PrepareContext(headers); + + response.SetContext(context); + response.ProcessResponse(); + + var page = response.GetJson().Value as Page; + + Assert.That(page?.Props, Is.EqualTo(new Dictionary + { + { "testFunc", "Func" }, + { "testAlways", "Always Async" }, + { "errors", new Dictionary(0) } + })); + } + + [Test] + [Description("Test if the always async data is fetched properly without specified partial props.")] + public void TestAlwaysAsyncPartialDataOmitted() + { + var testFunction = new Func>(async () => + { + await Task.Delay(100); + return "Always Async"; + }); + + var response = _factory.Render("Test/Page", new + { + TestFunc = new Func(() => "Func"), + TestAlways = _factory.Always(async () => await testFunction()) + }); + + var headers = new HeaderDictionary + { + { "X-Inertia-Partial-Data", "testFunc" }, + { "X-Inertia-Partial-Component", "Test/Page" } + }; + + var context = PrepareContext(headers); + + response.SetContext(context); + response.ProcessResponse(); + + var page = response.GetJson().Value as Page; + + Assert.That(page?.Props, Is.EqualTo(new Dictionary + { + { "testFunc", "Func" }, + { "testAlways", "Always Async" }, + { "errors", new Dictionary(0) } + })); + } +} From 77473d6572d676c97c7df24e9f560df3db4ae78e Mon Sep 17 00:00:00 2001 From: Austin Drummond Date: Sat, 14 Dec 2024 22:27:15 -0500 Subject: [PATCH 06/31] added async task wrapper --- InertiaCore/Inertia.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/InertiaCore/Inertia.cs b/InertiaCore/Inertia.cs index dec6eda..8519b53 100644 --- a/InertiaCore/Inertia.cs +++ b/InertiaCore/Inertia.cs @@ -31,5 +31,10 @@ public static class Inertia public static AlwaysProp Always(object? value) => _factory.Always(value); public static AlwaysProp Always(Func callback) => _factory.Always(callback); + + public static AlwaysProp Always(Func> callback) => _factory.Always(callback); + public static LazyProp Lazy(Func callback) => _factory.Lazy(callback); + + public static LazyProp Lazy(Func> callback) => _factory.Lazy(callback); } From f64ca769b65a239fd574d13ebfbc2ee82d57144e Mon Sep 17 00:00:00 2001 From: Austin Drummond Date: Sat, 14 Dec 2024 22:30:28 -0500 Subject: [PATCH 07/31] .net 8 --- InertiaCore/InertiaCore.csproj | 2 +- InertiaCoreTests/InertiaCoreTests.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/InertiaCore/InertiaCore.csproj b/InertiaCore/InertiaCore.csproj index b54f108..05d7e54 100644 --- a/InertiaCore/InertiaCore.csproj +++ b/InertiaCore/InertiaCore.csproj @@ -3,7 +3,7 @@ enable enable 0.0.9 - net6.0;net7.0 + net6.0;net7.0;net8.0 AspNetCore.InertiaCore kapi2289 Inertia.js ASP.NET Adapter. https://inertiajs.com/ diff --git a/InertiaCoreTests/InertiaCoreTests.csproj b/InertiaCoreTests/InertiaCoreTests.csproj index f85c6b7..81f3de9 100644 --- a/InertiaCoreTests/InertiaCoreTests.csproj +++ b/InertiaCoreTests/InertiaCoreTests.csproj @@ -1,7 +1,7 @@ - net6.0;net7.0 + net6.0;net7.0;net8.0 enable enable From 51fe030f143c64a7043776101d8a463c7755f953 Mon Sep 17 00:00:00 2001 From: Austin Drummond Date: Fri, 20 Dec 2024 23:56:40 -0500 Subject: [PATCH 08/31] restore header keys --- InertiaCore/Extensions/Configure.cs | 2 +- InertiaCore/Response.cs | 2 +- InertiaCore/Utils/LocationResult.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/InertiaCore/Extensions/Configure.cs b/InertiaCore/Extensions/Configure.cs index 2e9ce05..f0ff601 100644 --- a/InertiaCore/Extensions/Configure.cs +++ b/InertiaCore/Extensions/Configure.cs @@ -69,7 +69,7 @@ private static async Task OnVersionChange(HttpContext context, IApplicationBuild if (tempData.Any()) tempData.Keep(); - context.Response.Headers.Override(Header.Location, context.RequestedUri()); + context.Response.Headers.Override("X-Inertia-Location", context.RequestedUri()); context.Response.StatusCode = (int)HttpStatusCode.Conflict; await context.Response.CompleteAsync(); diff --git a/InertiaCore/Response.cs b/InertiaCore/Response.cs index 966a95f..ee364e6 100644 --- a/InertiaCore/Response.cs +++ b/InertiaCore/Response.cs @@ -81,7 +81,7 @@ protected internal void ProcessResponse() protected internal JsonResult GetJson() { - _context!.HttpContext.Response.Headers.Override(Header.Inertia, "true"); + _context!.HttpContext.Response.Headers.Override("X-Inertia", "true"); _context!.HttpContext.Response.Headers.Override("Vary", "Accept"); _context!.HttpContext.Response.StatusCode = 200; diff --git a/InertiaCore/Utils/LocationResult.cs b/InertiaCore/Utils/LocationResult.cs index 436f372..03887d9 100644 --- a/InertiaCore/Utils/LocationResult.cs +++ b/InertiaCore/Utils/LocationResult.cs @@ -14,7 +14,7 @@ public async Task ExecuteResultAsync(ActionContext context) { if (context.IsInertiaRequest()) { - context.HttpContext.Response.Headers.Override(Header.Location, _url); + context.HttpContext.Response.Headers.Override("X-Inertia-Location", _url); await new StatusCodeResult((int)HttpStatusCode.Conflict).ExecuteResultAsync(context); return; } From 62a0864c2f08ea422b742abc94e0511e3d2fe7d8 Mon Sep 17 00:00:00 2001 From: Austin Drummond Date: Fri, 20 Dec 2024 23:58:09 -0500 Subject: [PATCH 09/31] added .net 9 --- InertiaCore/InertiaCore.csproj | 2 +- InertiaCoreTests/InertiaCoreTests.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/InertiaCore/InertiaCore.csproj b/InertiaCore/InertiaCore.csproj index 05d7e54..5f68508 100644 --- a/InertiaCore/InertiaCore.csproj +++ b/InertiaCore/InertiaCore.csproj @@ -3,7 +3,7 @@ enable enable 0.0.9 - net6.0;net7.0;net8.0 + net6.0;net7.0;net8.0;net9.0 AspNetCore.InertiaCore kapi2289 Inertia.js ASP.NET Adapter. https://inertiajs.com/ diff --git a/InertiaCoreTests/InertiaCoreTests.csproj b/InertiaCoreTests/InertiaCoreTests.csproj index 81f3de9..328dafe 100644 --- a/InertiaCoreTests/InertiaCoreTests.csproj +++ b/InertiaCoreTests/InertiaCoreTests.csproj @@ -1,7 +1,7 @@ - net6.0;net7.0;net8.0 + net6.0;net7.0;net8.0;net9.0 enable enable From 2ab07651da7f48d66bcae34b187f60ec6aca4d6c Mon Sep 17 00:00:00 2001 From: Austin Drummond Date: Sat, 21 Dec 2024 00:21:26 -0500 Subject: [PATCH 10/31] added ignore first load --- InertiaCore/Response.cs | 2 +- InertiaCore/Utils/IgnoreFirstLoad.cs | 6 ++++++ InertiaCore/Utils/LazyProp.cs | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 InertiaCore/Utils/IgnoreFirstLoad.cs diff --git a/InertiaCore/Response.cs b/InertiaCore/Response.cs index 87b53da..263b5b5 100644 --- a/InertiaCore/Response.cs +++ b/InertiaCore/Response.cs @@ -117,7 +117,7 @@ public Response WithViewData(IDictionary viewData) if (!isPartial) { props = props - .Where(kv => kv.Value is not LazyProp) + .Where(kv => (kv.Value as IgnoreFirstLoad) == null) .ToDictionary(kv => kv.Key, kv => kv.Value); } diff --git a/InertiaCore/Utils/IgnoreFirstLoad.cs b/InertiaCore/Utils/IgnoreFirstLoad.cs new file mode 100644 index 0000000..f5ff0f7 --- /dev/null +++ b/InertiaCore/Utils/IgnoreFirstLoad.cs @@ -0,0 +1,6 @@ +namespace InertiaCore.Utils; + +public interface IgnoreFirstLoad +{ + +} diff --git a/InertiaCore/Utils/LazyProp.cs b/InertiaCore/Utils/LazyProp.cs index 1f74f9f..46004b5 100644 --- a/InertiaCore/Utils/LazyProp.cs +++ b/InertiaCore/Utils/LazyProp.cs @@ -1,6 +1,6 @@ namespace InertiaCore.Utils; -public class LazyProp +public class LazyProp : IgnoreFirstLoad { private readonly Func> _callback; From bf08e03df726867b3e8e06a6b90a62bd7f0e3683 Mon Sep 17 00:00:00 2001 From: Austin Drummond Date: Sat, 21 Dec 2024 00:22:03 -0500 Subject: [PATCH 11/31] add optional prop --- InertiaCore/Response.cs | 1 + InertiaCore/ResponseFactory.cs | 4 ++++ InertiaCore/Utils/OptionalProp.cs | 12 ++++++++++++ 3 files changed, 17 insertions(+) create mode 100644 InertiaCore/Utils/OptionalProp.cs diff --git a/InertiaCore/Response.cs b/InertiaCore/Response.cs index 263b5b5..a7344d7 100644 --- a/InertiaCore/Response.cs +++ b/InertiaCore/Response.cs @@ -56,6 +56,7 @@ protected internal void ProcessResponse() { Func f => f.Invoke(), LazyProp l => l.Invoke(), + OptionalProp l => l.Invoke(), AlwaysProp l => l.Invoke(), _ => pair.Value }); diff --git a/InertiaCore/ResponseFactory.cs b/InertiaCore/ResponseFactory.cs index 288721e..1ed9277 100644 --- a/InertiaCore/ResponseFactory.cs +++ b/InertiaCore/ResponseFactory.cs @@ -26,6 +26,8 @@ internal interface IResponseFactory public AlwaysProp Always(Func> callback); public LazyProp Lazy(Func callback); public LazyProp Lazy(Func> callback); + public OptionalProp Optional(Func callback); + public OptionalProp Optional(Func> callback); } internal class ResponseFactory : IResponseFactory @@ -127,4 +129,6 @@ public void Share(IDictionary data) public AlwaysProp Always(object? value) => new AlwaysProp(value); public AlwaysProp Always(Func callback) => new AlwaysProp(callback); public AlwaysProp Always(Func> callback) => new AlwaysProp(callback); + public OptionalProp Optional(Func callback) => new OptionalProp(callback); + public OptionalProp Optional(Func> callback) => new OptionalProp(callback); } diff --git a/InertiaCore/Utils/OptionalProp.cs b/InertiaCore/Utils/OptionalProp.cs new file mode 100644 index 0000000..20c6e5a --- /dev/null +++ b/InertiaCore/Utils/OptionalProp.cs @@ -0,0 +1,12 @@ +namespace InertiaCore.Utils; + +public class OptionalProp : IgnoreFirstLoad +{ + private readonly Func> _callback; + + public OptionalProp(Func callback) => _callback = async () => await Task.FromResult(callback()); + + public OptionalProp(Func> callback) => _callback = callback; + + public object? Invoke() => Task.Run(() => _callback.Invoke()).GetAwaiter().GetResult(); +} From 3e30450aebc9c3046dc54362a7d32d764ae962f7 Mon Sep 17 00:00:00 2001 From: Austin Drummond Date: Sat, 21 Dec 2024 00:45:24 -0500 Subject: [PATCH 12/31] added optional test --- InertiaCoreTests/UnitTestOptionalData.cs | 139 +++++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 InertiaCoreTests/UnitTestOptionalData.cs diff --git a/InertiaCoreTests/UnitTestOptionalData.cs b/InertiaCoreTests/UnitTestOptionalData.cs new file mode 100644 index 0000000..8d8365f --- /dev/null +++ b/InertiaCoreTests/UnitTestOptionalData.cs @@ -0,0 +1,139 @@ +using InertiaCore.Models; +using Microsoft.AspNetCore.Http; + +namespace InertiaCoreTests; + +public partial class Tests +{ + [Test] + [Description("Test if the optional data is fetched properly.")] + public void TestOptionalData() + { + var response = _factory.Render("Test/Page", new + { + Test = "Test", + TestFunc = new Func(() => "Func"), + TestOptional = _factory.Optional(() => + { + Assert.Fail(); + return "Optional"; + }) + }); + + var context = PrepareContext(); + + response.SetContext(context); + response.ProcessResponse(); + + var page = response.GetJson().Value as Page; + + Assert.That(page?.Props, Is.EqualTo(new Dictionary + { + { "test", "Test" }, + { "testFunc", "Func" }, + { "errors", new Dictionary(0) } + })); + } + + [Test] + [Description("Test if the optional data is fetched properly with specified partial props.")] + public void TestOptionalPartialData() + { + var response = _factory.Render("Test/Page", new + { + TestFunc = new Func(() => "Func"), + TestOptional = _factory.Optional(() => "Optional") + }); + + var headers = new HeaderDictionary + { + { "X-Inertia-Partial-Data", "testFunc,testOptional" }, + { "X-Inertia-Partial-Component", "Test/Page" } + }; + + var context = PrepareContext(headers); + + response.SetContext(context); + response.ProcessResponse(); + + var page = response.GetJson().Value as Page; + + Assert.That(page?.Props, Is.EqualTo(new Dictionary + { + { "testFunc", "Func" }, + { "testOptional", "Optional" }, + { "errors", new Dictionary(0) } + })); + } + + + [Test] + [Description("Test if the optional async data is fetched properly.")] + public void TestOptionalAsyncData() + { + var testFunction = new Func>(async () => + { + Assert.Fail(); + await Task.Delay(100); + return "Optional Async"; + }); + + var response = _factory.Render("Test/Page", new + { + Test = "Test", + TestFunc = new Func(() => "Func"), + TestOptional = _factory.Optional(testFunction) + }); + + var context = PrepareContext(); + + response.SetContext(context); + response.ProcessResponse(); + + var page = response.GetJson().Value as Page; + + Assert.That(page?.Props, Is.EqualTo(new Dictionary + { + { "test", "Test" }, + { "testFunc", "Func" }, + { "errors", new Dictionary(0) } + })); + } + + [Test] + [Description("Test if the optional async data is fetched properly with specified partial props.")] + public void TestOptionalAsyncPartialData() + { + var testFunction = new Func>(async () => + { + await Task.Delay(100); + return "Optional Async"; + }); + + var response = _factory.Render("Test/Page", new + { + TestFunc = new Func(() => "Func"), + TestOptional = _factory.Optional(async () => await testFunction()) + }); + + var headers = new HeaderDictionary + { + { "X-Inertia-Partial-Data", "testFunc,testOptional" }, + { "X-Inertia-Partial-Component", "Test/Page" } + }; + + var context = PrepareContext(headers); + + response.SetContext(context); + response.ProcessResponse(); + + var page = response.GetJson().Value as Page; + + Assert.That(page?.Props, Is.EqualTo(new Dictionary + { + { "testFunc", "Func" }, + { "testOptional", "Optional Async" }, + { "errors", new Dictionary(0) } + })); + } +} From 1d74592da708cb2346e303c1a797a73b50e2a97d Mon Sep 17 00:00:00 2001 From: Austin Drummond Date: Sat, 21 Dec 2024 00:44:50 -0500 Subject: [PATCH 13/31] added merge prop --- InertiaCore/Inertia.cs | 6 ++++++ InertiaCore/Models/Page.cs | 1 + InertiaCore/Response.cs | 30 ++++++++++++++++++++++++++++++ InertiaCore/ResponseFactory.cs | 6 ++++++ InertiaCore/Utils/Header.cs | 2 ++ InertiaCore/Utils/MergeProp.cs | 33 +++++++++++++++++++++++++++++++++ InertiaCore/Utils/Mergeable.cs | 15 +++++++++++++++ 7 files changed, 93 insertions(+) create mode 100644 InertiaCore/Utils/MergeProp.cs create mode 100644 InertiaCore/Utils/Mergeable.cs diff --git a/InertiaCore/Inertia.cs b/InertiaCore/Inertia.cs index 8519b53..aa87a2f 100644 --- a/InertiaCore/Inertia.cs +++ b/InertiaCore/Inertia.cs @@ -34,6 +34,12 @@ public static class Inertia public static AlwaysProp Always(Func> callback) => _factory.Always(callback); + public static MergeProp Merge(object? value) => _factory.Merge(value); + + public static MergeProp Merge(Func callback) => _factory.Merge(callback); + + public static MergeProp Merge(Func> callback) => _factory.Merge(callback); + public static LazyProp Lazy(Func callback) => _factory.Lazy(callback); public static LazyProp Lazy(Func> callback) => _factory.Lazy(callback); diff --git a/InertiaCore/Models/Page.cs b/InertiaCore/Models/Page.cs index 47abba7..230462a 100644 --- a/InertiaCore/Models/Page.cs +++ b/InertiaCore/Models/Page.cs @@ -6,4 +6,5 @@ internal class Page public string Component { get; set; } = default!; public string? Version { get; set; } public string Url { get; set; } = default!; + public List? MergeProps { get; set; } } diff --git a/InertiaCore/Response.cs b/InertiaCore/Response.cs index 87b53da..1125530 100644 --- a/InertiaCore/Response.cs +++ b/InertiaCore/Response.cs @@ -41,6 +41,8 @@ protected internal void ProcessResponse() Props = ResolveProperties(_props.GetType().GetProperties().ToDictionary(o => o.Name.ToCamelCase(), o => o.GetValue(_props))) }; + page.MergeProps = ResolveMergeProps(page.Props); + var shared = _context!.HttpContext.Features.Get(); if (shared != null) page.Props = shared.GetMerged(page.Props); @@ -57,6 +59,7 @@ protected internal void ProcessResponse() Func f => f.Invoke(), LazyProp l => l.Invoke(), AlwaysProp l => l.Invoke(), + MergeProp m => m.Invoke(), _ => pair.Value }); } @@ -158,4 +161,31 @@ public Response WithViewData(IDictionary viewData) .Where(kv => kv.Value is not AlwaysProp) .Concat(alwaysProps).ToDictionary(kv => kv.Key, kv => kv.Value); } + + private List? ResolveMergeProps(Dictionary props) + { + // Parse the "RESET" header into a collection of keys to reset + var resetProps = new HashSet( + _context!.HttpContext.Request.Headers[Header.Reset] + .ToString() + .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) + .Select(s => s.Trim()), + StringComparer.OrdinalIgnoreCase + ); + + var resolvedProps = props + .Select(kv => kv.Key.ToCamelCase()) // Convert property name to camelCase + .ToList(); + + // Filter the props that are Mergeable and should be merged + var mergeProps = _props.GetType().GetProperties().ToDictionary(o => o.Name.ToCamelCase(), o => o.GetValue(_props)) + .Where(kv => kv.Value is Mergeable mergeable && mergeable.ShouldMerge()) // Check if value is Mergeable and should merge + .Where(kv => !resetProps.Contains(kv.Key)) // Exclude reset keys + .Select(kv => kv.Key.ToCamelCase()) // Convert property name to camelCase + .Where(resolvedProps.Contains) // Filter only the props that are in the resolved props + .ToList(); + + // Return the result + return mergeProps; + } } diff --git a/InertiaCore/ResponseFactory.cs b/InertiaCore/ResponseFactory.cs index 288721e..e95f10d 100644 --- a/InertiaCore/ResponseFactory.cs +++ b/InertiaCore/ResponseFactory.cs @@ -26,6 +26,9 @@ internal interface IResponseFactory public AlwaysProp Always(Func> callback); public LazyProp Lazy(Func callback); public LazyProp Lazy(Func> callback); + public MergeProp Merge(object? value); + public MergeProp Merge(Func callback); + public MergeProp Merge(Func> callback); } internal class ResponseFactory : IResponseFactory @@ -127,4 +130,7 @@ public void Share(IDictionary data) public AlwaysProp Always(object? value) => new AlwaysProp(value); public AlwaysProp Always(Func callback) => new AlwaysProp(callback); public AlwaysProp Always(Func> callback) => new AlwaysProp(callback); + public MergeProp Merge(object? value) => new MergeProp(value); + public MergeProp Merge(Func callback) => new MergeProp(callback); + public MergeProp Merge(Func> callback) => new MergeProp(callback); } diff --git a/InertiaCore/Utils/Header.cs b/InertiaCore/Utils/Header.cs index b729dbf..b1f9abb 100644 --- a/InertiaCore/Utils/Header.cs +++ b/InertiaCore/Utils/Header.cs @@ -15,4 +15,6 @@ public static class Header public const string PartialOnly = "X-Inertia-Partial-Data"; public const string PartialExcept = "X-Inertia-Partial-Except"; + + public const string Reset = "X-Inertia-Reset"; } diff --git a/InertiaCore/Utils/MergeProp.cs b/InertiaCore/Utils/MergeProp.cs new file mode 100644 index 0000000..c26b7ad --- /dev/null +++ b/InertiaCore/Utils/MergeProp.cs @@ -0,0 +1,33 @@ +namespace InertiaCore.Utils; + +public class MergeProp : Mergeable +{ + public bool merge { get; set; } = true; + + private readonly object? _value; + + public MergeProp(object? value) + { + _value = value; + merge = true; + } + + public object? Invoke() + { + // Check if the value is a callable delegate + return Task.Run(async () => + { + if (_value is Func> asyncCallable) + { + return await asyncCallable.Invoke(); + } + + if (_value is Func callable) + { + return callable.Invoke(); + } + + return _value; + }).GetAwaiter().GetResult(); + } +} diff --git a/InertiaCore/Utils/Mergeable.cs b/InertiaCore/Utils/Mergeable.cs new file mode 100644 index 0000000..b7e5b6e --- /dev/null +++ b/InertiaCore/Utils/Mergeable.cs @@ -0,0 +1,15 @@ +namespace InertiaCore.Utils; + +public interface Mergeable +{ + public bool merge { get; set; } + + public Mergeable Merge() + { + merge = true; + + return this; + } + + public bool ShouldMerge() => merge; +} From 05bf42d85f5485638f29b7f729666e5432a54265 Mon Sep 17 00:00:00 2001 From: Austin Drummond Date: Sat, 21 Dec 2024 14:40:50 -0500 Subject: [PATCH 14/31] added unit test for merge prop --- InertiaCoreTests/UnitTestMergeData.cs | 206 ++++++++++++++++++++++++++ 1 file changed, 206 insertions(+) create mode 100644 InertiaCoreTests/UnitTestMergeData.cs diff --git a/InertiaCoreTests/UnitTestMergeData.cs b/InertiaCoreTests/UnitTestMergeData.cs new file mode 100644 index 0000000..a6e9312 --- /dev/null +++ b/InertiaCoreTests/UnitTestMergeData.cs @@ -0,0 +1,206 @@ +using InertiaCore.Models; +using Microsoft.AspNetCore.Http; + +namespace InertiaCoreTests; + +public partial class Tests +{ + [Test] + [Description("Test if the merge data is fetched properly.")] + public void TestMergeData() + { + var response = _factory.Render("Test/Page", new + { + Test = "Test", + TestFunc = new Func(() => "Func"), + TestMerge = _factory.Merge(() => + { + return "Merge"; + }) + }); + + var context = PrepareContext(); + + response.SetContext(context); + response.ProcessResponse(); + + var page = response.GetJson().Value as Page; + + Assert.That(page?.Props, Is.EqualTo(new Dictionary + { + { "test", "Test" }, + { "testFunc", "Func" }, + { "testMerge", "Merge" }, + { "errors", new Dictionary(0) } + })); + Assert.That(page?.MergeProps, Is.EqualTo(new List { "testMerge" })); + } + + [Test] + [Description("Test if the merge data is fetched properly with specified partial props.")] + public void TestMergePartialData() + { + var response = _factory.Render("Test/Page", new + { + TestFunc = new Func(() => "Func"), + TestMerge = _factory.Merge(() => "Merge") + }); + + var headers = new HeaderDictionary + { + { "X-Inertia-Partial-Data", "testFunc,testMerge" }, + { "X-Inertia-Partial-Component", "Test/Page" } + }; + + var context = PrepareContext(headers); + + response.SetContext(context); + response.ProcessResponse(); + + var page = response.GetJson().Value as Page; + + Assert.That(page?.Props, Is.EqualTo(new Dictionary + { + { "testFunc", "Func" }, + { "testMerge", "Merge" }, + { "errors", new Dictionary(0) } + })); + + Assert.That(page?.MergeProps, Is.EqualTo(new List { "testMerge" })); + } + + [Test] + [Description("Test if the merge async data is fetched properly.")] + public void TestMergeAsyncData() + { + var testFunction = new Func>(async () => + { + await Task.Delay(100); + return "Merge Async"; + }); + + var response = _factory.Render("Test/Page", new + { + Test = "Test", + TestFunc = new Func(() => "Func"), + TestMerge = _factory.Merge(testFunction) + }); + + var context = PrepareContext(); + + response.SetContext(context); + response.ProcessResponse(); + + var page = response.GetJson().Value as Page; + + Assert.That(page?.Props, Is.EqualTo(new Dictionary + { + { "test", "Test" }, + { "testFunc", "Func" }, + { "testMerge", "Merge Async" }, + { "errors", new Dictionary(0) } + })); + Assert.That(page?.MergeProps, Is.EqualTo(new List { "testMerge" })); + } + + [Test] + [Description("Test if the merge async data is fetched properly with specified partial props.")] + public void TestMergeAsyncPartialData() + { + var testFunction = new Func>(async () => + { + await Task.Delay(100); + return "Merge Async"; + }); + + var response = _factory.Render("Test/Page", new + { + TestFunc = new Func(() => "Func"), + TestMerge = _factory.Merge(async () => await testFunction()) + }); + + var headers = new HeaderDictionary + { + { "X-Inertia-Partial-Data", "testFunc,testMerge" }, + { "X-Inertia-Partial-Component", "Test/Page" } + }; + + var context = PrepareContext(headers); + + response.SetContext(context); + response.ProcessResponse(); + + var page = response.GetJson().Value as Page; + + Assert.That(page?.Props, Is.EqualTo(new Dictionary + { + { "testFunc", "Func" }, + { "testMerge", "Merge Async" }, + { "errors", new Dictionary(0) } + })); + + Assert.That(page?.MergeProps, Is.EqualTo(new List { "testMerge" })); + } + + [Test] + [Description("Test if the merge async data is fetched properly without specified partial props.")] + public void TestMergeAsyncPartialDataOmitted() + { + var testFunction = new Func>(async () => + { + await Task.Delay(100); + return "Merge Async"; + }); + + var response = _factory.Render("Test/Page", new + { + TestFunc = new Func(() => "Func"), + TestMerge = _factory.Merge(async () => await testFunction()) + }); + + var headers = new HeaderDictionary + { + { "X-Inertia-Partial-Data", "testFunc" }, + { "X-Inertia-Partial-Component", "Test/Page" } + }; + + var context = PrepareContext(headers); + + response.SetContext(context); + response.ProcessResponse(); + + var page = response.GetJson().Value as Page; + + Assert.That(page?.Props, Is.EqualTo(new Dictionary + { + { "testFunc", "Func" }, + { "errors", new Dictionary(0) } + })); + + Assert.That(page?.MergeProps, Is.EqualTo(new List { })); + } + + public void TestNoMergeProps() + { + var response = _factory.Render("Test/Page", new + { + Test = "Test", + TestFunc = new Func(() => "Func"), + }); + + var context = PrepareContext(); + + response.SetContext(context); + response.ProcessResponse(); + + var page = response.GetJson().Value as Page; + + Assert.That(page?.Props, Is.EqualTo(new Dictionary + { + { "test", "Test" }, + { "testFunc", "Func" }, + { "errors", new Dictionary(0) } + })); + Assert.That(page?.MergeProps, Is.EqualTo(new List { })); + } +} From 3cacdaa9bd86580b382e74282034306697dfb6e8 Mon Sep 17 00:00:00 2001 From: Austin Drummond Date: Sat, 21 Dec 2024 14:52:03 -0500 Subject: [PATCH 15/31] update version in actions --- .github/workflows/dotnet.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 25f43e7..235f1ef 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -19,7 +19,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v3 with: - dotnet-version: 6.0.x + dotnet-version: 9.0.x - name: Restore dependencies run: dotnet restore - name: Build From 52ad4b83296b667a599eaee30c7f1c8e32a03d99 Mon Sep 17 00:00:00 2001 From: Austin Drummond Date: Sat, 21 Dec 2024 14:52:03 -0500 Subject: [PATCH 16/31] update version in actions --- .github/workflows/dotnet.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 25f43e7..235f1ef 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -19,7 +19,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v3 with: - dotnet-version: 6.0.x + dotnet-version: 9.0.x - name: Restore dependencies run: dotnet restore - name: Build From 1e1869695e88d335a0a3451f4eeb40df362e334c Mon Sep 17 00:00:00 2001 From: Austin Drummond Date: Sat, 21 Dec 2024 15:19:04 -0500 Subject: [PATCH 17/31] dont resolve Lazy and Optional Props until they are invoked --- InertiaCore/Utils/LazyProp.cs | 25 +++++++++++++++++++++---- InertiaCore/Utils/OptionalProp.cs | 24 ++++++++++++++++++++---- 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/InertiaCore/Utils/LazyProp.cs b/InertiaCore/Utils/LazyProp.cs index 46004b5..7676e6c 100644 --- a/InertiaCore/Utils/LazyProp.cs +++ b/InertiaCore/Utils/LazyProp.cs @@ -2,10 +2,27 @@ namespace InertiaCore.Utils; public class LazyProp : IgnoreFirstLoad { - private readonly Func> _callback; + private readonly object? _value; - public LazyProp(Func callback) => _callback = async () => await Task.FromResult(callback()); - public LazyProp(Func> callback) => _callback = callback; + public LazyProp(Func callback) => _value = callback; + public LazyProp(Func> callback) => _value = callback; - public object? Invoke() => Task.Run(() => _callback.Invoke()).GetAwaiter().GetResult(); + public object? Invoke() + { + // Check if the value is a callable delegate + return Task.Run(async () => + { + if (_value is Func> asyncCallable) + { + return await asyncCallable.Invoke(); + } + + if (_value is Func callable) + { + return callable.Invoke(); + } + + return _value; + }).GetAwaiter().GetResult(); + } } diff --git a/InertiaCore/Utils/OptionalProp.cs b/InertiaCore/Utils/OptionalProp.cs index 20c6e5a..7818b17 100644 --- a/InertiaCore/Utils/OptionalProp.cs +++ b/InertiaCore/Utils/OptionalProp.cs @@ -2,11 +2,27 @@ namespace InertiaCore.Utils; public class OptionalProp : IgnoreFirstLoad { - private readonly Func> _callback; + private readonly object? _value; - public OptionalProp(Func callback) => _callback = async () => await Task.FromResult(callback()); + public OptionalProp(Func callback) => _value = callback; + public OptionalProp(Func> callback) => _value = callback; - public OptionalProp(Func> callback) => _callback = callback; + public object? Invoke() + { + // Check if the value is a callable delegate + return Task.Run(async () => + { + if (_value is Func> asyncCallable) + { + return await asyncCallable.Invoke(); + } - public object? Invoke() => Task.Run(() => _callback.Invoke()).GetAwaiter().GetResult(); + if (_value is Func callable) + { + return callable.Invoke(); + } + + return _value; + }).GetAwaiter().GetResult(); + } } From 7860eea4159556897454c13c5f60a03e3c09f3d2 Mon Sep 17 00:00:00 2001 From: Austin Drummond Date: Sat, 21 Dec 2024 16:11:43 -0500 Subject: [PATCH 18/31] dont include Merge props in the json --- InertiaCore/Models/Page.cs | 4 ++ InertiaCore/Response.cs | 5 +++ InertiaCoreTests/UnitTestMergeData.cs | 5 ++- InertiaCoreTests/UnitTestResult.cs | 57 +++++++++++++++++++++++++++ 4 files changed, 69 insertions(+), 2 deletions(-) diff --git a/InertiaCore/Models/Page.cs b/InertiaCore/Models/Page.cs index 230462a..93ab0a2 100644 --- a/InertiaCore/Models/Page.cs +++ b/InertiaCore/Models/Page.cs @@ -1,3 +1,5 @@ +using System.Text.Json.Serialization; + namespace InertiaCore.Models; internal class Page @@ -6,5 +8,7 @@ internal class Page public string Component { get; set; } = default!; public string? Version { get; set; } public string Url { get; set; } = default!; + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public List? MergeProps { get; set; } } diff --git a/InertiaCore/Response.cs b/InertiaCore/Response.cs index 1125530..5c1e0e4 100644 --- a/InertiaCore/Response.cs +++ b/InertiaCore/Response.cs @@ -185,6 +185,11 @@ public Response WithViewData(IDictionary viewData) .Where(resolvedProps.Contains) // Filter only the props that are in the resolved props .ToList(); + if (mergeProps.Count == 0) + { + return null; + } + // Return the result return mergeProps; } diff --git a/InertiaCoreTests/UnitTestMergeData.cs b/InertiaCoreTests/UnitTestMergeData.cs index a6e9312..6f35a0c 100644 --- a/InertiaCoreTests/UnitTestMergeData.cs +++ b/InertiaCoreTests/UnitTestMergeData.cs @@ -177,7 +177,7 @@ public void TestMergeAsyncPartialDataOmitted() { "errors", new Dictionary(0) } })); - Assert.That(page?.MergeProps, Is.EqualTo(new List { })); + Assert.That(page?.MergeProps, Is.EqualTo(null)); } public void TestNoMergeProps() @@ -201,6 +201,7 @@ public void TestNoMergeProps() { "testFunc", "Func" }, { "errors", new Dictionary(0) } })); - Assert.That(page?.MergeProps, Is.EqualTo(new List { })); + Assert.That(page?.MergeProps, Is.EqualTo(null)); } + } diff --git a/InertiaCoreTests/UnitTestResult.cs b/InertiaCoreTests/UnitTestResult.cs index f3ff8c0..38cfa34 100644 --- a/InertiaCoreTests/UnitTestResult.cs +++ b/InertiaCoreTests/UnitTestResult.cs @@ -1,6 +1,7 @@ using InertiaCore.Models; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using System.Text.Json; namespace InertiaCoreTests; @@ -40,6 +41,62 @@ public void TestJsonResult() { "test", "Test" }, { "errors", new Dictionary(0) } })); + + // Check the serialized JSON + var jsonString = JsonSerializer.Serialize(json); + var dictionary = JsonSerializer.Deserialize>(jsonString); + + Assert.That(dictionary, Is.Not.Null); + Assert.That(dictionary!.ContainsKey("MergeProps"), Is.False); + }); + } + [ + Test] + [Description("Test if the JSON result with merged data is created correctly.")] + public void TestJsonMergedResult() + { + var response = _factory.Render("Test/Page", new + { + Test = "Test", + TestMerged = _factory.Merge(() => "Merged") + }); + + var headers = new HeaderDictionary + { + { "X-Inertia", "true" } + }; + + var context = PrepareContext(headers); + + response.SetContext(context); + response.ProcessResponse(); + + var result = response.GetResult(); + + Assert.Multiple(() => + { + Assert.That(result, Is.InstanceOf(typeof(JsonResult))); + + var json = (result as JsonResult)?.Value; + Assert.That(json, Is.InstanceOf(typeof(Page))); + + Assert.That((json as Page)?.Component, Is.EqualTo("Test/Page")); + Assert.That((json as Page)?.Props, Is.EqualTo(new Dictionary + { + { "test", "Test" }, + { "testMerged", "Merged" }, + { "errors", new Dictionary(0) } + })); + Assert.That((json as Page)?.MergeProps, Is.EqualTo(new List { + "testMerged" + })); + + // Check the serialized JSON + var jsonString = JsonSerializer.Serialize(json); + var dictionary = JsonSerializer.Deserialize>(jsonString); + + Assert.That(dictionary, Is.Not.Null); + Assert.That(dictionary!.ContainsKey("MergeProps"), Is.True); }); } From b72751d61e40edd3a4f915388d7fcf2c24a3d4b6 Mon Sep 17 00:00:00 2001 From: Austin Drummond Date: Sat, 21 Dec 2024 16:55:38 -0500 Subject: [PATCH 19/31] add defer prop --- InertiaCore/Inertia.cs | 5 + InertiaCore/Models/Page.cs | 3 + InertiaCore/Response.cs | 38 ++- InertiaCore/ResponseFactory.cs | 4 + InertiaCore/Utils/DeferProp.cs | 46 ++++ InertiaCoreTests/UnitTestDeferData.cs | 326 ++++++++++++++++++++++++++ InertiaCoreTests/UnitTestResult.cs | 100 ++++++++ 7 files changed, 520 insertions(+), 2 deletions(-) create mode 100644 InertiaCore/Utils/DeferProp.cs create mode 100644 InertiaCoreTests/UnitTestDeferData.cs diff --git a/InertiaCore/Inertia.cs b/InertiaCore/Inertia.cs index aa87a2f..85dea75 100644 --- a/InertiaCore/Inertia.cs +++ b/InertiaCore/Inertia.cs @@ -1,4 +1,5 @@ using System.Runtime.CompilerServices; +using System.Text; using InertiaCore.Utils; using Microsoft.AspNetCore.Html; @@ -34,6 +35,10 @@ public static class Inertia public static AlwaysProp Always(Func> callback) => _factory.Always(callback); + public static DeferProp Defer(Func callback, string group = "default") => _factory.Defer(callback, group); + + public static DeferProp Defer(Func> callback, string group = "default") => _factory.Defer(callback, group); + public static MergeProp Merge(object? value) => _factory.Merge(value); public static MergeProp Merge(Func callback) => _factory.Merge(callback); diff --git a/InertiaCore/Models/Page.cs b/InertiaCore/Models/Page.cs index 93ab0a2..b94ff10 100644 --- a/InertiaCore/Models/Page.cs +++ b/InertiaCore/Models/Page.cs @@ -11,4 +11,7 @@ internal class Page [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public List? MergeProps { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public Dictionary>? DeferredProps { get; set; } } diff --git a/InertiaCore/Response.cs b/InertiaCore/Response.cs index 78bab4f..110e70b 100644 --- a/InertiaCore/Response.cs +++ b/InertiaCore/Response.cs @@ -42,6 +42,7 @@ protected internal void ProcessResponse() }; page.MergeProps = ResolveMergeProps(page.Props); + page.DeferredProps = ResolveDeferredProps(page.Props); var shared = _context!.HttpContext.Features.Get(); if (shared != null) @@ -58,9 +59,10 @@ protected internal void ProcessResponse() { Func f => f.Invoke(), LazyProp l => l.Invoke(), - OptionalProp l => l.Invoke(), - AlwaysProp l => l.Invoke(), + OptionalProp o => o.Invoke(), + AlwaysProp a => a.Invoke(), MergeProp m => m.Invoke(), + DeferProp d => d.Invoke(), _ => pair.Value }); } @@ -194,4 +196,36 @@ public Response WithViewData(IDictionary viewData) // Return the result return mergeProps; } + + private Dictionary>? ResolveDeferredProps(Dictionary props) + { + + bool isPartial = _context!.IsInertiaPartialComponent(_component); + if (isPartial) + { + return null; + } + + var deferredProps = _props.GetType().GetProperties().ToDictionary(o => o.Name.ToCamelCase(), o => o.GetValue(_props)) + .Where(kv => kv.Value is DeferProp) // Filter props that are instances of DeferProp + .Select(kv => new + { + Key = kv.Key, + Group = ((DeferProp)kv.Value!).Group() + }) // Map each prop to a new object with Key and Group + + .GroupBy(x => x.Group) // Group by 'Group' + .ToDictionary( + g => g.Key!, + g => g.Select(x => x.Key).ToList() // Extract 'Key' for each group + ); + + if (deferredProps.Count == 0) + { + return null; + } + + // Return the result + return deferredProps; + } } diff --git a/InertiaCore/ResponseFactory.cs b/InertiaCore/ResponseFactory.cs index cbece28..b565321 100644 --- a/InertiaCore/ResponseFactory.cs +++ b/InertiaCore/ResponseFactory.cs @@ -26,6 +26,8 @@ internal interface IResponseFactory public AlwaysProp Always(Func> callback); public LazyProp Lazy(Func callback); public LazyProp Lazy(Func> callback); + public DeferProp Defer(Func callback, string group = "default"); + public DeferProp Defer(Func> callback, string group = "default"); public MergeProp Merge(object? value); public MergeProp Merge(Func callback); public MergeProp Merge(Func> callback); @@ -132,6 +134,8 @@ public void Share(IDictionary data) public AlwaysProp Always(object? value) => new AlwaysProp(value); public AlwaysProp Always(Func callback) => new AlwaysProp(callback); public AlwaysProp Always(Func> callback) => new AlwaysProp(callback); + public DeferProp Defer(Func callback, string group = "default") => new DeferProp(callback, group); + public DeferProp Defer(Func> callback, string group = "default") => new DeferProp(callback, group); public MergeProp Merge(object? value) => new MergeProp(value); public MergeProp Merge(Func callback) => new MergeProp(callback); public MergeProp Merge(Func> callback) => new MergeProp(callback); diff --git a/InertiaCore/Utils/DeferProp.cs b/InertiaCore/Utils/DeferProp.cs new file mode 100644 index 0000000..ab0b831 --- /dev/null +++ b/InertiaCore/Utils/DeferProp.cs @@ -0,0 +1,46 @@ +namespace InertiaCore.Utils; + +public class DeferProp : IgnoreFirstLoad, Mergeable +{ + public bool merge { get; set; } + + private readonly object? _value; + protected readonly string _group; + + public DeferProp(object? value, string group) + { + _value = value; + _group = group; + } + + public Mergeable Merge() + { + merge = true; + + return this; + } + + public string? Group() + { + return _group; + } + + public object? Invoke() + { + // Check if the value is a callable delegate + return Task.Run(async () => + { + if (_value is Func> asyncCallable) + { + return await asyncCallable.Invoke(); + } + + if (_value is Func callable) + { + return callable.Invoke(); + } + + return _value; + }).GetAwaiter().GetResult(); + } +} diff --git a/InertiaCoreTests/UnitTestDeferData.cs b/InertiaCoreTests/UnitTestDeferData.cs new file mode 100644 index 0000000..ca6e9ee --- /dev/null +++ b/InertiaCoreTests/UnitTestDeferData.cs @@ -0,0 +1,326 @@ +using InertiaCore.Models; +using Microsoft.AspNetCore.Http; + +namespace InertiaCoreTests; + +public partial class Tests +{ + [Test] + [Description("Test if the defer data is fetched properly.")] + public void TestDeferData() + { + var response = _factory.Render("Test/Page", new + { + Test = "Test", + TestFunc = new Func(() => "Func"), + TestDefer = _factory.Defer(() => + { + return "Defer"; + }) + }); + + var context = PrepareContext(); + + response.SetContext(context); + response.ProcessResponse(); + + var page = response.GetJson().Value as Page; + + Assert.That(page?.Props, Is.EqualTo(new Dictionary + { + { "test", "Test" }, + { "testFunc", "Func" }, + { "errors", new Dictionary(0) } + })); + Assert.That(page?.DeferredProps, Is.EqualTo(new Dictionary> { + { "default", new List { "testDefer" } } + })); + Assert.That(page?.MergeProps, Is.EqualTo(null)); + } + + [Test] + [Description("Test if the defer data is fetched properly with specified partial props.")] + public void TestDeferPartialData() + { + var response = _factory.Render("Test/Page", new + { + TestFunc = new Func(() => "Func"), + TestDefer = _factory.Defer(() => "Deferred") + }); + + var headers = new HeaderDictionary + { + { "X-Inertia-Partial-Data", "testFunc,testDefer" }, + { "X-Inertia-Partial-Component", "Test/Page" } + }; + + var context = PrepareContext(headers); + + response.SetContext(context); + response.ProcessResponse(); + + var page = response.GetJson().Value as Page; + + Assert.That(page?.Props, Is.EqualTo(new Dictionary + { + { "testFunc", "Func" }, + { "testDefer", "Deferred" }, + { "errors", new Dictionary(0) } + })); + + Assert.That(page?.DeferredProps, Is.EqualTo(null)); + Assert.That(page?.MergeProps, Is.EqualTo(null)); + } + + [Test] + [Description("Test if the defer/merge data is fetched properly with specified partial props.")] + public void TestDeferMergePartialData() + { + var response = _factory.Render("Test/Page", new + { + TestFunc = new Func(() => "Func"), + TestDefer = _factory.Defer(() => "Deferred").Merge() + }); + + var headers = new HeaderDictionary + { + { "X-Inertia-Partial-Data", "testFunc,testDefer" }, + { "X-Inertia-Partial-Component", "Test/Page" } + }; + + var context = PrepareContext(headers); + + response.SetContext(context); + response.ProcessResponse(); + + var page = response.GetJson().Value as Page; + + Assert.That(page?.Props, Is.EqualTo(new Dictionary + { + { "testFunc", "Func" }, + { "testDefer", "Deferred" }, + { "errors", new Dictionary(0) } + })); + + Assert.That(page?.DeferredProps, Is.EqualTo(null)); + Assert.That(page?.MergeProps, Is.EqualTo(new List { "testDefer" })); + } + + [Test] + [Description("Test if the defer async data is fetched properly.")] + public void TestDeferAsyncData() + { + var testFunction = new Func>(async () => + { + await Task.Delay(100); + return "Defer Async"; + }); + + var response = _factory.Render("Test/Page", new + { + Test = "Test", + TestFunc = new Func(() => "Func"), + TestDefer = _factory.Defer(testFunction) + }); + + var context = PrepareContext(); + + response.SetContext(context); + response.ProcessResponse(); + + var page = response.GetJson().Value as Page; + + Assert.That(page?.Props, Is.EqualTo(new Dictionary + { + { "test", "Test" }, + { "testFunc", "Func" }, + { "errors", new Dictionary(0) } + })); + Assert.That(page?.DeferredProps, Is.EqualTo(new Dictionary> { + { "default", new List { "testDefer" } } + })); + Assert.That(page?.MergeProps, Is.EqualTo(null)); + } + + [Test] + [Description("Test if the defer async data is fetched properly with specified partial props.")] + public void TestDeferAsyncPartialData() + { + var testFunction = new Func>(async () => + { + await Task.Delay(100); + return "Defer Async"; + }); + + var response = _factory.Render("Test/Page", new + { + TestFunc = new Func(() => "Func"), + TestDefer = _factory.Defer(async () => await testFunction()) + }); + + var headers = new HeaderDictionary + { + { "X-Inertia-Partial-Data", "testFunc,testDefer" }, + { "X-Inertia-Partial-Component", "Test/Page" } + }; + + var context = PrepareContext(headers); + + response.SetContext(context); + response.ProcessResponse(); + + var page = response.GetJson().Value as Page; + + Assert.That(page?.Props, Is.EqualTo(new Dictionary + { + { "testFunc", "Func" }, + { "testDefer", "Defer Async" }, + { "errors", new Dictionary(0) } + })); + + Assert.That(page?.DeferredProps, Is.EqualTo(null)); + Assert.That(page?.MergeProps, Is.EqualTo(null)); + } + + [Test] + [Description("Test if the defer & merge async data is fetched properly with specified partial props.")] + public void TestDeferMergeAsyncPartialData() + { + var testFunction = new Func>(async () => + { + await Task.Delay(100); + return "Defer Async"; + }); + + var response = _factory.Render("Test/Page", new + { + TestFunc = new Func(() => "Func"), + TestDefer = _factory.Defer(async () => await testFunction()).Merge() + }); + + var headers = new HeaderDictionary + { + { "X-Inertia-Partial-Data", "testFunc,testDefer" }, + { "X-Inertia-Partial-Component", "Test/Page" } + }; + + var context = PrepareContext(headers); + + response.SetContext(context); + response.ProcessResponse(); + + var page = response.GetJson().Value as Page; + + Assert.That(page?.Props, Is.EqualTo(new Dictionary + { + { "testFunc", "Func" }, + { "testDefer", "Defer Async" }, + { "errors", new Dictionary(0) } + })); + + Assert.That(page?.DeferredProps, Is.EqualTo(null)); + Assert.That(page?.MergeProps, Is.EqualTo(new List { "testDefer" })); + } + + [Test] + [Description("Test if the defer async data is fetched properly without specified partial props.")] + public void TestDeferAsyncPartialDataOmitted() + { + var testFunction = new Func>(async () => + { + await Task.Delay(100); + return "Defer Async"; + }); + + var response = _factory.Render("Test/Page", new + { + TestFunc = new Func(() => "Func"), + TestDefer = _factory.Defer(async () => await testFunction()) + }); + + var headers = new HeaderDictionary + { + { "X-Inertia-Partial-Data", "testFunc" }, + { "X-Inertia-Partial-Component", "Test/Page" } + }; + + var context = PrepareContext(headers); + + response.SetContext(context); + response.ProcessResponse(); + + var page = response.GetJson().Value as Page; + + Assert.That(page?.Props, Is.EqualTo(new Dictionary + { + { "testFunc", "Func" }, + { "errors", new Dictionary(0) } + })); + + Assert.That(page?.DeferredProps, Is.EqualTo(null)); + Assert.That(page?.MergeProps, Is.EqualTo(null)); + } + + public void TestNoDeferredProps() + { + var response = _factory.Render("Test/Page", new + { + Test = "Test", + TestFunc = new Func(() => "Func"), + }); + + var context = PrepareContext(); + + response.SetContext(context); + response.ProcessResponse(); + + var page = response.GetJson().Value as Page; + + Assert.That(page?.Props, Is.EqualTo(new Dictionary + { + { "test", "Test" }, + { "testFunc", "Func" }, + { "errors", new Dictionary(0) } + })); + Assert.That(page?.DeferredProps, Is.EqualTo(null)); + Assert.That(page?.MergeProps, Is.EqualTo(null)); + } + + [Test] + [Description("Test if the defer data with multiple groups is fetched properly.")] + public void TestDeferMultipleGroupsData() + { + var response = _factory.Render("Test/Page", new + { + Test = "Test", + TestFunc = new Func(() => "Func"), + TestDefer = _factory.Defer(() => + { + return "Defer"; + }), + TestStats = _factory.Defer(() => + { + return "Stat"; + }, "stats") + }); + + var context = PrepareContext(); + + response.SetContext(context); + response.ProcessResponse(); + + var page = response.GetJson().Value as Page; + + Assert.That(page?.Props, Is.EqualTo(new Dictionary + { + { "test", "Test" }, + { "testFunc", "Func" }, + { "errors", new Dictionary(0) } + })); + Assert.That(page?.DeferredProps, Is.EqualTo(new Dictionary> { + { "default", new List { "testDefer" } }, + { "stats", new List { "testStats" } }, + })); + Assert.That(page?.MergeProps, Is.EqualTo(null)); + } +} diff --git a/InertiaCoreTests/UnitTestResult.cs b/InertiaCoreTests/UnitTestResult.cs index 38cfa34..771ab51 100644 --- a/InertiaCoreTests/UnitTestResult.cs +++ b/InertiaCoreTests/UnitTestResult.cs @@ -1,4 +1,5 @@ using InertiaCore.Models; +using InertiaCore.Utils; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using System.Text.Json; @@ -48,6 +49,7 @@ public void TestJsonResult() Assert.That(dictionary, Is.Not.Null); Assert.That(dictionary!.ContainsKey("MergeProps"), Is.False); + Assert.That(dictionary!.ContainsKey("DeferredProps"), Is.False); }); } [ @@ -100,6 +102,104 @@ public void TestJsonMergedResult() }); } + [Description("Test if the JSON result with deferred data is created correctly.")] + public void TestJsonDeferredResult() + { + var response = _factory.Render("Test/Page", new + { + Test = "Test", + TestDeferred = _factory.Defer(() => "Deferred") + }); + + var headers = new HeaderDictionary + { + { "X-Inertia", "true" } + }; + + var context = PrepareContext(headers); + + response.SetContext(context); + response.ProcessResponse(); + + var result = response.GetResult(); + + Assert.Multiple(() => + { + Assert.That(result, Is.InstanceOf(typeof(JsonResult))); + + var json = (result as JsonResult)?.Value; + Assert.That(json, Is.InstanceOf(typeof(Page))); + + Assert.That((json as Page)?.Component, Is.EqualTo("Test/Page")); + Assert.That((json as Page)?.Props, Is.EqualTo(new Dictionary + { + { "test", "Test" }, + { "testDeferred", "Deferred" }, + { "errors", new Dictionary(0) } + })); + Assert.That((json as Page)?.MergeProps, Is.EqualTo(null)); + // Assert.That((json as Page)?.MergeProps, Is.EqualTo(new List { + // "testDeferred" + // })); + + // Check the serialized JSON + var jsonString = JsonSerializer.Serialize(json); + var dictionary = JsonSerializer.Deserialize>(jsonString); + + Assert.That(dictionary, Is.Not.Null); + Assert.That(dictionary!.ContainsKey("MergeProps"), Is.False); + Assert.That(dictionary!.ContainsKey("DeferredProps"), Is.True); + }); + } + + [Description("Test if the JSON result with deferred & merge data is created correctly.")] + public void TestJsonMergeDeferredResult() + { + var response = _factory.Render("Test/Page", new + { + Test = "Test", + TestMerged = _factory.Defer(() => "Merged").Merge(), + }); + + var headers = new HeaderDictionary + { + { "X-Inertia", "true" } + }; + + var context = PrepareContext(headers); + + response.SetContext(context); + response.ProcessResponse(); + + var result = response.GetResult(); + + Assert.Multiple(() => + { + Assert.That(result, Is.InstanceOf(typeof(JsonResult))); + + var json = (result as JsonResult)?.Value; + Assert.That(json, Is.InstanceOf(typeof(Page))); + + Assert.That((json as Page)?.Component, Is.EqualTo("Test/Page")); + Assert.That((json as Page)?.Props, Is.EqualTo(new Dictionary + { + { "test", "Test" }, + { "testMerged", "Merged" }, + { "errors", new Dictionary(0) } + })); + Assert.That((json as Page)?.MergeProps, Is.EqualTo(new List { + "testMerged" + })); + + // Check the serialized JSON + var jsonString = JsonSerializer.Serialize(json); + var dictionary = JsonSerializer.Deserialize>(jsonString); + + Assert.That(dictionary, Is.Not.Null); + Assert.That(dictionary!.ContainsKey("MergeProps"), Is.True); + }); + } + [Test] [Description("Test if the view result is created correctly.")] public void TestViewResult() From f362bda2aaf54e7341298118fd5b592fd8eeccd4 Mon Sep 17 00:00:00 2001 From: Austin Drummond Date: Sat, 21 Dec 2024 17:03:50 -0500 Subject: [PATCH 20/31] fix result test of deferred props --- InertiaCoreTests/UnitTestResult.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/InertiaCoreTests/UnitTestResult.cs b/InertiaCoreTests/UnitTestResult.cs index 771ab51..2d635f3 100644 --- a/InertiaCoreTests/UnitTestResult.cs +++ b/InertiaCoreTests/UnitTestResult.cs @@ -102,6 +102,7 @@ public void TestJsonMergedResult() }); } + [Test] [Description("Test if the JSON result with deferred data is created correctly.")] public void TestJsonDeferredResult() { @@ -134,13 +135,9 @@ public void TestJsonDeferredResult() Assert.That((json as Page)?.Props, Is.EqualTo(new Dictionary { { "test", "Test" }, - { "testDeferred", "Deferred" }, { "errors", new Dictionary(0) } })); Assert.That((json as Page)?.MergeProps, Is.EqualTo(null)); - // Assert.That((json as Page)?.MergeProps, Is.EqualTo(new List { - // "testDeferred" - // })); // Check the serialized JSON var jsonString = JsonSerializer.Serialize(json); @@ -152,6 +149,7 @@ public void TestJsonDeferredResult() }); } + [Test] [Description("Test if the JSON result with deferred & merge data is created correctly.")] public void TestJsonMergeDeferredResult() { @@ -163,7 +161,9 @@ public void TestJsonMergeDeferredResult() var headers = new HeaderDictionary { - { "X-Inertia", "true" } + { Header.Inertia, "true" }, + { Header.PartialComponent, "Test/Page" }, + { Header.PartialOnly, "testMerged" }, }; var context = PrepareContext(headers); @@ -183,13 +183,13 @@ public void TestJsonMergeDeferredResult() Assert.That((json as Page)?.Component, Is.EqualTo("Test/Page")); Assert.That((json as Page)?.Props, Is.EqualTo(new Dictionary { - { "test", "Test" }, { "testMerged", "Merged" }, { "errors", new Dictionary(0) } })); Assert.That((json as Page)?.MergeProps, Is.EqualTo(new List { "testMerged" })); + Assert.That((json as Page)?.DeferredProps, Is.EqualTo(null)); // Check the serialized JSON var jsonString = JsonSerializer.Serialize(json); @@ -197,6 +197,7 @@ public void TestJsonMergeDeferredResult() Assert.That(dictionary, Is.Not.Null); Assert.That(dictionary!.ContainsKey("MergeProps"), Is.True); + Assert.That(dictionary!.ContainsKey("DeferredProps"), Is.False); }); } From 21a8f65ec643955981362902bb448757d817d53f Mon Sep 17 00:00:00 2001 From: Austin Drummond Date: Sat, 21 Dec 2024 17:04:05 -0500 Subject: [PATCH 21/31] fix formatting --- InertiaCoreTests/UnitTestResult.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/InertiaCoreTests/UnitTestResult.cs b/InertiaCoreTests/UnitTestResult.cs index 38cfa34..38988bb 100644 --- a/InertiaCoreTests/UnitTestResult.cs +++ b/InertiaCoreTests/UnitTestResult.cs @@ -50,8 +50,8 @@ public void TestJsonResult() Assert.That(dictionary!.ContainsKey("MergeProps"), Is.False); }); } - [ - Test] + + [Test] [Description("Test if the JSON result with merged data is created correctly.")] public void TestJsonMergedResult() { From cf2c45e05050c3c597c2d3e0dc4acb573833af47 Mon Sep 17 00:00:00 2001 From: kapi2289 Date: Mon, 6 Jan 2025 18:00:31 +0100 Subject: [PATCH 22/31] Minor fixes and changes --- InertiaCore/Props/LazyProp.cs | 23 +++++++++-------------- InertiaCore/Props/OptionalProp.cs | 23 +++++++++-------------- InertiaCore/Response.cs | 2 +- InertiaCore/Utils/IIgnoresFirstLoad.cs | 5 +++++ InertiaCore/Utils/IgnoreFirstLoad.cs | 6 ------ 5 files changed, 24 insertions(+), 35 deletions(-) create mode 100644 InertiaCore/Utils/IIgnoresFirstLoad.cs delete mode 100644 InertiaCore/Utils/IgnoreFirstLoad.cs diff --git a/InertiaCore/Props/LazyProp.cs b/InertiaCore/Props/LazyProp.cs index ade983e..8254830 100644 --- a/InertiaCore/Props/LazyProp.cs +++ b/InertiaCore/Props/LazyProp.cs @@ -2,7 +2,7 @@ namespace InertiaCore.Props; -public class LazyProp : IgnoreFirstLoad +public class LazyProp : IIgnoresFirstLoad { private readonly object? _value; @@ -13,18 +13,13 @@ public class LazyProp : IgnoreFirstLoad { // Check if the value is a callable delegate return Task.Run(async () => - { - if (_value is Func> asyncCallable) - { - return await asyncCallable.Invoke(); - } - - if (_value is Func callable) - { - return callable.Invoke(); - } - - return _value; - }).GetAwaiter().GetResult(); + { + return _value switch + { + Func> asyncCallable => await asyncCallable.Invoke(), + Func callable => callable.Invoke(), + _ => _value + }; + }).GetAwaiter().GetResult(); } } diff --git a/InertiaCore/Props/OptionalProp.cs b/InertiaCore/Props/OptionalProp.cs index d084786..9709b4f 100644 --- a/InertiaCore/Props/OptionalProp.cs +++ b/InertiaCore/Props/OptionalProp.cs @@ -2,7 +2,7 @@ namespace InertiaCore.Props; -public class OptionalProp : IgnoreFirstLoad +public class OptionalProp : IIgnoresFirstLoad { private readonly object? _value; @@ -13,18 +13,13 @@ public class OptionalProp : IgnoreFirstLoad { // Check if the value is a callable delegate return Task.Run(async () => - { - if (_value is Func> asyncCallable) - { - return await asyncCallable.Invoke(); - } - - if (_value is Func callable) - { - return callable.Invoke(); - } - - return _value; - }).GetAwaiter().GetResult(); + { + return _value switch + { + Func> asyncCallable => await asyncCallable.Invoke(), + Func callable => callable.Invoke(), + _ => _value + }; + }).GetAwaiter().GetResult(); } } diff --git a/InertiaCore/Response.cs b/InertiaCore/Response.cs index e071667..890bc5c 100644 --- a/InertiaCore/Response.cs +++ b/InertiaCore/Response.cs @@ -119,7 +119,7 @@ public Response WithViewData(IDictionary viewData) if (!isPartial) { props = props - .Where(kv => (kv.Value as IgnoreFirstLoad) == null) + .Where(kv => kv.Value is not IIgnoresFirstLoad) .ToDictionary(kv => kv.Key, kv => kv.Value); } else diff --git a/InertiaCore/Utils/IIgnoresFirstLoad.cs b/InertiaCore/Utils/IIgnoresFirstLoad.cs new file mode 100644 index 0000000..10fc9ba --- /dev/null +++ b/InertiaCore/Utils/IIgnoresFirstLoad.cs @@ -0,0 +1,5 @@ +namespace InertiaCore.Utils; + +public interface IIgnoresFirstLoad +{ +} diff --git a/InertiaCore/Utils/IgnoreFirstLoad.cs b/InertiaCore/Utils/IgnoreFirstLoad.cs deleted file mode 100644 index f5ff0f7..0000000 --- a/InertiaCore/Utils/IgnoreFirstLoad.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace InertiaCore.Utils; - -public interface IgnoreFirstLoad -{ - -} From 42396a772c0efdf572f262ae7775c499427e58fb Mon Sep 17 00:00:00 2001 From: kapi2289 Date: Mon, 6 Jan 2025 18:04:20 +0100 Subject: [PATCH 23/31] Add missing Inertia static methods --- InertiaCore/Inertia.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/InertiaCore/Inertia.cs b/InertiaCore/Inertia.cs index 9220dc6..6491693 100644 --- a/InertiaCore/Inertia.cs +++ b/InertiaCore/Inertia.cs @@ -29,13 +29,17 @@ public static class Inertia public static void Share(IDictionary data) => _factory.Share(data); + public static LazyProp Lazy(Func callback) => _factory.Lazy(callback); + + public static LazyProp Lazy(Func> callback) => _factory.Lazy(callback); + public static AlwaysProp Always(object? value) => _factory.Always(value); public static AlwaysProp Always(Func callback) => _factory.Always(callback); public static AlwaysProp Always(Func> callback) => _factory.Always(callback); - public static LazyProp Lazy(Func callback) => _factory.Lazy(callback); + public static OptionalProp Optional(Func callback) => _factory.Optional(callback); - public static LazyProp Lazy(Func> callback) => _factory.Lazy(callback); + public static OptionalProp Optional(Func> callback) => _factory.Optional(callback); } From ea4592ee5ae4abefd9af255d8681be37133bd1e7 Mon Sep 17 00:00:00 2001 From: Austin Drummond Date: Fri, 10 Jan 2025 21:32:27 -0500 Subject: [PATCH 24/31] make optional prop invokable --- InertiaCore/Props/OptionalProp.cs | 21 +++++---------------- InertiaCoreTests/UnitTestOptionalData.cs | 16 ++++++++-------- 2 files changed, 13 insertions(+), 24 deletions(-) diff --git a/InertiaCore/Props/OptionalProp.cs b/InertiaCore/Props/OptionalProp.cs index 9709b4f..cf9c971 100644 --- a/InertiaCore/Props/OptionalProp.cs +++ b/InertiaCore/Props/OptionalProp.cs @@ -2,24 +2,13 @@ namespace InertiaCore.Props; -public class OptionalProp : IIgnoresFirstLoad +public class OptionalProp : InvokableProp, IIgnoresFirstLoad { - private readonly object? _value; - - public OptionalProp(Func callback) => _value = callback; - public OptionalProp(Func> callback) => _value = callback; + internal OptionalProp(Func value) : base(value) + { + } - public object? Invoke() + internal OptionalProp(Func> value) : base(value) { - // Check if the value is a callable delegate - return Task.Run(async () => - { - return _value switch - { - Func> asyncCallable => await asyncCallable.Invoke(), - Func callable => callable.Invoke(), - _ => _value - }; - }).GetAwaiter().GetResult(); } } diff --git a/InertiaCoreTests/UnitTestOptionalData.cs b/InertiaCoreTests/UnitTestOptionalData.cs index 8d8365f..3d925ec 100644 --- a/InertiaCoreTests/UnitTestOptionalData.cs +++ b/InertiaCoreTests/UnitTestOptionalData.cs @@ -7,7 +7,7 @@ public partial class Tests { [Test] [Description("Test if the optional data is fetched properly.")] - public void TestOptionalData() + public async Task TestOptionalData() { var response = _factory.Render("Test/Page", new { @@ -23,7 +23,7 @@ public void TestOptionalData() var context = PrepareContext(); response.SetContext(context); - response.ProcessResponse(); + await response.ProcessResponse(); var page = response.GetJson().Value as Page; @@ -37,7 +37,7 @@ public void TestOptionalData() [Test] [Description("Test if the optional data is fetched properly with specified partial props.")] - public void TestOptionalPartialData() + public async Task TestOptionalPartialData() { var response = _factory.Render("Test/Page", new { @@ -54,7 +54,7 @@ public void TestOptionalPartialData() var context = PrepareContext(headers); response.SetContext(context); - response.ProcessResponse(); + await response.ProcessResponse(); var page = response.GetJson().Value as Page; @@ -69,7 +69,7 @@ public void TestOptionalPartialData() [Test] [Description("Test if the optional async data is fetched properly.")] - public void TestOptionalAsyncData() + public async Task TestOptionalAsyncData() { var testFunction = new Func>(async () => { @@ -88,7 +88,7 @@ public void TestOptionalAsyncData() var context = PrepareContext(); response.SetContext(context); - response.ProcessResponse(); + await response.ProcessResponse(); var page = response.GetJson().Value as Page; @@ -102,7 +102,7 @@ public void TestOptionalAsyncData() [Test] [Description("Test if the optional async data is fetched properly with specified partial props.")] - public void TestOptionalAsyncPartialData() + public async Task TestOptionalAsyncPartialData() { var testFunction = new Func>(async () => { @@ -125,7 +125,7 @@ public void TestOptionalAsyncPartialData() var context = PrepareContext(headers); response.SetContext(context); - response.ProcessResponse(); + await response.ProcessResponse(); var page = response.GetJson().Value as Page; From 1e949b0db5db84d2429c4593ea01aac783bab8c9 Mon Sep 17 00:00:00 2001 From: Austin Drummond Date: Fri, 10 Jan 2025 21:42:03 -0500 Subject: [PATCH 25/31] fix merge prop tests --- InertiaCore/Props/MergeProp.cs | 25 ++++++++++++++++++++ InertiaCore/Utils/MergeProp.cs | 33 --------------------------- InertiaCoreTests/UnitTestMergeData.cs | 24 +++++++++---------- InertiaCoreTests/UnitTestResult.cs | 4 ++-- 4 files changed, 39 insertions(+), 47 deletions(-) create mode 100644 InertiaCore/Props/MergeProp.cs delete mode 100644 InertiaCore/Utils/MergeProp.cs diff --git a/InertiaCore/Props/MergeProp.cs b/InertiaCore/Props/MergeProp.cs new file mode 100644 index 0000000..6d41883 --- /dev/null +++ b/InertiaCore/Props/MergeProp.cs @@ -0,0 +1,25 @@ +using InertiaCore.Props; + +namespace InertiaCore.Utils; + +public class MergeProp : InvokableProp, Mergeable +{ + public bool merge { get; set; } = true; + + public MergeProp(object? value) : base(value) + { + merge = true; + } + + internal MergeProp(Func value) : base(value) + { + merge = true; + } + + internal MergeProp(Func> value) : base(value) + { + merge = true; + } +} + + diff --git a/InertiaCore/Utils/MergeProp.cs b/InertiaCore/Utils/MergeProp.cs deleted file mode 100644 index c26b7ad..0000000 --- a/InertiaCore/Utils/MergeProp.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace InertiaCore.Utils; - -public class MergeProp : Mergeable -{ - public bool merge { get; set; } = true; - - private readonly object? _value; - - public MergeProp(object? value) - { - _value = value; - merge = true; - } - - public object? Invoke() - { - // Check if the value is a callable delegate - return Task.Run(async () => - { - if (_value is Func> asyncCallable) - { - return await asyncCallable.Invoke(); - } - - if (_value is Func callable) - { - return callable.Invoke(); - } - - return _value; - }).GetAwaiter().GetResult(); - } -} diff --git a/InertiaCoreTests/UnitTestMergeData.cs b/InertiaCoreTests/UnitTestMergeData.cs index 6f35a0c..844aaeb 100644 --- a/InertiaCoreTests/UnitTestMergeData.cs +++ b/InertiaCoreTests/UnitTestMergeData.cs @@ -7,7 +7,7 @@ public partial class Tests { [Test] [Description("Test if the merge data is fetched properly.")] - public void TestMergeData() + public async Task TestMergeData() { var response = _factory.Render("Test/Page", new { @@ -22,7 +22,7 @@ public void TestMergeData() var context = PrepareContext(); response.SetContext(context); - response.ProcessResponse(); + await response.ProcessResponse(); var page = response.GetJson().Value as Page; @@ -38,7 +38,7 @@ public void TestMergeData() [Test] [Description("Test if the merge data is fetched properly with specified partial props.")] - public void TestMergePartialData() + public async Task TestMergePartialData() { var response = _factory.Render("Test/Page", new { @@ -55,7 +55,7 @@ public void TestMergePartialData() var context = PrepareContext(headers); response.SetContext(context); - response.ProcessResponse(); + await response.ProcessResponse(); var page = response.GetJson().Value as Page; @@ -71,7 +71,7 @@ public void TestMergePartialData() [Test] [Description("Test if the merge async data is fetched properly.")] - public void TestMergeAsyncData() + public async Task TestMergeAsyncData() { var testFunction = new Func>(async () => { @@ -89,7 +89,7 @@ public void TestMergeAsyncData() var context = PrepareContext(); response.SetContext(context); - response.ProcessResponse(); + await response.ProcessResponse(); var page = response.GetJson().Value as Page; @@ -105,7 +105,7 @@ public void TestMergeAsyncData() [Test] [Description("Test if the merge async data is fetched properly with specified partial props.")] - public void TestMergeAsyncPartialData() + public async Task TestMergeAsyncPartialData() { var testFunction = new Func>(async () => { @@ -128,7 +128,7 @@ public void TestMergeAsyncPartialData() var context = PrepareContext(headers); response.SetContext(context); - response.ProcessResponse(); + await response.ProcessResponse(); var page = response.GetJson().Value as Page; @@ -144,7 +144,7 @@ public void TestMergeAsyncPartialData() [Test] [Description("Test if the merge async data is fetched properly without specified partial props.")] - public void TestMergeAsyncPartialDataOmitted() + public async Task TestMergeAsyncPartialDataOmitted() { var testFunction = new Func>(async () => { @@ -167,7 +167,7 @@ public void TestMergeAsyncPartialDataOmitted() var context = PrepareContext(headers); response.SetContext(context); - response.ProcessResponse(); + await response.ProcessResponse(); var page = response.GetJson().Value as Page; @@ -180,7 +180,7 @@ public void TestMergeAsyncPartialDataOmitted() Assert.That(page?.MergeProps, Is.EqualTo(null)); } - public void TestNoMergeProps() + public async Task TestNoMergeProps() { var response = _factory.Render("Test/Page", new { @@ -191,7 +191,7 @@ public void TestNoMergeProps() var context = PrepareContext(); response.SetContext(context); - response.ProcessResponse(); + await response.ProcessResponse(); var page = response.GetJson().Value as Page; diff --git a/InertiaCoreTests/UnitTestResult.cs b/InertiaCoreTests/UnitTestResult.cs index 8fb08c6..36e4cc9 100644 --- a/InertiaCoreTests/UnitTestResult.cs +++ b/InertiaCoreTests/UnitTestResult.cs @@ -53,7 +53,7 @@ public async Task TestJsonResult() [Test] [Description("Test if the JSON result with merged data is created correctly.")] - public void TestJsonMergedResult() + public async Task TestJsonMergedResult() { var response = _factory.Render("Test/Page", new { @@ -69,7 +69,7 @@ public void TestJsonMergedResult() var context = PrepareContext(headers); response.SetContext(context); - response.ProcessResponse(); + await response.ProcessResponse(); var result = response.GetResult(); From ad8c4762589386ad9d85f63a29b10effc0859152 Mon Sep 17 00:00:00 2001 From: Austin Drummond Date: Fri, 10 Jan 2025 21:49:12 -0500 Subject: [PATCH 26/31] fix test sdks? --- .github/workflows/dotnet.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index b78f820..599ad5b 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -23,7 +23,11 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v3 with: - dotnet-version: 9.0.x + dotnet-version: | + 6.0.x + 7.0.x + 8.0.x + 9.0.x - name: Restore dependencies run: dotnet restore - name: Build From 6333daa445558a6967f280585658af5230154353 Mon Sep 17 00:00:00 2001 From: Austin Drummond Date: Fri, 10 Jan 2025 21:49:12 -0500 Subject: [PATCH 27/31] fix test sdks? --- .github/workflows/dotnet.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index b78f820..599ad5b 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -23,7 +23,11 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v3 with: - dotnet-version: 9.0.x + dotnet-version: | + 6.0.x + 7.0.x + 8.0.x + 9.0.x - name: Restore dependencies run: dotnet restore - name: Build From 575f96fa0134ab1e0d0efd8d625ba05e42432dc1 Mon Sep 17 00:00:00 2001 From: Austin Drummond Date: Fri, 10 Jan 2025 21:59:40 -0500 Subject: [PATCH 28/31] fix defer after merge --- InertiaCore/Utils/DeferProp.cs | 38 +++++++++++++--------------------- 1 file changed, 14 insertions(+), 24 deletions(-) diff --git a/InertiaCore/Utils/DeferProp.cs b/InertiaCore/Utils/DeferProp.cs index ab0b831..6065fdf 100644 --- a/InertiaCore/Utils/DeferProp.cs +++ b/InertiaCore/Utils/DeferProp.cs @@ -1,15 +1,24 @@ +using InertiaCore.Props; + namespace InertiaCore.Utils; -public class DeferProp : IgnoreFirstLoad, Mergeable +public class DeferProp : InvokableProp, IgnoreFirstLoad, Mergeable { public bool merge { get; set; } + protected readonly string _group = "default"; - private readonly object? _value; - protected readonly string _group; + public DeferProp(object? value, string group) : base(value) + { + _group = group; + } - public DeferProp(object? value, string group) + internal DeferProp(Func value, string group) : base(value) + { + _group = group; + } + + internal DeferProp(Func> value, string group) : base(value) { - _value = value; _group = group; } @@ -24,23 +33,4 @@ public Mergeable Merge() { return _group; } - - public object? Invoke() - { - // Check if the value is a callable delegate - return Task.Run(async () => - { - if (_value is Func> asyncCallable) - { - return await asyncCallable.Invoke(); - } - - if (_value is Func callable) - { - return callable.Invoke(); - } - - return _value; - }).GetAwaiter().GetResult(); - } } From 07ea498a5d56238ff1c548a01e0943f4b84a1251 Mon Sep 17 00:00:00 2001 From: Austin Drummond Date: Fri, 10 Jan 2025 22:04:37 -0500 Subject: [PATCH 29/31] fix tests and namespace --- InertiaCore/{Utils => Props}/DeferProp.cs | 6 ++-- InertiaCoreTests/UnitTestDeferData.cs | 36 +++++++++++------------ InertiaCoreTests/UnitTestResult.cs | 8 ++--- 3 files changed, 25 insertions(+), 25 deletions(-) rename InertiaCore/{Utils => Props}/DeferProp.cs (82%) diff --git a/InertiaCore/Utils/DeferProp.cs b/InertiaCore/Props/DeferProp.cs similarity index 82% rename from InertiaCore/Utils/DeferProp.cs rename to InertiaCore/Props/DeferProp.cs index 6065fdf..0d421ac 100644 --- a/InertiaCore/Utils/DeferProp.cs +++ b/InertiaCore/Props/DeferProp.cs @@ -1,8 +1,8 @@ -using InertiaCore.Props; +using InertiaCore.Utils; -namespace InertiaCore.Utils; +namespace InertiaCore.Props; -public class DeferProp : InvokableProp, IgnoreFirstLoad, Mergeable +public class DeferProp : InvokableProp, IIgnoresFirstLoad, Mergeable { public bool merge { get; set; } protected readonly string _group = "default"; diff --git a/InertiaCoreTests/UnitTestDeferData.cs b/InertiaCoreTests/UnitTestDeferData.cs index ca6e9ee..67772d5 100644 --- a/InertiaCoreTests/UnitTestDeferData.cs +++ b/InertiaCoreTests/UnitTestDeferData.cs @@ -7,7 +7,7 @@ public partial class Tests { [Test] [Description("Test if the defer data is fetched properly.")] - public void TestDeferData() + public async Task TestDeferData() { var response = _factory.Render("Test/Page", new { @@ -22,7 +22,7 @@ public void TestDeferData() var context = PrepareContext(); response.SetContext(context); - response.ProcessResponse(); + await response.ProcessResponse(); var page = response.GetJson().Value as Page; @@ -40,7 +40,7 @@ public void TestDeferData() [Test] [Description("Test if the defer data is fetched properly with specified partial props.")] - public void TestDeferPartialData() + public async Task TestDeferPartialData() { var response = _factory.Render("Test/Page", new { @@ -57,7 +57,7 @@ public void TestDeferPartialData() var context = PrepareContext(headers); response.SetContext(context); - response.ProcessResponse(); + await response.ProcessResponse(); var page = response.GetJson().Value as Page; @@ -74,7 +74,7 @@ public void TestDeferPartialData() [Test] [Description("Test if the defer/merge data is fetched properly with specified partial props.")] - public void TestDeferMergePartialData() + public async Task TestDeferMergePartialData() { var response = _factory.Render("Test/Page", new { @@ -91,7 +91,7 @@ public void TestDeferMergePartialData() var context = PrepareContext(headers); response.SetContext(context); - response.ProcessResponse(); + await response.ProcessResponse(); var page = response.GetJson().Value as Page; @@ -108,7 +108,7 @@ public void TestDeferMergePartialData() [Test] [Description("Test if the defer async data is fetched properly.")] - public void TestDeferAsyncData() + public async Task TestDeferAsyncData() { var testFunction = new Func>(async () => { @@ -126,7 +126,7 @@ public void TestDeferAsyncData() var context = PrepareContext(); response.SetContext(context); - response.ProcessResponse(); + await response.ProcessResponse(); var page = response.GetJson().Value as Page; @@ -144,7 +144,7 @@ public void TestDeferAsyncData() [Test] [Description("Test if the defer async data is fetched properly with specified partial props.")] - public void TestDeferAsyncPartialData() + public async Task TestDeferAsyncPartialData() { var testFunction = new Func>(async () => { @@ -167,7 +167,7 @@ public void TestDeferAsyncPartialData() var context = PrepareContext(headers); response.SetContext(context); - response.ProcessResponse(); + await response.ProcessResponse(); var page = response.GetJson().Value as Page; @@ -184,7 +184,7 @@ public void TestDeferAsyncPartialData() [Test] [Description("Test if the defer & merge async data is fetched properly with specified partial props.")] - public void TestDeferMergeAsyncPartialData() + public async Task TestDeferMergeAsyncPartialData() { var testFunction = new Func>(async () => { @@ -207,7 +207,7 @@ public void TestDeferMergeAsyncPartialData() var context = PrepareContext(headers); response.SetContext(context); - response.ProcessResponse(); + await response.ProcessResponse(); var page = response.GetJson().Value as Page; @@ -224,7 +224,7 @@ public void TestDeferMergeAsyncPartialData() [Test] [Description("Test if the defer async data is fetched properly without specified partial props.")] - public void TestDeferAsyncPartialDataOmitted() + public async Task TestDeferAsyncPartialDataOmitted() { var testFunction = new Func>(async () => { @@ -247,7 +247,7 @@ public void TestDeferAsyncPartialDataOmitted() var context = PrepareContext(headers); response.SetContext(context); - response.ProcessResponse(); + await response.ProcessResponse(); var page = response.GetJson().Value as Page; @@ -261,7 +261,7 @@ public void TestDeferAsyncPartialDataOmitted() Assert.That(page?.MergeProps, Is.EqualTo(null)); } - public void TestNoDeferredProps() + public async Task TestNoDeferredProps() { var response = _factory.Render("Test/Page", new { @@ -272,7 +272,7 @@ public void TestNoDeferredProps() var context = PrepareContext(); response.SetContext(context); - response.ProcessResponse(); + await response.ProcessResponse(); var page = response.GetJson().Value as Page; @@ -288,7 +288,7 @@ public void TestNoDeferredProps() [Test] [Description("Test if the defer data with multiple groups is fetched properly.")] - public void TestDeferMultipleGroupsData() + public async Task TestDeferMultipleGroupsData() { var response = _factory.Render("Test/Page", new { @@ -307,7 +307,7 @@ public void TestDeferMultipleGroupsData() var context = PrepareContext(); response.SetContext(context); - response.ProcessResponse(); + await response.ProcessResponse(); var page = response.GetJson().Value as Page; diff --git a/InertiaCoreTests/UnitTestResult.cs b/InertiaCoreTests/UnitTestResult.cs index b1e6517..48c6db1 100644 --- a/InertiaCoreTests/UnitTestResult.cs +++ b/InertiaCoreTests/UnitTestResult.cs @@ -104,7 +104,7 @@ public async Task TestJsonMergedResult() [Test] [Description("Test if the JSON result with deferred data is created correctly.")] - public void TestJsonDeferredResult() + public async Task TestJsonDeferredResult() { var response = _factory.Render("Test/Page", new { @@ -120,7 +120,7 @@ public void TestJsonDeferredResult() var context = PrepareContext(headers); response.SetContext(context); - response.ProcessResponse(); + await response.ProcessResponse(); var result = response.GetResult(); @@ -151,7 +151,7 @@ public void TestJsonDeferredResult() [Test] [Description("Test if the JSON result with deferred & merge data is created correctly.")] - public void TestJsonMergeDeferredResult() + public async Task TestJsonMergeDeferredResult() { var response = _factory.Render("Test/Page", new { @@ -169,7 +169,7 @@ public void TestJsonMergeDeferredResult() var context = PrepareContext(headers); response.SetContext(context); - response.ProcessResponse(); + await response.ProcessResponse(); var result = response.GetResult(); From 2ae0bffe995101e9fd114729a76bd50fe823bcbc Mon Sep 17 00:00:00 2001 From: Austin Drummond Date: Sat, 8 Feb 2025 11:31:14 -0500 Subject: [PATCH 30/31] revert formatting --- .github/workflows/dotnet.yml | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 6788a6d..dfea3e2 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -15,21 +15,22 @@ on: jobs: build: + runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - name: Setup .NET - uses: actions/setup-dotnet@v3 - with: - dotnet-version: | - 6.0.x - 7.0.x - 8.0.x - 9.0.x - - name: Restore dependencies - run: dotnet restore - - name: Build - run: dotnet build --no-restore - - name: Test - run: dotnet test --no-build --verbosity normal + - uses: actions/checkout@v3 + - name: Setup .NET + uses: actions/setup-dotnet@v3 + with: + dotnet-version: | + 6.0.x + 7.0.x + 8.0.x + 9.0.x + - name: Restore dependencies + run: dotnet restore + - name: Build + run: dotnet build --no-restore + - name: Test + run: dotnet test --no-build --verbosity normal From 0e9353c18d22d15247f64eed354f15d2d0f7c8e1 Mon Sep 17 00:00:00 2001 From: Austin Drummond Date: Sat, 8 Feb 2025 11:31:39 -0500 Subject: [PATCH 31/31] one more formatting fix --- .github/workflows/dotnet.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index dfea3e2..899c0f2 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -15,7 +15,7 @@ on: jobs: build: - + runs-on: ubuntu-latest steps: