diff --git a/TinCan/ActivityDefinition.cs b/TinCan/ActivityDefinition.cs index a9fe2af..e0018f2 100644 --- a/TinCan/ActivityDefinition.cs +++ b/TinCan/ActivityDefinition.cs @@ -14,9 +14,13 @@ You may obtain a copy of the License at limitations under the License. */ using System; +using System.Linq; +using System.Collections.Generic; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; using TinCan.Json; + namespace TinCan { public class ActivityDefinition : JsonModel @@ -26,13 +30,13 @@ public class ActivityDefinition : JsonModel public LanguageMap name { get; set; } public LanguageMap description { get; set; } public Extensions extensions { get; set; } - //public InteractionType interactionType { get; set; } - //public List correctResponsesPattern { get; set; } - //public List choices { get; set; } - //public List scale { get; set; } - //public List source { get; set; } - //public List target { get; set; } - //public List steps { get; set; } + public InteractionType interactionType { get; set; } + public List correctResponsesPattern { get; set; } + public List choices { get; set; } + public List scale { get; set; } + public List source { get; set; } + public List target { get; set; } + public List steps { get; set; } public ActivityDefinition() {} @@ -60,6 +64,54 @@ public ActivityDefinition(JObject jobj) { extensions = (Extensions)jobj.Value("extensions"); } + if (jobj["interactionType"] != null) + { + interactionType = InteractionType.FromValue(jobj.Value("interactionType")); + } + if (jobj["correctResponsesPattern"] != null) + { + correctResponsesPattern = ((JArray)jobj["correctResponsesPattern"]).Select(x => x.Value()).ToList(); + } + if (jobj["choices"] != null) + { + choices = new List(); + foreach (JObject jchoice in jobj["choices"]) + { + choices.Add(new InteractionComponent(jchoice)); + } + } + if (jobj["scale"] != null) + { + scale = new List(); + foreach (JObject jscale in jobj["scale"]) + { + scale.Add(new InteractionComponent(jscale)); + } + } + if (jobj["source"] != null) + { + source = new List(); + foreach (JObject jsource in jobj["source"]) + { + source.Add(new InteractionComponent(jsource)); + } + } + if (jobj["target"] != null) + { + target = new List(); + foreach (JObject jtarget in jobj["target"]) + { + target.Add(new InteractionComponent(jtarget)); + } + } + if (jobj["steps"] != null) + { + steps = new List(); + foreach (JObject jstep in jobj["steps"]) + { + steps.Add(new InteractionComponent(jstep)); + } + } } public override JObject ToJObject(TCAPIVersion version) { @@ -85,6 +137,64 @@ public override JObject ToJObject(TCAPIVersion version) { { result.Add("extensions", extensions.ToJObject(version)); } + if (interactionType != null) + { + result.Add("interactionType", interactionType.Value); + } + if (correctResponsesPattern != null && correctResponsesPattern.Count > 0) + { + result.Add("correctResponsesPattern", JToken.FromObject(correctResponsesPattern)); + } + if (choices != null && choices.Count > 0) + { + var jchoices = new JArray(); + result.Add("choices", jchoices); + + foreach (InteractionComponent ichoice in choices) + { + jchoices.Add(ichoice.ToJObject(version)); + } + } + if (scale != null && scale.Count > 0) + { + var jscale = new JArray(); + result.Add("scale", jscale); + + foreach (InteractionComponent iscale in scale) + { + jscale.Add(iscale.ToJObject(version)); + } + } + if (source != null && source.Count > 0) + { + var jsource = new JArray(); + result.Add("source", jsource); + + foreach (InteractionComponent isource in source) + { + jsource.Add(isource.ToJObject(version)); + } + } + if (target != null && target.Count > 0) + { + var jtarget = new JArray(); + result.Add("target", jtarget); + + foreach (InteractionComponent itarget in target) + { + jtarget.Add(itarget.ToJObject(version)); + } + } + if (steps != null && steps.Count > 0) + { + var jsteps = new JArray(); + result.Add("steps", jsteps); + + foreach (InteractionComponent istep in steps) + { + jsteps.Add(istep.ToJObject(version)); + } + } return result; } diff --git a/TinCan/InteractionComponent.cs b/TinCan/InteractionComponent.cs new file mode 100644 index 0000000..b5601d1 --- /dev/null +++ b/TinCan/InteractionComponent.cs @@ -0,0 +1,68 @@ +/* + Copyright 2018 Rustici Software + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +using System; +using System.Collections.Generic; +using Newtonsoft.Json.Linq; +using TinCan.Json; + +namespace TinCan +{ + public class InteractionComponent : JsonModel + { + public String id; + public LanguageMap description { get; set; } + + public InteractionComponent() + { + + } + + public InteractionComponent(JObject jobj) + { + if (jobj["id"] != null) + { + id = jobj.Value("id"); + } + if (jobj["description"] != null) + { + description = (LanguageMap)jobj.Value("description"); + } + } + + public override JObject ToJObject(TCAPIVersion version) + { + JObject result = new JObject(); + + if (id != null) + { + result.Add("id", id); + } + if (description != null && !description.isEmpty()) + { + result.Add("description", description.ToJObject(version)); + } + + return result; + } + + public static explicit operator InteractionComponent(JObject jobj) + { + return new InteractionComponent(jobj); + } + + } + +} diff --git a/TinCan/InteractionType.cs b/TinCan/InteractionType.cs new file mode 100644 index 0000000..21d7fc6 --- /dev/null +++ b/TinCan/InteractionType.cs @@ -0,0 +1,76 @@ +using System; + +namespace TinCan +{ + public sealed class InteractionType + { + private const string CHOICE = "choice"; + private const string SEQUENCING = "sequencing"; + private const string LIKERT = "likert"; + private const string MATCHING = "matching"; + private const string PERFORMANCE = "performance"; + private const string TRUEFALSE = "true-false"; + private const string FILLIN = "fill-in"; + private const string LONGFILLIN = "long-fill-in"; + private const string NUMERIC = "numeric"; + private const string OTHER = "other"; + + public static readonly InteractionType Choice = new InteractionType(CHOICE); + public static readonly InteractionType Sequencing = new InteractionType(SEQUENCING); + public static readonly InteractionType Likert = new InteractionType(LIKERT); + public static readonly InteractionType Matching = new InteractionType(MATCHING); + public static readonly InteractionType Performance = new InteractionType(PERFORMANCE); + public static readonly InteractionType TrueFalse = new InteractionType(TRUEFALSE); + public static readonly InteractionType FillIn = new InteractionType(FILLIN); + public static readonly InteractionType LongFillIn = new InteractionType(LONGFILLIN); + public static readonly InteractionType Numeric = new InteractionType(NUMERIC); + public static readonly InteractionType Other = new InteractionType(OTHER); + + private InteractionType(string value) + { + Value = value; + } + + public static InteractionType FromValue(string value) + { + switch (value) + { + case CHOICE: + return Choice; + + case SEQUENCING: + return Sequencing; + + case LIKERT: + return Likert; + + case MATCHING: + return Matching; + + case PERFORMANCE: + return Performance; + + case TRUEFALSE: + return TrueFalse; + + case FILLIN: + return FillIn; + + case LONGFILLIN: + return LongFillIn; + + case NUMERIC: + return Numeric; + + case OTHER: + return Other; + + default: + throw new ArgumentOutOfRangeException(nameof(value), + $"'{value}' is not a valid interactionType."); + } + } + + public string Value { get; private set; } + } +} diff --git a/TinCan/TCAPIVersion.cs b/TinCan/TCAPIVersion.cs index cd0a80a..fcde90b 100644 --- a/TinCan/TCAPIVersion.cs +++ b/TinCan/TCAPIVersion.cs @@ -20,6 +20,7 @@ namespace TinCan { public sealed class TCAPIVersion { + public static readonly TCAPIVersion V103 = new TCAPIVersion("1.0.3"); public static readonly TCAPIVersion V102 = new TCAPIVersion("1.0.2"); public static readonly TCAPIVersion V101 = new TCAPIVersion("1.0.1"); public static readonly TCAPIVersion V100 = new TCAPIVersion("1.0.0"); @@ -28,7 +29,7 @@ public sealed class TCAPIVersion public static TCAPIVersion latest() { - return V101; + return V103; } private static Dictionary known; @@ -41,6 +42,7 @@ public static Dictionary GetKnown() } known = new Dictionary(); + known.Add("1.0.3", V103); known.Add("1.0.2", V102); known.Add("1.0.1", V101); known.Add("1.0.0", V100); @@ -57,6 +59,7 @@ public static Dictionary GetSupported() } supported = new Dictionary(); + supported.Add("1.0.3", V103); supported.Add("1.0.2", V102); supported.Add("1.0.1", V101); supported.Add("1.0.0", V100); diff --git a/TinCan/TinCan.csproj b/TinCan/TinCan.csproj index a4c09bf..c7f2f55 100644 --- a/TinCan/TinCan.csproj +++ b/TinCan/TinCan.csproj @@ -94,6 +94,8 @@ + + diff --git a/TinCanTests/ContextTest.cs b/TinCanTests/ContextTest.cs new file mode 100644 index 0000000..59de387 --- /dev/null +++ b/TinCanTests/ContextTest.cs @@ -0,0 +1,95 @@ +/* + Copyright 2018 Rustici Software + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +using Newtonsoft.Json.Linq; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using TinCan; + +namespace TinCanTests +{ + [TestFixture] + class ContextTest + { + [TestCase(false)] + [TestCase(true)] + public void TestGroupInstructor(bool isGroup) + { + // Build our test JObject + Context exampleContext = BuildTextContext(); + if (isGroup) + { + exampleContext.instructor = new Group(); + } + else + { + exampleContext.instructor = new Agent(); + } + JObject contextObj = exampleContext.ToJObject(); + + // Ensure that Context.instructor is the correct Type + Context testContext = new Context(contextObj); + Assert.IsTrue(testContext.instructor is Agent); + Assert.AreEqual(isGroup, testContext.instructor is Group); + } + + [TestCase(false)] + [TestCase(true)] + public void TestGroupTeam(bool isGroup) + { + // Build our test JObject + Context exampleContext = BuildTextContext(); + if (isGroup) + { + exampleContext.team = new Group(); + } + else + { + exampleContext.team = new Agent(); + } + JObject contextObj = exampleContext.ToJObject(); + + // Ensure that Context.instructor is the correct Type + Context testContext = new Context(contextObj); + Assert.IsTrue(testContext.team is Agent); + Assert.AreEqual(isGroup, testContext.team is Group); + } + + private Context BuildTextContext() + { + Guid registration = new Guid("42c0855b-8f64-47f3-b0e2-3f337930045a"); + ContextActivities contextActivities = new ContextActivities(); + string revision = ""; + string platform = ""; + string language = ""; + StatementRef statement = new StatementRef(); + TinCan.Extensions extensions = new TinCan.Extensions(); + + Context context = new Context(); + context.registration = registration; + context.contextActivities = contextActivities; + context.revision = revision; + context.platform = platform; + context.language = language; + context.statement = statement; + context.extensions = extensions; + + return context; + } + } +} diff --git a/TinCanTests/StatementTest.cs b/TinCanTests/StatementTest.cs index 4169408..f1848d8 100644 --- a/TinCanTests/StatementTest.cs +++ b/TinCanTests/StatementTest.cs @@ -46,7 +46,7 @@ public void TestEmptyCtr() Assert.IsNull(obj.timestamp); Assert.IsNull(obj.stored); - StringAssert.AreEqualIgnoringCase("{\"version\":\"1.0.1\"}", obj.ToJSON()); + StringAssert.AreEqualIgnoringCase("{\"version\":\"1.0.3\"}", obj.ToJSON()); } [Test] diff --git a/TinCanTests/Support.cs b/TinCanTests/Support.cs index 7366eab..4c78888 100644 --- a/TinCanTests/Support.cs +++ b/TinCanTests/Support.cs @@ -48,6 +48,27 @@ static Support () { activity.definition.description = new LanguageMap(); activity.definition.description.Add("en-US", "Unit test 0 in the test suite for the Tin Can C# library."); + activity.definition.interactionType = InteractionType.Choice; + activity.definition.choices = new List(); + + for (int i = 1; i <= 3; i++) + { + InteractionComponent interactionComponent = new InteractionComponent(); + + interactionComponent.id = "choice-" + i.ToString(); + interactionComponent.description = new LanguageMap(); + interactionComponent.description.Add("en-US", "Choice " + i.ToString()); + + activity.definition.choices.Add(interactionComponent); + } + + activity.definition.correctResponsesPattern = new List(); + + for (int i = 1; i <= 2; i++) + { + activity.definition.correctResponsesPattern.Add("choice-" + i.ToString()); + } + parent = new Activity(); parent.id = "http://tincanapi.com/TinCanCSharp/Test"; parent.definition = new ActivityDefinition(); @@ -78,6 +99,7 @@ static Support () { result.success = true; result.completion = true; result.duration = new TimeSpan(1, 2, 16, 43); + result.response = "choice-2"; subStatement = new SubStatement(); subStatement.actor = agent; diff --git a/TinCanTests/TinCanTests.csproj b/TinCanTests/TinCanTests.csproj index 863109d..d43e39e 100644 --- a/TinCanTests/TinCanTests.csproj +++ b/TinCanTests/TinCanTests.csproj @@ -88,6 +88,7 @@ +