Skip to content

Commit

Permalink
Merge pull request #521 from DFE-Digital/next
Browse files Browse the repository at this point in the history
New components: asset download, details, breadcrumbs
  • Loading branch information
mattb-hippo authored Oct 15, 2024
2 parents cdf8332 + 63f5084 commit 6748854
Show file tree
Hide file tree
Showing 25 changed files with 1,141 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ public class EntityResolverTests
[Test]
[TestCase("accordion", typeof(Accordion))]
[TestCase("accordionSection", typeof(AccordionSection))]
[TestCase("areaOfPractice", typeof(AreaOfPractice))]
[TestCase("areaOfPracticeList", typeof(AreaOfPracticeList))]
[TestCase("applicationFeature", typeof(ApplicationFeature))]
[TestCase("applicationFeatures", typeof(ApplicationFeatures))]
[TestCase("areaOfPractice", typeof(AreaOfPractice))]
[TestCase("areaOfPracticeList", typeof(AreaOfPracticeList))]
[TestCase("assetDownload", typeof(AssetDownload))]
[TestCase("audioResource", typeof(AudioResource))]
[TestCase("backToTop", typeof(BackToTop))]
[TestCase("columnLayout", typeof(ColumnLayout))]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public partial class PartialsFactoryTests
new object[] { new AccordionSection(), "_AccordionSection" },
new object[] { new AreaOfPractice(), "_AreaOfPractice" },
new object[] { new AreaOfPracticeList(), "_AreaOfPracticeList" },
new object[] { new AssetDownload(), "_AssetDownload" },
new object[] { new AudioResource(), "_AudioResource" },
new object[] { new BackToTop(), "_BackToTop" },
new object[] { new ColumnLayout(), "_ColumnLayout" },
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
using Childrens_Social_Care_CPD;
using Childrens_Social_Care_CPD.Contentful;
using Childrens_Social_Care_CPD.Contentful.Models;
using Childrens_Social_Care_CPD.Controllers;
using Childrens_Social_Care_CPD.Models;
using Contentful.Core.Models;
using Contentful.Core.Search;
using FluentAssertions;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.Extensions.Azure;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace Childrens_Social_Care_CPD_Tests.Controllers;

public class ContentControllerBreadcrumbTests
{
private ContentController _contentController;
private IRequestCookieCollection _cookies;
private HttpContext _httpContext;
private HttpRequest _httpRequest;
private ICpdContentfulClient _contentfulClient;

private void SetContent(List<KeyValuePair<string, Content>> content)
{
var contentCollections = new List<KeyValuePair<string, ContentfulCollection<Content>>>();
foreach (var contentDefinition in content)
{
var contentCollection = new ContentfulCollection<Content>();

contentCollection.Items = contentDefinition.Value == null
? new List<Content>()
: contentCollection.Items = new List<Content> { contentDefinition.Value };

contentCollections.Add(new KeyValuePair<string, ContentfulCollection<Content>>(contentDefinition.Key, contentCollection));
}

_contentfulClient
.GetEntries(Arg.Any<QueryBuilder<Content>>(), Arg.Any<CancellationToken>())
.Returns(x => {
var query = x.Arg<QueryBuilder<Content>>().Build();
foreach (var contentDefinition in content)
{
if (query.Contains("fields.id=" + contentDefinition.Key)) return contentCollections.First(x => x.Key == contentDefinition.Key).Value;
}
return new ContentfulCollection<Content>();
});

}

private void SetContent() {
var page = new Content()
{
Id = "page",
Title = "Content Page"
};

var content = new List<KeyValuePair<string, Content>>()
{
new KeyValuePair<string, Content>(page.Id, page),
};

SetContent(content);
}

[SetUp]
public void SetUp()
{
_cookies = Substitute.For<IRequestCookieCollection>();
_httpContext = Substitute.For<HttpContext>();
_httpRequest = Substitute.For<HttpRequest>();
var controllerContext = Substitute.For<ControllerContext>();

_httpRequest.Cookies.Returns(_cookies);
_httpContext.Request.Returns(_httpRequest);

controllerContext.HttpContext = _httpContext;

_contentfulClient = Substitute.For<ICpdContentfulClient>();

_contentController = new ContentController(_contentfulClient)
{
ControllerContext = controllerContext,
TempData = Substitute.For<ITempDataDictionary>()
};
}

[TearDown]
public void TearDown()
{
_contentfulClient = Substitute.For<ICpdContentfulClient>();
}

[Test]
public async Task Index_Sets_Breadcrumbs_In_ContextModel()
{
// arrange
var parentPage = new Content()
{
Id = "parent",
Title = "Parent Page"
};
var childPage = new Content(){
Id = "child",
Title = "Child Page",
ParentPages = new List<Content>(){parentPage}
};

var content = new List<KeyValuePair<string, Content>>(){
new KeyValuePair<string, Content>(parentPage.Id, parentPage),
new KeyValuePair<string, Content>(childPage.Id, childPage)
};

SetContent(content);

// act
await _contentController.Index("child");
var actual = _contentController.ViewData["ContextModel"] as ContextModel;
var breadcrumbTrail = actual?.BreadcrumbTrail;

// assert
actual.Should().NotBeNull();
breadcrumbTrail[0].Key.Should().Be("Child Page");
breadcrumbTrail[0].Value.Should().Be("child");
breadcrumbTrail[1].Key.Should().Be("Parent Page");
breadcrumbTrail[1].Value.Should().Be("parent");
}

[Test]
public async Task Index_Sets_Blank_Breadcrumbs_In_ContextModel_If_Page_Has_No_Parent()
{
// arrange
SetContent();

// act
await _contentController.Index("page");
var actual = _contentController.ViewData["ContextModel"] as ContextModel;
var breadcrumbTrail = actual?.BreadcrumbTrail;

// assert
actual.Should().NotBeNull();
breadcrumbTrail.Should().BeEmpty();
}

[Test]
public async Task Index_Sets_Breadcrumbs_Where_Page_Has_Multiple_Parents()
{
// arrange
var parentPage1 = new Content()
{
Id = "parent1",
Title = "First Parent Page"
};
var parentPage2 = new Content()
{
Id = "parent2",
Title = "Second Parent Page"
};
var childPage = new Content(){
Id = "child",
Title = "Child Page",
ParentPages = new List<Content>(){parentPage1, parentPage2}
};

var content = new List<KeyValuePair<string, Content>>(){
new KeyValuePair<string, Content>(parentPage1.Id, parentPage1),
new KeyValuePair<string, Content>(parentPage2.Id, parentPage2),
new KeyValuePair<string, Content>(childPage.Id, childPage)
};

SetContent(content);

// act
await _contentController.Index("child");
var actual = _contentController.ViewData["ContextModel"] as ContextModel;
var breadcrumbTrail = actual?.BreadcrumbTrail;

// assert
actual.Should().NotBeNull();
breadcrumbTrail[0].Key.Should().Be("Child Page");
breadcrumbTrail[0].Value.Should().Be("child");
breadcrumbTrail[1].Key.Should().Be("First Parent Page");
breadcrumbTrail[1].Value.Should().Be("parent1");
}
}
5 changes: 3 additions & 2 deletions Childrens-Social-Care-CPD/Contentful/EntityResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@ public Type Resolve(string contentTypeId)
{
"accordion" => typeof(Accordion),
"accordionSection" => typeof(AccordionSection),
"areaOfPractice" => typeof(AreaOfPractice),
"areaOfPracticeList" => typeof(AreaOfPracticeList),
"applicationFeature" => typeof(ApplicationFeature),
"applicationFeatures" => typeof(ApplicationFeatures),
"areaOfPractice" => typeof(AreaOfPractice),
"areaOfPracticeList" => typeof(AreaOfPracticeList),
"assetDownload" => typeof(AssetDownload),
"audioResource" => typeof(AudioResource),
"backToTop" => typeof(BackToTop),
"columnLayout" => typeof(ColumnLayout),
Expand Down
9 changes: 9 additions & 0 deletions Childrens-Social-Care-CPD/Contentful/Models/AssetDownload.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Contentful.Core.Models;

namespace Childrens_Social_Care_CPD.Contentful.Models;

public class AssetDownload : IContent
{
public string LinkText { get; set; }
public Asset Asset { get; set; }
}
2 changes: 2 additions & 0 deletions Childrens-Social-Care-CPD/Contentful/Models/Content.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ public class Content : IContent
public NavigationMenu Navigation { get; set; }
public RelatedContent RelatedContent { get; set; }
public int? EstimatedReadingTime { get; set; }
public List<Content> ParentPages { get; set; }
public string BreadcrumbText { get; set; }

[JsonProperty("$metadata")]
public ContentfulMetadata Metadata { get; set; }
Expand Down
1 change: 1 addition & 0 deletions Childrens-Social-Care-CPD/Contentful/PartialsFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public static string GetPartialFor(IContent item)
AccordionSection => "_AccordionSection",
AreaOfPractice => "_AreaOfPractice",
AreaOfPracticeList => "_AreaOfPracticeList",
AssetDownload => "_AssetDownload",
AudioResource => "_AudioResource",
BackToTop => "_BackToTop",
ColumnLayout => "_ColumnLayout",
Expand Down
61 changes: 60 additions & 1 deletion Childrens-Social-Care-CPD/Controllers/ContentController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
using Childrens_Social_Care_CPD.Models;
using Contentful.Core.Search;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;

namespace Childrens_Social_Care_CPD.Controllers;

Expand All @@ -20,6 +22,57 @@ private async Task<Content> FetchPageContentAsync(string contentId, Cancellation
return result?.FirstOrDefault();
}

private async Task<List<KeyValuePair<string, string>>> BuildBreadcrumbTrail(
List<KeyValuePair<string, string>> trail,
Content page,
List<string> pagesVisited,
CancellationToken ct)
{
var trailItem = new KeyValuePair<string, string>(
page.BreadcrumbText.IsNullOrEmpty() ?
page.Title :
page.BreadcrumbText,
page.Id);

if (page.ParentPages == null || page.ParentPages.Count == 0) {
if (trail.Count > 0) trail.Add(trailItem);
return trail;
}

trail.Add(trailItem);

Content parentPage = new Content();

if (page.ParentPages?.Count == 1) {
parentPage = page.ParentPages[0];
}
else
{
var parentPageIds = page.ParentPages
.Select(parent => parent.Id)
.ToList();

var checkPages = pagesVisited.Reverse<string>();
bool parentFound = false;

foreach (var pageId in checkPages)
{
if (parentPageIds.Contains(pageId))
{
parentPage = page.ParentPages.First(p => p.Id == pageId);
parentFound = true;
break;
}
};

// if we don't find a parent page in the recently vistied pages, just use the first in the list
if (!parentFound) parentPage = page.ParentPages[0];
}

var parentObject = await FetchPageContentAsync(parentPage.Id, ct);
return await BuildBreadcrumbTrail(trail, parentObject, pagesVisited, ct);
}

[HttpGet]
[Route("/")]
/*
Expand Down Expand Up @@ -49,6 +102,11 @@ public async Task<IActionResult> Index(string pageName = "home", bool preference
return NotFound();
}

var pagesVisited = HttpContext.Session.Get<List<string>>("pagesVisited");
if (pagesVisited == null) pagesVisited = new List<string>();
pagesVisited.Add(pageName);
HttpContext.Session.Set("pagesVisited", pagesVisited);

var contextModel = new ContextModel(
Id: content.Id,
Title: content.Title,
Expand All @@ -57,7 +115,8 @@ public async Task<IActionResult> Index(string pageName = "home", bool preference
UseContainers: content.Navigation == null,
PreferenceSet: preferenceSet,
BackLink: content.BackLink,
FeedbackSubmitted: fs);
FeedbackSubmitted: fs,
BreadcrumbTrail: await BuildBreadcrumbTrail(new List<KeyValuePair<string, string>>(), content, pagesVisited, cancellationToken));

ViewData["ContextModel"] = contextModel;
ViewData["StateModel"] = new StateModel();
Expand Down
19 changes: 19 additions & 0 deletions Childrens-Social-Care-CPD/Extensions/SessionExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using Newtonsoft.Json;

#nullable enable

namespace Childrens_Social_Care_CPD;

public static class SessionExtensions
{
public static void Set<T>(this ISession session, string key, T value)
{
session.SetString(key, JsonConvert.SerializeObject(value));
}

public static T? Get<T>(this ISession session, string key)
{
var value = session.GetString(key);
return value == null ? default : JsonConvert.DeserializeObject<T>(value);
}
}
12 changes: 11 additions & 1 deletion Childrens-Social-Care-CPD/Models/ContextModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,17 @@

namespace Childrens_Social_Care_CPD.Models;

public record ContextModel(string Id, string Title, string PageName, string Category, bool UseContainers, bool PreferenceSet, bool HideConsent = false, ContentLink BackLink = null, bool FeedbackSubmitted = false)
public record ContextModel(
string Id,
string Title,
string PageName,
string Category,
bool UseContainers,
bool PreferenceSet,
bool HideConsent = false,
ContentLink BackLink = null,
bool FeedbackSubmitted = false,
List<KeyValuePair<string, string>> BreadcrumbTrail = null)
{
public Stack<string> ContentStack { get; } = new Stack<string>();
}
Loading

0 comments on commit 6748854

Please sign in to comment.