using System;
using System.Reflection;
using NUnit.Framework;
using UnityEngine;
using MCPForUnity.Editor.Tools;
namespace MCPForUnityTests.Editor.Tools
{
/// <summary>
/// Tests for ManageScript delimiter-checking and token-finding logic,
/// specifically covering C# string variants that the old lexer missed:
/// verbatim strings, interpolated strings, raw string literals.
/// </summary>
public class ManageScriptDelimiterTests
{
// ── CheckBalancedDelimiters ──────────────────────────────────────
[Test]
public void CheckBalancedDelimiters_VerbatimString_WithBackslashes()
{
// @"C:\Users\file" — backslashes are NOT escape chars in verbatim strings
string code = "class C { string s = @\"C:\\Users\\file\"; }";
Assert.IsTrue(CallCheckBalancedDelimiters(code, out _, out _),
"Verbatim string with backslashes should not break delimiter balance");
}
[Test]
public void CheckBalancedDelimiters_VerbatimString_DoubledQuotes()
{
// @"He said ""hello""" — doubled quotes are the escape in verbatim strings
string code = "class C { string s = @\"He said \"\"hello\"\"\"; }";
Assert.IsTrue(CallCheckBalancedDelimiters(code, out _, out _),
"Verbatim string with doubled quotes should not break delimiter balance");
}
[Test]
public void CheckBalancedDelimiters_InterpolatedString_WithBraces()
{
// $"Value: {x}" — the { } are interpolation holes, not real braces
string code = "class C { void M() { int x = 1; string s = $\"Value: {x}\"; } }";
Assert.IsTrue(CallCheckBalancedDelimiters(code, out _, out _),
"Interpolated string braces should not be counted as delimiters");
}
[Test]
public void CheckBalancedDelimiters_InterpolatedVerbatim_Combined()
{
// $@"Path: {dir}\file" — interpolated + verbatim combined
string code = "class C { void M() { string dir = \"d\"; string s = $@\"Path: {dir}\\file\"; } }";
Assert.IsTrue(CallCheckBalancedDelimiters(code, out _, out _),
"Interpolated verbatim string should not break delimiter balance");
}
[Test]
public void CheckBalancedDelimiters_NestedInterpolation()
{
// $"Outer {$"Inner {x}"}" — nested interpolated strings
string code = "class C { void M() { int x = 1; string s = $\"Outer {$\"Inner {x}\"}\"; } }";
Assert.IsTrue(CallCheckBalancedDelimiters(code, out _, out _),
"Nested interpolated strings should not break delimiter balance");
}
[Test]
public void CheckBalancedDelimiters_RawStringLiteral()
{
// C# 11 raw string literal: """{ }"""
string code = "class C { string s = \"\"\"\n{ }\n\"\"\"; }";
Assert.IsTrue(CallCheckBalancedDelimiters(code, out _, out _),
"Raw string literal braces should not be counted as delimiters");
}
[Test]
public void CheckBalancedDelimiters_MultilineVerbatimString()
{
// Verbatim string spanning multiple lines with braces
string code = "class C { string s = @\"line1\n{ }\"; }";
Assert.IsTrue(CallCheckBalancedDelimiters(code, out _, out _),
"Multiline verbatim string with braces should not break balance");
}
[Test]
public void CheckBalancedDelimiters_InterpolatedEscapedBraces()
{
// $"literal {{braces}}" — escaped braces in interpolated string
string code = "class C { string s = $\"literal {{braces}}\"; }";
Assert.IsTrue(CallCheckBalancedDelimiters(code, out _, out _),
"Escaped braces in interpolated strings should not break balance");
}
[Test]
public void CheckBalancedDelimiters_InterpolatedRawString()
{
// $"""...{expr}...""" — interpolated raw string literal (C# 11)
string code = "class C { void M() { int x = 1; string s = $\"\"\"\n Hello {x}\n \"\"\"; } }";
Assert.IsTrue(CallCheckBalancedDelimiters(code, out _, out _),
"Interpolated raw string should not break delimiter balance");
}
[Test]
public void CheckBalancedDelimiters_MultiDollarRawString()
{
// $$"""...{{expr}}...""" — multi-dollar interpolated raw string
string code = "class C { void M() { int x = 1; string s = $$\"\"\"\n {literal} {{x}}\n \"\"\"; } }";
Assert.IsTrue(CallCheckBalancedDelimiters(code, out _, out _),
"Multi-dollar raw string should not break delimiter balance");
}
[Test]
public void CheckBalancedDelimiters_BracesInComments_Ignored()
{
string code = "class C {\n// {\n/* { */\nvoid M() { }\n}";
Assert.IsTrue(CallCheckBalancedDelimiters(code, out _, out _),
"Braces in comments should be ignored");
}
[Test]
public void CheckBalancedDelimiters_BracesInRegularStrings_Ignored()
{
string code = "class C { string s = \"{ }\"; }";
Assert.IsTrue(CallCheckBalancedDelimiters(code, out _, out _),
"Braces in regular strings should be ignored");
}
[Test]
public void CheckBalancedDelimiters_ActuallyUnbalanced_ReturnsFalse()
{
string code = "class C { void M() { }";
Assert.IsFalse(CallCheckBalancedDelimiters(code, out _, out _),
"Actually unbalanced code should return false");
}
[Test]
public void CheckBalancedDelimiters_ExtraClosingBrace_ReturnsFalse()
{
string code = "class C { } }";
Assert.IsFalse(CallCheckBalancedDelimiters(code, out _, out _),
"Extra closing brace should return false");
}
[Test]
public void CheckBalancedDelimiters_RealWorldUnityScript()
{
string code = @"using UnityEngine;
public class PlayerHUD : MonoBehaviour
{
private int score;
private string playerName;
void Start()
{
score = 0;
playerName = ""Player"";
}
void OnGUI()
{
string label = $""Score: {score}"";
string path = @""C:\Games\SaveData"";
string msg = $@""Player {playerName} at path {path}"";
Debug.Log($""HUD initialized for {playerName} with score {score}"");
Debug.Log(""Literal {{braces}}"");
}
}";
Assert.IsTrue(CallCheckBalancedDelimiters(code, out _, out _),
"Real-world Unity script with interpolated/verbatim strings should pass");
}
// ── IndexOfClassToken ────────────────────────────────────────────
[Test]
public void IndexOfClassToken_FindsClass_NormalCode()
{
string code = "public class Foo { }";
int idx = CallIndexOfClassToken(code, "Foo");
Assert.GreaterOrEqual(idx, 0, "Should find class Foo in normal code");
}
[Test]
public void IndexOfClassToken_SkipsClassInComment()
{
string code = "// class Foo\npublic class Real { }";
int idx = CallIndexOfClassToken(code, "Foo");
Assert.AreEqual(-1, idx, "Should not find 'class Foo' inside a comment");
}
[Test]
public void IndexOfClassToken_SkipsClassInString()
{
string code = "class Real { string s = \"class Foo { }\"; }";
int idx = CallIndexOfClassToken(code, "Foo");
Assert.AreEqual(-1, idx, "Should not find 'class Foo' inside a string literal");
}
[Test]
public void IndexOfClassToken_FindsSecondClass_WhenFirstInComment()
{
string code = "// class Fake\npublic class Real { }";
int idx = CallIndexOfClassToken(code, "Real");
Assert.GreaterOrEqual(idx, 0, "Should find class Real even when a commented class precedes it");
}
// ── Reflection helpers ───────────────────────────────────────────
private static bool CallCheckBalancedDelimiters(string text, out int line, out char expected)
{
line = 0;
expected = '\0';
var method = typeof(ManageScript).GetMethod("CheckBalancedDelimiters",
BindingFlags.NonPublic | BindingFlags.Static);
Assert.IsNotNull(method, "CheckBalancedDelimiters method should exist");
var parameters = new object[] { text, 0, '\0' };
var result = (bool)method.Invoke(null, parameters);
line = (int)parameters[1];
expected = (char)parameters[2];
return result;
}
private static int CallIndexOfClassToken(string source, string className)
{
var method = typeof(ManageScript).GetMethod("IndexOfClassToken",
BindingFlags.NonPublic | BindingFlags.Static);
Assert.IsNotNull(method, "IndexOfClassToken method should exist");
return (int)method.Invoke(null, new object[] { source, className });
}
}
}