Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
334 changes: 203 additions & 131 deletions .claude-plugin/skills/jaction/SKILL.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using Cysharp.Threading.Tasks;
using TMPro;
Expand Down Expand Up @@ -124,6 +125,64 @@ private static GameObject Prefab
internal static bool SimulateNoPrefab;
#endif

#if UNITY_INCLUDE_TESTS
/// <summary>
/// Test hook: Gets the pool state for verification in tests.
/// Returns (activeCount, pooledCount).
/// </summary>
internal static (int activeCount, int pooledCount) TestGetPoolState()
{
return (ActiveMessageBoxes.Count, PooledMessageBoxes.Count);
}

/// <summary>
/// Test hook: Simulates clicking a button on the most recently shown message box.
/// </summary>
/// <param name="clickOk">If true, simulates clicking OK; otherwise simulates clicking Cancel.</param>
/// <returns>True if a message box was found and the click was simulated.</returns>
internal static bool TestSimulateButtonClick(bool clickOk)
Comment on lines +139 to +143
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment/doc says this simulates clicking a button on the "most recently shown" message box, but ActiveMessageBoxes is a HashSet, so there is no defined ordering. Either adjust the documentation to say "an arbitrary active message box", or track the last shown instance explicitly if ordering matters for tests.

Copilot uses AI. Check for mistakes.
{
if (ActiveMessageBoxes.Count == 0) return false;

// Get the first message box (any will do for testing)
var target = ActiveMessageBoxes.First();
target.HandleEvent(clickOk);
return true;
Comment on lines +147 to +150
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using ActiveMessageBoxes.First() introduces a LINQ dependency/allocation (and still returns an arbitrary element because the collection is a HashSet). Prefer a foreach to grab any element without LINQ, or add explicit tracking for "most recent" if determinism is needed.

Copilot uses AI. Check for mistakes.
}

/// <summary>
/// Test hook: Gets the button visibility state of the most recently shown message box.
/// </summary>
/// <returns>Tuple of (okButtonVisible, noButtonVisible), or null if no active boxes.</returns>
internal static (bool okVisible, bool noVisible)? TestGetButtonVisibility()
{
if (ActiveMessageBoxes.Count == 0) return null;

var target = ActiveMessageBoxes.First();
if (target._buttonOk == null || target._buttonNo == null)
return null;

return (target._buttonOk.gameObject.activeSelf, target._buttonNo.gameObject.activeSelf);
Comment on lines +161 to +165
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ActiveMessageBoxes.First() relies on LINQ and still returns an arbitrary element due to HashSet ordering. Consider replacing this with a non-LINQ foreach to avoid allocations, and/or track a deterministic target if tests expect "most recently shown" behavior.

Copilot uses AI. Check for mistakes.
}

/// <summary>
/// Test hook: Gets the text content of the most recently shown message box.
/// </summary>
/// <returns>Tuple of (title, content, okText, noText), or null if no active boxes.</returns>
internal static (string title, string content, string okText, string noText)? TestGetContent()
{
if (ActiveMessageBoxes.Count == 0) return null;

var target = ActiveMessageBoxes.First();
return (
target._title?.text,
target._content?.text,
target._textOk?.text,
target._textNo?.text
);
Comment on lines +176 to +182
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ActiveMessageBoxes.First() uses LINQ (allocations) and is non-deterministic with a HashSet. Prefer using a foreach to select any element without LINQ, or keep explicit ordering state if you need the most recently shown message box.

Copilot uses AI. Check for mistakes.
}
#endif

private TextMeshProUGUI _content;
private TextMeshProUGUI _textNo;
private TextMeshProUGUI _textOk;
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,284 @@
// JComponentTests.cs
// EditMode unit tests for JComponent base class

using NUnit.Framework;
using UnityEngine.UIElements;
using JEngine.UI.Editor.Components;
using JEngine.UI.Editor.Components.Layout;

namespace JEngine.UI.Tests.Editor.Components.Base
{
[TestFixture]
public class JComponentTests
{
// Using JStack as concrete implementation of JComponent
private JStack _component;

[SetUp]
public void SetUp()
{
_component = new JStack();
}

#region Constructor Tests

[Test]
public void Constructor_WithBaseClassName_AddsClass()
{
// JStack inherits from JComponent with "j-stack" as base class
Assert.IsTrue(_component.ClassListContains("j-stack"));
}

#endregion

#region WithClass Tests

[Test]
public void WithClass_AddsClassName()
{
_component.WithClass("custom-class");
Assert.IsTrue(_component.ClassListContains("custom-class"));
}

[Test]
public void WithClass_ReturnsComponentForChaining()
{
var result = _component.WithClass("test");
Assert.AreSame(_component, result);
}

[Test]
public void WithClass_CanAddMultipleClasses()
{
_component.WithClass("class1");
_component.WithClass("class2");

Assert.IsTrue(_component.ClassListContains("class1"));
Assert.IsTrue(_component.ClassListContains("class2"));
}

#endregion

#region WithName Tests

[Test]
public void WithName_SetsElementName()
{
_component.WithName("test-element");
Assert.AreEqual("test-element", _component.name);
}

[Test]
public void WithName_ReturnsComponentForChaining()
{
var result = _component.WithName("test");
Assert.AreSame(_component, result);
}

[Test]
public void WithName_CanOverwritePreviousName()
{
_component.WithName("first");
_component.WithName("second");
Assert.AreEqual("second", _component.name);
}

#endregion

#region Add Tests

[Test]
public void Add_SingleChild_AddsToComponent()
{
var child = new Label("test");
_component.Add(child);

Assert.AreEqual(1, _component.childCount);
Assert.AreSame(child, _component[0]);
}

[Test]
public void Add_MultipleChildren_AddsAllToComponent()
{
var child1 = new Label("test1");
var child2 = new Label("test2");
var child3 = new Label("test3");

_component.Add(child1, child2, child3);

Assert.AreEqual(3, _component.childCount);
}

[Test]
public void Add_NullChild_IsIgnored()
{
_component.Add((VisualElement)null);
Assert.AreEqual(0, _component.childCount);
}

[Test]
public void Add_MixedNullAndValid_AddsOnlyValidChildren()
{
var child1 = new Label("test1");
var child2 = new Label("test2");

_component.Add(child1, null, child2);

Assert.AreEqual(2, _component.childCount);
}

[Test]
public void Add_ReturnsComponentForChaining()
{
var result = _component.Add(new Label());
Assert.AreSame(_component, result);
}

#endregion

#region WithFlexGrow Tests

[Test]
public void WithFlexGrow_SetsFlexGrowValue()
{
_component.WithFlexGrow(2f);
Assert.AreEqual(2f, _component.style.flexGrow.value);
}

[Test]
public void WithFlexGrow_ReturnsComponentForChaining()
{
var result = _component.WithFlexGrow(1f);
Assert.AreSame(_component, result);
}

[Test]
public void WithFlexGrow_ZeroValue_SetsToZero()
{
_component.WithFlexGrow(0f);
Assert.AreEqual(0f, _component.style.flexGrow.value);
}

#endregion

#region WithFlexShrink Tests

[Test]
public void WithFlexShrink_SetsFlexShrinkValue()
{
_component.WithFlexShrink(2f);
Assert.AreEqual(2f, _component.style.flexShrink.value);
}

[Test]
public void WithFlexShrink_ReturnsComponentForChaining()
{
var result = _component.WithFlexShrink(1f);
Assert.AreSame(_component, result);
}

#endregion

#region WithMargin Tests

[Test]
public void WithMargin_SetsAllMargins()
{
_component.WithMargin(10f);

Assert.AreEqual(10f, _component.style.marginTop.value.value);
Assert.AreEqual(10f, _component.style.marginRight.value.value);
Assert.AreEqual(10f, _component.style.marginBottom.value.value);
Assert.AreEqual(10f, _component.style.marginLeft.value.value);
}

[Test]
public void WithMargin_ReturnsComponentForChaining()
{
var result = _component.WithMargin(5f);
Assert.AreSame(_component, result);
}

#endregion

#region WithPadding Tests

[Test]
public void WithPadding_SetsAllPadding()
{
_component.WithPadding(10f);

Assert.AreEqual(10f, _component.style.paddingTop.value.value);
Assert.AreEqual(10f, _component.style.paddingRight.value.value);
Assert.AreEqual(10f, _component.style.paddingBottom.value.value);
Assert.AreEqual(10f, _component.style.paddingLeft.value.value);
}

[Test]
public void WithPadding_ReturnsComponentForChaining()
{
var result = _component.WithPadding(5f);
Assert.AreSame(_component, result);
}

#endregion

#region WithVisibility Tests

[Test]
public void WithVisibility_True_SetsDisplayFlex()
{
_component.WithVisibility(true);
Assert.AreEqual(DisplayStyle.Flex, _component.style.display.value);
}

[Test]
public void WithVisibility_False_SetsDisplayNone()
{
_component.WithVisibility(false);
Assert.AreEqual(DisplayStyle.None, _component.style.display.value);
}

[Test]
public void WithVisibility_ReturnsComponentForChaining()
{
var result = _component.WithVisibility(true);
Assert.AreSame(_component, result);
}

[Test]
public void WithVisibility_CanToggle()
{
_component.WithVisibility(false);
Assert.AreEqual(DisplayStyle.None, _component.style.display.value);

_component.WithVisibility(true);
Assert.AreEqual(DisplayStyle.Flex, _component.style.display.value);
}

#endregion

#region Chaining Tests

[Test]
public void FluentApi_CanChainMultipleMethods()
{
_component
.WithName("test")
.WithClass("custom")
.WithMargin(5f)
.WithPadding(10f)
.WithFlexGrow(1f)
.WithVisibility(true);

Assert.AreEqual("test", _component.name);
Assert.IsTrue(_component.ClassListContains("custom"));
Assert.AreEqual(5f, _component.style.marginTop.value.value);
Assert.AreEqual(10f, _component.style.paddingTop.value.value);
Assert.AreEqual(1f, _component.style.flexGrow.value);
Assert.AreEqual(DisplayStyle.Flex, _component.style.display.value);
}

#endregion
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading