using AcDream.App.UI.Layout;
namespace AcDream.App.Tests.UI.Layout;
///
/// Conformance for — the exact port of
/// retail's string-table escape codec
/// (StringTableMetaLanguage::UnescapeString @ 0x0067BDC0 /
/// EscapeString @ 0x0067BBC0, character tables
/// GetUnEscapedChar @ 0x0067B750 / GetEscapedChar @ 0x0067B6C0).
/// The metalanguage character set is byte-verified against the PDB-paired
/// 2013 binary (file offset 0x3FE178: []!{}#\|^$).
///
public sealed class RetailStringEscapesTests
{
[Theory]
[InlineData("line one\\nline two", "line one\nline two")]
[InlineData("a\\tb", "a\tb")]
[InlineData("a\\rb", "a\rb")]
[InlineData("say \\qhi\\q", "say \"hi\"")]
public void Unescape_DecodesTheFourCharacterEscapes(
string raw, string expected)
=> Assert.Equal(expected, RetailStringEscapes.Unescape(raw));
[Theory]
[InlineData("\\[", "[")]
[InlineData("\\]", "]")]
[InlineData("\\!", "!")]
[InlineData("\\{", "{")]
[InlineData("\\}", "}")]
[InlineData("\\#", "#")]
[InlineData("\\\\", "\\")]
[InlineData("\\|", "|")]
[InlineData("\\^", "^")]
[InlineData("\\$", "$")]
public void Unescape_DecodesEveryMetalanguageSelfEscape(
string raw, string expected)
=> Assert.Equal(expected, RetailStringEscapes.Unescape(raw));
///
/// GetUnEscapedChar returns 0 for anything else — retail keeps the
/// backslash verbatim (UnescapeString's else-branch), including a
/// trailing backslash whose "next" character is the terminator.
///
[Theory]
[InlineData("\\z", "\\z")]
[InlineData("C:\\path\\dir", "C:\\path\\dir")]
[InlineData("ends with \\", "ends with \\")]
[InlineData("\\N upper is not an escape", "\\N upper is not an escape")]
public void Unescape_KeepsUnrecognizedPairsVerbatim(
string raw, string expected)
=> Assert.Equal(expected, RetailStringEscapes.Unescape(raw));
///
/// The double-decode hazard the 2026-08-17 systemic round exists to
/// close: an authored escaped backslash before an 'n' decodes ONCE to
/// the literal two characters backslash+n — a second decode pass (the
/// retired per-consumer copies) would corrupt it into a line break.
///
[Fact]
public void Unescape_EscapedBackslashBeforeN_YieldsLiteralPair()
=> Assert.Equal("\\n", RetailStringEscapes.Unescape("\\\\n"));
[Fact]
public void Unescape_EmptyString_IsEmpty()
=> Assert.Equal(string.Empty, RetailStringEscapes.Unescape(string.Empty));
/// No backslash → no allocation: the same instance returns.
[Fact]
public void Unescape_NoEscapes_ReturnsTheSameInstance()
{
const string plain = "Please Wait";
Assert.Same(plain, RetailStringEscapes.Unescape(plain));
}
[Theory]
[InlineData("line one\nline two", "line one\\nline two")]
[InlineData("a\tb", "a\\tb")]
[InlineData("a\rb", "a\\rb")]
[InlineData("say \"hi\"", "say \\qhi\\q")]
[InlineData("[x]", "\\[x\\]")]
[InlineData("back\\slash", "back\\\\slash")]
[InlineData("plain", "plain")]
public void Escape_IsTheStorageInverse(string plain, string expected)
=> Assert.Equal(expected, RetailStringEscapes.Escape(plain));
///
/// Retail's template-variable round trip
/// (AddVariable_String @ 0x0042E6C0 escapes on insert;
/// InqString @ 0x0042E490 unescapes the composed whole): variable
/// content must come out verbatim.
///
[Theory]
[InlineData("plain name")]
[InlineData("Odd\\Name")]
[InlineData("multi\nline")]
[InlineData("tabs\tand \"quotes\"")]
[InlineData("[]!{}#\\|^$")]
[InlineData("")]
public void UnescapeOfEscape_RoundTripsVerbatim(string value)
=> Assert.Equal(value, RetailStringEscapes.Unescape(
RetailStringEscapes.Escape(value)));
}