diff --git a/Assets/Tests/Editor/SkillSourceFrontmatterReaderTests.cs b/Assets/Tests/Editor/SkillSourceFrontmatterReaderTests.cs new file mode 100644 index 0000000000..61a23f134c --- /dev/null +++ b/Assets/Tests/Editor/SkillSourceFrontmatterReaderTests.cs @@ -0,0 +1,71 @@ +using NUnit.Framework; + +using io.github.hatayama.UnityCliLoop.Infrastructure; + +namespace io.github.hatayama.UnityCliLoop.Tests.Editor +{ + /// + /// Verifies scalar normalization for SKILL.md frontmatter fields. + /// + [TestFixture] + public class SkillSourceFrontmatterReaderTests + { + // Tests that string scalar fields strip surrounding whitespace and single or double quotes. + [Test] + public void ParseScalarFields_WhenQuoted_StripsWhitespaceAndQuotes() + { + string content = + "---\n" + + "name: 'uloop-compile' \n" + + "toolName: \"compile\" \n" + + "description: 'Compile the project.' \n" + + "---\n"; + + Assert.That( + SkillSourceFrontmatterReader.ParseNameFromFrontmatter(content), + Is.EqualTo("uloop-compile")); + Assert.That( + SkillSourceFrontmatterReader.ParseToolNameFromFrontmatter(content), + Is.EqualTo("compile")); + Assert.That( + SkillSourceFrontmatterReader.ParseDescriptionFromFrontmatter(content), + Is.EqualTo("Compile the project.")); + } + + // Tests that tool names preserve their original casing after scalar normalization. + [Test] + public void ParseToolNameFromFrontmatter_WhenQuoted_PreservesCasing() + { + string content = "---\ntoolName: 'Compile'\n---\n"; + + string toolName = SkillSourceFrontmatterReader.ParseToolNameFromFrontmatter(content); + + Assert.That(toolName, Is.EqualTo("Compile")); + } + + // Tests that internal flags accept quoted true values without changing case-insensitive semantics. + [TestCase("\"TrUe\"")] + [TestCase("'TrUe'")] + [TestCase(" \"TrUe\" ")] + public void IsInternalSkill_WhenTrueIsQuoted_ReturnsTrue(string scalar) + { + string content = $"---\ninternal: {scalar}\n---\n"; + + bool isInternal = SkillSourceFrontmatterReader.IsInternalSkill(content); + + Assert.That(isInternal, Is.True); + } + + // Tests that quoted false values remain non-internal after scalar normalization. + [TestCase("\"FALSE\"")] + [TestCase("'false'")] + public void IsInternalSkill_WhenFalseIsQuoted_ReturnsFalse(string scalar) + { + string content = $"---\ninternal: {scalar}\n---\n"; + + bool isInternal = SkillSourceFrontmatterReader.IsInternalSkill(content); + + Assert.That(isInternal, Is.False); + } + } +} diff --git a/Assets/Tests/Editor/SkillSourceFrontmatterReaderTests.cs.meta b/Assets/Tests/Editor/SkillSourceFrontmatterReaderTests.cs.meta new file mode 100644 index 0000000000..2691bac707 --- /dev/null +++ b/Assets/Tests/Editor/SkillSourceFrontmatterReaderTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b3c91665d9964d978ebecdb2b1290d67 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/src/Editor/Infrastructure/SkillSetup/SkillSourceFrontmatterReader.cs b/Packages/src/Editor/Infrastructure/SkillSetup/SkillSourceFrontmatterReader.cs index f2885e03e1..4c1d27e221 100644 --- a/Packages/src/Editor/Infrastructure/SkillSetup/SkillSourceFrontmatterReader.cs +++ b/Packages/src/Editor/Infrastructure/SkillSetup/SkillSourceFrontmatterReader.cs @@ -11,6 +11,11 @@ namespace io.github.hatayama.UnityCliLoop.Infrastructure /// internal static class SkillSourceFrontmatterReader { + private static string NormalizeFrontmatterScalar(string value) + { + return value.Trim().Trim('"', '\''); + } + internal static string ParseToolNameFromFrontmatter(string content) { Match frontmatterMatch = Regex.Match(content, @"^---\r?\n([\s\S]*?)\r?\n---"); @@ -26,7 +31,7 @@ internal static string ParseToolNameFromFrontmatter(string content) return null; } - return toolNameMatch.Groups[1].Value.Trim(); + return NormalizeFrontmatterScalar(toolNameMatch.Groups[1].Value); } internal static string ParseNameFromFrontmatter(string content) @@ -44,7 +49,7 @@ internal static string ParseNameFromFrontmatter(string content) return null; } - return nameMatch.Groups[1].Value.Trim().Trim('"'); + return NormalizeFrontmatterScalar(nameMatch.Groups[1].Value); } internal static string ParseDescriptionFromFrontmatter(string content) @@ -62,7 +67,7 @@ internal static string ParseDescriptionFromFrontmatter(string content) return null; } - return descriptionMatch.Groups[1].Value.Trim().Trim('"'); + return NormalizeFrontmatterScalar(descriptionMatch.Groups[1].Value); } internal static string ResolveToolNameForSkillSource(string skillName, string toolName) @@ -133,7 +138,7 @@ internal static bool IsInternalSkill(string content) } return string.Equals( - internalMatch.Groups[1].Value.Trim(), + NormalizeFrontmatterScalar(internalMatch.Groups[1].Value), "true", StringComparison.OrdinalIgnoreCase); }