Search the source of every open source package.
80 results
global using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass]
public class TestInit
{
[AssemblyInitialize]
public static void ClassInitialize( TestContext context )
{
Sandbox.Application.InitUnitTest();
}
}
global using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass]
public class TestInit
{
[AssemblyInitialize]
public static void ClassInitialize( TestContext context )
{
Sandbox.Application.InitUnitTest();
}
}
using Sandbox.TailBox;
using System;
using System.IO;
using System.Linq;
[TestClass]
public sealed class TailBoxExtendedUtilityTests
{
[DataTestMethod]
[DataRow( "flex-auto", "flex-grow: 1;" )]
[DataRow( "flex-none", "flex-shrink: 0;" )]
[DataRow( "basis-[33px]", "flex-basis: 33px;" )]
[DataRow( "order-3", "order: 3;" )]
[DataRow( "-order-2", "order: -2;" )]
[DataRow( "h-full", "height: 100%;" )]
[DataRow( "min-w-0", "min-width: 0;" )]
[DataRow( "min-w-[220px]", "min-width: 220px;" )]
[DataRow( "min-h-[24px]", "min-height: 24px;" )]
[DataRow( "max-w-screen", "max-width: 100vw;" )]
[DataRow( "aspect-square", "aspect-ratio: 1;" )]
[DataRow( "aspect-[4/3]", "aspect-ratio: 4/3;" )]
public void GeneratesAdditionalLayoutAndSizingUtilities( string className, string declaration )
{
AssertUtility( className, declaration );
}
[DataTestMethod]
[DataRow( "border-x", "border-left: 1px solid rgba( 139, 154, 164, 0.32 );" )]
[DataRow( "border-y-accent", "border-bottom: 1px solid #d7b46a;" )]
[DataRow( "border-l-[3px]", "border-left: 3px solid rgba( 139, 154, 164, 0.32 );" )]
[DataRow( "border-[3px]", "border-width: 3px;" )]
[DataRow( "border-[#123456]/25", "border-color: rgba( 18, 52, 86, 0.25 );" )]
[DataRow( "rounded-br-md", "border-radius: 0px 0px 8px 0px;" )]
[DataRow( "rounded-s-lg", "border-radius: 12px 0px 0px 12px;" )]
[DataRow( "rounded-ee-[9px]", "border-radius: 0px 0px 9px 0px;" )]
public void GeneratesAdditionalBorderAndRadiusUtilities( string className, string declaration )
{
AssertUtility( className, declaration );
}
[DataTestMethod]
[DataRow( "text-right", "text-align: right;" )]
[DataRow( "lowercase", "text-transform: lowercase;" )]
[DataRow( "capitalize", "text-transform: capitalize;" )]
[DataRow( "text-[length:22px]/7", "line-height: 28px;" )]
[DataRow( "font-[Roboto_Slab]", "font-family: Roboto Slab;" )]
[DataRow( "-tracking-wide", "letter-spacing: -0.025em;" )]
[DataRow( "decoration-[3px]", "text-decoration-thickness: 3px;" )]
[DataRow( "underline-offset-4", "text-decoration-underline-offset: 4px;" )]
[DataRow( "-underline-offset-2", "text-decoration-underline-offset: -2px;" )]
[DataRow( "text-[color:#123456]/50", "color: rgba( 18, 52, 86, 0.5 );" )]
[DataRow( "text-accent/50", "color: rgba( 215, 180, 106, 0.5 );" )]
public void GeneratesAdditionalTypographyUtilities( string className, string declaration )
{
AssertUtility( className, declaration );
}
[DataTestMethod]
[DataRow( "bg-contain", "background-size: contain;" )]
[DataRow( "bg-center", "background-position: center;" )]
[DataRow( "bg-no-repeat", "background-repeat: no-repeat;" )]
[DataRow( "bg-[linear-gradient(red,_blue)]", "background-image: linear-gradient(red, blue);" )]
[DataRow( "bg-[image:url(/ui/panel.png)]", "background-image: url(/ui/panel.png);" )]
public void GeneratesAdditionalBackgroundSurfaceUtilities( string className, string declaration )
{
AssertUtility( className, declaration );
}
[DataTestMethod]
[DataRow( "opacity-75", "opacity: 0.75;" )]
[DataRow( "opacity-[0.35]", "opacity: 0.35;" )]
[DataRow( "shadow", "box-shadow: 0 12px 32px rgba( 0, 0, 0, 0.34 );" )]
[DataRow( "shadow-none", "box-shadow: none;" )]
[DataRow( "shadow-[0_0_12px_black]", "box-shadow: 0 0 12px black;" )]
[DataRow( "text-shadow", "text-shadow: 0 2px 4px rgba( 0, 0, 0, 0.4 );" )]
[DataRow( "text-shadow-[0_0_12px_black]", "text-shadow: 0 0 12px black;" )]
[DataRow( "blur", "filter-blur: 8px;" )]
[DataRow( "contrast-125", "filter-contrast: 1.25;" )]
[DataRow( "saturate-[1.25]", "filter-saturate: 1.25;" )]
[DataRow( "-hue-rotate-30", "filter-hue-rotate: -30deg;" )]
[DataRow( "invert", "filter-invert: 1;" )]
[DataRow( "grayscale-0", "filter-saturate: 0;" )]
[DataRow( "drop-shadow-sm", "filter-drop-shadow: 0 2px 8px rgba( 0, 0, 0, 0.24 );" )]
[DataRow( "backdrop-brightness-125", "backdrop-filter-brightness: 1.25;" )]
[DataRow( "backdrop-blur-sm", "backdrop-filter-blur: 4px;" )]
[DataRow( "mix-blend-multiply", "mix-blend-mode: multiply;" )]
public void GeneratesAdditionalEffectsUtilities( string className, string declaration )
{
AssertUtility( className, declaration );
}
[DataTestMethod]
[DataRow( "transition", "transition-property: all;" )]
[DataRow( "transition-all", "transition-property: all;" )]
[DataRow( "transition-colors", "transition-property: color, background-color, border-color, text-decoration-color;" )]
[DataRow( "transition-opacity", "transition-property: opacity;" )]
[DataRow( "transition-shadow", "transition-property: box-shadow, text-shadow, filter-drop-shadow;" )]
[DataRow( "transition-transform", "transition-property: transform;" )]
[DataRow( "duration-[275ms]", "transition-duration: 275ms;" )]
[DataRow( "delay-300", "transition-delay: 0.3s;" )]
[DataRow( "ease-[cubic-bezier(0.2,_0,_0,_1)]", "transition-timing-function: cubic-bezier(0.2, 0, 0, 1);" )]
[DataRow( "transform-none", "transform: none;" )]
[DataRow( "transform-[translateX(4px)_scale(1.04)]", "transform: translateX(4px) scale(1.04);" )]
[DataRow( "origin-bottom-right", "transform-origin: bottom right;" )]
[DataRow( "animate-none", "animation: none;" )]
[DataRow( "animate-[fade_1s_ease]", "animation: fade 1s ease;" )]
public void GeneratesMotionUtilities( string className, string declaration )
{
AssertUtility( className, declaration );
}
[DataTestMethod]
[DataRow( "fixed", TailBoxSkipReason.UnsupportedValue )]
[DataRow( "space-x-4", TailBoxSkipReason.UnsupportedSelectorVariant )]
[DataRow( "bg-gradient-to-r", TailBoxSkipReason.UnsupportedUtility )]
[DataRow( "bg-[paint-token]", TailBoxSkipReason.UnsupportedProperty )]
[DataRow( "border-dashed", TailBoxSkipReason.UnsupportedProperty )]
[DataRow( "decoration-wavy", TailBoxSkipReason.UnsupportedProperty )]
[DataRow( "text-lg/unknown", TailBoxSkipReason.UnsupportedModifier )]
[DataRow( "text-accent/not-real", TailBoxSkipReason.UnsupportedModifier )]
[DataRow( "animate-spin", TailBoxSkipReason.UnsupportedUtility )]
[DataRow( "translate-x-4", TailBoxSkipReason.UnsupportedUtility )]
[DataRow( "touch-pan-x", TailBoxSkipReason.UnsupportedProperty )]
public void ReportsAdditionalUnsupportedUtilitiesWithStableReasons( string className, TailBoxSkipReason reason )
{
var result = GenerateSafelist( className );
Assert.AreEqual( 0, result.GeneratedClassCount );
AssertSkip( result, className, reason );
Assert.IsTrue( result.Warnings.Single().StartsWith( className + ":", StringComparison.Ordinal ) );
}
[TestMethod]
public void ImportantArbitraryPropertyComposesInOneRule()
{
var result = GenerateSafelist( "![z-index:5]" );
StringAssert.Contains( result.GeneratedScss, RuleStart( "![z-index:5]" ) );
StringAssert.Contains( result.GeneratedScss, "z-index: 5 !important;" );
Assert.AreEqual( 1, result.GeneratedClassCount );
}
private static void AssertUtility( string className, string declaration )
{
var result = GenerateSafelist( className );
StringAssert.Contains( result.GeneratedScss, RuleStart( className ) );
StringAssert.Contains( result.GeneratedScss, declaration );
Assert.AreEqual( 1, result.GeneratedClassCount, $"Expected one generated rule for {className}." );
Assert.AreEqual( 0, result.SkippedClassCount, $"Expected no skipped classes for {className}." );
Assert.AreEqual( 0, result.Warnings.Count, $"Expected no warnings for {className}." );
}
private static void AssertSkip( TailBoxGenerationResult result, string className, TailBoxSkipReason reason )
{
var skipped = result.Skipped.SingleOrDefault( item => item.ClassName == className );
Assert.IsNotNull( skipped, $"Expected '{className}' to be skipped." );
Assert.AreEqual( reason, skipped.Reason, $"Unexpected skip reason for '{className}': {skipped.Detail}" );
Assert.IsFalse( string.IsNullOrWhiteSpace( skipped.Detail ) );
}
private static TailBoxGenerationResult GenerateSafelist( params string[] classes )
{
var root = CreateTempProject();
try
{
var config = TailBoxConfig.CreateDefault();
config.Safelist.AddRange( classes );
return TailBoxEditorProject.Generate( root, config, writeFile: false );
}
finally
{
DeleteTempProject( root );
}
}
private static string RuleStart( string className )
{
Assert.IsTrue( TailBoxCandidateParser.TryParse( className, out var candidate, out _ ), $"Expected '{className}' to parse." );
return "." + TailBoxUtilityCompiler.EscapeClassSelector( className )
+ string.Concat( candidate.Variants.Select( variant => variant.SelectorSuffix ) )
+ " {";
}
private static string CreateTempProject()
{
return TailBoxTestPaths.CreateTempProject();
}
private static void DeleteTempProject( string root )
{
if ( Directory.Exists( root ) )
{
Directory.Delete( root, true );
}
}
}
using Sandbox;
[TestClass]
public partial class LibraryTests
{
[TestMethod]
public void SceneTest()
{
var scene = new Scene();
using ( scene.Push() )
{
var go = new GameObject();
Assert.AreEqual( 1, scene.Directory.GameObjectCount );
}
}
}
global using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass]
public class TestInit
{
[AssemblyInitialize]
public static void ClassInitialize( TestContext context )
{
Sandbox.Application.InitUnitTest();
}
}
global using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass]
public class TestInit
{
public static Sandbox.TestAppSystem AppSystem;
[AssemblyInitialize]
public static void AssemblyInitialize( TestContext context )
{
AppSystem = new Sandbox.TestAppSystem();
AppSystem.Init();
}
[AssemblyCleanup]
public static void AssemblyCleanup()
{
AppSystem.Shutdown();
}
}
using Sandbox;
[TestClass]
public partial class LibraryTests
{
[TestMethod]
public void SceneTest()
{
var scene = new Scene();
using ( scene.Push() )
{
var go = new GameObject();
Assert.AreEqual( 1, scene.Directory.GameObjectCount );
}
}
}
using Sandbox;
[TestClass]
public partial class LibraryTests
{
[TestMethod]
public void SceneTest()
{
var scene = new Scene();
using ( scene.Push() )
{
var go = new GameObject();
Assert.AreEqual( 1, scene.Directory.GameObjectCount );
}
}
}
global using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace WackyLib.Tests;
[TestClass]
public class TestInit
{
[AssemblyInitialize]
public static void ClassInitialize( TestContext context )
{
Sandbox.Application.InitUnitTest<TestInit>( false );
}
[AssemblyCleanup]
public static void AssemblyCleanup()
{
Sandbox.Application.ShutdownUnitTest();
}
}
using Sandbox;
[TestClass]
public partial class LibraryTests
{
[TestMethod]
public void SceneTest()
{
var scene = new Scene();
using ( scene.Push() )
{
var go = new GameObject();
Assert.AreEqual( 1, scene.Directory.GameObjectCount );
}
}
}
using Sandbox;
[TestClass]
public partial class LibraryTests
{
[TestMethod]
public void SceneTest()
{
var scene = new Scene();
using ( scene.Push() )
{
var go = new GameObject();
Assert.AreEqual( 1, scene.Directory.GameObjectCount );
}
}
}
using Sandbox;
[TestClass]
public partial class LibraryTests
{
[TestMethod]
public void SceneTest()
{
var scene = new Scene();
using ( scene.Push() )
{
var go = new GameObject();
Assert.AreEqual( 1, scene.Directory.GameObjectCount );
}
}
}
global using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass]
public class TestInit
{
public static Sandbox.TestAppSystem AppSystem;
[AssemblyInitialize]
public static void AssemblyInitialize()
{
AppSystem = new Sandbox.TestAppSystem();
AppSystem.Init();
}
[AssemblyCleanup]
public static void AssemblyCleanup()
{
AppSystem.Shutdown();
}
}
global using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass]
public class TestInit
{
[AssemblyInitialize]
public static void ClassInitialize( TestContext context )
{
Sandbox.Application.InitUnitTest();
}
}
using Sandbox.TailBox;
using System;
using System.IO;
using System.Linq;
[TestClass]
public sealed class TailBoxDemoTests
{
[TestMethod]
public void DemoMenuGeneratesBroadUtilitySetWithoutStructuredSkips()
{
var repoRoot = FindRepositoryRoot();
if ( repoRoot is null )
Assert.Inconclusive( "Repository root could not be located for the TailBox demo fixture." );
var demoPath = Path.Combine( repoRoot, "Code", "Demo", "TailBoxDemoMenu.razor" );
var result = new TailBoxGenerator().GenerateFromSources(
new[] { new TailBoxSourceText( demoPath, File.ReadAllText( demoPath ) ) },
TailBoxConfig.CreateDefault(),
repoRoot,
"Code/tailwand.generated.scss" );
Assert.IsTrue( result.GeneratedClassCount > 130, "Expected the demo to exercise a broad utility set." );
Assert.AreEqual( 0, result.SkippedClassCount, string.Join( Environment.NewLine, result.Skipped.Select( item => $"{item.ClassName}: {item.Detail}" ) ) );
StringAssert.Contains( result.GeneratedScss, RuleStart( "blur-sm" ) );
StringAssert.Contains( result.GeneratedScss, "filter-blur: 4px;" );
}
private static string RuleSelector( string className )
{
return "." + TailBoxUtilityCompiler.EscapeClassSelector( className );
}
private static string RuleStart( string className )
{
Assert.IsTrue( TailBoxCandidateParser.TryParse( className, out var candidate, out _ ), $"Expected '{className}' to parse." );
return RuleSelector( className )
+ string.Concat( candidate.Variants.Select( variant => variant.SelectorSuffix ) )
+ " {";
}
private static string FindRepositoryRoot()
{
foreach ( var start in new[] { Directory.GetCurrentDirectory(), AppContext.BaseDirectory } )
{
var current = new DirectoryInfo( start );
while ( current is not null )
{
var fixture = Path.Combine( current.FullName, "Code", "Demo", "TailBoxDemoMenu.razor" );
if ( File.Exists( fixture ) )
return current.FullName;
current = current.Parent;
}
}
return null;
}
}
using Sandbox.TailBox;
using System;
using System.IO;
using System.Linq;
[TestClass]
public sealed class TailBoxGeneratorTests
{
[TestMethod]
public void ExtractsStaticDynamicAndSafelistedClasses()
{
var text = """
<div class="flex p-4 @(IsActive ? "hover:bg-accent" : "text-accent")"></div>
<Button class='w-1/2 px-[14px]' />
@* tailbox safelist: intro:opacity-0 bg-[#0d1418] *@
""";
var classes = TailBoxClassExtractor.ExtractClassesFromText( text );
Assert.IsTrue( classes.Contains( "flex" ) );
Assert.IsTrue( classes.Contains( "p-4" ) );
Assert.IsTrue( classes.Contains( "hover:bg-accent" ) );
Assert.IsTrue( classes.Contains( "text-accent" ) );
Assert.IsTrue( classes.Contains( "w-1/2" ) );
Assert.IsTrue( classes.Contains( "px-[14px]" ) );
Assert.IsTrue( classes.Contains( "intro:opacity-0" ) );
Assert.IsTrue( classes.Contains( "bg-[#0d1418]" ) );
}
[TestMethod]
public void SavesAndLoadsConfigWithDefaultsMerged()
{
var root = CreateTempProject();
try
{
var config = TailBoxEditorProject.SaveDefaultConfig( root );
config.Colors["brand"] = "#123456";
config.Safelist.Add( "text-brand" );
TailBoxEditorProject.SaveConfig( root, config );
var loaded = TailBoxEditorProject.LoadConfig( root );
Assert.AreEqual( "Code/tailwand.generated.scss", loaded.OutputPath );
Assert.AreEqual( "#d7b46a", loaded.Colors["accent"] );
Assert.AreEqual( "#123456", loaded.Colors["brand"] );
Assert.IsFalse( loaded.Screens.ContainsKey( "md" ) );
Assert.AreEqual( "0.15s", loaded.Durations["150"] );
Assert.AreEqual( "1", loaded.Opacity["100"] );
Assert.IsTrue( loaded.Safelist.Contains( "text-brand" ) );
}
finally
{
DeleteTempProject( root );
}
}
[TestMethod]
public void GeneratesRepresentativeUtilities()
{
var root = CreateTempProject();
try
{
File.WriteAllText( Path.Combine( root, "Code", "Screen.razor" ), """
<div class="flex w-full w-1/2 p-4 px-[14px] bg-[#0d1418] text-accent z-10"></div>
""" );
var result = TailBoxEditorProject.Generate( root, TailBoxConfig.CreateDefault() );
var scss = result.GeneratedScss;
StringAssert.Contains( scss, ".flex {" );
StringAssert.Contains( scss, "display: flex;" );
StringAssert.Contains( scss, RuleStart( "w-1/2" ) );
StringAssert.Contains( scss, "width: 50%;" );
StringAssert.Contains( scss, ".p-4 {" );
StringAssert.Contains( scss, "padding: 16px;" );
StringAssert.Contains( scss, RuleStart( "px-[14px]" ) );
StringAssert.Contains( scss, "padding-left: 14px;" );
StringAssert.Contains( scss, "padding-right: 14px;" );
StringAssert.Contains( scss, RuleStart( "bg-[#0d1418]" ) );
StringAssert.Contains( scss, "background-color: #0d1418;" );
StringAssert.Contains( scss, ".text-accent {" );
StringAssert.Contains( scss, "color: #d7b46a;" );
StringAssert.Contains( scss, RuleStart( "z-10" ) );
StringAssert.Contains( scss, "z-index: 10;" );
Assert.AreEqual( 8, result.GeneratedClassCount );
Assert.IsTrue( File.Exists( Path.Combine( root, "Code", "tailwand.generated.scss" ) ) );
}
finally
{
DeleteTempProject( root );
}
}
[TestMethod]
public void EscapesSpecialSelectorCharacters()
{
var root = CreateTempProject();
try
{
var config = TailBoxConfig.CreateDefault();
config.Safelist.AddRange( new[]
{
"w-1/2",
"bg-[#0d1418]",
"z-[100%]",
"rounded-[10px]",
"-mt-2"
} );
var result = TailBoxEditorProject.Generate( root, config, writeFile: false );
var scss = result.GeneratedScss;
StringAssert.Contains( scss, RuleSelector( "w-1/2" ) );
StringAssert.Contains( scss, RuleSelector( "bg-[#0d1418]" ) );
StringAssert.Contains( scss, RuleSelector( "z-[100%]" ) );
StringAssert.Contains( scss, RuleSelector( "rounded-[10px]" ) );
StringAssert.Contains( scss, RuleSelector( "-mt-2" ) );
StringAssert.Contains( scss, "margin-top: -8px;" );
}
finally
{
DeleteTempProject( root );
}
}
[TestMethod]
public void SkippedItemsCarryStableReasonsAndSourcePaths()
{
var root = CreateTempProject();
try
{
var razor = Path.Combine( root, "Code", "Screen.razor" );
File.WriteAllText( razor, """
<div class="grid unknown:flex hover:flex first:flex rotate-45 [--brand:#fff]"></div>
""" );
var result = TailBoxEditorProject.Generate( root, TailBoxConfig.CreateDefault(), writeFile: false );
Assert.AreEqual( 0, result.GeneratedClassCount );
AssertSkip( result, "grid", TailBoxSkipReason.UnsupportedValue, razor );
AssertSkip( result, "unknown:flex", TailBoxSkipReason.UnsupportedVariant, razor );
AssertSkip( result, "hover:flex", TailBoxSkipReason.UnsupportedSelectorVariant, razor );
AssertSkip( result, "first:flex", TailBoxSkipReason.UnsupportedSelectorVariant, razor );
AssertSkip( result, "rotate-45", TailBoxSkipReason.UnsupportedUtility, razor );
AssertSkip( result, "[--brand:#fff]", TailBoxSkipReason.UnsupportedArbitraryProperty, razor );
Assert.AreEqual( result.SkippedClassCount, result.SkippedClasses.Count );
}
finally
{
DeleteTempProject( root );
}
}
[TestMethod]
public void GeneratesConstructionDashboardExampleDeterministically()
{
var repoRoot = FindRepositoryRoot();
if ( repoRoot is null )
Assert.Inconclusive( "Repository root could not be located for the ConstructionDashboard fixture." );
var root = CreateTempProject();
try
{
var exampleRazor = Path.Combine( repoRoot, "Examples", "ConstructionDashboard", "Code", "ConstructionDashboard.razor.example" );
var exampleConfig = Path.Combine( repoRoot, "Examples", "ConstructionDashboard", TailBoxConfig.FileName );
Directory.CreateDirectory( Path.Combine( root, "Code" ) );
File.Copy( exampleRazor, Path.Combine( root, "Code", "ConstructionDashboard.razor" ), overwrite: true );
var config = TailBoxConfig.LoadJson( File.ReadAllText( exampleConfig ), exampleConfig );
var first = TailBoxEditorProject.Generate( root, config, writeFile: false );
var second = TailBoxEditorProject.Generate( root, config, writeFile: false );
Assert.IsTrue( first.GeneratedClassCount > 35, "Expected the example to generate a broad utility set." );
Assert.AreEqual( 0, first.SkippedClassCount );
Assert.AreEqual( first.GeneratedScss, second.GeneratedScss );
CollectionAssert.AreEqual( first.GeneratedClasses.ToArray(), second.GeneratedClasses.ToArray() );
StringAssert.Contains( first.GeneratedScss, RuleStart( "w-1/4" ) );
StringAssert.Contains( first.GeneratedScss, "color: #d7b46a;" );
}
finally
{
DeleteTempProject( root );
}
}
[TestMethod]
public void SboxStyleParserAcceptsGeneratedEscapedSelectorsWhenAvailable()
{
try
{
var panelType = Type.GetType( "Sandbox.UI.Panel, Sandbox.Engine", throwOnError: true );
var panel = Activator.CreateInstance( panelType );
var styleSheet = panelType.GetProperty( "StyleSheet" )?.GetValue( panel );
var parse = styleSheet?.GetType().GetMethod( "Parse", new[] { typeof( string ), typeof( bool ) } );
parse?.Invoke(
styleSheet,
new object[]
{
RuleStart( "bg-accent" ) + " background-color: #d7b46a; }\n" + RuleStart( "w-1/2" ) + " width: 50%; }",
false
} );
}
catch ( Exception ex ) when ( ex is NullReferenceException or InvalidOperationException or FileNotFoundException or System.Reflection.TargetInvocationException )
{
Assert.Inconclusive( "s&box style parsing is not available in this headless test context: " + ex.Message );
}
}
[TestMethod]
public void WatcherPathFilterIgnoresGeneratedOutputAndRequiresConfigForOptIn()
{
var root = CreateTempProject();
try
{
var output = Path.Combine( root, "Code", "tailwand.generated.scss" );
var razor = Path.Combine( root, "Code", "Screen.razor" );
File.WriteAllText( razor, "<div class=\"flex\"></div>" );
Assert.IsFalse( TailBoxEditorWatcher.IsProjectOptedIn( root ) );
Assert.IsFalse( TailBoxEditorWatcher.ShouldHandleChangedPath( root, output, output ) );
Assert.IsTrue( TailBoxEditorWatcher.ShouldHandleChangedPath( root, output, razor ) );
TailBoxEditorProject.SaveDefaultConfig( root );
Assert.IsTrue( TailBoxEditorWatcher.IsProjectOptedIn( root ) );
}
finally
{
DeleteTempProject( root );
}
}
private static string CreateTempProject()
{
return TailBoxTestPaths.CreateTempProject();
}
private static void DeleteTempProject( string root )
{
if ( Directory.Exists( root ) )
{
Directory.Delete( root, true );
}
}
private static void AssertSkip( TailBoxGenerationResult result, string className, TailBoxSkipReason reason, string sourcePath )
{
var skipped = result.Skipped.SingleOrDefault( item => item.ClassName == className );
Assert.IsNotNull( skipped, $"Expected '{className}' to be skipped." );
Assert.AreEqual( reason, skipped.Reason );
Assert.AreEqual( Path.GetFullPath( sourcePath ), Path.GetFullPath( skipped.SourcePath ) );
Assert.IsFalse( string.IsNullOrWhiteSpace( skipped.Detail ) );
}
private static string RuleSelector( string className )
{
return "." + TailBoxUtilityCompiler.EscapeClassSelector( className );
}
private static string RuleStart( string className )
{
Assert.IsTrue( TailBoxCandidateParser.TryParse( className, out var candidate, out _ ), $"Expected '{className}' to parse." );
return RuleSelector( className )
+ string.Concat( candidate.Variants.Select( variant => variant.SelectorSuffix ) )
+ " {";
}
private static string FindRepositoryRoot()
{
foreach ( var start in new[] { Directory.GetCurrentDirectory(), AppContext.BaseDirectory } )
{
var current = new DirectoryInfo( start );
while ( current is not null )
{
var fixture = Path.Combine( current.FullName, "Examples", "ConstructionDashboard", "Code", "ConstructionDashboard.razor.example" );
if ( File.Exists( fixture ) )
return current.FullName;
current = current.Parent;
}
}
return null;
}
}
using Sandbox;
[TestClass]
public class SyncToolYamlRendererTests
{
[TestMethod]
public void EmptyInputProducesEmptyOutput()
{
Assert.AreEqual( "", SyncToolYamlRenderer.RenderFromJson( null ) );
Assert.AreEqual( "", SyncToolYamlRenderer.RenderFromJson( "" ) );
}
[TestMethod]
public void EmptyObjectAndArrayRenderAsFlowStyle()
{
Assert.AreEqual( "{}\n", SyncToolYamlRenderer.RenderFromJson( "{}" ) );
Assert.AreEqual( "[]\n", SyncToolYamlRenderer.RenderFromJson( "[]" ) );
}
[TestMethod]
public void InvalidJsonReturnsInputUnchanged()
{
var notJson = "not: real: json: ::";
Assert.AreEqual( notJson, SyncToolYamlRenderer.RenderFromJson( notJson ) );
}
[TestMethod]
public void TopLevelKeysAreSortedAlphabetically()
{
var json = "{\"zeta\":1,\"alpha\":2,\"mu\":3}";
var yaml = SyncToolYamlRenderer.RenderFromJson( json );
Assert.AreEqual( "alpha: 2\nmu: 3\nzeta: 1\n", yaml );
}
[TestMethod]
public void NestedObjectsAreSortedRecursively()
{
var json = "{\"outer\":{\"zeta\":1,\"alpha\":2}}";
var yaml = SyncToolYamlRenderer.RenderFromJson( json );
Assert.AreEqual( "outer:\n alpha: 2\n zeta: 1\n", yaml );
}
[TestMethod]
public void DifferentKeyOrdersProduceIdenticalOutput()
{
var a = "{\"slug\":\"hello\",\"method\":\"POST\",\"enabled\":true}";
var b = "{\"enabled\":true,\"method\":\"POST\",\"slug\":\"hello\"}";
Assert.AreEqual(
SyncToolYamlRenderer.RenderFromJson( a ),
SyncToolYamlRenderer.RenderFromJson( b )
);
}
[TestMethod]
public void OutputUsesYamlSyntaxNotJsonSyntax()
{
var json = "{\"name\":\"hooked\",\"enabled\":true,\"max\":42}";
var yaml = SyncToolYamlRenderer.RenderFromJson( json );
// Smoke check: the rendered text must look like YAML, not JSON.
// This is the regression we're guarding: prior to the fix the diff
// view rendered structured data as JSON.
StringAssert.DoesNotMatch( yaml, new System.Text.RegularExpressions.Regex( @"^\s*\{" ) );
StringAssert.Contains( yaml, "enabled: true" );
StringAssert.Contains( yaml, "max: 42" );
StringAssert.Contains( yaml, "name: \"hooked\"" );
}
[TestMethod]
public void StringValuesAreQuotedAndEscapedSafely()
{
var json = "{\"text\":\"a:b\\nc\"}";
var yaml = SyncToolYamlRenderer.RenderFromJson( json );
// Strings go through JSON quoting which is also valid YAML.
// The colon/newline must not leak as YAML structure.
Assert.AreEqual( "text: \"a:b\\nc\"\n", yaml );
}
[TestMethod]
public void BooleanAndNullAreUnquoted()
{
var json = "{\"a\":true,\"b\":false,\"c\":null}";
var yaml = SyncToolYamlRenderer.RenderFromJson( json );
Assert.AreEqual( "a: true\nb: false\nc: null\n", yaml );
}
[TestMethod]
public void IntegerAndDoubleAreUnquoted()
{
var json = "{\"i\":7,\"d\":1.5}";
var yaml = SyncToolYamlRenderer.RenderFromJson( json );
Assert.AreEqual( "d: 1.5\ni: 7\n", yaml );
}
[TestMethod]
public void ArraysOfObjectsRenderAsBlockSequence()
{
var json = "{\"steps\":[{\"name\":\"first\",\"id\":1},{\"name\":\"second\",\"id\":2}]}";
var yaml = SyncToolYamlRenderer.RenderFromJson( json );
var expected =
"steps:\n" +
" -\n" +
" id: 1\n" +
" name: \"first\"\n" +
" -\n" +
" id: 2\n" +
" name: \"second\"\n";
Assert.AreEqual( expected, yaml );
}
[TestMethod]
public void ArraysOfScalarsRenderAsBlockSequence()
{
var json = "{\"tags\":[\"a\",\"b\",\"c\"]}";
var yaml = SyncToolYamlRenderer.RenderFromJson( json );
Assert.AreEqual( "tags:\n - \"a\"\n - \"b\"\n - \"c\"\n", yaml );
}
[TestMethod]
public void TopLevelArrayRenders()
{
var json = "[1,2,3]";
var yaml = SyncToolYamlRenderer.RenderFromJson( json );
Assert.AreEqual( "- 1\n- 2\n- 3\n", yaml );
}
[TestMethod]
public void KeysWithNonIdentifierCharactersAreQuoted()
{
var json = "{\"weird key\":1,\"x:y\":2}";
var yaml = SyncToolYamlRenderer.RenderFromJson( json );
// Bare YAML keys must not contain spaces or colons, so the renderer
// quotes them. Sort order is by raw key (Ordinal).
StringAssert.Contains( yaml, "\"weird key\": 1" );
StringAssert.Contains( yaml, "\"x:y\": 2" );
}
[TestMethod]
public void EmptyNestedObjectAndArrayUseFlowStyle()
{
var json = "{\"obj\":{},\"arr\":[]}";
var yaml = SyncToolYamlRenderer.RenderFromJson( json );
Assert.AreEqual( "arr: []\nobj: {}\n", yaml );
}
}
global using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass]
public class TestInit
{
[AssemblyInitialize]
public static void ClassInitialize( TestContext context )
{
Sandbox.Application.InitUnitTest();
}
}
using Sandbox;
[TestClass]
public partial class LibraryTests
{
[TestMethod]
public void SceneTest()
{
var scene = new Scene();
using ( scene.Push() )
{
var go = new GameObject();
Assert.AreEqual( 1, scene.Directory.GameObjectCount );
}
}
}
using System;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Editor;
using Sandbox;
[TestClass]
public sealed class ConnecterAssetScannerTests
{
private string TempRoot;
[TestInitialize]
public void SetUp()
{
TempRoot = Path.Combine( Path.GetTempPath(), "ConnectorIntegrationTests", Guid.NewGuid().ToString( "N" ) );
Directory.CreateDirectory( TempRoot );
}
[TestCleanup]
public void TearDown()
{
if ( Directory.Exists( TempRoot ) )
{
Directory.Delete( TempRoot, true );
}
}
[TestMethod]
public async Task AssetModeScansFilesRecursivelyWithoutFolders()
{
var props = Path.Combine( TempRoot, "Props" );
Directory.CreateDirectory( props );
File.WriteAllText( Path.Combine( props, "crate.fbx" ), "" );
File.WriteAllText( Path.Combine( props, "crate.vmat" ), "" );
File.WriteAllText( Path.Combine( props, "crate.png" ), "" );
File.WriteAllText( Path.Combine( props, "notes.txt" ), "" );
var repository = ConnecterRepository.FromPath( TempRoot );
var result = await ConnecterAssetScanner.ScanAssetsAsync( [repository], "", ConnecterBrowserFilter.All, CancellationToken.None );
Assert.AreEqual( 3, result.Items.Count );
Assert.IsFalse( result.Items.Any( x => x.IsDirectory ) );
Assert.IsTrue( result.Items.Any( x => x.Name == "crate.fbx" ) );
Assert.IsTrue( result.Items.Any( x => x.Name == "crate.vmat" ) );
Assert.IsTrue( result.Items.Any( x => x.Name == "crate.png" ) );
}
[TestMethod]
public async Task AssetModeFiltersMaterials()
{
File.WriteAllText( Path.Combine( TempRoot, "crate.fbx" ), "" );
File.WriteAllText( Path.Combine( TempRoot, "crate.vmat" ), "" );
var repository = ConnecterRepository.FromPath( TempRoot );
var result = await ConnecterAssetScanner.ScanAssetsAsync( [repository], "", ConnecterBrowserFilter.Materials, CancellationToken.None );
Assert.AreEqual( 1, result.Items.Count );
Assert.AreEqual( "crate.vmat", result.Items[0].Name );
Assert.AreEqual( ConnecterAssetKind.Material, result.Items[0].Kind );
}
}
using Sandbox.TailBox;
using System;
using System.IO;
using System.Linq;
[TestClass]
public sealed class TailBoxUtilityMatrixTests
{
[DataTestMethod]
[DataRow( "flex", "display: flex;" )]
[DataRow( "hidden", "display: none;" )]
[DataRow( "flex-col", "flex-direction: column;" )]
[DataRow( "flex-row-reverse", "flex-direction: row-reverse;" )]
[DataRow( "flex-wrap", "flex-wrap: wrap;" )]
[DataRow( "grow", "flex-grow: 1;" )]
[DataRow( "shrink-0", "flex-shrink: 0;" )]
[DataRow( "flex-1", "flex-basis: 0%;" )]
[DataRow( "order-first", "order: -9999;" )]
[DataRow( "items-center", "align-items: center;" )]
[DataRow( "self-end", "align-self: flex-end;" )]
[DataRow( "content-between", "align-content: space-between;" )]
[DataRow( "justify-around", "justify-content: space-around;" )]
[DataRow( "basis-1/2", "flex-basis: 50%;" )]
public void GeneratesFlexAndLayoutUtilities( string className, string declaration )
{
AssertUtility( className, declaration );
}
[DataTestMethod]
[DataRow( "static", "position: static;" )]
[DataRow( "relative", "position: relative;" )]
[DataRow( "absolute", "position: absolute;" )]
[DataRow( "inset-0", "top: 0;" )]
[DataRow( "inset-x-4", "left: 16px;" )]
[DataRow( "inset-y-[12px]", "top: 12px;" )]
[DataRow( "top-full", "top: 100%;" )]
[DataRow( "-left-2", "left: -8px;" )]
[DataRow( "bottom-auto", "bottom: auto;" )]
public void GeneratesPositionUtilities( string className, string declaration )
{
AssertUtility( className, declaration );
}
[DataTestMethod]
[DataRow( "w-0", "width: 0;" )]
[DataRow( "w-full", "width: 100%;" )]
[DataRow( "w-screen", "width: 100vw;" )]
[DataRow( "w-3/4", "width: 75%;" )]
[DataRow( "size-4", "width: 16px;" )]
[DataRow( "h-screen", "height: 100vh;" )]
[DataRow( "max-w-[640px]", "max-width: 640px;" )]
[DataRow( "max-h-full", "max-height: 100%;" )]
public void GeneratesSizingUtilities( string className, string declaration )
{
AssertUtility( className, declaration );
}
[DataTestMethod]
[DataRow( "p-0", "padding: 0;" )]
[DataRow( "p-4", "padding: 16px;" )]
[DataRow( "px-[14px]", "padding-left: 14px;" )]
[DataRow( "py-2", "padding-top: 8px;" )]
[DataRow( "pt-1", "padding-top: 4px;" )]
[DataRow( "pr-2", "padding-right: 8px;" )]
[DataRow( "pb-3", "padding-bottom: 12px;" )]
[DataRow( "pl-4", "padding-left: 16px;" )]
[DataRow( "m-auto", "margin: auto;" )]
[DataRow( "-mt-2", "margin-top: -8px;" )]
[DataRow( "mx-3", "margin-left: 12px;" )]
[DataRow( "gap-4", "gap: 16px;" )]
[DataRow( "gap-x-3", "gap: 0 12px;" )]
[DataRow( "gap-x-3", "column-gap: 12px;" )]
[DataRow( "gap-y-[18px]", "gap: 18px 0;" )]
[DataRow( "gap-y-[18px]", "row-gap: 18px;" )]
public void GeneratesSpacingUtilities( string className, string declaration )
{
AssertUtility( className, declaration );
}
[TestMethod]
public void GeneratesCompoundGapAxisRuleWhenBothAxesArePresent()
{
var config = TailBoxConfig.CreateDefault();
config.Safelist.Add( "gap-x-3 gap-y-2" );
var result = new TailBoxGenerator().GenerateFromSources( Array.Empty<TailBoxSourceText>(), config );
StringAssert.Contains( result.GeneratedScss, ".gap-x-3.gap-y-2 {" );
StringAssert.Contains( result.GeneratedScss, "gap: 8px 12px;" );
StringAssert.Contains( result.GeneratedScss, "row-gap: 8px;" );
StringAssert.Contains( result.GeneratedScss, "column-gap: 12px;" );
}
[DataTestMethod]
[DataRow( "bg-accent", "background-color: #d7b46a;" )]
[DataRow( "bg-accent/50", "background-color: rgba( 215, 180, 106, 0.5 );" )]
[DataRow( "bg-[#0d1418]", "background-color: #0d1418;" )]
[DataRow( "bg-[rgba(1,_2,_3,_0.5)]", "background-color: rgba(1, 2, 3, 0.5);" )]
[DataRow( "text-sm", "font-size: 13px;" )]
[DataRow( "text-lg/7", "line-height: 28px;" )]
[DataRow( "text-[18px]", "font-size: 18px;" )]
[DataRow( "text-accent", "color: #d7b46a;" )]
[DataRow( "text-[#fefefe]", "color: #fefefe;" )]
[DataRow( "border", "border: 1px solid rgba( 139, 154, 164, 0.32 );" )]
[DataRow( "border-2", "border-width: 2px;" )]
[DataRow( "border-x-2", "border-left: 2px solid rgba( 139, 154, 164, 0.32 );" )]
[DataRow( "border-t-[8px]", "border-top: 8px solid rgba( 139, 154, 164, 0.32 );" )]
[DataRow( "border-b-[8px]", "border-bottom: 8px solid rgba( 139, 154, 164, 0.32 );" )]
[DataRow( "border-accent", "border-color: #d7b46a;" )]
[DataRow( "border-t-accent", "border-top: 1px solid #d7b46a;" )]
[DataRow( "border-b-accent", "border-bottom: 1px solid #d7b46a;" )]
[DataRow( "rounded", "border-radius: 6px;" )]
[DataRow( "rounded-none", "border-radius: 0px;" )]
[DataRow( "rounded-full", "border-radius: 9999px;" )]
[DataRow( "rounded-[10px]", "border-radius: 10px;" )]
[DataRow( "rounded-t", "border-radius: 6px 6px 0px 0px;" )]
[DataRow( "rounded-r-lg", "border-radius: 0px 12px 12px 0px;" )]
[DataRow( "rounded-b-[10px]", "border-radius: 0px 0px 10px 10px;" )]
[DataRow( "rounded-l-md", "border-radius: 8px 0px 0px 8px;" )]
[DataRow( "rounded-tl-lg", "border-radius: 12px 0px 0px 0px;" )]
[DataRow( "rounded-tr-lg", "border-radius: 0px 12px 0px 0px;" )]
[DataRow( "rounded-br-lg", "border-radius: 0px 0px 12px 0px;" )]
[DataRow( "rounded-bl-lg", "border-radius: 0px 0px 0px 12px;" )]
public void GeneratesColorBorderAndRadiusUtilities( string className, string declaration )
{
AssertUtility( className, declaration );
}
[TestMethod]
public void GeneratesCompoundBorderSideRuleWhenSideWidthAndColorArePresent()
{
var config = TailBoxConfig.CreateDefault();
config.Safelist.Add( "border-t-[8px] border-t-accent border-l-2 border-l-danger" );
var result = new TailBoxGenerator().GenerateFromSources( Array.Empty<TailBoxSourceText>(), config );
StringAssert.Contains( result.GeneratedScss, ".border-t-\\00005b 8px\\00005d .border-t-accent {" );
StringAssert.Contains( result.GeneratedScss, "border-top: 8px solid #d7b46a;" );
StringAssert.Contains( result.GeneratedScss, ".border-l-2.border-l-danger {" );
StringAssert.Contains( result.GeneratedScss, "border-left: 2px solid #c95d5d;" );
}
[DataTestMethod]
[DataRow( "text-left", "text-align: left;" )]
[DataRow( "text-center", "text-align: center;" )]
[DataRow( "uppercase", "text-transform: uppercase;" )]
[DataRow( "normal-case", "text-transform: none;" )]
[DataRow( "italic", "font-style: italic;" )]
[DataRow( "not-italic", "font-style: normal;" )]
[DataRow( "font-bold", "font-weight: 700;" )]
[DataRow( "font-mono", "font-family: Roboto Mono;" )]
[DataRow( "font-[650]", "font-weight: 650;" )]
[DataRow( "leading-4", "line-height: 16px;" )]
[DataRow( "leading-relaxed", "line-height: 1.625em;" )]
[DataRow( "tracking-[2px]", "letter-spacing: 2px;" )]
[DataRow( "underline", "text-decoration-line: underline;" )]
[DataRow( "decoration-accent", "text-decoration-color: #d7b46a;" )]
[DataRow( "decoration-2", "text-decoration-thickness: 2px;" )]
[DataRow( "truncate", "text-overflow: ellipsis;" )]
[DataRow( "whitespace-nowrap", "white-space: nowrap;" )]
[DataRow( "break-all", "word-break: break-all;" )]
public void GeneratesTypographyUtilities( string className, string declaration )
{
AssertUtility( className, declaration );
}
[DataTestMethod]
[DataRow( "overflow-hidden", "overflow: hidden;" )]
[DataRow( "overflow-scroll", "overflow: scroll;" )]
[DataRow( "overflow-x-scroll", "overflow-x: scroll;" )]
[DataRow( "overflow-y-hidden", "overflow-y: hidden;" )]
[DataRow( "pointer-events-none", "pointer-events: none;" )]
[DataRow( "pointer-events-all", "pointer-events: all;" )]
[DataRow( "cursor-pointer", "cursor: pointer;" )]
[DataRow( "cursor-text", "cursor: text;" )]
[DataRow( "cursor-not-allowed", "cursor: not-allowed;" )]
[DataRow( "z-10", "z-index: 10;" )]
[DataRow( "-z-10", "z-index: -10;" )]
[DataRow( "z-[999]", "z-index: 999;" )]
public void GeneratesInteractionVisibilityAndLayerUtilities( string className, string declaration )
{
AssertUtility( className, declaration );
}
[DataTestMethod]
[DataRow( "blur-sm", "filter-blur: 4px;" )]
[DataRow( "brightness-50", "filter-brightness: 0.5;" )]
[DataRow( "hue-rotate-15", "filter-hue-rotate: 15deg;" )]
public void GeneratesEffectUtilities( string className, string declaration )
{
AssertUtility( className, declaration );
}
[TestMethod]
public void GeneratesImportantUtilities()
{
var result = GenerateSafelist( "!p-4" );
StringAssert.Contains( result.GeneratedScss, ".\\!p-4 {" );
StringAssert.Contains( result.GeneratedScss, "padding: 16px !important;" );
Assert.AreEqual( 1, result.GeneratedClassCount );
}
[DataTestMethod]
[DataRow( "cursor-none", "cursor: none;" )]
[DataRow( "cursor-pointer", "cursor: pointer;" )]
[DataRow( "cursor-progress", "cursor: progress;" )]
[DataRow( "cursor-wait", "cursor: wait;" )]
[DataRow( "cursor-crosshair", "cursor: crosshair;" )]
[DataRow( "cursor-text", "cursor: text;" )]
[DataRow( "cursor-move", "cursor: move;" )]
[DataRow( "cursor-not-allowed", "cursor: not-allowed;" )]
[DataRow( "cursor-[crosshair]", "cursor: crosshair;" )]
public void GeneratesDocumentedCursorUtilities( string className, string declaration )
{
AssertUtility( className, declaration );
}
[DataTestMethod]
[DataRow( "hover:bg-accent", ":hover", "background-color: #d7b46a;" )]
[DataRow( "active:bg-accent", ":active", "background-color: #d7b46a;" )]
[DataRow( "focus:border-accent", ":focus", "border-color: #d7b46a;" )]
[DataRow( "intro:opacity-0", ":intro", "opacity: 0;" )]
[DataRow( "outro:opacity-0", ":outro", "opacity: 0;" )]
public void GeneratesPseudoVariantUtilities( string className, string selectorSuffix, string declaration )
{
var result = GenerateSafelist( className );
StringAssert.Contains( result.GeneratedScss, RuleStart( className ) );
StringAssert.Contains( result.GeneratedScss, selectorSuffix + " {" );
StringAssert.Contains( result.GeneratedScss, declaration );
Assert.AreEqual( 1, result.GeneratedClassCount );
Assert.AreEqual( 0, result.SkippedClassCount );
}
[TestMethod]
public void ReportsUnsupportedUtilityLookingClasses()
{
var result = GenerateSafelist(
"grid",
"unknown:flex",
"-p-4",
"hover:grid",
"first:flex",
"rotate-45",
"[--brand-color:#fff]",
"[grid-template-columns:repeat(2,_1fr)]",
"overflow-auto",
"select-none",
"bg-[paint-token]",
"break-words",
"whitespace-pre-wrap" );
Assert.IsFalse( result.GeneratedClasses.Contains( "grid" ) );
Assert.IsTrue( result.SkippedClasses.Contains( "grid" ) );
Assert.IsTrue( result.SkippedClasses.Contains( "unknown:flex" ) );
Assert.IsTrue( result.SkippedClasses.Contains( "-p-4" ) );
Assert.IsTrue( result.SkippedClasses.Contains( "hover:grid" ) );
AssertSkip( result, "grid", TailBoxSkipReason.UnsupportedValue );
AssertSkip( result, "unknown:flex", TailBoxSkipReason.UnsupportedVariant );
AssertSkip( result, "-p-4", TailBoxSkipReason.UnsupportedValue );
AssertSkip( result, "hover:grid", TailBoxSkipReason.UnsupportedValue );
AssertSkip( result, "first:flex", TailBoxSkipReason.UnsupportedSelectorVariant );
AssertSkip( result, "rotate-45", TailBoxSkipReason.UnsupportedUtility );
AssertSkip( result, "[--brand-color:#fff]", TailBoxSkipReason.UnsupportedArbitraryProperty );
AssertSkip( result, "[grid-template-columns:repeat(2,_1fr)]", TailBoxSkipReason.UnsupportedProperty );
AssertSkip( result, "overflow-auto", TailBoxSkipReason.UnsupportedValue );
AssertSkip( result, "select-none", TailBoxSkipReason.UnsupportedProperty );
AssertSkip( result, "bg-[paint-token]", TailBoxSkipReason.UnsupportedProperty );
AssertSkip( result, "break-words", TailBoxSkipReason.UnsupportedValue );
AssertSkip( result, "whitespace-pre-wrap", TailBoxSkipReason.UnsupportedValue );
Assert.IsTrue( result.Warnings.Any( warning => warning.Contains( "Unsupported TailBox variant 'unknown'" ) ) );
}
[TestMethod]
public void GeneratesArbitraryPropertiesWhenSboxSupportsTheProperty()
{
var result = GenerateSafelist( "[background-color:#0d1418]", "[z-index:5]" );
StringAssert.Contains( result.GeneratedScss, RuleStart( "[background-color:#0d1418]" ) );
StringAssert.Contains( result.GeneratedScss, "background-color: #0d1418;" );
StringAssert.Contains( result.GeneratedScss, RuleStart( "[z-index:5]" ) );
StringAssert.Contains( result.GeneratedScss, "z-index: 5;" );
Assert.AreEqual( 2, result.GeneratedClassCount );
Assert.AreEqual( 0, result.SkippedClassCount );
}
[TestMethod]
public void GeneratesArbitraryPropertiesForDocumentedSboxStyleCatalog()
{
var classes = new[]
{
"[align-content:center]", "[align-items:center]", "[align-self:center]",
"[animation:fade_1s_ease]", "[animation-delay:0.1s]", "[animation-direction:alternate]",
"[animation-duration:1s]", "[animation-fill-mode:both]", "[animation-iteration-count:infinite]",
"[animation-name:fade]", "[animation-play-state:running]", "[animation-timing-function:ease-out]",
"[aspect-ratio:16/9]", "[backdrop-filter:blur(10px)]", "[backdrop-filter-blur:10px]",
"[backdrop-filter-brightness:1.2]", "[backdrop-filter-contrast:1.2]", "[backdrop-filter-hue-rotate:10deg]",
"[backdrop-filter-invert:1]", "[backdrop-filter-saturate:1.5]", "[backdrop-filter-sepia:1]",
"[background:linear-gradient(red,_blue)]", "[background-angle:10deg]", "[background-blend-mode:multiply]",
"[background-color:#fff]", "[background-image:url(/ui/panel.png)]", "[background-image-tint:#ffffffaa]",
"[background-position:10px_15px]", "[background-position-x:10px]", "[background-position-y:15px]",
"[background-repeat:repeat-x]", "[background-size:10px_15px]", "[background-size-x:10px]",
"[background-size-y:15px]", "[border:1px_solid_black]", "[border-bottom:1px_solid_black]",
"[border-bottom-color:#fff]", "[border-bottom-left-radius:8px]", "[border-bottom-right-radius:8px]",
"[border-bottom-width:1px]", "[border-color:#fff]", "[border-image:url(/ui/border.png)]",
"[border-image-tint:#ffffffaa]", "[border-image-tint:#000]", "[border-image-width-bottom:1px]", "[border-image-width-left:1px]",
"[border-image-width-right:1px]", "[border-image-width-top:1px]", "[border-left:1px_solid_black]",
"[border-left-color:#fff]", "[border-left-width:1px]", "[border-radius:8px]",
"[border-right:1px_solid_black]", "[border-right-color:#fff]", "[border-right-width:1px]",
"[border-top:1px_solid_black]", "[border-top-color:#fff]", "[border-top-left-radius:8px]",
"[border-top-right-radius:8px]", "[border-top-width:1px]", "[border-width:1px]",
"[bottom:10px]", "[box-shadow:0_0_12px_black]", "[color:#fff]", "[column-gap:10px]",
"[content:\"Loading\"]", "[cursor:crosshair]", "[display:flex]", "[filter:blur(10px)]",
"[filter-blur:10px]", "[filter-border-color:#fff]", "[filter-border-width:1px]",
"[filter-brightness:1.2]", "[filter-contrast:1.2]", "[filter-drop-shadow:0_0_12px_black]",
"[filter-hue-rotate:10deg]", "[filter-invert:1]", "[filter-saturate:1.5]", "[filter-sepia:1]",
"[filter-tint:1]", "[flex-basis:10px]", "[flex-direction:row]", "[flex-grow:1]",
"[flex-shrink:1]", "[flex-wrap:wrap]", "[font-color:#fff]", "[font-family:Poppins]",
"[font-size:16px]", "[font-smooth:always]", "[font-style:italic]", "[font-variant-numeric:tabular-nums]",
"[font-weight:600]", "[gap:8px_12px]", "[height:10px]", "[image-rendering:pixelated]",
"[justify-content:center]", "[left:10px]", "[letter-spacing:1px]", "[line-height:20px]",
"[margin:10px]", "[margin-bottom:10px]", "[margin-left:10px]", "[margin-right:10px]",
"[margin-top:10px]", "[mask:url(/ui/mask.png)]", "[mask-angle:10deg]", "[mask-image:url(/ui/mask.png)]",
"[mask-mode:alpha]", "[mask-position:10px_15px]", "[mask-position-x:10px]", "[mask-position-y:15px]",
"[mask-repeat:no-repeat]", "[mask-scope:filter]", "[mask-size:10px_15px]", "[mask-size-x:10px]",
"[mask-size-y:15px]", "[max-height:100px]", "[max-width:100px]", "[min-height:10px]",
"[min-width:10px]", "[mix-blend-mode:multiply]", "[opacity:0.5]", "[order:1]",
"[overflow:hidden]", "[overflow-x:scroll]", "[overflow-y:visible]", "[padding:10px]",
"[padding-bottom:10px]", "[padding-left:10px]", "[padding-right:10px]", "[padding-top:10px]",
"[perspective-origin:50%_50%]", "[perspective-origin-x:50%]", "[perspective-origin-y:50%]",
"[pointer-events:auto]", "[position:relative]", "[right:10px]", "[row-gap:10px]",
"[sound-in:hover.wav]", "[sound-out:leave.wav]", "[text-align:center]", "[text-background-angle:10deg]",
"[text-decoration:underline]", "[text-decoration-color:#fff]", "[text-decoration-line:underline]",
"[text-decoration-line-through-offset:2px]", "[text-decoration-overline-offset:2px]",
"[text-decoration-skip-ink:all]", "[text-decoration-thickness:2px]", "[text-decoration-underline-offset:2px]",
"[text-overflow:ellipsis]", "[text-shadow:0_0_12px_black]", "[text-stroke:1px_#fff]",
"[text-stroke-color:#fff]", "[text-stroke-width:1px]", "[text-transform:uppercase]",
"[top:10px]", "[transform:scale(1)]", "[transform-origin:50%_50%]", "[transform-origin-x:50%]",
"[transform-origin-y:50%]", "[transition:all_0.1s_ease]", "[transition-delay:0.1s]",
"[transition-duration:0.1s]", "[transition-property:opacity]", "[transition-timing-function:ease-out]",
"[white-space:pre]", "[width:10px]", "[word-break:break-all]", "[word-spacing:1px]",
"[z-index:5]"
};
var result = GenerateSafelist( classes );
Assert.AreEqual( classes.Length, result.GeneratedClassCount );
Assert.AreEqual( 0, result.SkippedClassCount, string.Join( Environment.NewLine, result.Skipped.Select( item => $"{item.ClassName}: {item.Detail}" ) ) );
}
[TestMethod]
public void ThemeBucketsCanOverrideTailwindLikeTokens()
{
var root = CreateTempProject();
try
{
var config = TailBoxConfig.CreateDefault();
config.LineHeights["panel"] = "30px";
config.FontFamilies["display"] = "Poppins";
config.Safelist.Add( "text-lg/panel font-display" );
var result = TailBoxEditorProject.Generate( root, config, writeFile: false );
StringAssert.Contains( result.GeneratedScss, "line-height: 30px;" );
StringAssert.Contains( result.GeneratedScss, "font-family: Poppins;" );
Assert.AreEqual( 2, result.GeneratedClassCount );
}
finally
{
DeleteTempProject( root );
}
}
private static void AssertUtility( string className, string declaration )
{
var result = GenerateSafelist( className );
StringAssert.Contains( result.GeneratedScss, RuleStart( className ) );
StringAssert.Contains( result.GeneratedScss, declaration );
Assert.AreEqual( 1, result.GeneratedClassCount, $"Expected one generated rule for {className}." );
Assert.AreEqual( 0, result.SkippedClasses.Count, $"Expected no skipped classes for {className}." );
Assert.AreEqual( 0, result.Warnings.Count, $"Expected no warnings for {className}." );
}
private static string RuleStart( string className )
{
Assert.IsTrue( TailBoxCandidateParser.TryParse( className, out var candidate, out _ ), $"Expected '{className}' to parse." );
return "." + TailBoxUtilityCompiler.EscapeClassSelector( className )
+ string.Concat( candidate.Variants.Select( variant => variant.SelectorSuffix ) )
+ " {";
}
private static void AssertSkip( TailBoxGenerationResult result, string className, TailBoxSkipReason reason )
{
var skipped = result.Skipped.SingleOrDefault( item => item.ClassName == className );
Assert.IsNotNull( skipped, $"Expected '{className}' to be skipped." );
Assert.AreEqual( reason, skipped.Reason, $"Unexpected skip reason for '{className}': {skipped.Detail}" );
}
private static TailBoxGenerationResult GenerateSafelist( params string[] classes )
{
var root = CreateTempProject();
try
{
var config = TailBoxConfig.CreateDefault();
config.Safelist.AddRange( classes );
return TailBoxEditorProject.Generate( root, config, writeFile: false );
}
finally
{
DeleteTempProject( root );
}
}
private static string CreateTempProject()
{
return TailBoxTestPaths.CreateTempProject();
}
private static void DeleteTempProject( string root )
{
if ( Directory.Exists( root ) )
{
Directory.Delete( root, true );
}
}
}
using Sandbox;
[TestClass]
public partial class LibraryTests
{
[TestMethod]
public void SceneTest()
{
var scene = new Scene();
using ( scene.Push() )
{
var go = new GameObject();
Assert.AreEqual( 1, scene.Directory.GameObjectCount );
}
}
}
using Sandbox;
[TestClass]
public partial class LibraryTests
{
[TestMethod]
public void SceneTest()
{
var scene = new Scene();
using ( scene.Push() )
{
var go = new GameObject();
Assert.AreEqual( 1, scene.Directory.GameObjectCount );
}
}
}
global using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass]
public class TestInit
{
[AssemblyInitialize]
public static void ClassInitialize( TestContext context )
{
Sandbox.Application.InitUnitTest();
}
}
global using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass]
public class TestInit
{
public static Sandbox.TestAppSystem AppSystem;
[AssemblyInitialize]
public static void AssemblyInitialize( TestContext context )
{
AppSystem = new Sandbox.TestAppSystem();
AppSystem.Init();
}
[AssemblyCleanup]
public static void AssemblyCleanup()
{
AppSystem.Shutdown();
}
}
using Sandbox;
[TestClass]
public partial class NetKitTests
{
[TestMethod]
public void NetRequest_HandleRegistersHandler()
{
NetRequest.Handle<TestRequest, TestResponse>( ( caller, req ) =>
{
return new TestResponse { Value = req.Input * 2 };
} );
// Handler is registered — no exception thrown.
Assert.IsTrue( true );
}
[TestMethod]
public void NetState_TransitionFiresEvent()
{
int backingField = 0;
var state = new NetState<TestEnum>(
() => backingField,
v => backingField = v
);
bool transitioned = false;
state.OnTransition += ( from, to ) =>
{
transitioned = true;
Assert.AreEqual( TestEnum.Idle, from );
Assert.AreEqual( TestEnum.Active, to );
};
state.TransitionTo( TestEnum.Active );
Assert.IsTrue( transitioned );
Assert.AreEqual( TestEnum.Idle, state.Previous );
}
[TestMethod]
public void NetState_PollDetectsExternalChange()
{
int backingField = 0;
var state = new NetState<TestEnum>(
() => backingField,
v => backingField = v
);
bool transitioned = false;
state.OnTransition += ( from, to ) => transitioned = true;
// Simulate external sync change (as if host pushed a new value)
backingField = (int)TestEnum.Active;
state.Poll();
Assert.IsTrue( transitioned );
}
[TestMethod]
public void NetState_NoEventOnSameState()
{
int backingField = 0;
var state = new NetState<TestEnum>(
() => backingField,
v => backingField = v
);
bool transitioned = false;
state.OnTransition += ( from, to ) => transitioned = true;
state.TransitionTo( TestEnum.Idle ); // same as initial
Assert.IsFalse( transitioned );
}
// ── Test types ────────────────────────────────────────────────────
public enum TestEnum { Idle = 0, Active = 1, Done = 2 }
public struct TestRequest { public int Input { get; set; } }
public struct TestResponse { public int Value { get; set; } }
}
global using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass]
public class TestInit
{
[AssemblyInitialize]
public static void ClassInitialize( TestContext context )
{
Sandbox.Application.InitUnitTest();
}
}
using PaneOS.InteractiveComputer;
using PaneOS.InteractiveComputer.Core;
var tests = new (string Name, Action Body)[]
{
("Ridge normalizes bare hosts to https", RidgeNormalizesBareHosts),
("Ridge blocks external websites by default", RidgeBlocksByDefault),
("Ridge allows PaneOS credit links by default", RidgeAllowsCreditLinksByDefault),
("Ridge allows exact whitelisted host when enabled", RidgeAllowsExactHost),
("Ridge allows wildcard subdomains only", RidgeAllowsWildcardSubdomainsOnly),
("Ridge blocks unsupported protocols", RidgeBlocksUnsupportedProtocols),
("Ridge parses and deduplicates allowed hosts", RidgeParsesAllowedHosts),
("Screensaver activates after configured delay", ScreenSaverActivatesAfterDelay),
("Screensaver does not run while sleeping", ScreenSaverDoesNotRunWhileSleeping),
("Screensaver pauses and resets while interacting", ScreenSaverPausesWhileInteracting),
("Screensaver bounces off top edge toward bottom", ScreenSaverBouncesTop),
("Screensaver bounces off right edge toward left", ScreenSaverBouncesRight),
("Screensaver bounces off bottom edge toward top", ScreenSaverBouncesBottom),
("Screensaver bounces off left edge toward right", ScreenSaverBouncesLeft),
("CPU input delay suppresses only the focused app", CpuDelaySuppressesFocusedAppOnly),
("Task Manager refresh policy scopes updates by visible tab", TaskManagerRefreshPolicyScopesByTab),
("Task Manager process sorting prefers highest CPU first", TaskManagerSortingUsesSelectedField),
("Archive user policy prefers Steam name and falls back to USERNAME", ArchiveUserPolicyPrefersSteamThenUsername),
("Archive ensure migrates legacy Player folder to persisted username", ArchiveMigratesLegacyPlayerFolder),
("Archive listing Users folder is safe", ArchiveListingUsersFolderIsSafe),
("File dialog policy normalizes extension filters", FileDialogPolicyNormalizesExtensions),
("File dialog policy resolves save paths in current folder", FileDialogPolicyResolvesSavePath),
("Wallpaper policy normalizes known wallpapers", WallpaperPolicyNormalizesKnownValues),
("Wallpaper policy uses the desktop background image by default", WallpaperPolicyUsesDesktopImageByDefault),
("Archive text files round-trip through My Documents", ArchiveTextFilesRoundTrip),
("Archive create resolves duplicate folder and file names", ArchiveCreateResolvesDuplicateNames),
("Archive rename updates file names in place", ArchiveRenameMovesEntries),
("Archive rename preserves file extension when omitted", ArchiveRenamePreservesFileExtension),
("Archive delete can move items to recycle bin and restore them", ArchiveRecycleBinRoundTrip),
("Archive restore reuses recreated empty parent folders", ArchiveRestoreReusesEmptyDefaultFolder),
("File associations open text files in Notepad", FileAssociationsOpenTextFiles),
("File associations use app-declared extensions", FileAssociationsUseAppDeclaredExtensions),
("File associations open url files in Ridge", FileAssociationsOpenUrlFiles),
("Corrupted url shortcuts are rejected with a specific dialog", CorruptedUrlShortcutsAreRejected),
("File associations launch executables by resolved name", FileAssociationsLaunchExecutables),
("Missing executables are flagged as corrupted applications", MissingExecutablesAreRejected),
("Desktop shortcut layout wraps into additional columns", DesktopShortcutLayoutWrapsColumns),
("Desktop selection rectangle captures intersecting shortcuts", DesktopSelectionCapturesIntersectingShortcuts),
("Window layout policy honors app defaults and cascades offsets", WindowLayoutPolicyHonorsDescriptorDefaults),
("Window layout policy honors saved size overrides", WindowLayoutPolicyHonorsSavedOverrides),
("Maintenance policy generates visible update and install logs", MaintenancePolicyGeneratesLogs),
("Media playlist policy respects repeat modes", MediaPlaylistPolicyRespectsRepeatModes),
("Media playlist shuffle preserves all items", MediaPlaylistShufflePreservesItems),
("Resolution policy prefers game settings by default and clamps minimum sizes", ResolutionPolicyUsesGameSettingsWhenEnabled),
("Computer state defaults include screensaver and app lists", ComputerStateDefaults),
};
var failed = 0;
foreach ( var test in tests )
{
try
{
test.Body();
Console.WriteLine( $"PASS {test.Name}" );
}
catch ( Exception ex )
{
failed++;
Console.WriteLine( $"FAIL {test.Name}: {ex.Message}" );
}
}
Console.WriteLine();
Console.WriteLine( failed == 0 ? $"All {tests.Length} tests passed." : $"{failed} of {tests.Length} tests failed." );
return failed == 0 ? 0 : 1;
static void RidgeNormalizesBareHosts()
{
Equal( "https://sbox.game", RidgeBrowserPolicy.NormalizeUrl( "sbox.game" ) );
Equal( "paneos://home", RidgeBrowserPolicy.NormalizeUrl( "" ) );
}
static void RidgeBlocksByDefault()
{
var result = RidgeBrowserPolicy.Evaluate( "https://sbox.game", null, "sbox.game" );
False( result.CanRenderWebPanel );
Equal( "Rendering disabled", result.Status );
}
static void RidgeAllowsCreditLinksByDefault()
{
var result = RidgeBrowserPolicy.Evaluate( "https://www.flaticon.com/free-icons/video", null, "" );
True( result.CanRenderWebPanel );
Equal( "Loaded www.flaticon.com", result.Status );
True( result.AllowedHosts.Contains( "github.com" ) );
True( result.AllowedHosts.Contains( "www.flaticon.com" ) );
}
static void RidgeAllowsExactHost()
{
var result = RidgeBrowserPolicy.Evaluate( "sbox.game", "true", "sbox.game docs.facepunch.com" );
True( result.CanRenderWebPanel );
Equal( "https://sbox.game", result.NormalizedUrl );
Equal( "Loaded sbox.game", result.Status );
}
static void RidgeAllowsWildcardSubdomainsOnly()
{
True( RidgeBrowserPolicy.Evaluate( "https://docs.example.com", "on", "*.example.com" ).CanRenderWebPanel );
False( RidgeBrowserPolicy.Evaluate( "https://example.com", "on", "*.example.com" ).CanRenderWebPanel );
}
static void RidgeBlocksUnsupportedProtocols()
{
var result = RidgeBrowserPolicy.Evaluate( "ftp://example.com", "true", "example.com" );
False( result.CanRenderWebPanel );
Equal( "Blocked", result.Status );
}
static void RidgeParsesAllowedHosts()
{
var hosts = RidgeBrowserPolicy.ParseHostList( "sbox.game, SBOX.game;docs.facepunch.com\n*.example.com" );
Equal( 3, hosts.Count );
True( hosts.Contains( "sbox.game" ) );
True( hosts.Contains( "docs.facepunch.com" ) );
True( hosts.Contains( "*.example.com" ) );
}
static void ScreenSaverActivatesAfterDelay()
{
var state = NewScreenSaverState();
var changedBefore = ScreenSaverSimulator.Tick( state, 59f, false );
False( changedBefore );
False( state.ScreenSaver.IsActive );
var changedAtDelay = ScreenSaverSimulator.Tick( state, 1f, false );
True( changedAtDelay );
True( state.ScreenSaver.IsActive );
}
static void ScreenSaverDoesNotRunWhileSleeping()
{
var state = NewScreenSaverState();
state.IsSleeping = true;
var changed = ScreenSaverSimulator.Tick( state, 120f, false );
False( changed );
False( state.ScreenSaver.IsActive );
Equal( 0f, state.ScreenSaver.IdleSeconds );
}
static void ScreenSaverPausesWhileInteracting()
{
var state = NewScreenSaverState();
state.ScreenSaver.IdleSeconds = 12f;
state.ScreenSaver.IsActive = true;
var changed = ScreenSaverSimulator.Tick( state, 1f, true );
True( changed );
False( state.ScreenSaver.IsActive );
Equal( 0f, state.ScreenSaver.IdleSeconds );
}
static void ScreenSaverBouncesTop()
{
var state = NewScreenSaverState();
state.ScreenSaver.LogoY = 2f;
state.ScreenSaver.VelocityY = -50f;
ScreenSaverSimulator.MoveLogo( state, 1f );
Equal( 0f, state.ScreenSaver.LogoY );
True( state.ScreenSaver.VelocityY > 0f );
}
static void ScreenSaverBouncesRight()
{
var state = NewScreenSaverState();
state.ScreenSaver.LogoX = 780f;
state.ScreenSaver.VelocityX = 50f;
ScreenSaverSimulator.MoveLogo( state, 1f );
Equal( 800f, state.ScreenSaver.LogoX );
True( state.ScreenSaver.VelocityX < 0f );
}
static void ScreenSaverBouncesBottom()
{
var state = NewScreenSaverState();
state.ScreenSaver.LogoY = 580f;
state.ScreenSaver.VelocityY = 50f;
ScreenSaverSimulator.MoveLogo( state, 1f );
Equal( 600f, state.ScreenSaver.LogoY );
True( state.ScreenSaver.VelocityY < 0f );
}
static void ScreenSaverBouncesLeft()
{
var state = NewScreenSaverState();
state.ScreenSaver.LogoX = 4f;
state.ScreenSaver.VelocityX = -50f;
ScreenSaverSimulator.MoveLogo( state, 1f );
Equal( 0f, state.ScreenSaver.LogoX );
True( state.ScreenSaver.VelocityX > 0f );
}
static void CpuDelaySuppressesFocusedAppOnly()
{
True( ComputerInputDelayPolicy.ShouldSuppressFocusedAppInput( true, true, true ) );
False( ComputerInputDelayPolicy.ShouldSuppressFocusedAppInput( false, true, true ) );
False( ComputerInputDelayPolicy.ShouldSuppressFocusedAppInput( true, false, true ) );
False( ComputerInputDelayPolicy.ShouldSuppressFocusedAppInput( true, true, false ) );
}
static void TaskManagerRefreshPolicyScopesByTab()
{
var processesA = TaskManagerRefreshPolicy.GetRefreshVersion( TaskManagerTab.Processes, 3, 10, 100 );
var processesB = TaskManagerRefreshPolicy.GetRefreshVersion( TaskManagerTab.Processes, 3, 11, 100 );
var storageA = TaskManagerRefreshPolicy.GetRefreshVersion( TaskManagerTab.Storage, 3, 10, 100 );
var storageB = TaskManagerRefreshPolicy.GetRefreshVersion( TaskManagerTab.Storage, 3, 11, 100 );
var storageC = TaskManagerRefreshPolicy.GetRefreshVersion( TaskManagerTab.Storage, 3, 10, 101 );
NotEqual( processesA, processesB );
Equal( storageA, storageB );
NotEqual( storageA, storageC );
}
static void TaskManagerSortingUsesSelectedField()
{
var rows = new[]
{
new TaskManagerProcessSortItem { InstanceId = "1", ProcessName = "Calculator", CpuPercent = 12f, RamPercent = 10f, Status = "Running", StartupProcess = false },
new TaskManagerProcessSortItem { InstanceId = "2", ProcessName = "Networking", CpuPercent = 3f, RamPercent = 22f, Status = "Running", StartupProcess = true },
new TaskManagerProcessSortItem { InstanceId = "3", ProcessName = "PaneOS32", CpuPercent = 88f, RamPercent = 18f, Status = "Running", StartupProcess = true }
};
var byCpu = TaskManagerProcessSortPolicy.Sort( rows, TaskManagerProcessSortField.Cpu, true );
var byRam = TaskManagerProcessSortPolicy.Sort( rows, TaskManagerProcessSortField.Ram, true );
Equal( "PaneOS32", byCpu[0].ProcessName );
Equal( "Networking", byRam[0].ProcessName );
}
static void ArchiveUserPolicyPrefersSteamThenUsername()
{
Equal( "Alice", ComputerArchiveUserPolicy.ResolveInitialUserName( "Alice", "WindowsUser" ) );
Equal( "WindowsUser", ComputerArchiveUserPolicy.ResolveInitialUserName( "Player", "WindowsUser" ) );
Equal( "Player", ComputerArchiveUserPolicy.ResolveInitialUserName( "Player", "" ) );
}
static void ArchiveMigratesLegacyPlayerFolder()
{
var tempPath = Path.Combine( Path.GetTempPath(), $"paneos-test-{Guid.NewGuid():N}.datc" );
try
{
var apps = Array.Empty<ComputerAppDescriptor>();
PaneArchiveFileSystem.EnsureArchive( tempPath, "Player", apps );
PaneArchiveFileSystem.CreateFile( tempPath, new[] { "C:", "Users", "Player", "My Documents" }, "Notes", "txt", "hello" );
PaneArchiveFileSystem.EnsureArchive( tempPath, "WindowsUser", apps );
var rootUsers = PaneArchiveFileSystem.GetItems( tempPath, new[] { "C:", "Users" } );
True( rootUsers.Any( x => x.Name == "WindowsUser" ) );
False( rootUsers.Any( x => x.Name == "Player" ) );
var docs = PaneArchiveFileSystem.GetItems( tempPath, new[] { "C:", "Users", "WindowsUser", "My Documents" } );
True( docs.Any( x => x.Name == "Notes.txt" ) );
}
finally
{
if ( File.Exists( tempPath ) )
File.Delete( tempPath );
}
}
static void FileDialogPolicyNormalizesExtensions()
{
var options = new ComputerFileDialogOptions
{
AllowedExtensions = new[] { ".txt", "Url" }
};
True( ComputerFileDialogPolicy.AllowsExtension( options, ".TXT" ) );
True( ComputerFileDialogPolicy.AllowsExtension( options, "url" ) );
False( ComputerFileDialogPolicy.AllowsExtension( options, ".exe" ) );
}
static void FileDialogPolicyResolvesSavePath()
{
var openOptions = new ComputerFileDialogOptions
{
Mode = ComputerFileDialogMode.Open
};
var saveOptions = new ComputerFileDialogOptions
{
Mode = ComputerFileDialogMode.Save
};
Equal(
"/C:/Users/Alice/My Documents/Notes.txt",
ComputerFileDialogPolicy.ResolvePath(
openOptions,
new[] { "C:", "Users", "Alice", "My Documents" },
"/C:/Users/Alice/My Documents/Notes.txt",
"" ) );
Equal(
"/C:/Users/Alice/My Documents/Todo.txt",
ComputerFileDialogPolicy.ResolvePath(
saveOptions,
new[] { "C:", "Users", "Alice", "My Documents" },
"",
"Todo.txt" ) );
Equal(
"",
ComputerFileDialogPolicy.ResolvePath(
saveOptions,
new[] { "C:", "Users", "Alice", "My Documents" },
"",
" " ) );
}
static void WallpaperPolicyNormalizesKnownValues()
{
Equal( "blue", ComputerWallpaperPolicy.Normalize( "Blue" ) );
Equal( "sunset", ComputerWallpaperPolicy.Normalize( "SUNSET" ) );
Equal( "default", ComputerWallpaperPolicy.Normalize( "something-else" ) );
}
static void WallpaperPolicyUsesDesktopImageByDefault()
{
var style = ComputerWallpaperPolicy.GetBackgroundStyle( "default" );
AssertContains( "background-color: #2c7cb7", style );
}
static void ArchiveListingUsersFolderIsSafe()
{
var tempPath = Path.Combine( Path.GetTempPath(), $"paneos-users-{Guid.NewGuid():N}.datc" );
try
{
var apps = new[]
{
new ComputerAppDescriptor
{
Id = "system.notepad",
Title = "Notepad",
Factory = () => new StubComputerApp()
}
};
PaneArchiveFileSystem.EnsureArchive( tempPath, "Alice", apps );
var root = PaneArchiveFileSystem.GetItems( tempPath, Array.Empty<string>() );
var users = PaneArchiveFileSystem.GetItems( tempPath, new[] { "C:", "Users" } );
var appFolderItems = PaneArchiveFileSystem.GetItems( tempPath, new[] { "C:", "Apps" } );
True( root.Any( x => x.Name == "C:" ) );
True( users.Any( x => x.Name == "Alice" ) );
True( appFolderItems.Count > 0 );
}
finally
{
if ( File.Exists( tempPath ) )
File.Delete( tempPath );
}
}
static void ArchiveTextFilesRoundTrip()
{
var tempPath = Path.Combine( Path.GetTempPath(), $"paneos-text-{Guid.NewGuid():N}.datc" );
try
{
PaneArchiveFileSystem.EnsureArchive( tempPath, "Alice", Array.Empty<ComputerAppDescriptor>() );
var filePath = new[] { "C:", "Users", "Alice", "My Documents", "Journal.txt" };
PaneArchiveFileSystem.WriteTextFile( tempPath, filePath, "Day one" );
Equal( "Day one", PaneArchiveFileSystem.ReadTextFile( tempPath, filePath ) );
True( PaneArchiveFileSystem.Exists( tempPath, filePath ) );
}
finally
{
if ( File.Exists( tempPath ) )
File.Delete( tempPath );
}
}
static void ArchiveCreateResolvesDuplicateNames()
{
var tempPath = Path.Combine( Path.GetTempPath(), $"paneos-create-{Guid.NewGuid():N}.datc" );
try
{
PaneArchiveFileSystem.EnsureArchive( tempPath, "Alice", Array.Empty<ComputerAppDescriptor>() );
var parentPath = new[] { "C:", "Users", "Alice", "My Documents" };
var folderA = PaneArchiveFileSystem.CreateFolder( tempPath, parentPath, "Projects" );
var folderB = PaneArchiveFileSystem.CreateFolder( tempPath, parentPath, "Projects" );
var fileA = PaneArchiveFileSystem.CreateFile( tempPath, parentPath, "Notes", "txt", "a" );
var fileB = PaneArchiveFileSystem.CreateFile( tempPath, parentPath, "Notes", "txt", "b" );
Equal( "Projects", folderA );
Equal( "Projects (2)", folderB );
Equal( "Notes.txt", fileA );
Equal( "Notes (2).txt", fileB );
}
finally
{
if ( File.Exists( tempPath ) )
File.Delete( tempPath );
}
}
static void ArchiveRenameMovesEntries()
{
var tempPath = Path.Combine( Path.GetTempPath(), $"paneos-rename-{Guid.NewGuid():N}.datc" );
try
{
PaneArchiveFileSystem.EnsureArchive( tempPath, "Alice", Array.Empty<ComputerAppDescriptor>() );
var originalPath = new[] { "C:", "Users", "Alice", "My Documents", "Notes.txt" };
var renamedPath = new[] { "C:", "Users", "Alice", "My Documents", "Todo.txt" };
PaneArchiveFileSystem.WriteTextFile( tempPath, originalPath, "todo" );
PaneArchiveFileSystem.Rename( tempPath, originalPath, "Todo.txt" );
False( PaneArchiveFileSystem.Exists( tempPath, originalPath ) );
True( PaneArchiveFileSystem.Exists( tempPath, renamedPath ) );
Equal( "todo", PaneArchiveFileSystem.ReadTextFile( tempPath, renamedPath ) );
}
finally
{
if ( File.Exists( tempPath ) )
File.Delete( tempPath );
}
}
static void ArchiveRecycleBinRoundTrip()
{
var tempPath = Path.Combine( Path.GetTempPath(), $"paneos-trash-{Guid.NewGuid():N}.datc" );
try
{
PaneArchiveFileSystem.EnsureArchive( tempPath, "Alice", Array.Empty<ComputerAppDescriptor>() );
var originalPath = new[] { "C:", "Users", "Alice", "My Documents", "Draft.txt" };
PaneArchiveFileSystem.WriteTextFile( tempPath, originalPath, "draft" );
var recycledPath = PaneArchiveFileSystem.MoveToRecycleBin( tempPath, originalPath );
False( PaneArchiveFileSystem.Exists( tempPath, originalPath ) );
True( PaneArchiveFileSystem.Exists( tempPath, recycledPath.TrimStart( '/' ).Split( '/' ) ) );
var restoredPath = PaneArchiveFileSystem.RestoreFromRecycleBin( tempPath, recycledPath.TrimStart( '/' ).Split( '/' ) );
True( PaneArchiveFileSystem.Exists( tempPath, originalPath ) );
Equal( "/C:/Users/Alice/My Documents/Draft.txt", restoredPath );
Equal( "draft", PaneArchiveFileSystem.ReadTextFile( tempPath, originalPath ) );
}
finally
{
if ( File.Exists( tempPath ) )
File.Delete( tempPath );
}
}
static void ArchiveRenamePreservesFileExtension()
{
var tempPath = Path.Combine( Path.GetTempPath(), $"paneos-rename-ext-{Guid.NewGuid():N}.datc" );
try
{
PaneArchiveFileSystem.EnsureArchive( tempPath, "Alice", Array.Empty<ComputerAppDescriptor>() );
var originalPath = new[] { "C:", "Users", "Alice", "My Documents", "Notes.txt" };
PaneArchiveFileSystem.WriteTextFile( tempPath, originalPath, "todo" );
var renamed = PaneArchiveFileSystem.Rename( tempPath, originalPath, "Todo" );
Equal( "Todo.txt", renamed );
True( PaneArchiveFileSystem.Exists( tempPath, new[] { "C:", "Users", "Alice", "My Documents", "Todo.txt" } ) );
}
finally
{
if ( File.Exists( tempPath ) )
File.Delete( tempPath );
}
}
static void ArchiveRestoreReusesEmptyDefaultFolder()
{
var tempPath = Path.Combine( Path.GetTempPath(), $"paneos-trash-empty-{Guid.NewGuid():N}.datc" );
try
{
PaneArchiveFileSystem.EnsureArchive( tempPath, "Alice", Array.Empty<ComputerAppDescriptor>() );
var documentsPath = new[] { "C:", "Users", "Alice", "My Documents" };
var recycledPath = PaneArchiveFileSystem.MoveToRecycleBin( tempPath, documentsPath );
False( PaneArchiveFileSystem.Exists( tempPath, documentsPath ) );
PaneArchiveFileSystem.EnsureArchive( tempPath, "Alice", Array.Empty<ComputerAppDescriptor>() );
True( PaneArchiveFileSystem.Exists( tempPath, documentsPath ) );
var restoredPath = PaneArchiveFileSystem.RestoreFromRecycleBin( tempPath, recycledPath.TrimStart( '/' ).Split( '/' ) );
Equal( "/C:/Users/Alice/My Documents", restoredPath );
False( PaneArchiveFileSystem.Exists( tempPath, new[] { "C:", "Users", "Alice", "My Documents (2)" } ) );
}
finally
{
if ( File.Exists( tempPath ) )
File.Delete( tempPath );
}
}
static void FileAssociationsOpenTextFiles()
{
var target = ComputerFileAssociationPolicy.ResolveLaunchTarget(
"/C:/Users/Alice/My Documents/Notes.txt",
"Notes.txt",
"hello",
Array.Empty<ComputerAppDescriptor>() );
Equal( "system.notepad", target?.AppId );
Equal( "/C:/Users/Alice/My Documents/Notes.txt", target?.InitialData["file_path"] );
}
static void FileAssociationsUseAppDeclaredExtensions()
{
var apps = new[]
{
new ComputerAppDescriptor
{
Id = "system.paint",
Title = "Paint",
AssociatedFileExtensions = new[] { ".png" },
Factory = () => new StubComputerApp()
}
};
var target = ComputerFileAssociationPolicy.ResolveLaunchTarget(
"/C:/Users/Alice/My Documents/Sketch.png",
"Sketch.png",
"",
apps );
Equal( "system.paint", target?.AppId );
Equal( "/C:/Users/Alice/My Documents/Sketch.png", target?.InitialData["file_path"] );
}
static void FileAssociationsOpenUrlFiles()
{
var target = ComputerFileAssociationPolicy.ResolveLaunchTarget(
"/C:/Users/Alice/My Documents/Search.url",
"Search.url",
"url=https://example.com",
Array.Empty<ComputerAppDescriptor>() );
Equal( "system.ridge", target?.AppId );
Equal( "https://example.com", target?.InitialData["url"] );
}
static void CorruptedUrlShortcutsAreRejected()
{
var result = ComputerFileAssociationPolicy.ResolveOpenResult(
"/C:/Users/Alice/My Documents/Broken.url",
"Broken.url",
" ",
Array.Empty<ComputerAppDescriptor>() );
False( result.CanOpen );
Equal( "Corrupted Shortcut", result.FailureTitle );
AssertContains( "corrupted", result.FailureMessage );
}
static void FileAssociationsLaunchExecutables()
{
var apps = new[]
{
new ComputerAppDescriptor
{
Id = "system.calc",
Title = "Calculator",
ExecutableName = "Calc.exe",
Factory = () => new StubComputerApp()
}
};
var target = ComputerFileAssociationPolicy.ResolveLaunchTarget(
"/C:/Apps/Calculator/Calc.exe",
"Calc.exe",
"",
apps );
Equal( "system.calc", target?.AppId );
}
static void MissingExecutablesAreRejected()
{
var result = ComputerFileAssociationPolicy.ResolveOpenResult(
"/C:/Apps/Unknown/Missing.exe",
"Missing.exe",
"",
Array.Empty<ComputerAppDescriptor>() );
False( result.CanOpen );
Equal( "Corrupted Application", result.FailureTitle );
AssertContains( "missing", result.FailureMessage );
}
static void DesktopShortcutLayoutWrapsColumns()
{
var first = DesktopShortcutLayoutPolicy.GetPosition( 0, 768 );
var eighth = DesktopShortcutLayoutPolicy.GetPosition( 7, 768 );
Equal( DesktopShortcutLayoutPolicy.OriginX, first.X );
True( eighth.X > first.X );
Equal( DesktopShortcutLayoutPolicy.OriginY, eighth.Y );
}
static void DesktopSelectionCapturesIntersectingShortcuts()
{
var items = new[]
{
new DesktopShortcutLayoutItem { Id = "a", Index = 0 },
new DesktopShortcutLayoutItem { Id = "b", Index = 1 },
new DesktopShortcutLayoutItem { Id = "c", Index = 7 }
};
var rect = DesktopShortcutSelectionRect.FromCorners( 0f, 0f, 100f, 190f );
var selected = DesktopShortcutLayoutPolicy.SelectIntersectingShortcutIds( items, rect, 768 );
Equal( 2, selected.Count );
True( selected.Contains( "a" ) );
True( selected.Contains( "b" ) );
False( selected.Contains( "c" ) );
}
static void WindowLayoutPolicyHonorsDescriptorDefaults()
{
var descriptor = new ComputerAppDescriptor
{
Id = "system.calc",
Title = "Calculator",
Icon = "CA",
DefaultWindowOffsetX = 32,
DefaultWindowOffsetY = 24,
DefaultWindowWidth = 320,
DefaultWindowHeight = 360,
Factory = () => new StubComputerApp()
};
var bounds = ComputerWindowLayoutPolicy.ResolveInitialBounds( descriptor, 1024, 768, 2 );
Equal( 76, bounds.X );
Equal( 68, bounds.Y );
Equal( 320, bounds.Width );
Equal( 360, bounds.Height );
}
static void WindowLayoutPolicyHonorsSavedOverrides()
{
var descriptor = new ComputerAppDescriptor
{
Id = "system.calc",
Title = "Calculator",
Icon = "CA",
DefaultWindowOffsetX = 32,
DefaultWindowOffsetY = 24,
DefaultWindowWidth = 320,
DefaultWindowHeight = 360,
Factory = () => new StubComputerApp()
};
var bounds = ComputerWindowLayoutPolicy.ResolveInitialBounds( descriptor, 1024, 768, 1, 420, 540 );
Equal( 54, bounds.X );
Equal( 46, bounds.Y );
Equal( 420, bounds.Width );
Equal( 540, bounds.Height );
}
static void MaintenancePolicyGeneratesLogs()
{
var state = new ComputerState();
state.InstalledApps.Add( new ComputerInstalledAppState { AppId = "system.notepad" } );
state.OpenApps.Add( new ComputerAppState { AppId = "system.notepad", Title = "Notepad" } );
var apps = new[]
{
new ComputerAppDescriptor
{
Id = "system.notepad",
Title = "Notepad",
Factory = () => new StubComputerApp()
}
};
var timestamp = new DateTime( 2026, 4, 30, 1, 2, 3, DateTimeKind.Utc );
var updateRecord = ComputerMaintenancePolicy.BuildUpdateScanRecord( state, apps, timestamp );
var installRecord = ComputerMaintenancePolicy.BuildPackageInstallRecord( "Media Codec Pack", timestamp );
Equal( "PaneOS Update Report.txt", updateRecord.FileName );
AssertContains( "Installed apps: 1", updateRecord.FileContent );
AssertContains( "Notepad", updateRecord.FileContent );
Equal( "Media Codec Pack Setup Log.txt", installRecord.FileName );
AssertContains( "Package staged successfully.", installRecord.FileContent );
}
static void MediaPlaylistPolicyRespectsRepeatModes()
{
Equal( 0, ComputerMediaPlaylistPolicy.ResolveNextIndex( 2, 3, ComputerMediaRepeatMode.Playlist ) );
Equal( 2, ComputerMediaPlaylistPolicy.ResolveNextIndex( 2, 3, ComputerMediaRepeatMode.None ) );
Equal( 1, ComputerMediaPlaylistPolicy.ResolveNextIndex( 1, 3, ComputerMediaRepeatMode.Single ) );
}
static void MediaPlaylistShufflePreservesItems()
{
var original = new[] { "a", "b", "c", "d" };
var shuffled = ComputerMediaPlaylistPolicy.Shuffle( original, 1234 );
Equal( original.Length, shuffled.Count );
True( original.All( item => shuffled.Contains( item ) ) );
}
static void ResolutionPolicyUsesGameSettingsWhenEnabled()
{
var gameResolution = ComputerResolutionPolicy.ResolveResolution( true, 1024, 768, 1920f, 1080f );
var manualResolution = ComputerResolutionPolicy.ResolveResolution( false, 800, 600, 1920f, 1080f );
var clampedResolution = ComputerResolutionPolicy.ResolveResolution( true, 10, 10, 200f, 120f );
Equal( (1920, 1080), gameResolution );
Equal( (800, 600), manualResolution );
Equal( (320, 240), clampedResolution );
}
static void ComputerStateDefaults()
{
var state = new ComputerState();
Equal( 1024, state.ResolutionX );
Equal( 768, state.ResolutionY );
True( state.ScreenSaver.Enabled );
Equal( 60f, state.ScreenSaver.DelaySeconds );
Equal( 2f, state.Hardware.RamGb );
Equal( 3.7f, state.Hardware.CpuCoreGhz );
Equal( 4, state.Hardware.CpuCoreCount );
Equal( 256f, state.Hardware.HddStorageGb );
Equal( 100f, state.Hardware.InternetSpeedGbps );
Equal( 1.54f, state.Hardware.GpuCoreGhz );
Equal( 4f, state.Hardware.GpuVramGb );
Equal( 0f, state.RestartLogSecondsRemaining );
Equal( 0f, state.BootSplashSecondsRemaining );
Equal( 0, state.RestartLogLines.Count );
Equal( 0, state.InstalledApps.Count );
Equal( 0, state.OpenApps.Count );
}
static ComputerState NewScreenSaverState()
{
return new ComputerState
{
ResolutionX = 1000,
ResolutionY = 700,
ScreenSaver = new ComputerScreenSaverState
{
Enabled = true,
DelaySeconds = 60f,
LogoWidth = 200f,
LogoHeight = 100f,
LogoX = 400f,
LogoY = 300f,
VelocityX = 40f,
VelocityY = -30f
}
};
}
static void True( bool value )
{
if ( !value )
throw new InvalidOperationException( "Expected true." );
}
static void False( bool value )
{
if ( value )
throw new InvalidOperationException( "Expected false." );
}
static void Equal<T>( T expected, T actual )
{
if ( !EqualityComparer<T>.Default.Equals( expected, actual ) )
throw new InvalidOperationException( $"Expected '{expected}', got '{actual}'." );
}
static void NotEqual<T>( T left, T right )
{
if ( EqualityComparer<T>.Default.Equals( left, right ) )
throw new InvalidOperationException( $"Expected '{left}' and '{right}' to differ." );
}
static void AssertContains( string expectedSubstring, string actual )
{
if ( actual.Contains( expectedSubstring, StringComparison.OrdinalIgnoreCase ) )
return;
throw new InvalidOperationException( $"Expected '{actual}' to contain '{expectedSubstring}'." );
}
file sealed class StubComputerApp : IComputerApp
{
}
global using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass]
public class TestInit
{
public static Sandbox.TestAppSystem AppSystem;
[AssemblyInitialize]
public static void AssemblyInitialize( TestContext context )
{
AppSystem = new Sandbox.TestAppSystem();
AppSystem.Init();
}
[AssemblyCleanup]
public static void AssemblyCleanup()
{
AppSystem.Shutdown();
}
}
global using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass]
public class TestInit
{
public static Sandbox.TestAppSystem AppSystem;
[AssemblyInitialize]
public static void AssemblyInitialize( TestContext context )
{
AppSystem = new Sandbox.TestAppSystem();
AppSystem.Init();
}
[AssemblyCleanup]
public static void AssemblyCleanup()
{
AppSystem.Shutdown();
}
}
using Sandbox;
[TestClass]
public partial class LibraryTests
{
[TestMethod]
public void SceneTest()
{
var scene = new Scene();
using ( scene.Push() )
{
var go = new GameObject();
Assert.AreEqual( 1, scene.Directory.GameObjectCount );
}
}
}
using Sandbox;
[TestClass]
public partial class LibraryTests
{
[TestMethod]
public void SceneTest()
{
var scene = new Scene();
using ( scene.Push() )
{
var go = new GameObject();
Assert.AreEqual( 1, scene.Directory.GameObjectCount );
}
}
}
using Sandbox;
[TestClass]
public partial class LibraryTests
{
[TestMethod]
public void SceneTest()
{
var scene = new Scene();
using ( scene.Push() )
{
var go = new GameObject();
Assert.AreEqual( 1, scene.Directory.GameObjectCount );
}
}
}
using Sandbox;
[TestClass]
public partial class LibraryTests
{
[TestMethod]
public void SceneTest()
{
var scene = new Scene();
using ( scene.Push() )
{
var go = new GameObject();
Assert.AreEqual( 1, scene.Directory.GameObjectCount );
}
}
}
using Sandbox;
[TestClass]
public partial class LibraryTests
{
[TestMethod]
public void SceneTest()
{
var scene = new Scene();
using ( scene.Push() )
{
var go = new GameObject();
Assert.AreEqual( 1, scene.Directory.GameObjectCount );
}
}
}
global using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass]
public class TestInit
{
public static Sandbox.TestAppSystem AppSystem;
[AssemblyInitialize]
public static void AssemblyInitialize( TestContext context )
{
AppSystem = new Sandbox.TestAppSystem();
AppSystem.Init();
}
[AssemblyCleanup]
public static void AssemblyCleanup()
{
AppSystem.Shutdown();
}
}
global using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass]
public class TestInit
{
public static Sandbox.TestAppSystem AppSystem;
[AssemblyInitialize]
public static void AssemblyInitialize( TestContext context )
{
AppSystem = new Sandbox.TestAppSystem();
AppSystem.Init();
}
[AssemblyCleanup]
public static void AssemblyCleanup()
{
AppSystem.Shutdown();
}
}
using Sandbox.TailBox;
using System;
using System.IO;
using System.Linq;
[TestClass]
public sealed class TailBoxBehaviorTests
{
[TestMethod]
public void ExtractorReadsComponentClassAttributesAndConditionalStringLiterals()
{
var text = """
<Panel Class="flex items-center">
<Button class=@(IsReady ? "bg-good text-white" : "bg-danger text-muted") />
<div class='px-4 py-2'></div>
</Panel>
""";
var classes = TailBoxClassExtractor.ExtractClassesFromText( text );
AssertContainsAll( classes, "flex", "items-center", "bg-good", "text-white", "bg-danger", "text-muted", "px-4", "py-2" );
}
[TestMethod]
public void ExtractorReadsSafelistCommentsInRazorAndHtml()
{
var text = """
@* tailbox safelist: intro:opacity-0 bg-[#0d1418] *@
<!-- tailbox safelist: hover:bg-accent active:bg-panel -->
""";
var classes = TailBoxClassExtractor.ExtractClassesFromText( text );
AssertContainsAll( classes, "intro:opacity-0", "bg-[#0d1418]", "hover:bg-accent", "active:bg-panel" );
}
[TestMethod]
public void ExtractorCleansPunctuationAndIgnoresRazorExpressions()
{
var text = """
<div class="flex, p-4; @ComputedClass"></div>
<span class="hover:bg-accent) (text-muted"></span>
""";
var classes = TailBoxClassExtractor.ExtractClassesFromText( text );
AssertContainsAll( classes, "flex", "p-4", "hover:bg-accent", "text-muted" );
Assert.IsFalse( classes.Contains( "@ComputedClass" ) );
}
[TestMethod]
public void ExtractorIgnoresPlainProseInBroadStringLiterals()
{
var text = """
<div class="transition"></div>
@code {
private const string Copy = "transform transition.";
private const string Conditional = IsReady ? "flex" : "bg-good";
}
""";
var classes = TailBoxClassExtractor.ExtractClassesFromText( text );
Assert.IsTrue( classes.Contains( "transition" ) );
Assert.IsTrue( classes.Contains( "flex" ) );
Assert.IsTrue( classes.Contains( "bg-good" ) );
Assert.IsFalse( classes.Contains( "transform" ) );
}
[TestMethod]
public void GeneratorUsesConfigSafelistAndDeduplicatesClasses()
{
var root = CreateTempProject();
try
{
WriteFile( root, "Code/Screen.razor", "<div class=\"flex flex\"></div>" );
var config = TailBoxConfig.CreateDefault();
config.Safelist.Add( "flex p-4, text-accent" );
var result = TailBoxEditorProject.Generate( root, config, writeFile: false );
Assert.AreEqual( 3, result.GeneratedClassCount );
AssertContainsAll( result.GeneratedClasses, "flex", "p-4", "text-accent" );
}
finally
{
DeleteTempProject( root );
}
}
[TestMethod]
public void GeneratorWritesOnlyWhenOutputChanges()
{
var root = CreateTempProject();
try
{
WriteFile( root, "Code/Screen.razor", "<div class=\"flex\"></div>" );
var generator = new TailBoxGenerator();
var config = TailBoxConfig.CreateDefault();
var first = TailBoxEditorProject.Generate( root, config );
var second = TailBoxEditorProject.Generate( root, config );
config.Safelist.Add( "p-4" );
var third = TailBoxEditorProject.Generate( root, config );
Assert.IsTrue( first.WroteFile );
Assert.IsFalse( second.WroteFile );
Assert.IsTrue( third.WroteFile );
}
finally
{
DeleteTempProject( root );
}
}
[TestMethod]
public void GeneratorHonorsCustomContentGlobs()
{
var root = CreateTempProject();
try
{
WriteFile( root, "Code/Screen.razor", "<div class=\"flex\"></div>" );
WriteFile( root, "Ui/Nested/Screen.razor", "<div class=\"p-4\"></div>" );
var config = TailBoxConfig.CreateDefault();
config.Content.Clear();
config.Content.Add( "Ui/**/*.razor" );
var result = TailBoxEditorProject.Generate( root, config, writeFile: false );
Assert.AreEqual( 1, result.ScannedFileCount );
AssertContainsAll( result.GeneratedClasses, "p-4" );
Assert.IsFalse( result.GeneratedClasses.Contains( "flex" ) );
}
finally
{
DeleteTempProject( root );
}
}
[TestMethod]
public void GeneratorIgnoresGeneratedOutputAndBuildFoldersEvenWithBroadGlobs()
{
var root = CreateTempProject();
try
{
WriteFile( root, "Code/Screen.razor", "<div class=\"flex\"></div>" );
WriteFile( root, "Code/tailwand.generated.scss", "\"text-danger\"" );
WriteFile( root, "Code/bin/Bogus.razor", "<div class=\"p-4\"></div>" );
WriteFile( root, "Code/obj/Bogus.razor", "<div class=\"px-4\"></div>" );
WriteFile( root, ".sbox/Bogus.razor", "<div class=\"py-4\"></div>" );
var config = TailBoxConfig.CreateDefault();
config.Content.Clear();
config.Content.Add( "**/*" );
var result = TailBoxEditorProject.Generate( root, config, writeFile: false );
Assert.AreEqual( 1, result.GeneratedClassCount );
AssertContainsAll( result.GeneratedClasses, "flex" );
Assert.IsFalse( result.GeneratedClasses.Contains( "text-danger" ) );
Assert.IsFalse( result.GeneratedClasses.Contains( "p-4" ) );
Assert.IsFalse( result.GeneratedClasses.Contains( "px-4" ) );
Assert.IsFalse( result.GeneratedClasses.Contains( "py-4" ) );
}
finally
{
DeleteTempProject( root );
}
}
[TestMethod]
public void GeneratorWritesCustomOutputPath()
{
var root = CreateTempProject();
try
{
var config = TailBoxConfig.CreateDefault();
config.OutputPath = "Assets/Generated/tailbox.scss";
config.Safelist.Add( "flex" );
var result = TailBoxEditorProject.Generate( root, config );
Assert.IsTrue( result.WroteFile );
Assert.IsTrue( File.Exists( Path.Combine( root, "Assets", "Generated", "tailbox.scss" ) ) );
}
finally
{
DeleteTempProject( root );
}
}
[TestMethod]
public void SavedConfigUsesCamelCaseAndLoadsWithDefaultsMerged()
{
var root = CreateTempProject();
try
{
var config = TailBoxEditorProject.SaveDefaultConfig( root );
config.Colors["brand"] = "#123456";
TailBoxEditorProject.SaveConfig( root, config );
var json = File.ReadAllText( TailBoxEditorProject.GetConfigPath( root ) );
var loaded = TailBoxEditorProject.LoadConfig( root );
StringAssert.Contains( json, "\"outputPath\"" );
StringAssert.Contains( json, "\"fontSizes\"" );
Assert.AreEqual( "#123456", loaded.Colors["brand"] );
Assert.AreEqual( "#d7b46a", loaded.Colors["accent"] );
}
finally
{
DeleteTempProject( root );
}
}
[TestMethod]
public void ConfigLoadAcceptsCamelCaseOverrides()
{
var root = CreateTempProject();
try
{
WriteFile( root, TailBoxConfig.FileName, """
{
"outputPath": "Code/generated/custom.scss",
"content": [ "Ui/**/*.razor" ],
"safelist": [ "text-brand" ],
"colors": {
"brand": "#123456"
}
}
""" );
var loaded = TailBoxEditorProject.LoadConfig( root );
Assert.AreEqual( "Code/generated/custom.scss", loaded.OutputPath );
Assert.AreEqual( "Ui/**/*.razor", loaded.Content.Single() );
Assert.AreEqual( "#123456", loaded.Colors["brand"] );
Assert.AreEqual( "#d7b46a", loaded.Colors["accent"] );
AssertContainsAll( loaded.Safelist, "text-brand" );
}
finally
{
DeleteTempProject( root );
}
}
[TestMethod]
public void ConfigResolvesRelativeAndAbsoluteOutputPaths()
{
var root = CreateTempProject();
try
{
var config = TailBoxConfig.CreateDefault();
Assert.AreEqual(
Path.GetFullPath( Path.Combine( root, "Code", "tailwand.generated.scss" ) ),
TailBoxEditorProject.ResolveOutputPath( root, config ) );
var absolute = Path.Combine( root, "Custom", "tailbox.scss" );
config.OutputPath = absolute;
Assert.AreEqual( Path.GetFullPath( absolute ), TailBoxEditorProject.ResolveOutputPath( root, config ) );
}
finally
{
DeleteTempProject( root );
}
}
[TestMethod]
public void WatcherHandlesConfigAndRazorOnly()
{
var root = CreateTempProject();
try
{
var output = Path.Combine( root, "Code", "tailwand.generated.scss" );
Assert.IsTrue( TailBoxEditorWatcher.ShouldHandleChangedPath( root, output, Path.Combine( root, TailBoxConfig.FileName ) ) );
Assert.IsTrue( TailBoxEditorWatcher.ShouldHandleChangedPath( root, output, Path.Combine( root, "Code", "Screen.razor" ) ) );
Assert.IsFalse( TailBoxEditorWatcher.ShouldHandleChangedPath( root, output, Path.Combine( root, "Code", "Screen.razor.scss" ) ) );
Assert.IsFalse( TailBoxEditorWatcher.ShouldHandleChangedPath( root, output, output ) );
}
finally
{
DeleteTempProject( root );
}
}
[TestMethod]
public void WatcherIgnoresBuildHiddenAndExternalPaths()
{
var root = CreateTempProject();
try
{
var output = Path.Combine( root, "Code", "tailwand.generated.scss" );
var outside = Path.Combine( Path.GetDirectoryName( root )!, Guid.NewGuid().ToString( "N" ), "Screen.razor" );
Assert.IsFalse( TailBoxEditorWatcher.ShouldHandleChangedPath( root, output, Path.Combine( root, "bin", "Screen.razor" ) ) );
Assert.IsFalse( TailBoxEditorWatcher.ShouldHandleChangedPath( root, output, Path.Combine( root, "obj", "Screen.razor" ) ) );
Assert.IsFalse( TailBoxEditorWatcher.ShouldHandleChangedPath( root, output, Path.Combine( root, ".sbox", "Screen.razor" ) ) );
Assert.IsFalse( TailBoxEditorWatcher.ShouldHandleChangedPath( root, output, outside ) );
Assert.IsTrue( TailBoxEditorWatcher.ShouldHandleChangedPath( root, output, Path.Combine( root, "..generated", "Screen.razor" ) ) );
}
finally
{
DeleteTempProject( root );
}
}
[TestMethod]
public void WatcherOptInRequiresConfigFile()
{
var root = CreateTempProject();
try
{
Assert.IsFalse( TailBoxEditorWatcher.IsProjectOptedIn( root ) );
TailBoxEditorProject.SaveDefaultConfig( root );
Assert.IsTrue( TailBoxEditorWatcher.IsProjectOptedIn( root ) );
}
finally
{
DeleteTempProject( root );
}
}
private static void AssertContainsAll( System.Collections.Generic.IEnumerable<string> values, params string[] expected )
{
var set = values.ToHashSet( StringComparer.Ordinal );
foreach ( var item in expected )
{
Assert.IsTrue( set.Contains( item ), $"Expected '{item}' to be present." );
}
}
private static void WriteFile( string root, string relativePath, string text )
{
var path = Path.Combine( root, relativePath.Replace( '/', Path.DirectorySeparatorChar ) );
Directory.CreateDirectory( Path.GetDirectoryName( path )! );
File.WriteAllText( path, text );
}
private static string CreateTempProject()
{
return TailBoxTestPaths.CreateTempProject();
}
private static void DeleteTempProject( string root )
{
if ( Directory.Exists( root ) )
{
Directory.Delete( root, true );
}
}
}
using System;
using System.IO;
internal static class TailBoxTestPaths
{
public static string CreateTempProject()
{
var baseRoot = Environment.GetEnvironmentVariable( "TAILBOX_TEST_ROOT" );
if ( string.IsNullOrWhiteSpace( baseRoot ) )
baseRoot = Path.Combine( Path.GetTempPath(), "tailbox-tests" );
try
{
Directory.CreateDirectory( baseRoot );
}
catch ( UnauthorizedAccessException )
{
baseRoot = Path.Combine( Directory.GetCurrentDirectory(), ".tailbox-tests" );
Directory.CreateDirectory( baseRoot );
}
var root = Path.Combine( baseRoot, Guid.NewGuid().ToString( "N" ) );
Directory.CreateDirectory( Path.Combine( root, "Code" ) );
return root;
}
}
using Sandbox.TailBox;
using System;
using System.Linq;
[TestClass]
public sealed class TailBoxPipelineTests
{
[TestMethod]
public void GenerateFromSourcesHandlesNullSourcesAndConfig()
{
var result = new TailBoxGenerator().GenerateFromSources( null, null, "C:/Project", "C:/Project/Code/out.scss" );
Assert.AreEqual( "C:/Project", result.ProjectRoot );
Assert.AreEqual( "C:/Project/Code/out.scss", result.OutputPath );
Assert.AreEqual( 0, result.ScannedFileCount );
Assert.AreEqual( 0, result.DiscoveredClassCount );
Assert.AreEqual( 0, result.GeneratedClassCount );
Assert.AreEqual( 0, result.SkippedClassCount );
Assert.IsFalse( result.WroteFile );
StringAssert.Contains( result.GeneratedScss, "Source files scanned: 0" );
StringAssert.Contains( result.GeneratedScss, "Config: tailwand.config.json" );
}
[TestMethod]
public void GenerateFromSourcesDeduplicatesSortsAndKeepsFirstSourceForSkips()
{
var firstSource = "C:/Project/Code/First.razor";
var secondSource = "C:/Project/Code/Second.razor";
var sources = new[]
{
new TailBoxSourceText( firstSource, "<div class=\"p-4 grid flex\"></div>" ),
new TailBoxSourceText( secondSource, "<div class=\"grid text-accent\"></div>" )
};
var config = TailBoxConfig.CreateDefault();
config.Safelist.Add( "text-accent; flex p-4" );
var result = new TailBoxGenerator().GenerateFromSources( sources, config, "C:/Project", "C:/Project/Code/out.scss" );
CollectionAssert.AreEqual( new[] { "flex", "p-4", "text-accent" }, result.GeneratedClasses.ToArray() );
CollectionAssert.AreEqual( new[] { "grid" }, result.SkippedClasses.ToArray() );
Assert.AreEqual( 4, result.DiscoveredClassCount );
Assert.AreEqual( firstSource, result.Skipped.Single().SourcePath );
Assert.IsTrue( result.Warnings.Single().StartsWith( "grid:", StringComparison.Ordinal ) );
}
[TestMethod]
public void GenerateFromSourcesRendersConfigFileNameAndScannedFileCount()
{
var config = TailBoxConfig.CreateDefault();
config.ConfigPath = "C:/Project/Config/tailwand.custom.json";
var sources = new[]
{
new TailBoxSourceText( "C:/Project/Code/Screen.razor", "<div class=\"flex\"></div>" ),
new TailBoxSourceText( "C:/Project/Code/Empty.razor", "" )
};
var result = new TailBoxGenerator().GenerateFromSources( sources, config, "C:/Project" );
Assert.AreEqual( 2, result.ScannedFileCount );
StringAssert.Contains( result.GeneratedScss, "Source files scanned: 2" );
StringAssert.Contains( result.GeneratedScss, "Config: tailwand.custom.json" );
StringAssert.Contains( result.GeneratedScss, ".flex {" );
}
[TestMethod]
public void ScanClassesFromSourcesSortsAndDeduplicates()
{
var sources = new[]
{
new TailBoxSourceText( "A.razor", "<div class=\"p-4 flex\"></div>" ),
new TailBoxSourceText( "B.razor", "<div class=\"flex text-accent\"></div>" )
};
var classes = new TailBoxGenerator().ScanClassesFromSources( sources );
CollectionAssert.AreEqual( new[] { "flex", "p-4", "text-accent" }, classes.ToArray() );
}
[TestMethod]
public void SafelistSkipsHaveWarningsButNoSourcePath()
{
var config = TailBoxConfig.CreateDefault();
config.Safelist.Add( "grid unknown:flex" );
var result = new TailBoxGenerator().GenerateFromSources( Array.Empty<TailBoxSourceText>(), config );
CollectionAssert.AreEqual( new[] { "grid", "unknown:flex" }, result.SkippedClasses.ToArray() );
Assert.IsTrue( result.Skipped.All( item => item.SourcePath is null ) );
Assert.IsTrue( result.Warnings.Any( warning => warning.StartsWith( "grid:", StringComparison.Ordinal ) ) );
Assert.IsTrue( result.Warnings.Any( warning => warning.StartsWith( "unknown:flex:", StringComparison.Ordinal ) ) );
}
[TestMethod]
public void ProjectPathGeneratorMethodsStayEditorOnly()
{
var generator = new TailBoxGenerator();
var config = TailBoxConfig.CreateDefault();
Assert.ThrowsException<NotSupportedException>( () => generator.Generate( "C:/Project" ) );
Assert.ThrowsException<NotSupportedException>( () => generator.Generate( "C:/Project", config ) );
Assert.ThrowsException<NotSupportedException>( () => generator.ScanClasses( "C:/Project", config ) );
Assert.ThrowsException<NotSupportedException>( () => generator.FindContentFiles( "C:/Project", config ) );
}
}
global using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass]
public class TestInit
{
[AssemblyInitialize]
public static void ClassInitialize( TestContext context )
{
Sandbox.Application.InitUnitTest();
}
}
global using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass]
public class TestInit
{
public static Sandbox.TestAppSystem AppSystem;
[AssemblyInitialize]
public static void AssemblyInitialize( TestContext context )
{
AppSystem = new Sandbox.TestAppSystem();
AppSystem.Init();
}
[AssemblyCleanup]
public static void AssemblyCleanup()
{
AppSystem.Shutdown();
}
}
#nullable enable
using System.Collections.Generic;
using HitShapes;
using Sandbox;
using Xunit;
namespace HitShapes.Tests;
public class SlotDispatcherTests
{
// 2-slot horizontal split: left half = slot 0, right half = slot 1, outside rect = null.
static IHitShape TwoSlot()
=> HitShape.CustomRaw(slotCount: 2, (pos, size) =>
{
if (pos.x < 0 || pos.y < 0 || pos.x >= size.x || pos.y >= size.y) return null;
return pos.x < size.x * 0.5f ? 0 : 1;
});
[Fact]
public void Null_to_A_fires_OnSlotEnter_only()
{
var events = new List<string>();
var disp = new SlotDispatcher(TwoSlot())
{
OnSlotEnter = s => events.Add($"enter:{s}"),
OnSlotLeave = s => events.Add($"leave:{s}"),
};
disp.UpdateHoverAt(new Vector2(20, 50), new Vector2(100, 100));
Assert.Equal(new[] { "enter:0" }, events);
Assert.Equal(0, disp.CurrentSlot);
}
[Fact]
public void A_to_A_fires_nothing()
{
var events = new List<string>();
var disp = new SlotDispatcher(TwoSlot())
{
OnSlotEnter = s => events.Add($"enter:{s}"),
OnSlotLeave = s => events.Add($"leave:{s}"),
};
disp.UpdateHoverAt(new Vector2(20, 50), new Vector2(100, 100));
events.Clear();
disp.UpdateHoverAt(new Vector2(30, 50), new Vector2(100, 100));
Assert.Empty(events);
}
[Fact]
public void A_to_B_fires_leave_then_enter_in_order()
{
var events = new List<string>();
var disp = new SlotDispatcher(TwoSlot())
{
OnSlotEnter = s => events.Add($"enter:{s}"),
OnSlotLeave = s => events.Add($"leave:{s}"),
};
disp.UpdateHoverAt(new Vector2(20, 50), new Vector2(100, 100));
events.Clear();
disp.UpdateHoverAt(new Vector2(80, 50), new Vector2(100, 100));
Assert.Equal(new[] { "leave:0", "enter:1" }, events);
Assert.Equal(1, disp.CurrentSlot);
}
[Fact]
public void B_to_null_fires_leave_only()
{
var events = new List<string>();
var disp = new SlotDispatcher(TwoSlot())
{
OnSlotEnter = s => events.Add($"enter:{s}"),
OnSlotLeave = s => events.Add($"leave:{s}"),
};
disp.UpdateHoverAt(new Vector2(80, 50), new Vector2(100, 100));
events.Clear();
disp.UpdateHoverAt(new Vector2(-10, 50), new Vector2(100, 100));
Assert.Equal(new[] { "leave:1" }, events);
Assert.Null(disp.CurrentSlot);
}
[Fact]
public void Reset_clears_state_without_firing_leave()
{
var events = new List<string>();
var disp = new SlotDispatcher(TwoSlot())
{
OnSlotEnter = s => events.Add($"enter:{s}"),
OnSlotLeave = s => events.Add($"leave:{s}"),
};
disp.UpdateHoverAt(new Vector2(20, 50), new Vector2(100, 100));
events.Clear();
disp.Reset();
Assert.Empty(events);
Assert.Null(disp.CurrentSlot);
}
[Fact]
public void Null_shape_throws()
{
Assert.Throws<System.ArgumentNullException>(() => new SlotDispatcher(null!));
}
}
using Sandbox;
[TestClass]
public partial class LibraryTests
{
[TestMethod]
public void SceneTest()
{
var scene = new Scene();
using ( scene.Push() )
{
var go = new GameObject();
Assert.AreEqual( 1, scene.Directory.GameObjectCount );
}
}
}
global using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass]
public class TestInit
{
[AssemblyInitialize]
public static void ClassInitialize( TestContext context )
{
Sandbox.Application.InitUnitTest();
}
}
global using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass]
public class TestInit
{
public static Sandbox.TestAppSystem AppSystem;
[AssemblyInitialize]
public static void AssemblyInitialize( TestContext context )
{
AppSystem = new Sandbox.TestAppSystem();
AppSystem.Init();
}
[AssemblyCleanup]
public static void AssemblyCleanup()
{
AppSystem.Shutdown();
}
}
using System;
using System.IO;
using Editor;
using Sandbox;
[TestClass]
public sealed class ConnecterWorkspaceReaderTests
{
private string TempRoot;
[TestInitialize]
public void SetUp()
{
TempRoot = Path.Combine( Path.GetTempPath(), "ConnectorIntegrationTests", Guid.NewGuid().ToString( "N" ) );
Directory.CreateDirectory( TempRoot );
}
[TestCleanup]
public void TearDown()
{
if ( Directory.Exists( TempRoot ) )
{
Directory.Delete( TempRoot, true );
}
}
[TestMethod]
public void ReadsRepositoryRootsFromSettingsXml()
{
var firstRoot = Path.Combine( TempRoot, "Basic Assets" );
var secondRoot = Path.Combine( TempRoot, "Game Assets" );
Directory.CreateDirectory( firstRoot );
Directory.CreateDirectory( secondRoot );
var settingsPath = Path.Combine( TempRoot, "settings.xml" );
File.WriteAllText( settingsPath, $"""
<?xml version="1.0" encoding="utf-8"?>
<settings>
<setting key="2007">["{EscapeJsonPath( firstRoot )}","{EscapeJsonPath( secondRoot )}","{EscapeJsonPath( Path.Combine( TempRoot, "Missing" ) )}"]</setting>
</settings>
""" );
var repositories = ConnecterWorkspaceReader.ReadRepositoriesFromSettingsXml( settingsPath );
Assert.AreEqual( 2, repositories.Count );
Assert.AreEqual( "Basic Assets", repositories[0].Name );
Assert.AreEqual( "Game Assets", repositories[1].Name );
}
[TestMethod]
public void ReadsRepositoryRootsFromReadOnlyDatabaseBytes()
{
var repositoryRoot = Path.Combine( TempRoot, "Assetpack Assets" );
Directory.CreateDirectory( repositoryRoot );
var databasePath = Path.Combine( TempRoot, "default.dcdb" );
File.WriteAllText( databasePath, $"sqlite header\0Repositories\0{repositoryRoot}\0other data" );
var repositories = ConnecterWorkspaceReader.ReadRepositoriesFromDatabase( databasePath );
Assert.AreEqual( 1, repositories.Count );
Assert.AreEqual( "Assetpack Assets", repositories[0].Name );
Assert.AreEqual( ConnecterPathUtility.NormalizeDirectoryPath( repositoryRoot ), repositories[0].FullPath );
}
[TestMethod]
public void DiscoversWorkspacePathFromCandidates()
{
var workspacePath = Path.Combine( TempRoot, "Connecter" );
Directory.CreateDirectory( workspacePath );
File.WriteAllText( Path.Combine( workspacePath, "settings.xml" ), "<settings />" );
var discovered = ConnecterWorkspaceReader.DiscoverWorkspacePaths( new[] { Path.Combine( TempRoot, "Missing" ), workspacePath } );
Assert.AreEqual( 1, discovered.Count );
Assert.AreEqual( Path.GetFullPath( workspacePath ), discovered[0] );
}
[TestMethod]
public void ReadsExplicitWorkspacePath()
{
var workspacePath = Path.Combine( TempRoot, "Connecter" );
var repositoryRoot = Path.Combine( TempRoot, "Basic Assets" );
Directory.CreateDirectory( workspacePath );
Directory.CreateDirectory( repositoryRoot );
File.WriteAllText( Path.Combine( workspacePath, "settings.xml" ), $"""
<?xml version="1.0" encoding="utf-8"?>
<settings>
<setting key="2007">["{EscapeJsonPath( repositoryRoot )}"]</setting>
</settings>
""" );
var workspace = ConnecterWorkspaceReader.Read( workspacePath, allowAutoDiscover: false );
Assert.AreEqual( Path.GetFullPath( workspacePath ), workspace.WorkspacePath );
Assert.AreEqual( 1, workspace.Repositories.Count );
Assert.AreEqual( "Basic Assets", workspace.Repositories[0].Name );
}
private static string EscapeJsonPath( string path )
{
return path.Replace( "\\", "\\\\" );
}
}
global using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass]
public class TestInit
{
[AssemblyInitialize]
public static void ClassInitialize( TestContext context )
{
Sandbox.Application.InitUnitTest();
}
}
using Sandbox;
[TestClass]
public partial class LibraryTests
{
[TestMethod]
public void SceneTest()
{
var scene = new Scene();
using ( scene.Push() )
{
var go = new GameObject();
Assert.AreEqual( 1, scene.Directory.GameObjectCount );
}
}
}
using Sandbox;
[TestClass]
public partial class LibraryTests
{
[TestMethod]
public void SceneTest()
{
var scene = new Scene();
using ( scene.Push() )
{
var go = new GameObject();
Assert.AreEqual( 1, scene.Directory.GameObjectCount );
}
}
}