Grid Placement

GridPlacement 5.1 (GDScript) — How to Test a Plugin

Scope: GridPlacement / GridBuilding 5.0–5.1 GDScript legacy line.

Policy: 5.1 is maintenance-mode. Prefer small, focused tests. Avoid heavy demo-scene megasuites.


Test Types: What to Write (Decision Rules)

Contract-first testing (behavior, not implementation)

Treat tests as a behavior contract that the runtime must satisfy. This is how we keep tests stable while allowing runtime refactors.

  • What tests MUST assert
    • Observable outcomes (returned values/results, emitted signals/events, externally visible state).
    • Invariants (what must remain true after operations).
    • Failure modes (what issues/errors are surfaced for invalid inputs).
  • What tests MUST NOT assert
    • Internal call order, helper methods, node tree layout specifics, or private implementation details.
    • Exact timings/frame counts or micro-optimizations.

If a refactor breaks a test, treat it as contract drift: either behavior changed unintentionally (fix runtime), or the contract changed intentionally (update tests + docs together).

1) Write C# Core unit tests when:

  • You can express the behavior as pure logic (math, rules, selection, transforms, state transitions).
  • You want fast, deterministic tests with minimal engine dependency.
  • The behavior is intended to be canonical for 6.0 and beyond.

Good examples

  • Grid coordinate conversion, snapping, bounds checks.
  • Rule evaluation logic and validation result objects.

2) Write C# Godot integration (harness-based) tests when:

  • The behavior needs Godot types/nodes (TileMap, collisions, signals) but can be exercised in a minimal scene.
  • You can avoid loading the full demo.
  • You need confidence that runtime wiring works (services + adapters).

Good examples

  • Collision probing against a minimal TileMap.
  • A placement flow that requires signals/state holders.

3) Write 5.1 GDScript GdUnit smoke tests when:

  • The behavior is specific to the legacy addon and realistically won’t be ported immediately.
  • You need confidence that the 5.1 addon still works for existing users.
  • You can keep the test thin (wiring + a small number of assertions).

Good examples

  • Curated Tier-1/Tier-2 addon-level suites.
  • Targeting/positioning contracts that are hard to represent without the 5.1 nodes.

Keep Tests Small (Patterns)

Prefer: “minimal harness” setup

  • Create only the nodes you need.
  • Use a minimal TileMap/scene where applicable.
  • Use deterministic inputs (fixed coordinates, fixed tile sizes, fixed seeded randomness if any).

Avoid: “demo megasetup” patterns

  • Avoid loading full demo scenes.
  • Avoid old TestEnvironment/mega-harness style tests.
  • Avoid fragile pixel-assertions unless the feature is explicitly visual.

Acceptance Criteria (What Good Looks Like)

A test is acceptable for the 5.1 curated slice if:

  • It runs headless via the standard runner.
  • It has deterministic assertions (no timing flakiness).
  • It doesn’t require full demo scenes.
  • It aligns with the curated KEEP list (or is a clearly justified small addition).

Running Tests (5.1 GdUnit)

Use the runbook from ../../ROADMAP.md.

To validate the 5.1 public/internal boundary (no preload() of class_name scripts), run the canonical check from the game_dev/docs repo:

1
./scripts/validate_gdscript_public_boundary.sh

Validation entrypoints (post-shim cleanup)

As a low-impact breaking change, several unused GBCompositionContainer validation shims were removed. When writing tests or diagnostics, use the authoritative entrypoints instead:

  • Runtime issues: composition_container.get_runtime_issues()
  • Structure issues: composition_container.get_structure_issues()
  • Injector boolean: GBInjectorSystem.validate_runtime()

Parameterized Tests (GdUnit4 Canonical Pattern)

GdUnit4 parameterization is done by adding a trailing argument named test_parameters or _test_parameters to the test function.

  • Name rule
    • Use test_parameters or _test_parameters.
    • Prefer _test_parameters in this repo to avoid warnings-as-errors for an unused argument.
  • Shape rule
    • The value must be an Array of parameter rows.
    • Each row is an Array whose values map 1:1 to the preceding function parameters.
  • Typing rule
    • Give the parameter set an explicit type: _test_parameters: Array = [...].

Example:

1
2
3
4
5
6
func test_example(a: String, b: int, _test_parameters: Array = [
    ["case-1", 1],
    ["case-2", 2],
]) -> void:
    # assertions...
    pass

Reference implementation:

  • gdscript/grid_building/test/systems/placement/unit/rule_check_indicator_logic_unit_test.gd