Rules and patterns for creating Avalonia UI visual elements (controls, dialogs, panels, windows). Use when creating or modifying XAML controls, dialogs, panels, or windows in this project.
日本語の概要は準備中です。原文の説明を表示しています。
Rules and patterns for writing tests of Avalonia UI visual elements (controls, dialogs, panels). Use when creating or modifying tests for XAML controls, dialogs, or panels.
インストールする前に、エージェントに与えられる指示の中身を確認できます。
Follow these rules strictly when writing tests for visual elements (controls, dialogs, panels) in this project.
| What you're testing | Attribute |
|---|---|
| Pure logic (controllers, models) | [Fact] or [Theory] |
| Anything that instantiates a control/dialog/window | [AvaloniaFact] |
| Parameterized tests | [Theory] + [InlineData(...)] |
Rule: If the test creates ANY Avalonia UserControl, Window, or Control subclass, it MUST use [AvaloniaFact] (not [Fact]). This includes dialogs.
MethodUnderTest_Scenario_ExpectedBehavior
Examples:
Value_WithValidText_ShouldReturnMeasurementConstructor_ValidArc_ControlsPopulatedFromElementSave_ModifiedControls_ElementUpdatedGetUnits_ForDistanceUnit_ShouldReturnAllUnitsIncrementValue_AtMaximum_ShouldNotExceedMaximum| Test target | Namespace | Location |
|---|---|---|
| Controller (pure logic) | [Project].Tests.Controllers | Tests/Controllers/ |
| Control (UI) | [Project].Tests.UI | Tests/UI/ |
| Dialog | [Project].Tests.Dialogs | Tests/Dialogs/ |
#region blockspublic class MeasurementControllerTests
{
#region Unit Enumeration Tests
[Fact]
public void GetUnits_ForDistanceUnit_ShouldReturnAllUnits() { }
#endregion
#region Value Parsing Tests
[Fact]
public void Value_WithValidText_ShouldReturnMeasurement() { }
#endregion
#region Increment/Decrement Tests
[Fact]
public void IncrementValue_AtMaximum_ShouldNotExceedMaximum() { }
#endregion
}
Always use AwesomeAssertions fluent API. Never use Assert.Equal() or xUnit asserts.
using AwesomeAssertions;
// Null checks
result.Should().NotBeNull();
result.Should().BeNull();
// Equality
value.Should().Be(expected);
value.Should().NotBe(unexpected);
value.Should().BeApproximately(expected, tolerance);
// Comparison
value.Should().BeGreaterThan(min);
value.Should().BeLessThanOrEqualTo(max);
// Boolean
flag.Should().BeTrue();
flag.Should().BeFalse();
// String
text.Should().Be("100.46");
text.Should().NotBeNullOrEmpty();
text.Should().Contain(".");
// Collection
items.Should().NotBeEmpty();
items.Should().HaveCount(5);
items.Should().Contain(item);
items.Should().AllSatisfy(x => x.Should().NotBeNull());
// Type
result.Should().BeOfType<EditLineDialog>();
// With context message
tables.Should().Contain(t => t.Value == enumValue,
$"DragTableId.{enumValue} should be in the returned tables");
[AvaloniaFact]
public void Control_ShouldInitialize()
{
// Arrange & Act
var control = new MeasurementControl { UnitType = typeof(DistanceUnit) };
// Assert
control.Should().NotBeNull();
control.IsEmpty.Should().BeTrue();
}
Rule: UnitType must be set BEFORE calling SetValue() — it creates the internal controller.
[AvaloniaFact]
public void Value_SetAndGet_ShouldRoundTrip()
{
// Arrange
var control = new MeasurementControl { UnitType = typeof(DistanceUnit) };
var measurement = new Measurement<DistanceUnit>(100, DistanceUnit.Meter);
// Act
control.SetValue(measurement);
var result = control.GetValue<DistanceUnit>();
// Assert
result.Should().NotBeNull();
result!.Value.Value.Should().Be(100);
result.Value.Unit.Should().Be(DistanceUnit.Meter);
}
[AvaloniaFact]
public void Value_SetAndGet_WithoutEdit_ShouldPreserveOriginalPrecision()
{
// Arrange
var control = new BallisticCoefficientControl { DecimalPoints = 3 };
var original = new BallisticCoefficient(0.45678, DragTableId.G1);
// Act
control.Value = original;
// Assert — display is rounded but getter returns exact original
control.NumericPart.Text.Should().Be("0.457");
control.Value!.Value.Value.Should().Be(0.45678);
}
[Fact]
public void Value_BelowMinimum_ShouldStillAccept()
{
// Arrange: Min/Max are only for increment/decrement, not validation
var controller = new MeasurementController<DistanceUnit> { Minimum = 0 };
// Act: Negative values should be accepted
var result = controller.Value("-10", DistanceUnit.Meter, null, CultureInfo.InvariantCulture);
// Assert
result.Should().NotBeNull();
result!.Value.Value.Should().Be(-10);
}
Tests can read internal control parts directly:
// Text content
control.NumericPart.Text.Should().Be("100.46");
// ComboBox selection
control.UnitPart.Items.Count.Should().BeGreaterThan(0);
var selectedUnit = (UnitItem)control.UnitPart.SelectedItem!;
selectedUnit.Unit.Should().Be(DistanceUnit.Meter);
// CheckBox state
dialog.ClockwiseCheckBox.IsChecked.Should().BeTrue();
This works because control parts use x:FieldModifier="internal" and the test project has a project reference.
[AvaloniaFact]
public void Constructor_ValidElement_ControlsPopulatedFromElement()
{
// Arrange
var arc = new ReticlePathElementArc
{
Position = new ReticlePosition(1, 2, AngularUnit.Mil),
Radius = new Measurement<AngularUnit>(0.5, AngularUnit.Mil),
ClockwiseDirection = true,
MajorArc = true
};
// Act
var dialog = new EditArcDialog(arc);
// Assert
dialog.PositionX.GetValue<AngularUnit>().Should().Be(arc.Position.X);
dialog.PositionY.GetValue<AngularUnit>().Should().Be(arc.Position.Y);
dialog.RadiusControl.GetValue<AngularUnit>().Should().Be(arc.Radius);
dialog.ClockwiseCheckBox.IsChecked.Should().BeTrue();
dialog.MajorArcCheckBox.IsChecked.Should().BeTrue();
}
[AvaloniaFact]
public void Save_ModifiedControls_ElementUpdated()
{
// Arrange
var arc = new ReticlePathElementArc
{
Position = new ReticlePosition(1, 2, AngularUnit.Mil),
Radius = new Measurement<AngularUnit>(0.5, AngularUnit.Mil)
};
var dialog = new EditArcDialog(arc);
// Act — modify controls
dialog.PositionX.SetValue(new Measurement<AngularUnit>(10, AngularUnit.MOA));
dialog.RadiusControl.SetValue(new Measurement<AngularUnit>(2, AngularUnit.Mil));
// Assert — element NOT changed yet (no auto-save)
arc.Position.X.Should().NotBe(new Measurement<AngularUnit>(10, AngularUnit.MOA));
// Act — explicit save
dialog.Save();
// Assert — element IS changed now
arc.Position.X.Should().Be(new Measurement<AngularUnit>(10, AngularUnit.MOA));
arc.Radius.Should().Be(new Measurement<AngularUnit>(2, AngularUnit.Mil));
}
[AvaloniaFact]
public void Save_NullPosition_InitializesPosition()
{
// Arrange
var arc = new ReticlePathElementArc { Position = null };
var dialog = new EditArcDialog(arc);
// Act
dialog.PositionX.SetValue(new Measurement<AngularUnit>(5, AngularUnit.Mil));
dialog.Save();
// Assert
arc.Position.Should().NotBeNull();
arc.Position!.X.Should().Be(new Measurement<AngularUnit>(5, AngularUnit.Mil));
}
[AvaloniaFact]
public void CreateDialogForElement_ReticleLine_ReturnsEditLineDialog()
{
var line = new ReticleLine { Start = new ReticlePosition(0, 0, AngularUnit.Mil) };
var dialog = DialogFactory.CreateDialogForElement(line);
dialog.Should().NotBeNull();
dialog.Should().BeOfType<EditLineDialog>();
}
[AvaloniaFact]
public void CreateDialogForElement_UnknownType_ReturnsNull()
{
DialogFactory.CreateDialogForElement(new object()).Should().BeNull();
}
Use [Theory] + [InlineData] when testing the same logic across multiple inputs:
[Theory]
[InlineData(MeasurementSystem.Metric, "Range (m)")]
[InlineData(MeasurementSystem.Imperial, "Range (yd)")]
public void XAxisTitle_ForSystem_ReturnsCorrectTitle(
MeasurementSystem system, string expected)
{
var controller = new ChartController(system, AngularUnit.MOA, mode, dropBase, trajectory);
controller.XAxisTitle.Should().Be(expected);
}
For complex test data, create private static helper methods in the test class:
private static TrajectoryPoint[] CreateSampleTrajectory()
{
return new[]
{
CreatePoint(0, 900, 2.70, 0, 0, 0, 0, 0, 0),
CreatePoint(100, 820, 2.46, 2.5, 2.0, 0.8, 1.5, 1.0, 0.4),
CreatePoint(200, 745, 2.23, 8.2, 5.1, 1.4, 3.2, 2.5, 0.9),
};
}
private static ReticleDefinition CreateTestReticle()
{
return new ReticleDefinition
{
Size = new ReticlePosition(10, 10, AngularUnit.Mil),
Zero = new ReticlePosition(5, 5, AngularUnit.Mil)
};
}
Test both invariant and locale-specific cultures when testing value parsing:
[Fact]
public void Value_WithInvariantCulture_ShouldUseDotDecimal()
{
var result = controller.Value("100.5", DistanceUnit.Meter, null, CultureInfo.InvariantCulture);
result!.Value.Value.Should().Be(100.5);
}
[Fact]
public void Value_WithGermanCulture_ShouldUseCommaDecimal()
{
var culture = new CultureInfo("de-DE");
var result = controller.Value("100,5", DistanceUnit.Meter, null, culture);
result!.Value.Value.Should().Be(100.5);
}
Assert.Equal() — use AwesomeAssertions .Should().Be()[Fact] for tests that create Avalonia controls — use [AvaloniaFact]まだレビューはありません。使ってみた感想をお寄せください。
概要と使いどころ
Rules and patterns for creating Avalonia UI visual elements (controls, dialogs, panels, windows). Use when creating or modifying XAML controls, dialogs, panels, or windows in this project.
日本語の概要は準備中です。原文の説明を表示しています。
Use whenever writing, reviewing, or debugging .NET/C# code that uses the BallisticCalculator NuGet package (namespace BallisticCalculator) or its Gehtsoft.Measurements unit types — even when the user doesn't name the package explicitly but is clearly doing external/interior ballistics in .NET (bullet drop, velocity, energy, windage, spin drift, time of flight, zeroing a scope) for rifles, air rifles, bows, or artillery. Covers the full public API (Ammunition, Rifle, Atmosphere, Wind, ShotParameters, TrajectoryCalculator, TrajectoryPoint); standard, custom, .drg, and multi-BC drag tables; saving/loading data and building your own file format around it (BXml and System.Text.Json serialization); and building or rendering scope reticles (e.g. to SVG). Also use it for this library's Gehtsoft.Measurements Measurement<T> unit types (DistanceUnit, VelocityUnit, AngularUnit, WeightUnit, PressureUnit, …). Skip for: generic physics or projectile-motion exercises, a different ballistics library (e.g. GNU/JBM), game-engine or Unity projectile motion, plain unit conversions, and non-code ballistics questions (load-data or forensics advice). Self-contained: no access to the library source or binaries is required — prefer this over scanning the package to rediscover the API. Describes 1.1.13+, including the named ZeroRangeCantBeReachedException / TrajectoryCannotBeCalculatedException failures.
日本語の概要は準備中です。原文の説明を表示しています。
Author and modify documentation for projects that use Gehtsoft's docgen utility. Use this skill whenever the user mentions docgen, .ds files, the doc/ directory of a repository, project.xml or project.proj for documentation, or wants to write/edit/build/debug API documentation in any codebase that uses docgen. Also use it whenever the user asks about generating C#, C++, or Java API documentation that follows Gehtsoft conventions, even if they don't say "docgen" explicitly.
日本語の概要は準備中です。原文の説明を表示しています。
Design a rifle-scope reticle from a plain-language description and emit it as a BallisticCalculator `.reticle` file (BXml) plus a rendered SVG preview. Covers the complete `.reticle` element/attribute specification, reticle design principles (mrad vs MOA subtension, FFP/SFP, stroke-weight hierarchy, hash-mark ladders, christmas-tree wind holds, clutter budget, BDC anchors) and worked, verified patterns for duplex, mil-dot, hash-ladder, christmas-tree, chevron, circle-dot, dense-grid and rangefinding-stadia reticles. Also covers reproducing an existing commercial reticle from its manufacturer's technical data sheet. Use this skill whenever the user wants to create, modify, review or render a scope reticle, crosshair, mil-dot / MOA grid, holdover ladder, christmas tree, BDC ladder or rangefinding stadia — including any mention of a `.reticle` file, `ReticleDefinition`, `ReticleDrawController`, `SvgCanvasFactory`, or "draw me a reticle", and including requests to clone or model a named commercial reticle (Horus H58/H59, TReMoR, Mil-C, MOAR, Tremor3, EBR, and the like). Trigger it even when the user only says something like "make a crosshair with 0.2 mil hashes out to 5 mils" or "turn this reticle picture into a file", and trigger it before hand-writing reticle XML or hand-rolling reticle geometry, because the file format silently ignores misspelled attributes and mis-cased booleans.
日本語の概要は準備中です。原文の説明を表示しています。