Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Implement deep copy methods for components
- Implemented `CopyFrom` method in multiple classes for deep copying.
- Removed questions in code and implemented solution
- Updated `ICopyable` interface documentation.
- Introduced pattern matching for better readability
- Added new test methods in `CopyComponentTests` class for deep copying various calendar components.
- Fixed broken SerializationTests.AttendeesSerialized() (Corrected attendee names and fixed typos in assertions.)
- Refactored `CalendarObjectBase` (should eventually become abstract)
- Added `ExcludeFromCodeCoverage` attribute to `CalendarObjectList`.
- Updated `Ical.Net.csproj` to latest C# version.

Fixes #149
  • Loading branch information
axunonb committed Oct 20, 2024
commit f309d47e7d3f8f57ef28c84eae4a2a1cb166c65b
198 changes: 198 additions & 0 deletions Ical.Net.Tests/CopyComponentTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
using Ical.Net.CalendarComponents;
using Ical.Net.DataTypes;
using Ical.Net.Serialization;
using NUnit.Framework;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text.RegularExpressions;

namespace Ical.Net.Tests
{
/// <summary>
/// Tests for deep copying of ICal components.
/// </summary>
[TestFixture]
public class CopyComponentTests
{
[Test, TestCaseSource(nameof(CopyCalendarTest_TestCases)), Category("Copy tests")]
public void CopyCalendarTest(string calendarString)
{
var iCal1 = Calendar.Load(calendarString);
var iCal2 = iCal1.Copy<Calendar>();
SerializationTests.CompareCalendars(iCal1, iCal2);
}

public static IEnumerable CopyCalendarTest_TestCases()
{
yield return new TestCaseData(IcsFiles.Attachment3).SetName("Attachment3");
yield return new TestCaseData(IcsFiles.Bug2148092).SetName("Bug2148092");
yield return new TestCaseData(IcsFiles.CaseInsensitive1).SetName("CaseInsensitive1");
yield return new TestCaseData(IcsFiles.CaseInsensitive2).SetName("CaseInsensitive2");
yield return new TestCaseData(IcsFiles.CaseInsensitive3).SetName("CaseInsensitive3");
yield return new TestCaseData(IcsFiles.Categories1).SetName("Categories1");
yield return new TestCaseData(IcsFiles.Duration1).SetName("Duration1");
yield return new TestCaseData(IcsFiles.Encoding1).SetName("Encoding1");
yield return new TestCaseData(IcsFiles.Event1).SetName("Event1");
yield return new TestCaseData(IcsFiles.Event2).SetName("Event2");
yield return new TestCaseData(IcsFiles.Event3).SetName("Event3");
yield return new TestCaseData(IcsFiles.Event4).SetName("Event4");
yield return new TestCaseData(IcsFiles.GeographicLocation1).SetName("GeographicLocation1");
yield return new TestCaseData(IcsFiles.Language1).SetName("Language1");
yield return new TestCaseData(IcsFiles.Language2).SetName("Language2");
yield return new TestCaseData(IcsFiles.Language3).SetName("Language3");
yield return new TestCaseData(IcsFiles.TimeZone1).SetName("TimeZone1");
yield return new TestCaseData(IcsFiles.TimeZone2).SetName("TimeZone2");
yield return new TestCaseData(IcsFiles.TimeZone3).SetName("TimeZone3");
yield return new TestCaseData(IcsFiles.XProperty1).SetName("XProperty1");
yield return new TestCaseData(IcsFiles.XProperty2).SetName("XProperty2");
}

private static readonly DateTime _now = DateTime.Now;
private static readonly DateTime _later = _now.AddHours(1);

private static CalendarEvent GetSimpleEvent() => new CalendarEvent
{
DtStart = new CalDateTime(_now),
DtEnd = new CalDateTime(_later),
Duration = TimeSpan.FromHours(1),
};

private static string SerializeEvent(CalendarEvent e) => new CalendarSerializer().SerializeToString(new Calendar { Events = { e } });

[Test]
public void CopyCalendarEventTest()
{
var orig = GetSimpleEvent();
orig.Uid = "Hello";
orig.Summary = "Original summary";
orig.Resources = new[] { "A", "B" };
orig.GeographicLocation = new GeographicLocation(48.210033, 16.363449);
orig.Transparency = TransparencyType.Opaque;
orig.Attachments.Add(new Attachment("https://original.org/"));
var copy = orig.Copy<CalendarEvent>();

copy.Uid = "Goodbye";
copy.Summary = "Copy summary";

var resourcesCopyFromOrig = new List<string>(copy.Resources);
copy.Resources = new[] { "C", "D" };
copy.Attachments[0].Uri = new Uri("https://copy.org/");
const string uidPattern = "UID:";
var serializedOrig = SerializeEvent(orig);
var serializedCopy = SerializeEvent(copy);

Assert.Multiple(() =>
{
// Should be a deep copy and changes only apply to the copy instance
Assert.That(copy.Uid, Is.Not.EqualTo(orig.Uid));
Assert.That(copy.Summary, Is.Not.EqualTo(orig.Summary));
Assert.That(copy.Attachments[0].Uri, Is.Not.EqualTo(orig.Attachments[0].Uri));
Assert.That(copy.Resources[0], Is.Not.EqualTo(orig.Resources[0]));

Assert.That(resourcesCopyFromOrig, Is.EquivalentTo(orig.Resources));
Assert.That(copy.GeographicLocation, Is.EqualTo(orig.GeographicLocation));
Assert.That(copy.Transparency, Is.EqualTo(orig.Transparency));

Assert.That(Regex.Matches(serializedOrig, uidPattern, RegexOptions.Compiled, TimeSpan.FromSeconds(100)), Has.Count.EqualTo(1));
Assert.That(Regex.Matches(serializedCopy, uidPattern, RegexOptions.Compiled, TimeSpan.FromSeconds(100)), Has.Count.EqualTo(1));
});
}

[Test]
public void CopyFreeBusyTest()
{
var orig = new FreeBusy
{
Start = new CalDateTime(_now),
End = new CalDateTime(_later),
Entries = { new FreeBusyEntry { Language = "English", StartTime = new CalDateTime(2024, 10, 1), Duration = TimeSpan.FromDays(1), Status = FreeBusyStatus.Busy}}
};

var copy = orig.Copy<FreeBusy>();

Assert.Multiple(() =>
{
// Start/DtStart and End/DtEnd are the same
Assert.That(copy.Start, Is.EqualTo(orig.DtStart));
Assert.That(copy.End, Is.EqualTo(orig.DtEnd));
Assert.That(copy.Entries[0].Language, Is.EqualTo(orig.Entries[0].Language));
Assert.That(copy.Entries[0].StartTime, Is.EqualTo(orig.Entries[0].StartTime));
Assert.That(copy.Entries[0].Duration, Is.EqualTo(orig.Entries[0].Duration));
Assert.That(copy.Entries[0].Status, Is.EqualTo(orig.Entries[0].Status));
});
}

[Test]
public void CopyAlarmTest()
{
var orig = new Alarm
{
Action = AlarmAction.Display,
Trigger = new Trigger(TimeSpan.FromMinutes(15)),
Description = "Test Alarm"
};

var copy = orig.Copy<Alarm>();

Assert.Multiple(() =>
{
Assert.That(copy.Action, Is.EqualTo(orig.Action));
Assert.That(copy.Trigger, Is.EqualTo(orig.Trigger));
Assert.That(copy.Description, Is.EqualTo(orig.Description));
});
}

[Test]
public void CopyTodoTest()
{
var orig = new Todo
{
Summary = "Test Todo",
Description = "This is a test todo",
Due = new CalDateTime(DateTime.Now.AddDays(10)),
Priority = 1,
Contacts = new[] { "John", "Paul" },
Status = "NeedsAction"
};

var copy = orig.Copy<Todo>();

Assert.Multiple(() =>
{
Assert.That(copy.Summary, Is.EqualTo(orig.Summary));
Assert.That(copy.Description, Is.EqualTo(orig.Description));
Assert.That(copy.Due, Is.EqualTo(orig.Due));
Assert.That(copy.Priority, Is.EqualTo(orig.Priority));
Assert.That(copy.Contacts, Is.EquivalentTo(orig.Contacts));
Assert.That(copy.Status, Is.EqualTo(orig.Status));
});
}

[Test]
public void CopyJournalTest()
{
var orig = new Journal
{
Summary = "Test Journal",
Description = "This is a test journal",
DtStart = new CalDateTime(DateTime.Now),
Categories = new List<string> { "Category1", "Category2" },
Priority = 1,
Status = "Draft"
};

var copy = orig.Copy<Journal>();

Assert.Multiple(() =>
{
Assert.That(copy.Summary, Is.EqualTo(orig.Summary));
Assert.That(copy.Description, Is.EqualTo(orig.Description));
Assert.That(copy.DtStart, Is.EqualTo(orig.DtStart));
Assert.That(copy.Categories, Is.EquivalentTo(orig.Categories));
Assert.That(copy.Priority, Is.EqualTo(orig.Priority));
Assert.That(copy.Status, Is.EqualTo(orig.Status));
});
}
}
}
77 changes: 0 additions & 77 deletions Ical.Net.Tests/CopyTest.cs

This file was deleted.

28 changes: 14 additions & 14 deletions Ical.Net.Tests/DeserializationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -229,20 +229,20 @@ public void Encoding2()
var evt = iCal.Events.First();

Assert.That(
evt.Attachments[0].ToString(),
Is.EqualTo("This is a test to try out base64 encoding without being too large.\r\n" +
"This is a test to try out base64 encoding without being too large.\r\n" +
"This is a test to try out base64 encoding without being too large.\r\n" +
"This is a test to try out base64 encoding without being too large.\r\n" +
"This is a test to try out base64 encoding without being too large.\r\n" +
"This is a test to try out base64 encoding without being too large.\r\n" +
"This is a test to try out base64 encoding without being too large.\r\n" +
"This is a test to try out base64 encoding without being too large.\r\n" +
"This is a test to try out base64 encoding without being too large.\r\n" +
"This is a test to try out base64 encoding without being too large.\r\n" +
"This is a test to try out base64 encoding without being too large.\r\n" +
"This is a test to try out base64 encoding without being too large."),
"Attached value does not match.");
evt.Attachments[0].ToString(),
Is.EqualTo("This is a test to try out base64 encoding without being too large.\r\n" +
"This is a test to try out base64 encoding without being too large.\r\n" +
"This is a test to try out base64 encoding without being too large.\r\n" +
"This is a test to try out base64 encoding without being too large.\r\n" +
"This is a test to try out base64 encoding without being too large.\r\n" +
"This is a test to try out base64 encoding without being too large.\r\n" +
"This is a test to try out base64 encoding without being too large.\r\n" +
"This is a test to try out base64 encoding without being too large.\r\n" +
"This is a test to try out base64 encoding without being too large.\r\n" +
"This is a test to try out base64 encoding without being too large.\r\n" +
"This is a test to try out base64 encoding without being too large.\r\n" +
"This is a test to try out base64 encoding without being too large."),
"Attached value does not match.");
}

[Test]
Expand Down
17 changes: 9 additions & 8 deletions Ical.Net.Tests/SerializationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ public static string InspectSerializedSection(string serialized, string sectionN
var end = serialized.IndexOf(searchFor, begin);
Assert.That(end, Is.Not.EqualTo(-1), () => string.Format(notFound, searchFor));

var searchRegion = serialized.Substring(begin, end - begin + 1);
var searchRegion = serialized.Substring(begin, end - begin + searchFor.Length);

foreach (var e in elements)
{
Expand Down Expand Up @@ -284,14 +284,14 @@ public void EventPropertiesSerialized()
{
new Attendee("MAILTO:[email protected]")
{
CommonName = "James James",
CommonName = "James",
Role = ParticipationRole.RequiredParticipant,
Rsvp = true,
ParticipationStatus = EventParticipationStatus.Tentative
},
new Attendee("MAILTO:[email protected]")
{
CommonName = "Mary Mary",
CommonName = "Mary",
Role = ParticipationRole.RequiredParticipant,
Rsvp = true,
ParticipationStatus = EventParticipationStatus.Accepted
Expand All @@ -301,7 +301,6 @@ public void EventPropertiesSerialized()
[Test, Category("Serialization")]
public void AttendeesSerialized()
{
//ToDo: This test is broken as of 2016-07-13
var cal = new Calendar
{
Method = "REQUEST",
Expand All @@ -310,12 +309,14 @@ public void AttendeesSerialized()

var evt = AttendeeTest.VEventFactory();
cal.Events.Add(evt);
const string org = "MAILTO:[email protected]";
// new Uri() creates lowercase for the "MAILTO:" part
// according to the RFC 2368 specification
const string org = "MAILTO:[email protected]";
evt.Organizer = new Organizer(org);

evt.Attendees.AddRange(_attendees);

// However a bug, when a participation value is changed, ultimately re-serialises as an array (PARTSTAT=ACCEPTED,DECLINED)
// Changing the ParticipationStatus just keeps the last status
evt.Attendees[0].ParticipationStatus = EventParticipationStatus.Declined;

var serializer = new CalendarSerializer();
Expand All @@ -325,7 +326,7 @@ public void AttendeesSerialized()

foreach (var a in evt.Attendees)
{
var vals = GetValues(vEvt, "ATTENDEE", a.Value.OriginalString);
var vals = GetValues(vEvt, "ATTENDEE", a.Value.ToString());
foreach (var v in new Dictionary<string, string>
{
["CN"] = a.CommonName,
Expand All @@ -338,7 +339,7 @@ public void AttendeesSerialized()
Assert.Multiple(() =>
{
Assert.That(vals.ContainsKey(v.Key), Is.True, $"could not find key '{v.Key}'");
Assert.That(vals[v.Key], Is.EqualTo(v.Value), $"ATENDEE prop '{v.Key}' differ");
Assert.That(vals[v.Key], Is.EqualTo(v.Value), $"ATTENDEE prop '{v.Key}' differ");
});
}
}
Expand Down
4 changes: 3 additions & 1 deletion Ical.Net/CalendarComponents/CalendarComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ protected override void OnDeserializing(StreamingContext context)
Initialize();
}

/// <inheritdoc/>
public override void CopyFrom(ICopyable obj)
{
base.CopyFrom(obj);
Expand All @@ -50,7 +51,8 @@ public override void CopyFrom(ICopyable obj)
Properties.Clear();
foreach (var p in c.Properties)
{
Properties.Add(p);
// Uses CalendarObjectBase.Copy<T>() for a deep copy
Properties.Add(p.Copy<ICalendarProperty>());
}
}

Expand Down
Loading