diff --git a/src/Umbraco.Core/Models/Content.cs b/src/Umbraco.Core/Models/Content.cs index d73c948094..94f6d0beb7 100644 --- a/src/Umbraco.Core/Models/Content.cs +++ b/src/Umbraco.Core/Models/Content.cs @@ -92,10 +92,36 @@ namespace Umbraco.Core.Models } /// + [DoNotClone] public ContentScheduleCollection ContentSchedule { - get => _schedule ?? (_schedule = new ContentScheduleCollection()); - set => SetPropertyValueAndDetectChanges(value, ref _schedule, Ps.Value.ContentScheduleSelector); + get + { + if (_schedule == null) + { + _schedule = new ContentScheduleCollection(); + _schedule.CollectionChanged += ScheduleCollectionChanged; + } + return _schedule ?? (_schedule = new ContentScheduleCollection()); + } + set + { + if(_schedule != null) + _schedule.CollectionChanged -= ScheduleCollectionChanged; + SetPropertyValueAndDetectChanges(value, ref _schedule, Ps.Value.ContentScheduleSelector); + if (_schedule != null) + _schedule.CollectionChanged += ScheduleCollectionChanged; + } + } + + /// + /// Collection changed event handler to ensure the schedule field is set to dirty when the schedule changes + /// + /// + /// + private void ScheduleCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + OnPropertyChanged(Ps.Value.ContentScheduleSelector); } /// @@ -125,13 +151,13 @@ namespace Umbraco.Core.Models return ContentStatus.Trashed; //fixme - deal with variants - var expires = ContentSchedule.GetSchedule(ContentScheduleChange.End).FirstOrDefault(); - if (expires != null && expires.Date > DateTime.MinValue && DateTime.Now > expires.Date) + var expires = ContentSchedule.GetSchedule(ContentScheduleChange.End); + if (expires != null && expires.Any(x => x.Date > DateTime.MinValue && DateTime.Now > x.Date)) return ContentStatus.Expired; //fixme - deal with variants - var release = ContentSchedule.GetSchedule(ContentScheduleChange.Start).FirstOrDefault(); - if (release != null && release.Date > DateTime.MinValue && release.Date > DateTime.Now) + var release = ContentSchedule.GetSchedule(ContentScheduleChange.Start); + if (release != null && release.Any(x => x.Date > DateTime.MinValue && x.Date > DateTime.Now)) return ContentStatus.AwaitingRelease; if(Published) @@ -508,6 +534,14 @@ namespace Umbraco.Core.Models clone._publishInfos.CollectionChanged += clone.PublishNamesCollectionChanged; //re-assign correct event handler } + //if properties exist then deal with event bindings + if (clone._schedule != null) + { + clone._schedule.CollectionChanged -= ScheduleCollectionChanged; //clear this event handler if any + clone._schedule = (ContentScheduleCollection)_schedule.DeepClone(); //manually deep clone + clone._schedule.CollectionChanged += clone.ScheduleCollectionChanged; //re-assign correct event handler + } + //re-enable tracking clone.EnableChangeTracking(); diff --git a/src/Umbraco.Core/Models/ContentSchedule.cs b/src/Umbraco.Core/Models/ContentSchedule.cs index f137b85ee7..d2905ed54d 100644 --- a/src/Umbraco.Core/Models/ContentSchedule.cs +++ b/src/Umbraco.Core/Models/ContentSchedule.cs @@ -9,7 +9,7 @@ namespace Umbraco.Core.Models /// [Serializable] [DataContract(IsReference = true)] - public class ContentSchedule + public class ContentSchedule : IDeepCloneable { public ContentSchedule(int id, string culture, DateTime date, ContentScheduleChange change) { @@ -45,5 +45,10 @@ namespace Umbraco.Core.Models /// [DataMember] public ContentScheduleChange Change { get; } + + public object DeepClone() + { + return new ContentSchedule(Id, Culture, Date, Change); + } } } diff --git a/src/Umbraco.Core/Models/ContentScheduleCollection.cs b/src/Umbraco.Core/Models/ContentScheduleCollection.cs index 73607271a3..52dcad9e45 100644 --- a/src/Umbraco.Core/Models/ContentScheduleCollection.cs +++ b/src/Umbraco.Core/Models/ContentScheduleCollection.cs @@ -2,14 +2,22 @@ using System.Linq; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Collections.Specialized; namespace Umbraco.Core.Models { - public class ContentScheduleCollection + public class ContentScheduleCollection : INotifyCollectionChanged, IDeepCloneable { //underlying storage for the collection backed by a sorted list so that the schedule is always in order of date private readonly Dictionary> _schedule = new Dictionary>(); + public event NotifyCollectionChangedEventHandler CollectionChanged; + + private void OnCollectionChanged(NotifyCollectionChangedEventArgs args) + { + CollectionChanged?.Invoke(this, args); + } + /// /// Add an existing schedule /// @@ -24,6 +32,8 @@ namespace Umbraco.Core.Models //TODO: Below will throw if there are duplicate dates added, validate/return bool? changes.Add(schedule.Date, schedule); + + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, schedule)); } /// @@ -62,9 +72,20 @@ namespace Umbraco.Core.Models // but the bool won't indicate which date was in error, maybe have 2 diff methods to schedule start/end? if (releaseDate.HasValue) - changes.Add(releaseDate.Value, new ContentSchedule(0, culture, releaseDate.Value, ContentScheduleChange.Start)); + { + var entry = new ContentSchedule(0, culture, releaseDate.Value, ContentScheduleChange.Start); + changes.Add(releaseDate.Value, entry); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, entry)); + } + if (expireDate.HasValue) - changes.Add(expireDate.Value, new ContentSchedule(0, culture, expireDate.Value, ContentScheduleChange.End)); + { + var entry = new ContentSchedule(0, culture, expireDate.Value, ContentScheduleChange.End); + changes.Add(expireDate.Value, entry); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, entry)); + } + + } @@ -76,7 +97,14 @@ namespace Umbraco.Core.Models { if (_schedule.TryGetValue(change.Culture, out var s)) { - s.Remove(change.Date); + var removed = s.Remove(change.Date); + if (removed) + { + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, change)); + if (s.Count == 0) + _schedule.Remove(change.Culture); + } + } } @@ -99,7 +127,16 @@ namespace Umbraco.Core.Models if (_schedule.TryGetValue(culture, out var s)) { foreach (var ofChange in s.Where(x => x.Value.Change == changeType).ToList()) - s.Remove(ofChange.Value.Date); + { + var removed = s.Remove(ofChange.Value.Date); + if (removed) + { + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, ofChange.Value)); + if (s.Count == 0) + _schedule.Remove(culture); + } + } + } } @@ -132,5 +169,18 @@ namespace Umbraco.Core.Models { return _schedule.ToDictionary(x => x.Key, x => (IEnumerable)x.Value.Values); } + + public object DeepClone() + { + var clone = new ContentScheduleCollection(); + foreach(var cultureSched in _schedule) + { + var list = new SortedList(); + foreach (var schedEntry in cultureSched.Value) + list.Add(schedEntry.Key, (ContentSchedule)schedEntry.Value.DeepClone()); + clone._schedule[cultureSched.Key] = list; + } + return clone; + } } } diff --git a/src/Umbraco.Core/Persistence/Dtos/DocumentDto.cs b/src/Umbraco.Core/Persistence/Dtos/DocumentDto.cs index be2ad45bcf..30e8f295fb 100644 --- a/src/Umbraco.Core/Persistence/Dtos/DocumentDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/DocumentDto.cs @@ -55,9 +55,6 @@ namespace Umbraco.Core.Persistence.Dtos [ResultColumn] [Reference(ReferenceType.OneToOne)] public DocumentVersionDto PublishedVersionDto { get; set; } - - [ResultColumn] - [Reference(ReferenceType.Many, ReferenceMemberName = "NodeId")] - public IEnumerable ContentSchedule { get; set; } + } } diff --git a/src/Umbraco.Core/Persistence/Factories/ContentBaseFactory.cs b/src/Umbraco.Core/Persistence/Factories/ContentBaseFactory.cs index 8ad4f96396..fc81f3e6a4 100644 --- a/src/Umbraco.Core/Persistence/Factories/ContentBaseFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/ContentBaseFactory.cs @@ -15,7 +15,7 @@ namespace Umbraco.Core.Persistence.Factories /// /// Builds an IContent item from a dto and content type. /// - public static Content BuildEntity(DocumentDto dto, IContentType contentType, ILanguageRepository languageRepository ) + public static Content BuildEntity(DocumentDto dto, IContentType contentType) { var contentDto = dto.ContentDto; var nodeDto = contentDto.NodeDto; @@ -49,18 +49,6 @@ namespace Umbraco.Core.Persistence.Factories content.Published = dto.Published; content.Edited = dto.Edited; - var schedule = new ContentScheduleCollection(); - foreach(var entry in dto.ContentSchedule ?? Enumerable.Empty()) - { - schedule.Add(new ContentSchedule(entry.Id, - languageRepository.GetIsoCodeById(entry.LanguageId), - entry.Date, - entry.Action == ContentScheduleChange.Start.ToString() - ? ContentScheduleChange.Start - : ContentScheduleChange.End)); - } - content.ContentSchedule = schedule; - // fixme - shall we get published infos or not? //if (dto.Published) if (publishedVersionDto != null) @@ -170,7 +158,7 @@ namespace Umbraco.Core.Persistence.Factories /// /// Builds a dto from an IContent item. /// - public static DocumentDto BuildDto(IContent entity, Guid objectType, ILanguageRepository languageRepository) + public static DocumentDto BuildDto(IContent entity, Guid objectType) { var contentDto = BuildContentDto(entity, objectType); @@ -182,10 +170,15 @@ namespace Umbraco.Core.Persistence.Factories DocumentVersionDto = BuildDocumentVersionDto(entity, contentDto) }; + return dto; + } + + public static IEnumerable BuildScheduleDto(IContent entity, ILanguageRepository languageRepository) + { var schedule = new List(); - foreach(var schedByCulture in entity.ContentSchedule.GetFullSchedule()) + foreach (var schedByCulture in entity.ContentSchedule.GetFullSchedule()) { - foreach(var cultureSched in schedByCulture.Value) + foreach (var cultureSched in schedByCulture.Value) { schedule.Add(new ContentScheduleDto { @@ -197,9 +190,7 @@ namespace Umbraco.Core.Persistence.Factories }); } } - dto.ContentSchedule = schedule; - - return dto; + return schedule; } /// diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs index 2bb930dd79..a3dee9317f 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs @@ -121,7 +121,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement .Select(documentDto => documentDto.PublishedVersionDto, "pdv", r1 => r1.Select(documentVersionDto => documentVersionDto.ContentVersionDto, "pcv"))) - // select the variant name, coalesce to the invariant name, as "variantName" + // select the variant name, coalesce to the invariant name, as "variantName" .AndSelect(VariantNameSqlExpression + " AS variantName"); break; } @@ -143,11 +143,12 @@ namespace Umbraco.Core.Persistence.Repositories.Implement .On((left, right) => left.Id == right.Id && right.Published, "pcv", "pdv"), "pcv") .On((left, right) => left.NodeId == right.NodeId, aliasRight: "pcv") + //fixme - should we be joining this when the query type is not single/many? // left join on optional culture variation //the magic "[[[ISOCODE]]]" parameter value will be replaced in ContentRepositoryBase.GetPage() by the actual ISO code .LeftJoin(nested => - nested.InnerJoin("lang").On((ccv, lang) => ccv.LanguageId == lang.Id && lang.IsoCode == "[[[ISOCODE]]]", "ccv", "lang"), "ccv") - .On((version, ccv) => version.Id == ccv.VersionId, aliasRight: "ccv"); + nested.InnerJoin("lang").On((ccv, lang) => ccv.LanguageId == lang.Id && lang.IsoCode == "[[[ISOCODE]]]", "ccv", "lang"), "ccv") + .On((version, ccv) => version.Id == ccv.VersionId, aliasRight: "ccv"); sql .Where(x => x.NodeObjectType == NodeObjectTypeId); @@ -261,7 +262,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // however, it's not just so we have access to AddingEntity // there are tons of things at the end of the methods, that can only work with a true Content // and basically, the repository requires a Content, not an IContent - var content = (Content) entity; + var content = (Content)entity; content.AddingEntity(); var publishing = content.PublishedState == PublishedState.Publishing; @@ -278,7 +279,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement entity.SanitizeEntityPropertiesForXmlStorage(); // create the dto - var dto = ContentBaseFactory.BuildDto(entity, NodeObjectTypeId, LanguageRepository); + var dto = ContentBaseFactory.BuildDto(entity, NodeObjectTypeId); // derive path and level from parent var parent = GetParentNodeDto(entity.ParentId); @@ -366,7 +367,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement Database.Insert(dto); //insert the schedule - foreach(var c in dto.ContentSchedule) + var scheduleDto = ContentBaseFactory.BuildScheduleDto(content, LanguageRepository); + foreach (var c in scheduleDto) { c.NodeId = nodeDto.NodeId; Database.Insert(c); @@ -442,10 +444,11 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // however, it's not just so we have access to AddingEntity // there are tons of things at the end of the methods, that can only work with a true Content // and basically, the repository requires a Content, not an IContent - var content = (Content) entity; + var content = (Content)entity; // check if we need to make any database changes at all - if ((content.PublishedState == PublishedState.Published || content.PublishedState == PublishedState.Unpublished) && !content.IsEntityDirty() && !content.IsAnyUserPropertyDirty()) + if ((content.PublishedState == PublishedState.Published || content.PublishedState == PublishedState.Unpublished) + && !content.IsEntityDirty() && !content.IsAnyUserPropertyDirty()) return; // no change to save, do nothing, don't even update dates // whatever we do, we must check that we are saving the current version @@ -482,7 +485,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement } // create the dto - var dto = ContentBaseFactory.BuildDto(entity, NodeObjectTypeId, LanguageRepository); + var dto = ContentBaseFactory.BuildDto(entity, NodeObjectTypeId); // update the node dto var nodeDto = dto.ContentDto.NodeDto; @@ -590,22 +593,26 @@ namespace Umbraco.Core.Persistence.Repositories.Implement content.Edited = dto.Edited = !dto.Published || edited; // if not published, always edited Database.Update(dto); - //update the schedule, get the existing one so we know what to update - var existingSched = Database.Fetch(Sql() - .Select(x => x.Id) - .From() - .Where(x => x.NodeId == content.Id)); - //remove any that no longer exist - var schedToRemove = existingSched.Except(dto.ContentSchedule.Select(x => x.Id)); - foreach(var c in schedToRemove) - Database.DeleteWhere("id = @id", new { id = c }); - //add/update the rest - foreach (var c in dto.ContentSchedule) + if (content.IsPropertyDirty("ContentSchedule")) { - if (c.Id == 0) - Database.Insert(c); - else - Database.Update(c); + //update the schedule, get the existing one so we know what to update + var dtoSchedule = ContentBaseFactory.BuildScheduleDto(content, LanguageRepository); + var existingSched = Database.Fetch(Sql() + .Select(x => x.Id) + .From() + .Where(x => x.NodeId == content.Id)); + //remove any that no longer exist + var schedToRemove = existingSched.Except(dtoSchedule.Select(x => x.Id)); + foreach (var c in schedToRemove) + Database.DeleteWhere("id = @id", new { id = c }); + //add/update the rest + foreach (var c in dtoSchedule) + { + if (c.Id == 0) + Database.Insert(c); + else + Database.Update(c); + } } // if entity is publishing, update tags, else leave tags there @@ -760,7 +767,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement foreach (var filterClause in filter.GetWhereClauses()) { var clauseSql = filterClause.Item1; - var clauseArgs = filterClause.Item2; + var clauseArgs = filterClause.Item2; // replace the name field // we cannot reference an aliased field in a WHERE clause, so have to repeat the expression here @@ -1011,7 +1018,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement var cached = IsolatedCache.GetCacheItem(RepositoryCacheKeys.GetKey(dto.NodeId)); if (cached != null && cached.VersionId == dto.DocumentVersionDto.ContentVersionDto.Id) { - content[i] = (Content) cached; + content[i] = (Content)cached; continue; } } @@ -1024,7 +1031,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (contentTypes.TryGetValue(contentTypeId, out var contentType) == false) contentTypes[contentTypeId] = contentType = _contentTypeRepository.Get(contentTypeId); - var c = content[i] = ContentBaseFactory.BuildEntity(dto, contentType, LanguageRepository); + var c = content[i] = ContentBaseFactory.BuildEntity(dto, contentType); if (!slim) { @@ -1059,6 +1066,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // load all properties for all documents from database in 1 query - indexed by version id var properties = GetPropertyCollections(temps); + var schedule = GetContentSchedule(temps.Select(x => x.Content.Id).ToArray()); // assign templates and properties foreach (var temp in temps) @@ -1069,10 +1077,14 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (temp.Template2Id.HasValue && templates.TryGetValue(temp.Template2Id.Value, out template)) temp.Content.PublishTemplate = template; - if (properties.ContainsKey(temp.VersionId)) - temp.Content.Properties = properties[temp.VersionId]; - else - throw new InvalidOperationException($"No property data found for version: '{temp.VersionId}'."); + if (properties.ContainsKey(temp.VersionId)) + temp.Content.Properties = properties[temp.VersionId]; + else + throw new InvalidOperationException($"No property data found for version: '{temp.VersionId}'."); + + //load in the schedule + if (schedule.TryGetValue(temp.Content.Id, out var s)) + temp.Content.ContentSchedule = s; // reset dirty initial properties (U4-1946) temp.Content.ResetDirtyProperties(false); @@ -1096,35 +1108,72 @@ namespace Umbraco.Core.Persistence.Repositories.Implement private IContent MapDtoToContent(DocumentDto dto) { var contentType = _contentTypeRepository.Get(dto.ContentDto.ContentTypeId); - var content = ContentBaseFactory.BuildEntity(dto, contentType, LanguageRepository); + var content = ContentBaseFactory.BuildEntity(dto, contentType); - // get template - if (dto.DocumentVersionDto.TemplateId.HasValue && dto.DocumentVersionDto.TemplateId.Value > 0) - content.Template = _templateRepository.Get(dto.DocumentVersionDto.TemplateId.Value); - - // get properties - indexed by version id - var versionId = dto.DocumentVersionDto.Id; - - // fixme - shall we get published properties or not? - //var publishedVersionId = dto.Published ? dto.PublishedVersionDto.Id : 0; - var publishedVersionId = dto.PublishedVersionDto != null ? dto.PublishedVersionDto.Id : 0; - - var temp = new TempContent(dto.NodeId, versionId, publishedVersionId, contentType); - var ltemp = new List> { temp }; - var properties = GetPropertyCollections(ltemp); - content.Properties = properties[dto.DocumentVersionDto.Id]; - - // set variations, if varying - if (contentType.VariesByCulture()) + try { - var contentVariations = GetContentVariations(ltemp); - var documentVariations = GetDocumentVariations(ltemp); - SetVariations(content, contentVariations, documentVariations); - } + content.DisableChangeTracking(); - // reset dirty initial properties (U4-1946) - content.ResetDirtyProperties(false); - return content; + // get template + if (dto.DocumentVersionDto.TemplateId.HasValue && dto.DocumentVersionDto.TemplateId.Value > 0) + content.Template = _templateRepository.Get(dto.DocumentVersionDto.TemplateId.Value); + + // get properties - indexed by version id + var versionId = dto.DocumentVersionDto.Id; + + // fixme - shall we get published properties or not? + //var publishedVersionId = dto.Published ? dto.PublishedVersionDto.Id : 0; + var publishedVersionId = dto.PublishedVersionDto != null ? dto.PublishedVersionDto.Id : 0; + + var temp = new TempContent(dto.NodeId, versionId, publishedVersionId, contentType); + var ltemp = new List> { temp }; + var properties = GetPropertyCollections(ltemp); + content.Properties = properties[dto.DocumentVersionDto.Id]; + + // set variations, if varying + if (contentType.VariesByCulture()) + { + var contentVariations = GetContentVariations(ltemp); + var documentVariations = GetDocumentVariations(ltemp); + SetVariations(content, contentVariations, documentVariations); + } + + //load in the schedule + var schedule = GetContentSchedule(dto.NodeId); + if (schedule.TryGetValue(dto.NodeId, out var s)) + content.ContentSchedule = s; + + // reset dirty initial properties (U4-1946) + content.ResetDirtyProperties(false); + return content; + } + finally + { + content.EnableChangeTracking(); + } + } + + private IDictionary GetContentSchedule(params int[] contentIds) + { + var result = new Dictionary(); + foreach(var ids in contentIds.InGroupsOf(2000)) + { + var sql = Sql().Select().From().Where(x => ids.Contains(x.NodeId)); + var scheduleDto = Database.Fetch(sql); + + foreach (var dto in scheduleDto) + { + var schedule = new ContentScheduleCollection(); + schedule.Add(new ContentSchedule(dto.Id, + LanguageRepository.GetIsoCodeById(dto.LanguageId) ?? string.Empty, + dto.Date, + dto.Action == ContentScheduleChange.Start.ToString() + ? ContentScheduleChange.Start + : ContentScheduleChange.End)); + result[dto.NodeId] = schedule; + } + } + return result; } private void SetVariations(Content content, IDictionary> contentVariations, IDictionary> documentVariations) diff --git a/src/Umbraco.Tests/Models/ContentScheduleTests.cs b/src/Umbraco.Tests/Models/ContentScheduleTests.cs new file mode 100644 index 0000000000..1e28b2e9b8 --- /dev/null +++ b/src/Umbraco.Tests/Models/ContentScheduleTests.cs @@ -0,0 +1,107 @@ +using System; +using System.Linq; +using NUnit.Framework; +using Umbraco.Core.Models; + +namespace Umbraco.Tests.Models +{ + [TestFixture] + public class ContentScheduleTests + { + [Test] + public void Release_Date_Less_Than_Expire_Date() + { + var now = DateTime.Now; + var schedule = new ContentScheduleCollection(); + Assert.Throws(() => schedule.Add(now, now)); + } + + [Test] + public void Cannot_Add_Duplicate_Dates_Invariant() + { + var now = DateTime.Now; + var schedule = new ContentScheduleCollection(); + schedule.Add(now, null); + Assert.Throws(() => schedule.Add(null, now)); + } + + [Test] + public void Cannot_Add_Duplicate_Dates_Variant() + { + var now = DateTime.Now; + var schedule = new ContentScheduleCollection(); + schedule.Add(now, null); + schedule.Add("en-US", now, null); + Assert.Throws(() => schedule.Add("en-US", null, now)); + Assert.Throws(() => schedule.Add(null, now)); + } + + [Test] + public void Can_Remove_Invariant() + { + var now = DateTime.Now; + var schedule = new ContentScheduleCollection(); + schedule.Add(now, null); + var invariantSched = schedule.GetSchedule(string.Empty); + schedule.Remove(invariantSched.First()); + Assert.AreEqual(0, schedule.GetFullSchedule().Count()); + } + + [Test] + public void Can_Remove_Variant() + { + var now = DateTime.Now; + var schedule = new ContentScheduleCollection(); + schedule.Add(now, null); + schedule.Add("en-US", now, null); + var invariantSched = schedule.GetSchedule(string.Empty); + schedule.Remove(invariantSched.First()); + Assert.AreEqual(0, schedule.GetSchedule(string.Empty).Count()); + Assert.AreEqual(1, schedule.GetFullSchedule().Count()); + var variantSched = schedule.GetSchedule("en-US"); + schedule.Remove(variantSched.First()); + Assert.AreEqual(0, schedule.GetSchedule("en-US").Count()); + Assert.AreEqual(0, schedule.GetFullSchedule().Count()); + } + + [Test] + public void Can_Clear_Start_Invariant() + { + var now = DateTime.Now; + var schedule = new ContentScheduleCollection(); + schedule.Add(now, now.AddDays(1)); + + schedule.Clear(ContentScheduleChange.Start); + + Assert.AreEqual(0, schedule.GetSchedule(ContentScheduleChange.Start).Count()); + Assert.AreEqual(1, schedule.GetSchedule(ContentScheduleChange.End).Count()); + Assert.AreEqual(1, schedule.GetFullSchedule().Count()); + } + + [Test] + public void Can_Clear_End_Variant() + { + var now = DateTime.Now; + var schedule = new ContentScheduleCollection(); + schedule.Add(now, now.AddDays(1)); + schedule.Add("en-US", now, now.AddDays(1)); + + schedule.Clear(ContentScheduleChange.End); + + Assert.AreEqual(0, schedule.GetSchedule(ContentScheduleChange.End).Count()); + Assert.AreEqual(1, schedule.GetSchedule(ContentScheduleChange.Start).Count()); + Assert.AreEqual(1, schedule.GetSchedule("en-US", ContentScheduleChange.End).Count()); + Assert.AreEqual(1, schedule.GetSchedule("en-US", ContentScheduleChange.Start).Count()); + Assert.AreEqual(2, schedule.GetFullSchedule().Count()); + + schedule.Clear("en-US", ContentScheduleChange.End); + + Assert.AreEqual(0, schedule.GetSchedule(ContentScheduleChange.End).Count()); + Assert.AreEqual(1, schedule.GetSchedule(ContentScheduleChange.Start).Count()); + Assert.AreEqual(0, schedule.GetSchedule("en-US", ContentScheduleChange.End).Count()); + Assert.AreEqual(1, schedule.GetSchedule("en-US", ContentScheduleChange.Start).Count()); + Assert.AreEqual(2, schedule.GetFullSchedule().Count()); + } + + } +} diff --git a/src/Umbraco.Tests/Models/ContentTests.cs b/src/Umbraco.Tests/Models/ContentTests.cs index 8f0ba39749..211de9a645 100644 --- a/src/Umbraco.Tests/Models/ContentTests.cs +++ b/src/Umbraco.Tests/Models/ContentTests.cs @@ -22,6 +22,7 @@ using Umbraco.Tests.Testing; namespace Umbraco.Tests.Models { + [TestFixture] public class ContentTests : UmbracoTestBase { diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index 2394302aad..35b5ed0c0d 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -260,11 +260,17 @@ namespace Umbraco.Tests.Services contentService.Save(content, Constants.Security.SuperUserId); content = contentService.GetById(content.Id); + var sched = content.ContentSchedule.GetFullSchedule(); + Assert.AreEqual(1, sched.Count()); + Assert.AreEqual(1, sched[string.Empty].Count()); content.ContentSchedule.Clear(ContentScheduleChange.End); contentService.Save(content, Constants.Security.SuperUserId); // Assert + content = contentService.GetById(content.Id); + sched = content.ContentSchedule.GetFullSchedule(); + Assert.AreEqual(0, sched.Count()); Assert.IsTrue(contentService.SaveAndPublish(content).Success); } diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 156bc06a14..8cff47c41b 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -122,6 +122,7 @@ +