From 6cd9102fac2b28d056c7fd5573f78e0c541b9948 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 18 Oct 2018 21:12:55 +1100 Subject: [PATCH] Gets publish names tracking changes, adds unit tests --- src/Umbraco.Core/Models/Content.cs | 22 ++++- src/Umbraco.Core/Models/CultureName.cs | 21 ++++- .../Models/CultureNameCollection.cs | 6 +- src/Umbraco.Tests/Models/ContentTests.cs | 40 ++++++++- .../Services/ContentServiceTests.cs | 86 +++++++++++++++++++ 5 files changed, 164 insertions(+), 11 deletions(-) diff --git a/src/Umbraco.Core/Models/Content.cs b/src/Umbraco.Core/Models/Content.cs index 2ee6762e61..eb95804c2c 100644 --- a/src/Umbraco.Core/Models/Content.cs +++ b/src/Umbraco.Core/Models/Content.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.Specialized; using System.Linq; using System.Reflection; using System.Runtime.Serialization; @@ -88,6 +89,7 @@ namespace Umbraco.Core.Models public readonly PropertyInfo PublishedSelector = ExpressionHelper.GetPropertyInfo(x => x.Published); public readonly PropertyInfo ReleaseDateSelector = ExpressionHelper.GetPropertyInfo(x => x.ReleaseDate); public readonly PropertyInfo ExpireDateSelector = ExpressionHelper.GetPropertyInfo(x => x.ExpireDate); + public readonly PropertyInfo PublishNamesSelector = ExpressionHelper.GetPropertyInfo>(x => x.PublishNames); } /// @@ -222,7 +224,7 @@ namespace Umbraco.Core.Models public bool IsCulturePublished(string culture) // just check _publishInfos // a non-available culture could not become published anyways - => _publishInfos != null && _publishInfos.Contains(culture); + => _publishInfos != null && _publishInfos.Contains(culture); /// public bool WasCulturePublished(string culture) @@ -268,9 +270,10 @@ namespace Umbraco.Core.Models throw new ArgumentNullOrEmptyException(nameof(culture)); if (_publishInfos == null) + { _publishInfos = new CultureNameCollection(); - - //TODO: Track changes? + _publishInfos.CollectionChanged += PublishNamesCollectionChanged; + } _publishInfos.AddOrUpdate(culture, name, date); } @@ -314,6 +317,16 @@ namespace Umbraco.Core.Models } } + /// + /// Event handler for when the culture names collection is modified + /// + /// + /// + private void PublishNamesCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + OnPropertyChanged(Ps.Value.PublishNamesSelector); + } + [IgnoreDataMember] public int PublishedVersionId { get; internal set; } @@ -430,7 +443,8 @@ namespace Umbraco.Core.Models // take care of the published state _publishedState = _published ? PublishedState.Published : PublishedState.Unpublished; - // take care of publish infos + // Make a copy of the _publishInfos, this is purely so that we can detect + // if this entity's previous culture publish state (regardless of the rememberDirty flag) _publishInfosOrig = _publishInfos == null ? null : new CultureNameCollection(_publishInfos); diff --git a/src/Umbraco.Core/Models/CultureName.cs b/src/Umbraco.Core/Models/CultureName.cs index 64db50c36d..d683f07b49 100644 --- a/src/Umbraco.Core/Models/CultureName.cs +++ b/src/Umbraco.Core/Models/CultureName.cs @@ -14,12 +14,27 @@ namespace Umbraco.Core.Models private string _name; private static readonly Lazy Ps = new Lazy(); - public CultureName(string culture, string name, DateTime date) + /// + /// Used for cloning without change tracking + /// + /// + /// + /// + private CultureName(string culture, string name, DateTime date) + : this(culture) + { + _name = name; + _date = date; + } + + /// + /// Constructor + /// + /// + public CultureName(string culture) { if (string.IsNullOrWhiteSpace(culture)) throw new ArgumentException("message", nameof(culture)); Culture = culture; - _name = name; - _date = date; } public string Culture { get; private set; } diff --git a/src/Umbraco.Core/Models/CultureNameCollection.cs b/src/Umbraco.Core/Models/CultureNameCollection.cs index 3c00603c88..be6540c399 100644 --- a/src/Umbraco.Core/Models/CultureNameCollection.cs +++ b/src/Umbraco.Core/Models/CultureNameCollection.cs @@ -56,7 +56,11 @@ namespace Umbraco.Core.Models OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, found, found)); } else - Add(new CultureName(culture, name, date)); + Add(new CultureName(culture) + { + Name = name, + Date = date + }); } /// diff --git a/src/Umbraco.Tests/Models/ContentTests.cs b/src/Umbraco.Tests/Models/ContentTests.cs index a7d0f2f050..1c3e2453c3 100644 --- a/src/Umbraco.Tests/Models/ContentTests.cs +++ b/src/Umbraco.Tests/Models/ContentTests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Linq; +using System.Threading; using Moq; using NUnit.Framework; using Umbraco.Core; @@ -43,7 +44,7 @@ namespace Umbraco.Tests.Models } [Test] - public void Variant_Names_Track_Dirty_Changes() + public void Variant_Culture_Names_Track_Dirty_Changes() { var contentType = new ContentType(-1) { Alias = "contentType" }; var content = new Content("content", -1, contentType) { Id = 1, VersionId = 1 }; @@ -54,21 +55,54 @@ namespace Umbraco.Tests.Models Assert.IsFalse(content.IsPropertyDirty("CultureNames")); //hasn't been changed + Thread.Sleep(500); //The "Date" wont be dirty if the test runs too fast since it will be the same date content.SetCultureName("name-fr", langFr); Assert.IsTrue(content.IsPropertyDirty("CultureNames")); //now it will be changed since the collection has changed var frCultureName = content.CultureNames[langFr]; - Assert.IsFalse(frCultureName.IsPropertyDirty("Date")); //this won't be dirty because it wasn't actually updated, just created + Assert.IsTrue(frCultureName.IsPropertyDirty("Date")); content.ResetDirtyProperties(); Assert.IsFalse(content.IsPropertyDirty("CultureNames")); //it's been reset Assert.IsTrue(content.WasPropertyDirty("CultureNames")); + Thread.Sleep(500); //The "Date" wont be dirty if the test runs too fast since it will be the same date content.SetCultureName("name-fr", langFr); - Assert.IsTrue(frCultureName.IsPropertyDirty("Date")); //this will be dirty because it was already created and now has been updated + Assert.IsTrue(frCultureName.IsPropertyDirty("Date")); Assert.IsTrue(content.IsPropertyDirty("CultureNames")); //it's true now since we've updated a name } + [Test] + public void Variant_Published_Culture_Names_Track_Dirty_Changes() + { + var contentType = new ContentType(-1) { Alias = "contentType" }; + var content = new Content("content", -1, contentType) { Id = 1, VersionId = 1 }; + + const string langFr = "fr-FR"; + + contentType.Variations = ContentVariation.Culture; + + Assert.IsFalse(content.IsPropertyDirty("PublishNames")); //hasn't been changed + + Thread.Sleep(500); //The "Date" wont be dirty if the test runs too fast since it will be the same date + content.SetCultureName("name-fr", langFr); + content.PublishCulture(langFr); //we've set the name, now we're publishing it + Assert.IsTrue(content.IsPropertyDirty("PublishNames")); //now it will be changed since the collection has changed + var frCultureName = content.PublishNames[langFr]; + Assert.IsTrue(frCultureName.IsPropertyDirty("Date")); + + content.ResetDirtyProperties(); + + Assert.IsFalse(content.IsPropertyDirty("PublishNames")); //it's been reset + Assert.IsTrue(content.WasPropertyDirty("PublishNames")); + + Thread.Sleep(500); //The "Date" wont be dirty if the test runs too fast since it will be the same date + content.SetCultureName("name-fr", langFr); + content.PublishCulture(langFr); //we've set the name, now we're publishing it + Assert.IsTrue(frCultureName.IsPropertyDirty("Date")); + Assert.IsTrue(content.IsPropertyDirty("PublishNames")); //it's true now since we've updated a name + } + [Test] public void Get_Non_Grouped_Properties() { diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index f187b8b70d..4d31d8342a 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -1234,6 +1234,92 @@ namespace Umbraco.Tests.Services } } + [Test] + public void Can_Unpublish_Content_Variation() + { + // Arrange + + var langUk = new Language("en-UK") { IsDefault = true }; + var langFr = new Language("fr-FR"); + + ServiceContext.LocalizationService.Save(langFr); + ServiceContext.LocalizationService.Save(langUk); + + var contentType = MockedContentTypes.CreateBasicContentType(); + contentType.Variations = ContentVariation.Culture; + ServiceContext.ContentTypeService.Save(contentType); + + IContent content = new Content("content", -1, contentType); + content.SetCultureName("content-fr", langFr.IsoCode); + content.SetCultureName("content-en", langUk.IsoCode); + content.PublishCulture(langFr.IsoCode); + content.PublishCulture(langUk.IsoCode); + Assert.IsTrue(content.IsCulturePublished(langFr.IsoCode)); + Assert.IsFalse(content.WasCulturePublished(langFr.IsoCode)); //not persisted yet, will be false + Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode)); + Assert.IsFalse(content.WasCulturePublished(langUk.IsoCode)); //not persisted yet, will be false + + var published = ServiceContext.ContentService.SavePublishing(content); + //re-get + content = ServiceContext.ContentService.GetById(content.Id); + Assert.IsTrue(published.Success); + Assert.IsTrue(content.IsCulturePublished(langFr.IsoCode)); + Assert.IsTrue(content.WasCulturePublished(langFr.IsoCode)); + Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode)); + Assert.IsTrue(content.WasCulturePublished(langUk.IsoCode)); + + var unpublished = ServiceContext.ContentService.Unpublish(content, langFr.IsoCode); + Assert.IsTrue(unpublished.Success); + + Assert.IsFalse(content.IsCulturePublished(langFr.IsoCode)); + //this is slightly confusing but this will be false because this method is used for checking the state of the current model, + //but the state on the model has changed with the above Unpublish call + Assert.IsFalse(content.WasCulturePublished(langFr.IsoCode)); + + //re-get + content = ServiceContext.ContentService.GetById(content.Id); + Assert.IsFalse(content.IsCulturePublished(langFr.IsoCode)); + //this is slightly confusing but this will be false because this method is used for checking the state of a current model, + //but we've re-fetched from the database + Assert.IsFalse(content.WasCulturePublished(langFr.IsoCode)); + Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode)); + Assert.IsTrue(content.WasCulturePublished(langUk.IsoCode)); + + } + + [Test] + public void Can_Publish_Content_Variation_And_Detect_Changed_Cultures() + { + // Arrange + + var langUk = new Language("en-UK") { IsDefault = true }; + var langFr = new Language("fr-FR"); + + ServiceContext.LocalizationService.Save(langFr); + ServiceContext.LocalizationService.Save(langUk); + + var contentType = MockedContentTypes.CreateBasicContentType(); + contentType.Variations = ContentVariation.Culture; + ServiceContext.ContentTypeService.Save(contentType); + + IContent content = new Content("content", -1, contentType); + content.SetCultureName("content-fr", langFr.IsoCode); + content.PublishCulture(langFr.IsoCode); + var published = ServiceContext.ContentService.SavePublishing(content); + //audit log will only show that french was published + var lastLog = ServiceContext.AuditService.GetLogs(content.Id).Last(); + Assert.AreEqual($"Published culture fr-fr", lastLog.Comment); + + //re-get + content = ServiceContext.ContentService.GetById(content.Id); + content.SetCultureName("content-en", langUk.IsoCode); + content.PublishCulture(langUk.IsoCode); + published = ServiceContext.ContentService.SavePublishing(content); + //audit log will only show that english was published + lastLog = ServiceContext.AuditService.GetLogs(content.Id).Last(); + Assert.AreEqual($"Published culture en-uk", lastLog.Comment); + } + [Test] public void Can_Publish_Content_1() {