Skip to content

Commit 2a0bead

Browse files
authored
Add duration property to the Item model to support the new duration feature. (#35)
* Added duration property to the Item model. * Extracted the nested ternary operation in Duration model into an independent if statement. To follow the clean code rule: csharpsquid:S3358 (Ternary operators should not be nested) * Replace if statements with switch pattern in Duration.TimeValue property getter. * Moved Duration arguments validation to full properties. * Added unit tests for Duration model. * Configured the JsonProperty attribute on Duration to include nulls. This fixes the issue with update requests that removes durations. * Added integration test for task Duration property.
1 parent 75642f4 commit 2a0bead

File tree

5 files changed

+221
-0
lines changed

5 files changed

+221
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
using System;
2+
3+
using Todoist.Net.Models;
4+
using Todoist.Net.Tests.Extensions;
5+
using Xunit;
6+
7+
namespace Todoist.Net.Tests.Models
8+
{
9+
[Trait(Constants.TraitName, Constants.UnitTraitValue)]
10+
public class DurationTests
11+
{
12+
[Fact]
13+
public void AmountAssignment_InvalidValue_ThrowsException()
14+
{
15+
Duration duration;
16+
17+
Assert.Throws<ArgumentOutOfRangeException>(() =>
18+
duration = new Duration(0, DurationUnit.Minute));
19+
20+
duration = new Duration(15, DurationUnit.Minute);
21+
22+
Assert.Throws<ArgumentOutOfRangeException>(() =>
23+
duration.Amount = -5);
24+
}
25+
26+
[Fact]
27+
public void UnitAssignment_InvalidValue_ThrowsException()
28+
{
29+
Duration duration;
30+
31+
Assert.Throws<ArgumentNullException>(() =>
32+
duration = new Duration(15, null));
33+
34+
duration = new Duration(15, DurationUnit.Minute);
35+
36+
Assert.Throws<ArgumentNullException>(() =>
37+
duration.Unit = null);
38+
}
39+
40+
[Fact]
41+
public void TimeValueEvaluation_Success()
42+
{
43+
var duration = new Duration(15, DurationUnit.Minute);
44+
45+
Assert.Equal(TimeSpan.FromMinutes(15), duration.TimeValue);
46+
47+
duration.Amount = 3;
48+
duration.Unit = DurationUnit.Day;
49+
50+
Assert.Equal(TimeSpan.FromDays(3), duration.TimeValue);
51+
}
52+
53+
}
54+
}

src/Todoist.Net.Tests/Services/ItemsServiceTests.cs

+32
Original file line numberDiff line numberDiff line change
@@ -197,5 +197,37 @@ public void CreateNewItem_DueDateIsLocal_DueDateNotChanged()
197197

198198
client.Items.DeleteAsync(item.Id).Wait();
199199
}
200+
201+
202+
[Fact]
203+
[Trait(Constants.TraitName, Constants.IntegrationPremiumTraitValue)]
204+
public void CreateItemClearDurationAndDelete_Success()
205+
{
206+
var client = TodoistClientFactory.Create(_outputHelper);
207+
208+
var item = new Item("duration task")
209+
{
210+
DueDate = new DueDate("22 Dec 2021 at 9:15", language: Language.English),
211+
Duration = new Duration(45, DurationUnit.Minute)
212+
};
213+
client.Items.AddAsync(item).Wait();
214+
215+
var itemInfo = client.Items.GetAsync(item.Id).Result;
216+
217+
Assert.True(itemInfo.Item.Content == item.Content);
218+
Assert.Equal("2021-12-22T09:15:00", itemInfo.Item.DueDate.StringDate);
219+
220+
Assert.Equal(item.Duration.Amount, itemInfo.Item.Duration.Amount);
221+
Assert.Equal(item.Duration.Unit, itemInfo.Item.Duration.Unit);
222+
223+
itemInfo.Item.Duration = null;
224+
client.Items.UpdateAsync(itemInfo.Item).Wait();
225+
226+
itemInfo = client.Items.GetAsync(item.Id).Result;
227+
Assert.Null(itemInfo.Item.Duration);
228+
229+
client.Items.DeleteAsync(item.Id).Wait();
230+
}
231+
200232
}
201233
}

src/Todoist.Net/Models/Duration.cs

+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
using System;
2+
3+
using Newtonsoft.Json;
4+
5+
namespace Todoist.Net.Models
6+
{
7+
/// <summary>
8+
/// Represents durations for tasks.
9+
/// </summary>
10+
public class Duration
11+
{
12+
13+
private int _amount;
14+
private DurationUnit _unit;
15+
16+
/// <summary>
17+
/// Initializes a new instance of the <see cref="Duration" /> class.
18+
/// </summary>
19+
/// <param name="amount">The time amount.</param>
20+
/// <param name="unit">The time unit.</param>
21+
public Duration(int amount, DurationUnit unit)
22+
{
23+
Amount = amount;
24+
Unit = unit;
25+
}
26+
27+
internal Duration()
28+
{
29+
}
30+
31+
/// <summary>
32+
/// Gets or sets the duration time amount.
33+
/// </summary>
34+
/// <remarks>
35+
/// Must be a positive integer (greater than zero).
36+
/// </remarks>
37+
/// <value>
38+
/// The time amount.
39+
/// </value>
40+
/// <exception cref="ArgumentOutOfRangeException">Duration amount must be greater than zero.</exception>"
41+
[JsonProperty("amount")]
42+
public int Amount
43+
{
44+
get => _amount;
45+
set => _amount = value > 0
46+
? value
47+
: throw new ArgumentOutOfRangeException(nameof(Amount), "Duration amount must be greater than zero.");
48+
}
49+
50+
/// <summary>
51+
/// Gets or sets the duration time unit.
52+
/// </summary>
53+
/// <remarks>
54+
/// Either <c>minute</c> or <c>day</c>.
55+
/// </remarks>
56+
/// <value>
57+
/// The duration unit.
58+
/// </value>
59+
/// <exception cref="ArgumentNullException">Unit</exception>
60+
[JsonProperty("unit")]
61+
public DurationUnit Unit
62+
{
63+
get => _unit;
64+
set => _unit = value ?? throw new ArgumentNullException(nameof(Unit));
65+
}
66+
67+
68+
/// <summary>
69+
/// Gets the value of the duration as a <see cref="TimeSpan"/> object.
70+
/// </summary>
71+
/// <value>
72+
/// The <see cref="TimeSpan"/> value of the duration.
73+
/// </value>
74+
[JsonIgnore]
75+
public TimeSpan TimeValue
76+
{
77+
get
78+
{
79+
switch (Unit)
80+
{
81+
case var _ when Unit == DurationUnit.Minute:
82+
return TimeSpan.FromMinutes(Amount);
83+
case var _ when Unit == DurationUnit.Day:
84+
return TimeSpan.FromDays(Amount);
85+
default:
86+
throw new NotImplementedException();
87+
}
88+
}
89+
}
90+
91+
}
92+
}
+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
namespace Todoist.Net.Models
2+
{
3+
/// <summary>
4+
/// Represents a duration unit.
5+
/// </summary>
6+
/// <seealso cref="Todoist.Net.Models.StringEnum" />
7+
public class DurationUnit : StringEnum
8+
{
9+
/// <summary>
10+
/// Initializes a new instance of the <see cref="DurationUnit" /> class.
11+
/// </summary>
12+
/// <param name="value">The value.</param>
13+
private DurationUnit(string value)
14+
: base(value)
15+
{
16+
}
17+
18+
/// <summary>
19+
/// Gets the minute unit.
20+
/// </summary>
21+
/// <value>The minute unit.</value>
22+
public static DurationUnit Minute { get; } = new DurationUnit("minute");
23+
24+
/// <summary>
25+
/// Gets the day unit.
26+
/// </summary>
27+
/// <value>The day unit.</value>
28+
public static DurationUnit Day { get; } = new DurationUnit("day");
29+
30+
}
31+
}

src/Todoist.Net/Models/Item.cs

+12
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,18 @@ internal Item()
103103
[JsonProperty("due")]
104104
public DueDate DueDate { get; set; }
105105

106+
/// <summary>
107+
/// Gets or sets the duration.
108+
/// </summary>
109+
/// <remarks>
110+
/// Durations are only available for Todoist Premium users.
111+
/// </remarks>
112+
/// <value>
113+
/// The duration.
114+
/// </value>
115+
[JsonProperty("duration", NullValueHandling = NullValueHandling.Include)]
116+
public Duration Duration { get; set; }
117+
106118
/// <summary>
107119
/// Gets a value indicating whether this instance is checked.
108120
/// </summary>

0 commit comments

Comments
 (0)