# KStars Cline Rules

## General Behavior
- Always use `read_file` to read source files before editing. Never assume file contents.
- Always read the relevant header file before editing a `.cpp` file.
- Prefer small, focused changes. Do not refactor unrelated code while fixing a bug or adding a feature.

## Build System
- Configure: `cmake -S . -B build -G Ninja`
- Build: `cmake --build build -j$(nproc)`
- Build directory is `build/` — never modify files inside it directly.
- Check the relevant `CMakeLists.txt` before adding new source files.

## Writing and Running Tests

### Test Infrastructure
- All tests live under `Tests/`. The build directory is `build/Tests/`.
- Tests link against `${TEST_LIBRARIES}` which includes `Qt6::Core`, `Qt6::Test`, `KStarsLib`, `htmesh`, and `ZLIB`.
- `KStarsLib` transitively provides Qt6 Widgets; no need to add it explicitly unless using `QTEST_MAIN`.
- Build a single test target: `cmake --build build --target <testname> -- -j$(nproc)`
- Run a test directly: `./build/Tests/auxiliary/<testname>`
- Do not run any tests automatically. User will run all tests manually.


### KStarsData Requirement
- `KSPlanetBase::updateCoords()` requires `KStarsData::Instance()` to be non-null.  
  It calls `kd->skyComposite()->earth()` for planet/sun/moon position updates.  
  Without it, `updateCoords` returns early and all solar-system objects remain at RA=0°/Dec=0°.
- Any test that exercises `KSAlmanac`, planet positions, or anything that calls `KSPlanetBase::updateCoords` **must** initialize `KStarsData` first.

### How to Initialize KStarsData in a Test
1. **Use `QTEST_MAIN` (not `QTEST_GUILESS_MAIN`)** — `KStarsData::initialize()` creates `ObservingList` (a `QDialog`), which requires a `QApplication`.  
   `QTEST_MAIN` creates `QApplication`; `QTEST_GUILESS_MAIN` only creates `QCoreApplication` and will crash.
2. **Include `"../testhelpers.h"`** — the static `EarlyEnvironment` object it defines calls `setupTestEnvironment()` at startup, which sets `QT_QPA_PLATFORM=offscreen` when no display is present, so GUI objects work headlessly.
3. **Call `KTEST_BEGIN()` in `initTestCase()`** — this:
   - Sets the application name to `"kstars"` (required for `KSPaths::locate` to find data files in `/usr/share/kstars/` or the test data dir)
   - Enables `QStandardPaths` test mode
   - Symlinks/copies all data files from `kstars/data/` (VSOP87, `citydb.sqlite`, `TZrules.dat`, star catalogs, etc.) into the test-mode standard paths
4. **Call `KStarsData::Create()->initialize()` after `KTEST_BEGIN()`** — creates the singleton and loads the `SkyMapComposite` (including the Earth object needed by `updateCoords`).
5. **Call `KTEST_END()` in `cleanupTestCase()`** — cleans up test-mode directories.

### Minimal initTestCase/cleanupTestCase Template
```cpp
#include "../testhelpers.h"
#include "kstarsdata.h"
// Use QTEST_MAIN (not QTEST_GUILESS_MAIN) at the bottom of the .cpp file.

void TestFoo::initTestCase()
{
    KTEST_BEGIN();
    KStarsData *data = KStarsData::Create();
    QVERIFY2(data != nullptr, "KStarsData::Create() returned null");
    QVERIFY2(data->initialize(), "KStarsData::initialize() failed");
}

void TestFoo::cleanupTestCase()
{
    KTEST_END();
}
```

### What NOT to do
- Do **not** modify `KSPlanetBase::updateCoords`, `KSSun`, `KSMoon`, or other production classes to remove their `KStarsData` dependency just to make a test pass.  
  The proper fix is always to initialize `KStarsData` in the test.
- Do **not** use `QTEST_GUILESS_MAIN` for tests that call any code path involving planet position updates or `KStarsData`-dependent singletons.

## UI / End-to-End Tests (Tests/kstars_ui/)

### Simulate a Real User Session — No Shortcuts

UI tests in `Tests/kstars_ui/` exist to verify behaviour **as seen by an end user running a full Ekos profile**.  
Every test must exercise the complete production code path through the UI, not call internal C++ methods directly to simulate what the UI would do.

**Mandatory rules:**

1. **Start a full profile.**  
   Always call `prepareTestCase()` (or the equivalent helper chain: `startEkosProfile()` → `prepareOpticalTrains()` → `prepareMountModule()` → `prepareFocusModule()` → `prepareCaptureModule()` → …).  
   Never skip profile startup to "save time".

2. **Drive the UI, not the internals.**  
   Use the KTRY_* / QTRY_* macros (`KTRY_CLICK`, `KTRY_SET_COMBO`, `KTRY_SET_CHECKBOX`, `KTRY_CAPTURE_ADD_LIGHT`, …) to interact with widgets exactly as a user would.  
   Do **not** call module methods (e.g. `focuser->checkFocus(...)`, `capture->startCapture()`, `filtermanager->setFilterPosition(...)`) directly as a substitute for clicking a button or starting a sequence through the UI.

3. **Use the capture workflow to trigger focus checks.**  
   To test in-sequence HFR, refocus-on-temperature, or filter-restoration behaviour:
   - Enable the relevant checkbox in the Capture module UI (`enforceAutofocusHFR`, `enforceRefocusEveryN`, …).
   - Add a capture sequence via `KTRY_CAPTURE_ADD_LIGHT` / `KTRY_CAPTURE_ADD_FRAME`.
   - Start capture with `KTRY_CLICK(capture, startB)` and wait for the expected state queue.
   - Never call `checkFocus()`, `startFocusing()`, or similar internal slots directly to bypass this flow.

4. **Run autofocus through `executeFocusing()`.**  
   When a baseline HFR or a focuser position is required before capture, call `m_CaptureHelper->executeFocusing()`.  
   Do **not** fabricate a fake baseline by directly setting internal focus variables.

5. **Assert through observable state, not internal state.**  
   Verify results via `filtermanager->getFilterPosition()`, capture state queues (`expectedCaptureStates`, `expectedFocusStates`), or UI widget values — not by reading private member variables of production classes.

6. **Reproduce the exact user-facing bug path.**  
   If a bug was reported as "capture sequence ran with filter X but images came out as filter Y", the test must:
   - Configure the same sequence and settings a user would set.
   - Run the full sequence.
   - Assert the observable output (filter position, saved FITS header, state transitions).  
   A test that reproduces the _symptom_ via an API shortcut (e.g. calling `checkFocus` with a magic HFR value) is **not** an acceptable regression test.

**Examples of shortcuts that are NOT allowed:**
```cpp
// ✗ Wrong: calls internal slot directly, bypasses the capture workflow
focuser->checkFocus(99.0);

// ✗ Wrong: manually repositions the filter wheel instead of letting capture do it
filtermanager->setFilterPosition(redFilterPos, Ekos::FilterManager::CHANGE_POLICY);

// ✓ Correct: start the full capture sequence and let it trigger the HFR check
KTRY_CLICK(capture, startB);
KVERIFY_EMPTY_QUEUE_WITH_TIMEOUT(m_CaptureHelper->expectedCaptureStates, 120000);
```

### Ekos Simulator Integration Tests

When writing end-to-end UI tests that interact with `Ekos::Manager` and require custom device profiles:

- **Profile Initialization:** If `KTRY_OPEN_EKOS()` has already executed, `Ekos::Manager` caches the existing profile list. Inserting new profiles directly into the database (e.g. `userdb()->AddProfile()`) will not update the `profileCombo` UI. Always construct new test profiles through UI-driven helpers (like `TestEkosHelper::setupEkosProfile()`) which automatically trigger `Ekos::Manager::loadProfiles()` upon saving.
- **Immediate Job Execution:** When asserting that a scheduled job begins immediately, explicitly disable situational execution limits in the `.esl` file (`enforceTwilight = false`, `enforceArtificialHorizon = false`, `minAltitude = 0`). Failing to disable these limits will cause the job to pause indefinitely until the simulated `KStarsData::ut()` reaches the valid capture window.
