# Remote Protocol Unit Test Framework

The remote protocol unit test framework allows testing GacUI applications without real OS windows or rendering. It reuses the same remote protocol architecture as production remote deployment, replacing the renderer side with a mock implementation (`UnitTestRemoteProtocol`) that captures rendering results as snapshots and feeds simulated user input through the protocol pipeline. The GacUI core side runs identically to production.

Implementation lives in `Source/UnitTestUtilities/`.

## Comprehensive Example

Below is a complete example demonstrating the key concepts of the framework: test executable setup, GacUI XML resource definition, frame-based interaction with named snapshots, control lookup, and mouse input simulation.

### Test Executable Entry Point

The test executable's `main` function initializes the framework once, runs all test cases, and finalizes:

```cpp
int UnitTestMain(int argc, T* argv[])
{
    UnitTestFrameworkConfig config;
    config.snapshotFolder = GetTestSnapshotPath();
    config.resourceFolder = GetTestDataPath();

    GacUIUnitTest_Initialize(&config);
    int result = UnitTest::RunAndDisposeTests(argc, argv);
    GacUIUnitTest_Finalize();
    return result;
}
```

### A Test Case: Clicking a Button

This example defines a GacUI XML resource with a window containing an OK button, then tests hovering, pressing, and releasing the button in separate frames. Each frame callback describes the action it performs; the frame name is assigned to the **rendering snapshot that results from the previous frame's action**.

```cpp
const auto resource = LR"GacUISrc(
<Resource>
  <Instance name="MainWindowResource">
    <Instance ref.Class="gacuisrc_unittest::MainWindow">
      <Window ref.Name="self" Text="Hello, world!" ClientSize="x:320 y:240">
        <Button ref.Name="buttonOK" Text="OK">
          <att.BoundsComposition-set AlignmentToParent="left:5 top:5 right:-1 bottom:-1"/>
          <ev.Clicked-eval><![CDATA {
            Application::GetApplication().InvokeInMainThread(self, func():void{
              // changing UI structures that affects the sender of an event
              // is supposed to be put in InvokeInMainThread
              // Otherwise followed up events on the same control may gone
              self.Close();
            });
          }]]></ev.Clicked-eval>
        </Button>
      </Window>
    </Instance>
  </Instance>
</Resource>
)GacUISrc";

TEST_CASE(L"Click Button")
{
    GacUIUnitTest_SetGuiMainProxy([](UnitTestRemoteProtocol* protocol, IUnitTestContext*)
    {
        // Frame 0: snapshot named "Ready" — shows the initial rendering after the window opens.
        // Callback action: move the mouse on the OK button.
        protocol->OnNextIdleFrame(L"Ready", [=]()
        {
            auto window = GetApplication()->GetMainWindow();
            auto buttonOK = TryFindObjectByName<GuiButton>(window, L"buttonOK");
            // MouseMove(location) + LClick() could be merged into one operation
            // LClick(location) will perform both
            // but when the side effect of MouseMove is important
            // you are given choices
            protocol->MouseMove(protocol->LocationOf(buttonOK));
        });
        // Frame 1: snapshot named "Hover" — shows the button in hover state (result of MouseMove above).
        // Callback action: click the OK button, triggering Clicked → window closes.
        protocol->OnNextIdleFrame(L"Hover", [=]()
        {
            protocol->LClick();
        });
        // If the user interaction above does not cause the application to exit,
        // an additional frame is needed to close the window explicitly:
        //
        // protocol->OnNextIdleFrame(L"Frame name describing what the previous frame does", [=]()
        // {
        //     auto window = GetApplication()->GetMainWindow();
        //     window->Hide();
        // });
    });
    GacUIUnitTest_StartFast_WithResourceAsText<darkskin::Theme>(
        WString::Unmanaged(L"UnitTestFramework/WindowWithOKButton"),
        WString::Unmanaged(L"gacuisrc_unittest::MainWindow"),
        resource
        );
});
```

### What This Example Demonstrates

- **`GacUIUnitTest_SetGuiMainProxy`**: Registers the test's frame callback function. It receives `UnitTestRemoteProtocol*` (for input simulation and snapshot access) and `IUnitTestContext*`.
- **`OnNextIdleFrame(name, callback)`**: Registers a callback to run after the next rendering frame settles. The `name` is attached to the snapshot that was just captured (the rendering result), not to what the callback will do.
- **`TryFindObjectByName<T>(window, name)`**: Looks up a named control from the GacUI XML resource by its `ref.Name`.
- **`protocol->LocationOf(control)`**: Computes the absolute screen coordinate of the center of a control, for use with mouse input methods.
- **`LClick(location)`**: Simulates a full left mouse click (mouse move + button down + button up) at the given location. Use this for simple clicks instead of separate `_LDown`/`_LUp` calls.
- **`GacUIUnitTest_StartFast_WithResourceAsText<Theme>`**: Compiles the XML resource, registers the theme, creates the window, runs the application, and captures snapshots — all in one call.
- **Frame name semantics**: `"Ready"` names the initial rendering. Each snapshot file (`frame_0.json`) records the full rendering DOM at that point.
- **Closing the window**: The button's click handler uses `InvokeInMainThread` to defer `self.Close()`. This is necessary because `Close()` would otherwise be called synchronously inside the IO event dispatch, which could block the frame callback (see the blocking function caveat below). If user interaction does not cause the application to exit, an extra frame must be added at the end to call `window->Hide()` explicitly.

## Test Executable Initialization and Finalization

### Global Setup (GacUIUnitTest_Initialize)

Each test executable's `main` calls `GacUIUnitTest_Initialize(&config)` with a `UnitTestFrameworkConfig` containing `snapshotFolder` and `resourceFolder`. This function:
- Sets `GACUI_UNITTEST_ONLY_SKIP_THREAD_LOCAL_STORAGE_DISPOSE_STORAGES` and `GACUI_UNITTEST_ONLY_SKIP_TYPE_AND_PLUGIN_LOAD_UNLOAD` to `true`, allowing the type manager and plugin system to be loaded once and reused across all test cases.
- Calls `GetGlobalTypeManager()->Load()` and `GetPluginManager()->Load(true, false)` once.

### Global Teardown (GacUIUnitTest_Finalize)

Called after all tests complete. Tears down the type manager and plugin manager.

## Per-Test-Case Setup and the Proxy Chaining Pattern

### Registering Frame Callbacks

Each test case follows a standard pattern:
1. Call `GacUIUnitTest_SetGuiMainProxy(callback)` to register a `UnitTestMainFunc` (signature: `void(UnitTestRemoteProtocol*, IUnitTestContext*)`). This is the test's frame-based action callback that registers idle frame handlers via `OnNextIdleFrame`.
2. Optionally call `GacUIUnitTest_LinkGuiMainProxy(linkCallback)` to wrap additional setup around the test proxy.
3. Call one of the start functions to launch the test.

### Proxy Chaining (GacUIUnitTest_LinkGuiMainProxy)

`GacUIUnitTest_LinkGuiMainProxy` captures the current proxy as `previousMainProxy` and installs a new proxy that calls the link function with the previous proxy as a parameter. This enables decorator-style composition — each link layer can perform setup before delegating to the inner proxy and cleanup after.

### GacUIUnitTest_StartFast_WithResourceAsText

This template function is the most commonly used entry point. `GacUIUnitTest_StartFast_WithResourceAsText` and `GacUIUnitTest_Start_WithResourceAsText` install two proxy layers. When `GuiMain` invokes the resulting proxy chain, it performs these steps in order:
1. Compile and load the GacUI XML resource via `GacUIUnitTest_CompileAndLoad`.
2. Save the compiled Workflow script text as a snapshot file (`[x64].txt` or `[x86].txt`) for Workflow generation stability verification.
3. Send `OnControllerConnect` with `ControllerGlobalConfig` to the protocol.
4. Register the requested theme type.
5. Call the optional `GacUIUnitTest_Installer::initialize` callback.
6. Create the main window via `Value::Create(windowTypeFullName)`.
7. Call the optional `GacUIUnitTest_Installer::installWindow` callback, then center the window with `MoveToScreenCenter()`.
8. Call `previousMainProxy(protocol, context)` to let the test register its frame callbacks.
9. Run the application via `GetApplication()->Run(window)`.
10. Call the optional `GacUIUnitTest_Installer::finalize` callback and unregister the theme during cleanup.

### Start Functions

- `GacUIUnitTest_Start(appName, config)` — synchronous in-process test with optional serialization channel.
- `GacUIUnitTest_StartAsync(appName, config)` — async test with core and renderer on separate threads.
- `GacUIUnitTest_Start_WithResourceAsText(appName, config, resourceText)` — wraps resource compilation via `GacUIUnitTest_LinkGuiMainProxy`, then delegates to `GacUIUnitTest_Start` or `GacUIUnitTest_StartAsync` depending on `config.useChannel`.

## Protocol Stack Construction

### Synchronous Mode (GacUIUnitTest_Start)

`GacUIUnitTest_Start` constructs the protocol stack in-process. When `useChannel == UnitTestRemoteChannel::Sync`, it creates a local Parser2 JSON channel server and two local channel clients:

**Renderer side:**
- `UnitTestRemoteProtocol` — mock `IGuiRemoteProtocol` implementation and `IGuiRemoteEventProcessor`.
- `GuiRemoteProtocolLocalChannelClient` — local renderer-side JSON channel client.
- `GuiRemoteProtocolRendererChannel` — bridges the renderer client's `IJsonChannel` to `UnitTestRemoteProtocol`.

**Core side:**
- `GuiRemoteProtocolLocalChannelClient` — local core-side JSON channel client.
- `GuiRemoteProtocolCoreChannel` — implements `IGuiRemoteProtocol` over the core client's `IJsonChannel`, using the unit-test executable path and `UnitTestRemoteProtocol` event processor.

**Protocol filter layers on core side:**
- `GuiRemoteProtocolFilterVerifier` — validates repeat-filtering invariants.
- `GuiRemoteProtocolFilter` — filters redundant messages.
- `GuiRemoteProtocolDomDiffConverter` — (optional, when `useDomDiff` is true) converts full DOM to DOM diffs.

When `useChannel == UnitTestRemoteChannel::None`, the verifier directly wraps `UnitTestRemoteProtocol`'s `IGuiRemoteProtocol`, bypassing the channel layer for speed. When `useChannel == UnitTestRemoteChannel::Sync`, the channel path tests round-trip JSON package fidelity through the local channel server.

Finally, `SetupRemoteNativeController(protocol)` creates the runtime stack: `GuiRemoteController` → `GuiHostedController` → resource managers, then calls `GuiApplicationMain()` → `GuiMain()` → the registered test proxy.

### Async Mode (GacUIUnitTest_StartAsync)

`GacUIUnitTest_StartAsync` uses the same local channel server/client setup, but wraps the core client's protocol channel in `GuiRemoteProtocolAsyncJsonChannel`. The renderer still uses `GuiRemoteProtocolRendererChannel`; the core thread creates `GuiRemoteProtocolCoreChannel` over the async JSON channel and then runs the normal verifier/filter/DOM-diff stack. `GuiRemoteProtocolAsyncJsonChannel` queues channel events and responses, exposes its `IGuiRemoteEventProcessor`, and uses connection counters to keep pending request groups consistent across disconnects.

## UnitTestRemoteProtocol Class Hierarchy

`UnitTestRemoteProtocol` inherits from `UnitTestRemoteProtocolFeatures` and `IGuiRemoteEventProcessor`. Its base classes are:
- `UnitTestRemoteProtocolBase` — holds `IGuiRemoteProtocolEvents*`, `UnitTestScreenConfig`, default `Impl_*` stubs, `Initialize`, `GetExecutablePath`.
- `UnitTestRemoteProtocol_MainWindow` — simulates window management (bounds, sizing config, styles).
- `UnitTestRemoteProtocol_IO` — simulates controller connection and IO event forwarding.
- `UnitTestRemoteProtocol_Rendering` — simulates element creation/destruction, rendering begin/end, DOM capture.
- `UnitTestRemoteProtocol_IOCommands` — provides high-level input simulation methods.
- `UnitTestRemoteProtocolFeatures` — combines all feature bases and implements `LogRenderingResult()`, the core frame capture mechanism.

`UnitTestRemoteProtocol` adds:
- `processRemoteEvents` — a list of `(Nullable<WString>, Func<void()>)` pairs representing named frame callbacks.
- `OnNextIdleFrame(callback)` and `OnNextIdleFrame(name, callback)` — register callbacks to execute after specific rendering frames settle.
- `ProcessRemoteEvents()` — the main event processing method called each cycle.

## The Main Event Loop

`GuiRemoteController::RunOneCycle()` drives the core side per cycle:
1. `remoteProtocol->GetRemoteEventProcessor()->ProcessRemoteEvents()` — this invokes `UnitTestRemoteProtocol::ProcessRemoteEvents()`.
2. `remoteMessages.Submit(disconnected)` — flushes pending protocol messages.
3. `callbackService.InvokeGlobalTimer()` — triggers `GuiHostedController::GlobalTimer()`, which performs layout and rendering, generating new DOM/element data.
4. `asyncService.ExecuteAsyncTasks()`.

The cycle repeats until `connectionStopped` is true (application closes).

## Frame Capture and Snapshot Mechanism

### Passive Rendering Capture (UnitTestRemoteProtocol_Rendering)

Each time the GacUI core calls `RequestRendererBeginRendering`, `UnitTestRemoteProtocol_Rendering::Impl_RendererBeginRendering` creates a new `UnitTestLoggedFrame` and records the `frameId` from the core's monotonic counter. When `Impl_RendererEndRendering` is called, the frame's `renderingDom` is captured from the rendering commands.

### Active Frame Logging (LogRenderingResult)

`LogRenderingResult()` is called at the start of each `ProcessRemoteEvents()` cycle in `UnitTestRemoteProtocolFeatures`:
1. `TryGetLastRenderingFrameAndReset()` checks if a rendering frame completed since the last check. If yes, it becomes the `candidateFrame`.
2. On the next cycle where no new rendering occurred (the UI has settled), the `candidateFrame` is committed to `loggedTrace.frames` with a copy of `lastElementDescs` and `sizingConfig`. This ensures the snapshot captures the final stable rendering state after all layout passes.
3. `LogRenderingResult()` returns `true`, signaling that a frame is ready.
4. In `ProcessRemoteEvents()`, the `frameName` is set on the just-committed frame (from the callback's registered name), and then the callback function executes.
5. If 100 consecutive cycles pass without any rendering change after previously rendering, the test fails with an error.

### Frame Name Semantics

The frame name is assigned to the **already-committed snapshot** (the rendering result), then the callback executes. This means:
- The `frameName` describes what **led to** the current visual state, not what the callback will do next.
- The first frame is conventionally named `"Ready"`, representing the initial rendering state after the window opens.
- Subsequent frame names describe the action taken in the previous callback (e.g., `"Hover"` means the previous callback moved the mouse, and this snapshot shows the hover state).
- Register all `OnNextIdleFrame` callbacks flatly in the test proxy; do not register a new idle frame from inside another idle-frame callback.
- A frame callback should usually perform an action that can trigger a rendering update, such as typing, clicking, changing selection, showing/closing a dialog, or hiding the window. Avoid standalone verification-only frames, because the framework expects progress toward a new settled rendering frame.
- Re-find windows and controls inside each frame callback instead of caching GUI control pointers across frames.

The sequence is:
1. Application renders → DOM/elements captured as `candidateFrame`.
2. UI settles (no more rendering) → `candidateFrame` committed to `loggedTrace.frames`.
3. `frameName` is set on the committed frame from the callback registration.
4. Frame callback executes (may trigger events → triggering more rendering).
5. Return to step 1.

### Snapshot File Structure

For a test named `Controls/Basic/GuiButton/ClickOnMouseUp`:
- **Main trace file**: `{snapshotFolder}/Controls/Basic/GuiButton/ClickOnMouseUp.json` — `UnitTest_RenderingTrace` with `createdElements`, `imageCreations`, `imageMetadatas`, and `frames` (each frame only has `frameId` and `frameName`, detail stripped).
- **Per-frame files**: `{snapshotFolder}/Controls/Basic/GuiButton/ClickOnMouseUp/frame_0.json`, `frame_1.json`, etc. — full `UnitTest_RenderingFrame` with `frameId`, `frameName`, `windowSize`, `elements` (all rendered element descriptions keyed by ID), and `root` (the rendering DOM tree).
- **Workflow snapshot**: `ClickOnMouseUp[x64].txt` or `[x86].txt` — compiled Workflow script text for generation stability verification.
- **Commands log**: `ClickOnMouseUp[commands].txt` — rendering command logs per frame (when not using DOM diff).
- **Diffs log**: `ClickOnMouseUp[diffs].txt` — DOM diff logs per frame.

### Write Strategy (GacUIUnitTest_LogUI)

`GacUIUnitTest_LogUI` performs the following:
1. Serializes the full `loggedTrace` to JSON and verifies round-trip fidelity (serialize → deserialize → re-serialize must match).
2. Writes each frame as an individual `frame_N.json` file via `GacUIUnitTest_WriteSnapshotFileIfChanged`, which only writes if content differs from the existing file (enabling clean `git diff`).
3. Strips frame detail from the in-memory trace (keeping only `frameId` and `frameName`) and writes the main trace file.
4. Deletes any leftover `frame_*.json` files that no longer correspond to actual frames.

## User Interaction Simulation

### UnitTestRemoteProtocol_IOCommands

`UnitTestRemoteProtocol_IOCommands` provides methods to simulate all user input. It maintains virtual state:
- `mousePosition` — current mouse position (`Nullable<NativePoint>`, initially unset; first `MouseMove` triggers `OnIOMouseEntered`).
- `pressingKeys` — `SortedList<VKEY>` of currently pressed keys.
- `leftPressing`, `middlePressing`, `rightPressing` — mouse button states.
- `capslockToggled` — capslock toggle state.

Each method constructs the appropriate `NativeWindowMouseInfo`, `NativeWindowKeyInfo`, or `NativeWindowCharInfo` struct (via `MakeMouseInfo()`, `MakeKeyInfo()`, `MakeCharInfo()`) and calls the corresponding event on `IGuiRemoteProtocolEvents` (obtained via `UseEvents()`).

### Location Calculation (LocationOf)

`LocationOf(controlOrComposition, ratioX, ratioY, offsetX, offsetY)` computes the target point in absolute screen coordinates:
1. Gets the composition's `GetGlobalBounds()` (logical coordinates).
2. Converts to native coordinates using `nativeWindow->Convert()`.
3. Applies the ratio (default 0.5 = center) and offset.
4. Adds the window's screen position `nativeWindow->GetBounds().LeftTop()`.

Overloads accept either `GuiGraphicsComposition*` or `GuiControl*`.

### Mouse Input Methods

- `MouseMove(location)` — sends `OnIOMouseMoving`. If mouse was previously unset, first sends `OnIOMouseEntered`.
- `_LDown(location)` / `_LUp(location)` — low-level left button down/up. `_LDown` calls `MouseMove` if position changed, then `UseEvents().OnIOButtonDown({Left, ...})`.
- `LClick(location)` — `_LDown` followed by `_LUp`.
- `LDBClick(location)` — sends a normal down/up pair, an explicit `OnIOButtonDoubleClick`, and the final up event.
- Analogous `RClick`, `MClick`, `RDBClick`, `MDBClick` for right and middle buttons.
- `WheelUp(jumps)` / `WheelDown(jumps)` — `OnIOVWheel` with delta scaled by 120 per jump.
- `HWheelLeft(jumps)` / `HWheelRight(jumps)` — `OnIOHWheel` similarly.

### Key and Character Input Methods

- `_KeyDown(key)` / `_KeyUp(key)` — sends `OnIOKeyDown` / `OnIOKeyUp`. Tracks `pressingKeys` state. Special handling for `VKEY::KEY_CAPITAL` toggles `capslockToggled`.
- `_KeyDownRepeat(key)` — sends an auto-repeat `OnIOKeyDown` for a key that is already pressed.
- `KeyPress(key)` — `_KeyDown` followed by `_KeyUp`.
- `KeyPress(key, ctrl, shift, alt)` — wraps the key press with modifier key down/up events.
- `TypeString(text)` — sends a sequence of `OnIOChar` events for each character via `MakeCharInfo`, without synthesizing key down/up events.

Keyboard helpers target the currently focused control. Focus the target control in the frame (for example with `SetFocused()` or a click) before using `TypeString`, `KeyPress`, `_KeyDown`, or `_KeyUp`.

### Event Flow Through the Pipeline

When a test calls `protocol->LClick(location)`:
1. `_LDown(location)` → `MouseMove(location)` if position changed → `UseEvents().OnIOButtonDown({Left, MakeMouseInfo()})`.
2. `_LUp(location)` → `UseEvents().OnIOButtonUp({Left, MakeMouseInfo()})`.
3. These events reach `GuiRemoteEvents` (the `IGuiRemoteProtocolEvents` implementation in `GuiRemoteController`).
4. `GuiRemoteEvents::OnIOButtonDown()` dispatches to `listener->LeftButtonDown(info)` on `remoteWindow`.
5. `GuiGraphicsHost` receives the call as `INativeWindowListener`.
6. `GuiGraphicsHost::LeftButtonDown()` calls `OnMouseInput()`, which converts native coordinates to logical coordinates, performs hit-testing via `FindVisibleComposition()`, creates `GuiMouseEventArgs`, and dispatches via `RaiseMouseEvent()`.
7. The event bubbles through the composition tree, triggering control-level handlers.

All of this happens **synchronously within a single `ProcessRemoteEvents()` call**, so by the time the frame callback returns, all side effects of the interaction have been processed.

### Blocking Function Caveat

If an IO action triggers a blocking function (like `ShowDialog`), the frame callback would never return. The unit test framework detects this via the `frameExecuting` flag and fails. The solution is to wrap the IO action in `GetApplication()->InvokeInMainThread(window, [=]() { protocol->LClick(location); })`, which defers the action to the next event loop iteration.

## Running Multiple Test Applications

Each call to `GacUIUnitTest_Start` runs one complete application lifecycle (from `GuiMain()` to window close). Typically each `TEST_CASE` calls the full `GacUIUnitTest_SetGuiMainProxy` → `GacUIUnitTest_Start` sequence independently.

Within a single application instance, multiple windows can be created inside frame callbacks using `Value::Create(L"namespace::ClassName")` and shown with `Show()`, `ShowWithOwner()`, or `ShowModal()`. `ShowDialog()` belongs to dialog component classes such as `GuiOpenFileDialog`, not to `GuiWindow`.

`GacUIUnitTest_LinkGuiMainProxy` can be called multiple times before `GacUIUnitTest_Start`, chaining multiple setup layers (e.g., one for resources, one for theme, one for additional initialization).
