//+------------------------------------------------------------------+
//|                                            TestHtmlBuilder.mq5   |
//+------------------------------------------------------------------+

#include <PositionDashboard/PositionSnapshot.mqh> // Data structure shared by both the reader and the builder.
#include <PositionDashboard/HtmlBuilder.mqh>      // The class under test; no reader or writer is needed for this script.

//--- ASSERT macro: evaluates a boolean condition and prints PASSED or
//--- FAILED with the descriptive message; using a macro keeps call
//--- sites concise without hiding the test logic in a function
#define ASSERT(cond, msg) \
   if(!(cond)) \
     { PrintFormat("ASSERT FAILED : %s", msg); } \
   else \
     { PrintFormat("ASSERT PASSED : %s", msg); }

//+-------------------------------------------------------------------+
//| Script entry point.                                               |
//| Constructs two hand-built snapshots (one profitable BUY, one      |
//| losing SELL) and one empty array, then asserts that the generated |
//| HTML contains the expected color codes, direction labels, and     |
//| empty-state text without needing any live terminal positions.     |
//+-------------------------------------------------------------------+
void OnStart()
  {
//--- build the profitable BUY snapshot: a long EURUSD position currently
//--- in profit whose row should receive the green background color
   CPositionSnapshot winner;
   winner.ticket        = 1001;
   winner.symbol        = "EURUSD";
   winner.type          = POSITION_TYPE_BUY;
   winner.volume        = 0.10;
   winner.open_price    = 1.10000;
   winner.current_price = 1.10500;
   winner.stop_loss     = 1.09500;
   winner.take_profit   = 1.11000;
   winner.profit        = 50.00;  // Positive: should produce green.
   winner.swap          = -0.50;
   winner.comment       = "test winner";
   winner.open_time     = TimeCurrent();

//--- build the losing SELL snapshot: a short GBPUSD position currently
//--- in loss whose row should receive the red background color
   CPositionSnapshot loser;
   loser.ticket        = 1002;
   loser.symbol        = "GBPUSD";
   loser.type          = POSITION_TYPE_SELL;
   loser.volume        = 0.20;
   loser.open_price    = 1.27000;
   loser.current_price = 1.27300;
   loser.stop_loss     = 1.28000;
   loser.take_profit   = 1.26000;
   loser.profit        = -60.00; // Negative: should produce red.
   loser.swap          = -1.00;
   loser.comment       = "test loser";
   loser.open_time     = TimeCurrent();

//--- pack both snapshots into a local array for Build()
   CPositionSnapshot snapshots[];
   ArrayResize(snapshots, 2);
   snapshots[0] = winner;
   snapshots[1] = loser;

   CHtmlBuilder builder(3, "Test Dashboard"); // Instantiate the builder; 3-second refresh and a generic title are sufficient for testing.
   string html = builder.Build(snapshots, 2, TimeCurrent()); // Generate the HTML page for both positions.

   //--- Assertion 1: pale green tint present.
   //--- ProfitColor() must return "#C8E6C9" for winner.profit = 50.00;
   //--- BuildTableRow() inserts that value as an inline background-color;
   //--- StringFind() returns -1 on failure, so >= 0 confirms presence.
   ASSERT(StringFind(html, "#C8E6C9") >= 0,
          "Pale green tint #C8E6C9 present for profitable BUY row");

   //--- Assertion 2: pale rose tint present.
   //--- ProfitColor() must return "#FFCDD2" for loser.profit = -60.00.
   ASSERT(StringFind(html, "#FFCDD2") >= 0,
          "Pale rose tint #FFCDD2 present for losing SELL row");

   //--- Assertion 3: BUY direction label present.
   //--- TypeString() on POSITION_TYPE_BUY must return "BUY" and
   //--- BuildTableRow() must insert it into a <td> element.
   ASSERT(StringFind(html, "BUY") >= 0,
          "BUY direction label present in generated page");

   //--- Assertion 4: SELL direction label present.
   //--- TypeString() on POSITION_TYPE_SELL must return "SELL".
   ASSERT(StringFind(html, "SELL") >= 0,
          "SELL direction label present in generated page");

   //--- Assertion 5: empty state text when count is zero.
   //--- Build() must take the count == 0 branch and call BuildEmptyState(),
   //--- which inserts the literal text "No open positions."
   CPositionSnapshot empty[];
   ArrayResize(empty, 0);
   string empty_html = builder.Build(empty, 0, TimeCurrent());
   ASSERT(StringFind(empty_html, "No open positions.") >= 0,
          "Empty-state text rendered when snapshot count is zero");

//--- build a snapshot whose comment contains a raw less-than sign;
//--- EscapeHtml() must convert it to &lt; before it reaches the page,
//--- preventing the browser from interpreting it as an HTML tag
   CPositionSnapshot injected;
   injected.ticket        = 1003;
   injected.symbol        = "USDJPY";
   injected.type          = POSITION_TYPE_BUY;
   injected.volume        = 0.10;
   injected.open_price    = 150.000;
   injected.current_price = 150.500;
   injected.stop_loss     = 149.000;
   injected.take_profit   = 152.000;
   injected.profit        = 33.00;
   injected.swap          = -0.10;
   injected.comment       = "<script>alert(1)</script>"; // Injection attempt via the free-text comment field.
   injected.open_time     = TimeCurrent();

   CPositionSnapshot injection_test[];
   ArrayResize(injection_test, 1);
   injection_test[0] = injected;
   string injection_html = builder.Build(injection_test, 1, TimeCurrent());

   //--- Assertion 6: raw <script> tag must NOT appear verbatim in the output.
   ASSERT(StringFind(injection_html, "<script>alert(1)</script>") < 0,
          "Raw <script> tag in comment is not present verbatim in output");

   //--- Assertion 7: escaped form must appear instead, confirming EscapeHtml() ran.
   ASSERT(StringFind(injection_html, "&lt;script&gt;") >= 0,
          "Escaped &lt;script&gt; form IS present, confirming EscapeHtml()");

   //--- Assertion 8: setInterval appears with the correct millisecond interval.
   //--- The builder multiplies m_refresh_seconds (3) by 1000; "3000" must
   //--- appear inside the <script> block of the generated page.
   ASSERT(StringFind(html, "3000") >= 0,
          "setInterval delay of 3000 ms present in page script block");

//--- all assertions have been printed above; check the Experts tab
//--- for the pass/fail lines; no assertion should read FAILED
   Print("TestHtmlBuilder complete. Review the lines above for results.");
  }
//+------------------------------------------------------------------+