@memberjunction/ng-core-entity-forms 2.129.0 → 2.130.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/dist/lib/custom/Tests/entity-link-pill.component.d.ts +44 -0
  2. package/dist/lib/custom/Tests/entity-link-pill.component.d.ts.map +1 -0
  3. package/dist/lib/custom/Tests/entity-link-pill.component.js +124 -0
  4. package/dist/lib/custom/Tests/entity-link-pill.component.js.map +1 -0
  5. package/dist/lib/custom/Tests/test-form.component.d.ts +94 -8
  6. package/dist/lib/custom/Tests/test-form.component.d.ts.map +1 -1
  7. package/dist/lib/custom/Tests/test-form.component.js +1527 -276
  8. package/dist/lib/custom/Tests/test-form.component.js.map +1 -1
  9. package/dist/lib/custom/Tests/test-run-form.component.d.ts +48 -8
  10. package/dist/lib/custom/Tests/test-run-form.component.d.ts.map +1 -1
  11. package/dist/lib/custom/Tests/test-run-form.component.js +1078 -426
  12. package/dist/lib/custom/Tests/test-run-form.component.js.map +1 -1
  13. package/dist/lib/custom/Tests/test-suite-form.component.d.ts +227 -5
  14. package/dist/lib/custom/Tests/test-suite-form.component.d.ts.map +1 -1
  15. package/dist/lib/custom/Tests/test-suite-form.component.js +3307 -200
  16. package/dist/lib/custom/Tests/test-suite-form.component.js.map +1 -1
  17. package/dist/lib/custom/Tests/test-suite-run-form.component.d.ts +86 -2
  18. package/dist/lib/custom/Tests/test-suite-run-form.component.d.ts.map +1 -1
  19. package/dist/lib/custom/Tests/test-suite-run-form.component.js +1975 -262
  20. package/dist/lib/custom/Tests/test-suite-run-form.component.js.map +1 -1
  21. package/dist/lib/custom/ai-agent-run/ai-agent-run.component.d.ts +9 -2
  22. package/dist/lib/custom/ai-agent-run/ai-agent-run.component.d.ts.map +1 -1
  23. package/dist/lib/custom/ai-agent-run/ai-agent-run.component.js +275 -244
  24. package/dist/lib/custom/ai-agent-run/ai-agent-run.component.js.map +1 -1
  25. package/dist/lib/custom/custom-forms.module.d.ts +27 -26
  26. package/dist/lib/custom/custom-forms.module.d.ts.map +1 -1
  27. package/dist/lib/custom/custom-forms.module.js +9 -3
  28. package/dist/lib/custom/custom-forms.module.js.map +1 -1
  29. package/dist/lib/generated/Entities/AIAgent/aiagent.form.component.d.ts.map +1 -1
  30. package/dist/lib/generated/Entities/AIAgent/aiagent.form.component.js +154 -122
  31. package/dist/lib/generated/Entities/AIAgent/aiagent.form.component.js.map +1 -1
  32. package/dist/lib/generated/Entities/AIAgentModality/aiagentmodality.form.component.d.ts +11 -0
  33. package/dist/lib/generated/Entities/AIAgentModality/aiagentmodality.form.component.d.ts.map +1 -0
  34. package/dist/lib/generated/Entities/AIAgentModality/aiagentmodality.form.component.js +75 -0
  35. package/dist/lib/generated/Entities/AIAgentModality/aiagentmodality.form.component.js.map +1 -0
  36. package/dist/lib/generated/Entities/AIArchitecture/aiarchitecture.form.component.d.ts +11 -0
  37. package/dist/lib/generated/Entities/AIArchitecture/aiarchitecture.form.component.d.ts.map +1 -0
  38. package/dist/lib/generated/Entities/AIArchitecture/aiarchitecture.form.component.js +121 -0
  39. package/dist/lib/generated/Entities/AIArchitecture/aiarchitecture.form.component.js.map +1 -0
  40. package/dist/lib/generated/Entities/AIConfiguration/aiconfiguration.form.component.d.ts.map +1 -1
  41. package/dist/lib/generated/Entities/AIConfiguration/aiconfiguration.form.component.js +77 -41
  42. package/dist/lib/generated/Entities/AIConfiguration/aiconfiguration.form.component.js.map +1 -1
  43. package/dist/lib/generated/Entities/AIModality/aimodality.form.component.d.ts +11 -0
  44. package/dist/lib/generated/Entities/AIModality/aimodality.form.component.d.ts.map +1 -0
  45. package/dist/lib/generated/Entities/AIModality/aimodality.form.component.js +167 -0
  46. package/dist/lib/generated/Entities/AIModality/aimodality.form.component.js.map +1 -0
  47. package/dist/lib/generated/Entities/AIModel/aimodel.form.component.d.ts.map +1 -1
  48. package/dist/lib/generated/Entities/AIModel/aimodel.form.component.js +160 -102
  49. package/dist/lib/generated/Entities/AIModel/aimodel.form.component.js.map +1 -1
  50. package/dist/lib/generated/Entities/AIModelArchitecture/aimodelarchitecture.form.component.d.ts +11 -0
  51. package/dist/lib/generated/Entities/AIModelArchitecture/aimodelarchitecture.form.component.d.ts.map +1 -0
  52. package/dist/lib/generated/Entities/AIModelArchitecture/aimodelarchitecture.form.component.js +73 -0
  53. package/dist/lib/generated/Entities/AIModelArchitecture/aimodelarchitecture.form.component.js.map +1 -0
  54. package/dist/lib/generated/Entities/AIModelModality/aimodelmodality.form.component.d.ts +11 -0
  55. package/dist/lib/generated/Entities/AIModelModality/aimodelmodality.form.component.d.ts.map +1 -0
  56. package/dist/lib/generated/Entities/AIModelModality/aimodelmodality.form.component.js +89 -0
  57. package/dist/lib/generated/Entities/AIModelModality/aimodelmodality.form.component.js.map +1 -0
  58. package/dist/lib/generated/Entities/AIModelType/aimodeltype.form.component.d.ts.map +1 -1
  59. package/dist/lib/generated/Entities/AIModelType/aimodeltype.form.component.js +27 -13
  60. package/dist/lib/generated/Entities/AIModelType/aimodeltype.form.component.js.map +1 -1
  61. package/dist/lib/generated/Entities/ConversationDetail/conversationdetail.form.component.d.ts.map +1 -1
  62. package/dist/lib/generated/Entities/ConversationDetail/conversationdetail.form.component.js +39 -21
  63. package/dist/lib/generated/Entities/ConversationDetail/conversationdetail.form.component.js.map +1 -1
  64. package/dist/lib/generated/Entities/ConversationDetailAttachment/conversationdetailattachment.form.component.d.ts +11 -0
  65. package/dist/lib/generated/Entities/ConversationDetailAttachment/conversationdetailattachment.form.component.d.ts.map +1 -0
  66. package/dist/lib/generated/Entities/ConversationDetailAttachment/conversationdetailattachment.form.component.js +95 -0
  67. package/dist/lib/generated/Entities/ConversationDetailAttachment/conversationdetailattachment.form.component.js.map +1 -0
  68. package/dist/lib/generated/Entities/Entity/entity.form.component.d.ts.map +1 -1
  69. package/dist/lib/generated/Entities/Entity/entity.form.component.js +61 -43
  70. package/dist/lib/generated/Entities/Entity/entity.form.component.js.map +1 -1
  71. package/dist/lib/generated/Entities/File/file.form.component.d.ts.map +1 -1
  72. package/dist/lib/generated/Entities/File/file.form.component.js +22 -4
  73. package/dist/lib/generated/Entities/File/file.form.component.js.map +1 -1
  74. package/dist/lib/generated/Entities/FileStorageProvider/filestorageprovider.form.component.d.ts.map +1 -1
  75. package/dist/lib/generated/Entities/FileStorageProvider/filestorageprovider.form.component.js +40 -4
  76. package/dist/lib/generated/Entities/FileStorageProvider/filestorageprovider.form.component.js.map +1 -1
  77. package/dist/lib/generated/Entities/Test/test.form.component.js +17 -15
  78. package/dist/lib/generated/Entities/Test/test.form.component.js.map +1 -1
  79. package/dist/lib/generated/Entities/TestRun/testrun.form.component.d.ts.map +1 -1
  80. package/dist/lib/generated/Entities/TestRun/testrun.form.component.js +55 -43
  81. package/dist/lib/generated/Entities/TestRun/testrun.form.component.js.map +1 -1
  82. package/dist/lib/generated/Entities/TestRunFeedback/testrunfeedback.form.component.d.ts.map +1 -1
  83. package/dist/lib/generated/Entities/TestRunFeedback/testrunfeedback.form.component.js +9 -15
  84. package/dist/lib/generated/Entities/TestRunFeedback/testrunfeedback.form.component.js.map +1 -1
  85. package/dist/lib/generated/Entities/TestSuite/testsuite.form.component.d.ts.map +1 -1
  86. package/dist/lib/generated/Entities/TestSuite/testsuite.form.component.js +39 -19
  87. package/dist/lib/generated/Entities/TestSuite/testsuite.form.component.js.map +1 -1
  88. package/dist/lib/generated/Entities/TestSuiteRun/testsuiterun.form.component.d.ts.map +1 -1
  89. package/dist/lib/generated/Entities/TestSuiteRun/testsuiterun.form.component.js +40 -16
  90. package/dist/lib/generated/Entities/TestSuiteRun/testsuiterun.form.component.js.map +1 -1
  91. package/dist/lib/generated/generated-forms.module.d.ts +145 -134
  92. package/dist/lib/generated/generated-forms.module.d.ts.map +1 -1
  93. package/dist/lib/generated/generated-forms.module.js +168 -87
  94. package/dist/lib/generated/generated-forms.module.js.map +1 -1
  95. package/package.json +28 -27
@@ -4,22 +4,47 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
4
4
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
- import { Component, ChangeDetectionStrategy } from '@angular/core';
7
+ import { Component, ChangeDetectionStrategy, HostListener, ViewChild } from '@angular/core';
8
+ import * as d3 from 'd3';
8
9
  import { Subject } from 'rxjs';
9
- import { CompositeKey, RunView } from '@memberjunction/core';
10
+ import { takeUntil, debounceTime, distinctUntilChanged } from 'rxjs/operators';
11
+ import { CompositeKey, Metadata, RunView } from '@memberjunction/core';
12
+ import { UserInfoEngine } from '@memberjunction/core-entities';
10
13
  import { BaseFormComponent } from '@memberjunction/ng-base-forms';
11
14
  import { RegisterClass } from '@memberjunction/global';
12
15
  import { SharedService } from '@memberjunction/ng-shared';
13
16
  import { TestSuiteFormComponent } from '../../generated/Entities/TestSuite/testsuite.form.component';
17
+ import { TagsHelper } from '@memberjunction/ng-testing';
14
18
  import * as i0 from "@angular/core";
15
19
  import * as i1 from "@memberjunction/ng-shared";
16
20
  import * as i2 from "@angular/router";
17
21
  import * as i3 from "@memberjunction/ng-testing";
18
22
  import * as i4 from "@angular/common";
19
- import * as i5 from "@progress/kendo-angular-dialog";
20
- import * as i6 from "@progress/kendo-angular-buttons";
21
- function TestSuiteFormComponentExtended_div_15_Template(rf, ctx) { if (rf & 1) {
22
- i0.ɵɵelementStart(0, "div", 23)(1, "p");
23
+ import * as i5 from "@angular/forms";
24
+ import * as i6 from "@progress/kendo-angular-dialog";
25
+ import * as i7 from "@progress/kendo-angular-buttons";
26
+ import * as i8 from "@memberjunction/ng-shared-generic";
27
+ const _c0 = ["chartContainer"];
28
+ const _c1 = () => [1, 2, 3, 4, 5];
29
+ const _c2 = () => [1, 2, 3];
30
+ const _c3 = (a0, a1) => ({ "trend-up": a0, "trend-down": a1 });
31
+ const _c4 = (a0, a1, a2) => ({ "fa-arrow-up": a0, "fa-arrow-down": a1, "fa-minus": a2 });
32
+ const _c5 = (a0, a1, a2) => ({ "high": a0, "medium": a1, "low": a2 });
33
+ const _c6 = (a0, a1) => ({ "positive": a0, "negative": a1 });
34
+ const _c7 = (a0, a1, a2) => ({ "fa-arrow-down": a0, "fa-arrow-up": a1, "fa-minus": a2 });
35
+ const _c8 = (a0, a1) => ({ "improved": a0, "regressed": a1 });
36
+ function TestSuiteFormComponentExtended_span_13_Template(rf, ctx) { if (rf & 1) {
37
+ i0.ɵɵelementStart(0, "span", 37);
38
+ i0.ɵɵelement(1, "i", 23);
39
+ i0.ɵɵtext(2);
40
+ i0.ɵɵelementEnd();
41
+ } if (rf & 2) {
42
+ const ctx_r0 = i0.ɵɵnextContext();
43
+ i0.ɵɵadvance(2);
44
+ i0.ɵɵtextInterpolate1(" ", ctx_r0.suiteTests.length, " tests ");
45
+ } }
46
+ function TestSuiteFormComponentExtended_div_25_Template(rf, ctx) { if (rf & 1) {
47
+ i0.ɵɵelementStart(0, "div", 38)(1, "p");
23
48
  i0.ɵɵtext(2);
24
49
  i0.ɵɵelementEnd()();
25
50
  } if (rf & 2) {
@@ -27,8 +52,8 @@ function TestSuiteFormComponentExtended_div_15_Template(rf, ctx) { if (rf & 1) {
27
52
  i0.ɵɵadvance(2);
28
53
  i0.ɵɵtextInterpolate(ctx_r0.record.Description);
29
54
  } }
30
- function TestSuiteFormComponentExtended_span_24_Template(rf, ctx) { if (rf & 1) {
31
- i0.ɵɵelementStart(0, "span", 24);
55
+ function TestSuiteFormComponentExtended_span_34_Template(rf, ctx) { if (rf & 1) {
56
+ i0.ɵɵelementStart(0, "span", 39);
32
57
  i0.ɵɵtext(1);
33
58
  i0.ɵɵelementEnd();
34
59
  } if (rf & 2) {
@@ -36,8 +61,8 @@ function TestSuiteFormComponentExtended_span_24_Template(rf, ctx) { if (rf & 1)
36
61
  i0.ɵɵadvance();
37
62
  i0.ɵɵtextInterpolate(ctx_r0.suiteTests.length);
38
63
  } }
39
- function TestSuiteFormComponentExtended_span_28_Template(rf, ctx) { if (rf & 1) {
40
- i0.ɵɵelementStart(0, "span", 24);
64
+ function TestSuiteFormComponentExtended_span_38_Template(rf, ctx) { if (rf & 1) {
65
+ i0.ɵɵelementStart(0, "span", 39);
41
66
  i0.ɵɵtext(1);
42
67
  i0.ɵɵelementEnd();
43
68
  } if (rf & 2) {
@@ -45,229 +70,1813 @@ function TestSuiteFormComponentExtended_span_28_Template(rf, ctx) { if (rf & 1)
45
70
  i0.ɵɵadvance();
46
71
  i0.ɵɵtextInterpolate(ctx_r0.suiteRuns.length);
47
72
  } }
48
- function TestSuiteFormComponentExtended_div_30_Template(rf, ctx) { if (rf & 1) {
49
- i0.ɵɵelementStart(0, "div", 25)(1, "div", 26)(2, "h3");
50
- i0.ɵɵtext(3, "Suite Information");
73
+ function TestSuiteFormComponentExtended_div_46_Template(rf, ctx) { if (rf & 1) {
74
+ const _r2 = i0.ɵɵgetCurrentView();
75
+ i0.ɵɵelementStart(0, "div", 40)(1, "div", 41)(2, "h3");
76
+ i0.ɵɵelement(3, "i", 42);
77
+ i0.ɵɵtext(4, " Suite Information");
78
+ i0.ɵɵelementEnd();
79
+ i0.ɵɵelementStart(5, "div", 43)(6, "div", 44)(7, "div", 45);
80
+ i0.ɵɵtext(8, "Name");
81
+ i0.ɵɵelementEnd();
82
+ i0.ɵɵelementStart(9, "div", 46);
83
+ i0.ɵɵtext(10);
84
+ i0.ɵɵelementEnd()();
85
+ i0.ɵɵelementStart(11, "div", 44)(12, "div", 45);
86
+ i0.ɵɵtext(13, "Status");
87
+ i0.ɵɵelementEnd();
88
+ i0.ɵɵelementStart(14, "div", 46)(15, "span", 47);
89
+ i0.ɵɵtext(16);
90
+ i0.ɵɵelementEnd()()();
91
+ i0.ɵɵelementStart(17, "div", 44)(18, "div", 45);
92
+ i0.ɵɵtext(19, "Created");
93
+ i0.ɵɵelementEnd();
94
+ i0.ɵɵelementStart(20, "div", 46);
95
+ i0.ɵɵtext(21);
96
+ i0.ɵɵpipe(22, "date");
97
+ i0.ɵɵelementEnd()();
98
+ i0.ɵɵelementStart(23, "div", 44)(24, "div", 45);
99
+ i0.ɵɵtext(25, "Updated");
100
+ i0.ɵɵelementEnd();
101
+ i0.ɵɵelementStart(26, "div", 46);
102
+ i0.ɵɵtext(27);
103
+ i0.ɵɵpipe(28, "date");
104
+ i0.ɵɵelementEnd()();
105
+ i0.ɵɵelementStart(29, "div", 44)(30, "div", 45);
106
+ i0.ɵɵtext(31, "Max Execution Time");
107
+ i0.ɵɵelementEnd();
108
+ i0.ɵɵelementStart(32, "div", 46);
109
+ i0.ɵɵtext(33);
110
+ i0.ɵɵelementEnd()()()();
111
+ i0.ɵɵelementStart(34, "div", 48)(35, "h3");
112
+ i0.ɵɵelement(36, "i", 49);
113
+ i0.ɵɵtext(37, " Execution Settings");
114
+ i0.ɵɵelementEnd();
115
+ i0.ɵɵelementStart(38, "div", 50)(39, "div", 51)(40, "label");
116
+ i0.ɵɵtext(41, "Max Execution Time (ms)");
117
+ i0.ɵɵelementEnd();
118
+ i0.ɵɵelementStart(42, "input", 52);
119
+ i0.ɵɵtwoWayListener("ngModelChange", function TestSuiteFormComponentExtended_div_46_Template_input_ngModelChange_42_listener($event) { i0.ɵɵrestoreView(_r2); const ctx_r0 = i0.ɵɵnextContext(); i0.ɵɵtwoWayBindingSet(ctx_r0.record.MaxExecutionTimeMS, $event) || (ctx_r0.record.MaxExecutionTimeMS = $event); return i0.ɵɵresetView($event); });
120
+ i0.ɵɵelementEnd();
121
+ i0.ɵɵelementStart(43, "span", 53);
122
+ i0.ɵɵtext(44, "Default timeout for tests in this suite");
123
+ i0.ɵɵelementEnd()()()()();
124
+ } if (rf & 2) {
125
+ const ctx_r0 = i0.ɵɵnextContext();
126
+ i0.ɵɵadvance(10);
127
+ i0.ɵɵtextInterpolate(ctx_r0.record.Name);
128
+ i0.ɵɵadvance(5);
129
+ i0.ɵɵproperty("ngClass", ctx_r0.getStatusClass());
130
+ i0.ɵɵadvance();
131
+ i0.ɵɵtextInterpolate(ctx_r0.record.Status);
132
+ i0.ɵɵadvance(5);
133
+ i0.ɵɵtextInterpolate(i0.ɵɵpipeBind2(22, 7, ctx_r0.record.__mj_CreatedAt, "medium"));
134
+ i0.ɵɵadvance(6);
135
+ i0.ɵɵtextInterpolate(i0.ɵɵpipeBind2(28, 10, ctx_r0.record.__mj_UpdatedAt, "medium"));
136
+ i0.ɵɵadvance(6);
137
+ i0.ɵɵtextInterpolate(ctx_r0.formatTimeout(ctx_r0.record.MaxExecutionTimeMS));
138
+ i0.ɵɵadvance(9);
139
+ i0.ɵɵtwoWayProperty("ngModel", ctx_r0.record.MaxExecutionTimeMS);
140
+ } }
141
+ function TestSuiteFormComponentExtended_div_47_div_1_div_2_Template(rf, ctx) { if (rf & 1) {
142
+ i0.ɵɵelementStart(0, "div", 61);
143
+ i0.ɵɵelement(1, "div", 62)(2, "div", 63);
144
+ i0.ɵɵelementStart(3, "div", 64);
145
+ i0.ɵɵelement(4, "div", 65)(5, "div", 66);
146
+ i0.ɵɵelementEnd()();
147
+ } }
148
+ function TestSuiteFormComponentExtended_div_47_div_1_Template(rf, ctx) { if (rf & 1) {
149
+ i0.ɵɵelementStart(0, "div", 58)(1, "div", 59);
150
+ i0.ɵɵtemplate(2, TestSuiteFormComponentExtended_div_47_div_1_div_2_Template, 6, 0, "div", 60);
151
+ i0.ɵɵelementEnd()();
152
+ } if (rf & 2) {
153
+ i0.ɵɵadvance(2);
154
+ i0.ɵɵproperty("ngForOf", i0.ɵɵpureFunction0(1, _c1));
155
+ } }
156
+ function TestSuiteFormComponentExtended_div_47_div_2_div_1_span_9_Template(rf, ctx) { if (rf & 1) {
157
+ i0.ɵɵelementStart(0, "span");
158
+ i0.ɵɵelement(1, "i", 42);
159
+ i0.ɵɵtext(2);
160
+ i0.ɵɵelementEnd();
161
+ } if (rf & 2) {
162
+ const test_r4 = i0.ɵɵnextContext().$implicit;
163
+ i0.ɵɵadvance(2);
164
+ i0.ɵɵtextInterpolate1(" ", test_r4.Status, "");
165
+ } }
166
+ function TestSuiteFormComponentExtended_div_47_div_2_div_1_Template(rf, ctx) { if (rf & 1) {
167
+ const _r3 = i0.ɵɵgetCurrentView();
168
+ i0.ɵɵelementStart(0, "div", 69);
169
+ i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_div_47_div_2_div_1_Template_div_click_0_listener() { const test_r4 = i0.ɵɵrestoreView(_r3).$implicit; const ctx_r0 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r0.openTest(test_r4.TestID)); });
170
+ i0.ɵɵelementStart(1, "div", 70);
171
+ i0.ɵɵtext(2);
172
+ i0.ɵɵelementEnd();
173
+ i0.ɵɵelementStart(3, "div", 71);
174
+ i0.ɵɵelement(4, "i", 23);
175
+ i0.ɵɵelementEnd();
176
+ i0.ɵɵelementStart(5, "div", 72)(6, "div", 73);
177
+ i0.ɵɵtext(7);
178
+ i0.ɵɵelementEnd();
179
+ i0.ɵɵelementStart(8, "div", 74);
180
+ i0.ɵɵtemplate(9, TestSuiteFormComponentExtended_div_47_div_2_div_1_span_9_Template, 3, 1, "span", 75);
181
+ i0.ɵɵelementEnd()();
182
+ i0.ɵɵelement(10, "i", 76);
183
+ i0.ɵɵelementEnd();
184
+ } if (rf & 2) {
185
+ const test_r4 = ctx.$implicit;
186
+ i0.ɵɵadvance(2);
187
+ i0.ɵɵtextInterpolate(test_r4.Sequence);
188
+ i0.ɵɵadvance(5);
189
+ i0.ɵɵtextInterpolate(test_r4.Test);
190
+ i0.ɵɵadvance(2);
191
+ i0.ɵɵproperty("ngIf", test_r4.Status);
192
+ } }
193
+ function TestSuiteFormComponentExtended_div_47_div_2_Template(rf, ctx) { if (rf & 1) {
194
+ i0.ɵɵelementStart(0, "div", 67);
195
+ i0.ɵɵtemplate(1, TestSuiteFormComponentExtended_div_47_div_2_div_1_Template, 11, 3, "div", 68);
196
+ i0.ɵɵelementEnd();
197
+ } if (rf & 2) {
198
+ const ctx_r0 = i0.ɵɵnextContext(2);
199
+ i0.ɵɵadvance();
200
+ i0.ɵɵproperty("ngForOf", ctx_r0.suiteTests);
201
+ } }
202
+ function TestSuiteFormComponentExtended_div_47_div_3_Template(rf, ctx) { if (rf & 1) {
203
+ i0.ɵɵelementStart(0, "div", 77)(1, "div", 78);
204
+ i0.ɵɵelement(2, "i", 23);
205
+ i0.ɵɵelementEnd();
206
+ i0.ɵɵelementStart(3, "h4");
207
+ i0.ɵɵtext(4, "No Tests in Suite");
208
+ i0.ɵɵelementEnd();
209
+ i0.ɵɵelementStart(5, "p");
210
+ i0.ɵɵtext(6, "Add tests to this suite to start running them together.");
211
+ i0.ɵɵelementEnd()();
212
+ } }
213
+ function TestSuiteFormComponentExtended_div_47_Template(rf, ctx) { if (rf & 1) {
214
+ i0.ɵɵelementStart(0, "div", 54);
215
+ i0.ɵɵtemplate(1, TestSuiteFormComponentExtended_div_47_div_1_Template, 3, 2, "div", 55)(2, TestSuiteFormComponentExtended_div_47_div_2_Template, 2, 1, "div", 56)(3, TestSuiteFormComponentExtended_div_47_div_3_Template, 7, 0, "div", 57);
216
+ i0.ɵɵelementEnd();
217
+ } if (rf & 2) {
218
+ const ctx_r0 = i0.ɵɵnextContext();
219
+ i0.ɵɵadvance();
220
+ i0.ɵɵproperty("ngIf", ctx_r0.loadingTests);
221
+ i0.ɵɵadvance();
222
+ i0.ɵɵproperty("ngIf", !ctx_r0.loadingTests && ctx_r0.suiteTests.length > 0);
223
+ i0.ɵɵadvance();
224
+ i0.ɵɵproperty("ngIf", ctx_r0.testsLoaded && !ctx_r0.loadingTests && ctx_r0.suiteTests.length === 0);
225
+ } }
226
+ function TestSuiteFormComponentExtended_div_48_div_1_div_2_Template(rf, ctx) { if (rf & 1) {
227
+ i0.ɵɵelementStart(0, "div", 61);
228
+ i0.ɵɵelement(1, "div", 63);
229
+ i0.ɵɵelementStart(2, "div", 64);
230
+ i0.ɵɵelement(3, "div", 65)(4, "div", 66);
231
+ i0.ɵɵelementEnd()();
232
+ } }
233
+ function TestSuiteFormComponentExtended_div_48_div_1_Template(rf, ctx) { if (rf & 1) {
234
+ i0.ɵɵelementStart(0, "div", 58)(1, "div", 59);
235
+ i0.ɵɵtemplate(2, TestSuiteFormComponentExtended_div_48_div_1_div_2_Template, 5, 0, "div", 60);
236
+ i0.ɵɵelementEnd()();
237
+ } if (rf & 2) {
238
+ i0.ɵɵadvance(2);
239
+ i0.ɵɵproperty("ngForOf", i0.ɵɵpureFunction0(1, _c2));
240
+ } }
241
+ function TestSuiteFormComponentExtended_div_48_div_2_div_1_span_13_Template(rf, ctx) { if (rf & 1) {
242
+ i0.ɵɵelementStart(0, "span");
243
+ i0.ɵɵelement(1, "i", 97);
244
+ i0.ɵɵtext(2);
245
+ i0.ɵɵelementEnd();
246
+ } if (rf & 2) {
247
+ const run_r6 = i0.ɵɵnextContext().$implicit;
248
+ const ctx_r0 = i0.ɵɵnextContext(3);
249
+ i0.ɵɵadvance(2);
250
+ i0.ɵɵtextInterpolate3(" ", run_r6.PassedTests, "/", run_r6.TotalTests, " (", ctx_r0.getPassRate(run_r6).toFixed(0), "%) ");
251
+ } }
252
+ function TestSuiteFormComponentExtended_div_48_div_2_div_1_span_15_Template(rf, ctx) { if (rf & 1) {
253
+ i0.ɵɵelementStart(0, "span", 98);
254
+ i0.ɵɵelement(1, "i", 85);
255
+ i0.ɵɵelementEnd();
256
+ } if (rf & 2) {
257
+ const run_r6 = i0.ɵɵnextContext().$implicit;
258
+ i0.ɵɵclassMap("status-" + run_r6.Status.toLowerCase());
259
+ i0.ɵɵadvance();
260
+ i0.ɵɵclassProp("fa-circle-check", run_r6.Status === "Completed")("fa-circle-xmark", run_r6.Status === "Failed")("fa-spinner", run_r6.Status === "Running")("fa-clock", run_r6.Status === "Pending")("fa-ban", run_r6.Status === "Cancelled");
261
+ } }
262
+ function TestSuiteFormComponentExtended_div_48_div_2_div_1_span_16_Template(rf, ctx) { if (rf & 1) {
263
+ i0.ɵɵelementStart(0, "span", 99);
264
+ i0.ɵɵelement(1, "i", 100);
265
+ i0.ɵɵelementStart(2, "span", 101);
266
+ i0.ɵɵelement(3, "i", 102);
267
+ i0.ɵɵelementEnd()();
268
+ } }
269
+ function TestSuiteFormComponentExtended_div_48_div_2_div_1_span_17_Template(rf, ctx) { if (rf & 1) {
270
+ i0.ɵɵelementStart(0, "span", 103);
271
+ i0.ɵɵelement(1, "i", 104);
272
+ i0.ɵɵelementStart(2, "span");
273
+ i0.ɵɵtext(3);
274
+ i0.ɵɵelementEnd()();
275
+ } if (rf & 2) {
276
+ const run_r6 = i0.ɵɵnextContext().$implicit;
277
+ const ctx_r0 = i0.ɵɵnextContext(3);
278
+ i0.ɵɵclassProp("high", ctx_r0.getPassRate(run_r6) >= 80)("medium", ctx_r0.getPassRate(run_r6) >= 50 && ctx_r0.getPassRate(run_r6) < 80)("low", ctx_r0.getPassRate(run_r6) < 50);
279
+ i0.ɵɵadvance(3);
280
+ i0.ɵɵtextInterpolate1("", ctx_r0.getPassRate(run_r6).toFixed(0), "%");
281
+ } }
282
+ function TestSuiteFormComponentExtended_div_48_div_2_div_1_div_18_span_1_Template(rf, ctx) { if (rf & 1) {
283
+ i0.ɵɵelementStart(0, "span", 107);
284
+ i0.ɵɵtext(1);
285
+ i0.ɵɵelementEnd();
286
+ } if (rf & 2) {
287
+ const tag_r7 = ctx.$implicit;
288
+ i0.ɵɵadvance();
289
+ i0.ɵɵtextInterpolate(tag_r7);
290
+ } }
291
+ function TestSuiteFormComponentExtended_div_48_div_2_div_1_div_18_Template(rf, ctx) { if (rf & 1) {
292
+ i0.ɵɵelementStart(0, "div", 105);
293
+ i0.ɵɵtemplate(1, TestSuiteFormComponentExtended_div_48_div_2_div_1_div_18_span_1_Template, 2, 1, "span", 106);
294
+ i0.ɵɵelementEnd();
295
+ } if (rf & 2) {
296
+ const run_r6 = i0.ɵɵnextContext().$implicit;
297
+ const ctx_r0 = i0.ɵɵnextContext(3);
298
+ i0.ɵɵadvance();
299
+ i0.ɵɵproperty("ngForOf", ctx_r0.getRunTags(run_r6));
300
+ } }
301
+ function TestSuiteFormComponentExtended_div_48_div_2_div_1_Template(rf, ctx) { if (rf & 1) {
302
+ const _r5 = i0.ɵɵgetCurrentView();
303
+ i0.ɵɵelementStart(0, "div", 83);
304
+ i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_div_48_div_2_div_1_Template_div_click_0_listener() { const run_r6 = i0.ɵɵrestoreView(_r5).$implicit; const ctx_r0 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r0.openSuiteRun(run_r6.ID)); });
305
+ i0.ɵɵelementStart(1, "div", 84);
306
+ i0.ɵɵelement(2, "i", 85);
307
+ i0.ɵɵelementEnd();
308
+ i0.ɵɵelementStart(3, "div", 86)(4, "div", 87)(5, "span", 88);
309
+ i0.ɵɵtext(6);
310
+ i0.ɵɵelementEnd();
311
+ i0.ɵɵelementStart(7, "span", 89);
312
+ i0.ɵɵtext(8);
313
+ i0.ɵɵelementEnd()();
314
+ i0.ɵɵelementStart(9, "div", 90)(10, "span");
315
+ i0.ɵɵelement(11, "i", 91);
316
+ i0.ɵɵtext(12);
317
+ i0.ɵɵelementEnd();
318
+ i0.ɵɵtemplate(13, TestSuiteFormComponentExtended_div_48_div_2_div_1_span_13_Template, 3, 3, "span", 75);
319
+ i0.ɵɵelementEnd();
320
+ i0.ɵɵelementStart(14, "div", 92);
321
+ i0.ɵɵtemplate(15, TestSuiteFormComponentExtended_div_48_div_2_div_1_span_15_Template, 2, 12, "span", 93)(16, TestSuiteFormComponentExtended_div_48_div_2_div_1_span_16_Template, 4, 0, "span", 94)(17, TestSuiteFormComponentExtended_div_48_div_2_div_1_span_17_Template, 4, 7, "span", 95);
322
+ i0.ɵɵelementEnd();
323
+ i0.ɵɵtemplate(18, TestSuiteFormComponentExtended_div_48_div_2_div_1_div_18_Template, 2, 1, "div", 96);
324
+ i0.ɵɵelementEnd();
325
+ i0.ɵɵelement(19, "i", 76);
326
+ i0.ɵɵelementEnd();
327
+ } if (rf & 2) {
328
+ const run_r6 = ctx.$implicit;
329
+ const ctx_r0 = i0.ɵɵnextContext(3);
330
+ i0.ɵɵadvance();
331
+ i0.ɵɵstyleProp("background-color", ctx_r0.getRunStatusColor(run_r6.Status));
332
+ i0.ɵɵadvance();
333
+ i0.ɵɵclassProp("fa-check", run_r6.Status === "Completed")("fa-times", run_r6.Status === "Failed")("fa-spinner", run_r6.Status === "Running")("fa-clock", run_r6.Status === "Pending")("fa-ban", run_r6.Status === "Cancelled");
334
+ i0.ɵɵadvance(4);
335
+ i0.ɵɵtextInterpolate1("Run #", run_r6.ID.substring(0, 8), "");
336
+ i0.ɵɵadvance();
337
+ i0.ɵɵstyleProp("color", ctx_r0.getRunStatusColor(run_r6.Status));
338
+ i0.ɵɵadvance();
339
+ i0.ɵɵtextInterpolate(run_r6.Status);
340
+ i0.ɵɵadvance(4);
341
+ i0.ɵɵtextInterpolate1(" ", ctx_r0.getRelativeTime(run_r6.StartedAt), "");
342
+ i0.ɵɵadvance();
343
+ i0.ɵɵproperty("ngIf", run_r6.TotalTests);
344
+ i0.ɵɵadvance(2);
345
+ i0.ɵɵproperty("ngIf", ctx_r0.evalPreferences.showExecution);
346
+ i0.ɵɵadvance();
347
+ i0.ɵɵproperty("ngIf", ctx_r0.evalPreferences.showHuman);
348
+ i0.ɵɵadvance();
349
+ i0.ɵɵproperty("ngIf", ctx_r0.evalPreferences.showAuto && run_r6.TotalTests);
350
+ i0.ɵɵadvance();
351
+ i0.ɵɵproperty("ngIf", ctx_r0.getRunTags(run_r6).length > 0);
352
+ } }
353
+ function TestSuiteFormComponentExtended_div_48_div_2_Template(rf, ctx) { if (rf & 1) {
354
+ i0.ɵɵelementStart(0, "div", 81);
355
+ i0.ɵɵtemplate(1, TestSuiteFormComponentExtended_div_48_div_2_div_1_Template, 20, 22, "div", 82);
356
+ i0.ɵɵelementEnd();
357
+ } if (rf & 2) {
358
+ const ctx_r0 = i0.ɵɵnextContext(2);
359
+ i0.ɵɵadvance();
360
+ i0.ɵɵproperty("ngForOf", ctx_r0.suiteRuns);
361
+ } }
362
+ function TestSuiteFormComponentExtended_div_48_div_3_Template(rf, ctx) { if (rf & 1) {
363
+ const _r8 = i0.ɵɵgetCurrentView();
364
+ i0.ɵɵelementStart(0, "div", 77)(1, "div", 78);
365
+ i0.ɵɵelement(2, "i", 108);
366
+ i0.ɵɵelementEnd();
367
+ i0.ɵɵelementStart(3, "h4");
368
+ i0.ɵɵtext(4, "No Suite Runs Yet");
369
+ i0.ɵɵelementEnd();
370
+ i0.ɵɵelementStart(5, "p");
371
+ i0.ɵɵtext(6, "Run this suite to see execution history and results here.");
372
+ i0.ɵɵelementEnd();
373
+ i0.ɵɵelementStart(7, "button", 15);
374
+ i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_div_48_div_3_Template_button_click_7_listener() { i0.ɵɵrestoreView(_r8); const ctx_r0 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r0.runSuite()); });
375
+ i0.ɵɵelement(8, "i", 16);
376
+ i0.ɵɵtext(9, " Run Suite Now ");
377
+ i0.ɵɵelementEnd()();
378
+ } }
379
+ function TestSuiteFormComponentExtended_div_48_Template(rf, ctx) { if (rf & 1) {
380
+ i0.ɵɵelementStart(0, "div", 79);
381
+ i0.ɵɵtemplate(1, TestSuiteFormComponentExtended_div_48_div_1_Template, 3, 2, "div", 55)(2, TestSuiteFormComponentExtended_div_48_div_2_Template, 2, 1, "div", 80)(3, TestSuiteFormComponentExtended_div_48_div_3_Template, 10, 0, "div", 57);
382
+ i0.ɵɵelementEnd();
383
+ } if (rf & 2) {
384
+ const ctx_r0 = i0.ɵɵnextContext();
385
+ i0.ɵɵadvance();
386
+ i0.ɵɵproperty("ngIf", ctx_r0.loadingRuns);
387
+ i0.ɵɵadvance();
388
+ i0.ɵɵproperty("ngIf", !ctx_r0.loadingRuns && ctx_r0.suiteRuns.length > 0);
389
+ i0.ɵɵadvance();
390
+ i0.ɵɵproperty("ngIf", ctx_r0.runsLoaded && !ctx_r0.loadingRuns && ctx_r0.suiteRuns.length === 0);
391
+ } }
392
+ function TestSuiteFormComponentExtended_div_49_div_1_Template(rf, ctx) { if (rf & 1) {
393
+ i0.ɵɵelementStart(0, "div", 58);
394
+ i0.ɵɵelement(1, "mj-loading", 110);
395
+ i0.ɵɵelementEnd();
396
+ } }
397
+ function TestSuiteFormComponentExtended_div_49_ng_container_2_span_20_span_2_Template(rf, ctx) { if (rf & 1) {
398
+ i0.ɵɵelementStart(0, "span");
399
+ i0.ɵɵtext(1);
400
+ i0.ɵɵelementEnd();
401
+ } if (rf & 2) {
402
+ const ctx_r0 = i0.ɵɵnextContext(4);
403
+ i0.ɵɵadvance();
404
+ i0.ɵɵtextInterpolate1(" \u00B7 ", ctx_r0.selectedTags.length, " tags");
405
+ } }
406
+ function TestSuiteFormComponentExtended_div_49_ng_container_2_span_20_Template(rf, ctx) { if (rf & 1) {
407
+ i0.ɵɵelementStart(0, "span", 123);
408
+ i0.ɵɵtext(1);
409
+ i0.ɵɵtemplate(2, TestSuiteFormComponentExtended_div_49_ng_container_2_span_20_span_2_Template, 2, 1, "span", 75);
410
+ i0.ɵɵelementEnd();
411
+ } if (rf & 2) {
412
+ const ctx_r0 = i0.ɵɵnextContext(3);
413
+ i0.ɵɵadvance();
414
+ i0.ɵɵtextInterpolate1(" ", ctx_r0.analyticsTimeRange === "all" ? "All Time" : ctx_r0.analyticsTimeRange, " ");
415
+ i0.ɵɵadvance();
416
+ i0.ɵɵproperty("ngIf", ctx_r0.selectedTags.length > 0);
417
+ } }
418
+ function TestSuiteFormComponentExtended_div_49_ng_container_2_div_22_div_13_span_3_Template(rf, ctx) { if (rf & 1) {
419
+ i0.ɵɵelementStart(0, "span", 133);
420
+ i0.ɵɵtext(1);
421
+ i0.ɵɵelementEnd();
422
+ } if (rf & 2) {
423
+ const ctx_r0 = i0.ɵɵnextContext(5);
424
+ i0.ɵɵadvance();
425
+ i0.ɵɵtextInterpolate1("(", ctx_r0.selectedTags.length, " selected)");
426
+ } }
427
+ function TestSuiteFormComponentExtended_div_49_ng_container_2_div_22_div_13_button_8_i_1_Template(rf, ctx) { if (rf & 1) {
428
+ i0.ɵɵelement(0, "i", 136);
429
+ } }
430
+ function TestSuiteFormComponentExtended_div_49_ng_container_2_div_22_div_13_button_8_Template(rf, ctx) { if (rf & 1) {
431
+ const _r12 = i0.ɵɵgetCurrentView();
432
+ i0.ɵɵelementStart(0, "button", 134);
433
+ i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_div_49_ng_container_2_div_22_div_13_button_8_Template_button_click_0_listener() { const tag_r13 = i0.ɵɵrestoreView(_r12).$implicit; const ctx_r0 = i0.ɵɵnextContext(5); return i0.ɵɵresetView(ctx_r0.toggleTagFilter(tag_r13)); });
434
+ i0.ɵɵtemplate(1, TestSuiteFormComponentExtended_div_49_ng_container_2_div_22_div_13_button_8_i_1_Template, 1, 0, "i", 135);
435
+ i0.ɵɵtext(2);
436
+ i0.ɵɵelementEnd();
437
+ } if (rf & 2) {
438
+ const tag_r13 = ctx.$implicit;
439
+ const ctx_r0 = i0.ɵɵnextContext(5);
440
+ i0.ɵɵclassProp("active", ctx_r0.isTagSelected(tag_r13));
441
+ i0.ɵɵadvance();
442
+ i0.ɵɵproperty("ngIf", ctx_r0.isTagSelected(tag_r13));
443
+ i0.ɵɵadvance();
444
+ i0.ɵɵtextInterpolate1(" ", tag_r13, " ");
445
+ } }
446
+ function TestSuiteFormComponentExtended_div_49_ng_container_2_div_22_div_13_Template(rf, ctx) { if (rf & 1) {
447
+ const _r11 = i0.ɵɵgetCurrentView();
448
+ i0.ɵɵelementStart(0, "div", 125)(1, "label");
449
+ i0.ɵɵtext(2, "Filter by Tag ");
450
+ i0.ɵɵtemplate(3, TestSuiteFormComponentExtended_div_49_ng_container_2_div_22_div_13_span_3_Template, 2, 1, "span", 129);
451
+ i0.ɵɵelementEnd();
452
+ i0.ɵɵelementStart(4, "div", 130)(5, "button", 131);
453
+ i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_div_49_ng_container_2_div_22_div_13_Template_button_click_5_listener() { i0.ɵɵrestoreView(_r11); const ctx_r0 = i0.ɵɵnextContext(4); return i0.ɵɵresetView(ctx_r0.toggleTagFilter(null)); });
454
+ i0.ɵɵelement(6, "i", 6);
455
+ i0.ɵɵtext(7, " All Tags ");
456
+ i0.ɵɵelementEnd();
457
+ i0.ɵɵtemplate(8, TestSuiteFormComponentExtended_div_49_ng_container_2_div_22_div_13_button_8_Template, 3, 4, "button", 132);
458
+ i0.ɵɵelementEnd()();
459
+ } if (rf & 2) {
460
+ const ctx_r0 = i0.ɵɵnextContext(4);
461
+ i0.ɵɵadvance(3);
462
+ i0.ɵɵproperty("ngIf", ctx_r0.selectedTags.length > 0);
463
+ i0.ɵɵadvance(2);
464
+ i0.ɵɵclassProp("active", ctx_r0.selectedTags.length === 0);
465
+ i0.ɵɵadvance(3);
466
+ i0.ɵɵproperty("ngForOf", ctx_r0.uniqueTags);
467
+ } }
468
+ function TestSuiteFormComponentExtended_div_49_ng_container_2_div_22_Template(rf, ctx) { if (rf & 1) {
469
+ const _r10 = i0.ɵɵgetCurrentView();
470
+ i0.ɵɵelementStart(0, "div", 124)(1, "div", 125)(2, "label");
471
+ i0.ɵɵtext(3, "Time Range");
472
+ i0.ɵɵelementEnd();
473
+ i0.ɵɵelementStart(4, "div", 126)(5, "button", 127);
474
+ i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_div_49_ng_container_2_div_22_Template_button_click_5_listener() { i0.ɵɵrestoreView(_r10); const ctx_r0 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r0.setTimeRange("7d")); });
475
+ i0.ɵɵtext(6, "7 Days");
476
+ i0.ɵɵelementEnd();
477
+ i0.ɵɵelementStart(7, "button", 127);
478
+ i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_div_49_ng_container_2_div_22_Template_button_click_7_listener() { i0.ɵɵrestoreView(_r10); const ctx_r0 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r0.setTimeRange("30d")); });
479
+ i0.ɵɵtext(8, "30 Days");
480
+ i0.ɵɵelementEnd();
481
+ i0.ɵɵelementStart(9, "button", 127);
482
+ i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_div_49_ng_container_2_div_22_Template_button_click_9_listener() { i0.ɵɵrestoreView(_r10); const ctx_r0 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r0.setTimeRange("90d")); });
483
+ i0.ɵɵtext(10, "90 Days");
484
+ i0.ɵɵelementEnd();
485
+ i0.ɵɵelementStart(11, "button", 127);
486
+ i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_div_49_ng_container_2_div_22_Template_button_click_11_listener() { i0.ɵɵrestoreView(_r10); const ctx_r0 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r0.setTimeRange("all")); });
487
+ i0.ɵɵtext(12, "All Time");
488
+ i0.ɵɵelementEnd()()();
489
+ i0.ɵɵtemplate(13, TestSuiteFormComponentExtended_div_49_ng_container_2_div_22_div_13_Template, 9, 4, "div", 128);
490
+ i0.ɵɵelementEnd();
491
+ } if (rf & 2) {
492
+ const ctx_r0 = i0.ɵɵnextContext(3);
493
+ i0.ɵɵadvance(5);
494
+ i0.ɵɵclassProp("active", ctx_r0.analyticsTimeRange === "7d");
495
+ i0.ɵɵadvance(2);
496
+ i0.ɵɵclassProp("active", ctx_r0.analyticsTimeRange === "30d");
497
+ i0.ɵɵadvance(2);
498
+ i0.ɵɵclassProp("active", ctx_r0.analyticsTimeRange === "90d");
499
+ i0.ɵɵadvance(2);
500
+ i0.ɵɵclassProp("active", ctx_r0.analyticsTimeRange === "all");
501
+ i0.ɵɵadvance(2);
502
+ i0.ɵɵproperty("ngIf", ctx_r0.uniqueTags.length > 0);
503
+ } }
504
+ function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_23_tr_60_span_20_Template(rf, ctx) { if (rf & 1) {
505
+ i0.ɵɵelementStart(0, "span", 161);
506
+ i0.ɵɵtext(1);
507
+ i0.ɵɵelementEnd();
508
+ } if (rf & 2) {
509
+ const tag_r16 = ctx.$implicit;
510
+ i0.ɵɵadvance();
511
+ i0.ɵɵtextInterpolate(tag_r16);
512
+ } }
513
+ function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_23_tr_60_span_21_Template(rf, ctx) { if (rf & 1) {
514
+ i0.ɵɵelementStart(0, "span", 162);
515
+ i0.ɵɵtext(1);
516
+ i0.ɵɵelementEnd();
517
+ } if (rf & 2) {
518
+ const dp_r15 = i0.ɵɵnextContext().$implicit;
519
+ i0.ɵɵadvance();
520
+ i0.ɵɵtextInterpolate1("+", dp_r15.tags.length - 2, "");
521
+ } }
522
+ function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_23_tr_60_Template(rf, ctx) { if (rf & 1) {
523
+ const _r14 = i0.ɵɵgetCurrentView();
524
+ i0.ɵɵelementStart(0, "tr", 154);
525
+ i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_23_tr_60_Template_tr_click_0_listener() { const dp_r15 = i0.ɵɵrestoreView(_r14).$implicit; const ctx_r0 = i0.ɵɵnextContext(4); return i0.ɵɵresetView(ctx_r0.openSuiteRun(dp_r15.runId)); });
526
+ i0.ɵɵelementStart(1, "td");
527
+ i0.ɵɵtext(2);
528
+ i0.ɵɵpipe(3, "date");
529
+ i0.ɵɵelementEnd();
530
+ i0.ɵɵelementStart(4, "td")(5, "span", 155);
531
+ i0.ɵɵtext(6);
532
+ i0.ɵɵelementEnd()();
533
+ i0.ɵɵelementStart(7, "td")(8, "div", 156);
534
+ i0.ɵɵelement(9, "div", 157);
535
+ i0.ɵɵelementStart(10, "span");
536
+ i0.ɵɵtext(11);
537
+ i0.ɵɵelementEnd()()();
538
+ i0.ɵɵelementStart(12, "td");
539
+ i0.ɵɵtext(13);
540
+ i0.ɵɵelementEnd();
541
+ i0.ɵɵelementStart(14, "td");
542
+ i0.ɵɵtext(15);
543
+ i0.ɵɵelementEnd();
544
+ i0.ɵɵelementStart(16, "td");
545
+ i0.ɵɵtext(17);
546
+ i0.ɵɵelementEnd();
547
+ i0.ɵɵelementStart(18, "td")(19, "div", 158);
548
+ i0.ɵɵtemplate(20, TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_23_tr_60_span_20_Template, 2, 1, "span", 159)(21, TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_23_tr_60_span_21_Template, 2, 1, "span", 160);
549
+ i0.ɵɵelementEnd()()();
550
+ } if (rf & 2) {
551
+ const dp_r15 = ctx.$implicit;
552
+ const ctx_r0 = i0.ɵɵnextContext(4);
553
+ i0.ɵɵadvance(2);
554
+ i0.ɵɵtextInterpolate(i0.ɵɵpipeBind2(3, 13, dp_r15.date, "short"));
555
+ i0.ɵɵadvance(3);
556
+ i0.ɵɵproperty("ngClass", "status-" + dp_r15.status.toLowerCase());
557
+ i0.ɵɵadvance();
558
+ i0.ɵɵtextInterpolate(dp_r15.status);
559
+ i0.ɵɵadvance(3);
560
+ i0.ɵɵstyleProp("width", dp_r15.passRate, "%");
561
+ i0.ɵɵproperty("ngClass", i0.ɵɵpureFunction3(16, _c5, dp_r15.passRate >= 80, dp_r15.passRate >= 50 && dp_r15.passRate < 80, dp_r15.passRate < 50));
562
+ i0.ɵɵadvance(2);
563
+ i0.ɵɵtextInterpolate1("", dp_r15.passRate.toFixed(0), "%");
564
+ i0.ɵɵadvance(2);
565
+ i0.ɵɵtextInterpolate2("", dp_r15.passedTests, "/", dp_r15.totalTests, "");
566
+ i0.ɵɵadvance(2);
567
+ i0.ɵɵtextInterpolate(ctx_r0.formatDuration(dp_r15.duration));
568
+ i0.ɵɵadvance(2);
569
+ i0.ɵɵtextInterpolate(ctx_r0.formatCost(dp_r15.cost));
570
+ i0.ɵɵadvance(3);
571
+ i0.ɵɵproperty("ngForOf", dp_r15.tags.slice(0, 2));
572
+ i0.ɵɵadvance();
573
+ i0.ɵɵproperty("ngIf", dp_r15.tags.length > 2);
574
+ } }
575
+ function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_23_div_61_Template(rf, ctx) { if (rf & 1) {
576
+ i0.ɵɵelementStart(0, "div", 163)(1, "p");
577
+ i0.ɵɵtext(2, "No runs match the current filters.");
578
+ i0.ɵɵelementEnd()();
579
+ } }
580
+ function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_23_Template(rf, ctx) { if (rf & 1) {
581
+ i0.ɵɵelementContainerStart(0);
582
+ i0.ɵɵelementStart(1, "div", 137)(2, "div", 138)(3, "div", 139);
583
+ i0.ɵɵelement(4, "i", 108);
584
+ i0.ɵɵelementEnd();
585
+ i0.ɵɵelementStart(5, "div", 140)(6, "div", 141);
586
+ i0.ɵɵtext(7);
587
+ i0.ɵɵelementEnd();
588
+ i0.ɵɵelementStart(8, "div", 142);
589
+ i0.ɵɵtext(9, "Total Runs");
590
+ i0.ɵɵelementEnd()()();
591
+ i0.ɵɵelementStart(10, "div", 138)(11, "div", 143);
592
+ i0.ɵɵelement(12, "i", 97);
593
+ i0.ɵɵelementEnd();
594
+ i0.ɵɵelementStart(13, "div", 140)(14, "div", 141);
595
+ i0.ɵɵtext(15);
596
+ i0.ɵɵelementEnd();
597
+ i0.ɵɵelementStart(16, "div", 142);
598
+ i0.ɵɵtext(17, "Avg Pass Rate");
599
+ i0.ɵɵelementEnd();
600
+ i0.ɵɵelementStart(18, "div", 144);
601
+ i0.ɵɵelement(19, "i", 10);
602
+ i0.ɵɵtext(20);
603
+ i0.ɵɵelementEnd()()();
604
+ i0.ɵɵelementStart(21, "div", 138)(22, "div", 145);
605
+ i0.ɵɵelement(23, "i", 102);
606
+ i0.ɵɵelementEnd();
607
+ i0.ɵɵelementStart(24, "div", 140)(25, "div", 141);
608
+ i0.ɵɵtext(26);
609
+ i0.ɵɵelementEnd();
610
+ i0.ɵɵelementStart(27, "div", 142);
611
+ i0.ɵɵtext(28, "Avg Duration");
612
+ i0.ɵɵelementEnd()()();
613
+ i0.ɵɵelementStart(29, "div", 138)(30, "div", 146);
614
+ i0.ɵɵelement(31, "i", 147);
615
+ i0.ɵɵelementEnd();
616
+ i0.ɵɵelementStart(32, "div", 140)(33, "div", 141);
617
+ i0.ɵɵtext(34);
618
+ i0.ɵɵelementEnd();
619
+ i0.ɵɵelementStart(35, "div", 142);
620
+ i0.ɵɵtext(36, "Total Cost");
621
+ i0.ɵɵelementEnd()()()();
622
+ i0.ɵɵelementStart(37, "div", 148)(38, "h3");
623
+ i0.ɵɵelement(39, "i", 149);
624
+ i0.ɵɵtext(40, " Run History");
625
+ i0.ɵɵelementEnd();
626
+ i0.ɵɵelementStart(41, "div", 150)(42, "table", 151)(43, "thead")(44, "tr")(45, "th");
627
+ i0.ɵɵtext(46, "Date");
628
+ i0.ɵɵelementEnd();
629
+ i0.ɵɵelementStart(47, "th");
630
+ i0.ɵɵtext(48, "Status");
631
+ i0.ɵɵelementEnd();
632
+ i0.ɵɵelementStart(49, "th");
633
+ i0.ɵɵtext(50, "Pass Rate");
634
+ i0.ɵɵelementEnd();
635
+ i0.ɵɵelementStart(51, "th");
636
+ i0.ɵɵtext(52, "Tests");
637
+ i0.ɵɵelementEnd();
638
+ i0.ɵɵelementStart(53, "th");
639
+ i0.ɵɵtext(54, "Duration");
640
+ i0.ɵɵelementEnd();
641
+ i0.ɵɵelementStart(55, "th");
642
+ i0.ɵɵtext(56, "Cost");
643
+ i0.ɵɵelementEnd();
644
+ i0.ɵɵelementStart(57, "th");
645
+ i0.ɵɵtext(58, "Tags");
646
+ i0.ɵɵelementEnd()()();
647
+ i0.ɵɵelementStart(59, "tbody");
648
+ i0.ɵɵtemplate(60, TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_23_tr_60_Template, 22, 20, "tr", 152);
649
+ i0.ɵɵelementEnd()()();
650
+ i0.ɵɵtemplate(61, TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_23_div_61_Template, 3, 0, "div", 153);
651
+ i0.ɵɵelementEnd();
652
+ i0.ɵɵelementContainerEnd();
653
+ } if (rf & 2) {
654
+ const ctx_r0 = i0.ɵɵnextContext(3);
655
+ i0.ɵɵadvance(7);
656
+ i0.ɵɵtextInterpolate(ctx_r0.getTotalRuns());
657
+ i0.ɵɵadvance(8);
658
+ i0.ɵɵtextInterpolate1("", ctx_r0.getAveragePassRate().toFixed(1), "%");
659
+ i0.ɵɵadvance(3);
660
+ i0.ɵɵproperty("ngClass", i0.ɵɵpureFunction2(9, _c3, ctx_r0.getPassRateTrend().direction === "up", ctx_r0.getPassRateTrend().direction === "down"));
661
+ i0.ɵɵadvance();
662
+ i0.ɵɵproperty("ngClass", i0.ɵɵpureFunction3(12, _c4, ctx_r0.getPassRateTrend().direction === "up", ctx_r0.getPassRateTrend().direction === "down", ctx_r0.getPassRateTrend().direction === "stable"));
663
+ i0.ɵɵadvance();
664
+ i0.ɵɵtextInterpolate1(" ", ctx_r0.getPassRateTrend().value.toFixed(1), "% ");
665
+ i0.ɵɵadvance(6);
666
+ i0.ɵɵtextInterpolate(ctx_r0.formatDuration(ctx_r0.getAverageDuration()));
667
+ i0.ɵɵadvance(8);
668
+ i0.ɵɵtextInterpolate(ctx_r0.formatCost(ctx_r0.getTotalCost()));
669
+ i0.ɵɵadvance(26);
670
+ i0.ɵɵproperty("ngForOf", ctx_r0.getFilteredAnalyticsData());
671
+ i0.ɵɵadvance();
672
+ i0.ɵɵproperty("ngIf", ctx_r0.getFilteredAnalyticsData().length === 0);
673
+ } }
674
+ function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_1_Template(rf, ctx) { if (rf & 1) {
675
+ i0.ɵɵelementStart(0, "div", 58);
676
+ i0.ɵɵelement(1, "mj-loading", 165);
677
+ i0.ɵɵelementEnd();
678
+ } }
679
+ function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_button_9_Template(rf, ctx) { if (rf & 1) {
680
+ const _r18 = i0.ɵɵgetCurrentView();
681
+ i0.ɵɵelementStart(0, "button", 188);
682
+ i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_button_9_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r18); const ctx_r0 = i0.ɵɵnextContext(5); return i0.ɵɵresetView(ctx_r0.clearMatrixFilter()); });
683
+ i0.ɵɵelement(1, "i", 189);
684
+ i0.ɵɵelementEnd();
685
+ } }
686
+ function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_th_25_div_3_span_1_Template(rf, ctx) { if (rf & 1) {
687
+ i0.ɵɵelementStart(0, "span", 198);
688
+ i0.ɵɵtext(1);
689
+ i0.ɵɵelementEnd();
690
+ } if (rf & 2) {
691
+ const tag_r21 = ctx.$implicit;
692
+ i0.ɵɵadvance();
693
+ i0.ɵɵtextInterpolate(tag_r21);
694
+ } }
695
+ function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_th_25_div_3_span_2_Template(rf, ctx) { if (rf & 1) {
696
+ i0.ɵɵelementStart(0, "span", 199);
697
+ i0.ɵɵtext(1);
698
+ i0.ɵɵelementEnd();
699
+ } if (rf & 2) {
700
+ const run_r20 = i0.ɵɵnextContext(2).$implicit;
701
+ i0.ɵɵadvance();
702
+ i0.ɵɵtextInterpolate1("+", run_r20.tags.length - 2, "");
703
+ } }
704
+ function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_th_25_div_3_Template(rf, ctx) { if (rf & 1) {
705
+ i0.ɵɵelementStart(0, "div", 195);
706
+ i0.ɵɵtemplate(1, TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_th_25_div_3_span_1_Template, 2, 1, "span", 196)(2, TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_th_25_div_3_span_2_Template, 2, 1, "span", 197);
707
+ i0.ɵɵelementEnd();
708
+ } if (rf & 2) {
709
+ const run_r20 = i0.ɵɵnextContext().$implicit;
710
+ i0.ɵɵadvance();
711
+ i0.ɵɵproperty("ngForOf", run_r20.tags.slice(0, 2));
712
+ i0.ɵɵadvance();
713
+ i0.ɵɵproperty("ngIf", run_r20.tags.length > 2);
714
+ } }
715
+ function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_th_25_Template(rf, ctx) { if (rf & 1) {
716
+ const _r19 = i0.ɵɵgetCurrentView();
717
+ i0.ɵɵelementStart(0, "th", 190);
718
+ i0.ɵɵpipe(1, "date");
719
+ i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_th_25_Template_th_click_0_listener() { const run_r20 = i0.ɵɵrestoreView(_r19).$implicit; const ctx_r0 = i0.ɵɵnextContext(5); return i0.ɵɵresetView(ctx_r0.openSuiteRun(run_r20.runId)); });
720
+ i0.ɵɵelementStart(2, "div", 191);
721
+ i0.ɵɵtemplate(3, TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_th_25_div_3_Template, 3, 2, "div", 192);
722
+ i0.ɵɵelementStart(4, "div", 193);
723
+ i0.ɵɵtext(5);
724
+ i0.ɵɵelementEnd();
725
+ i0.ɵɵelementStart(6, "div", 194);
726
+ i0.ɵɵtext(7);
727
+ i0.ɵɵelementEnd()()();
728
+ } if (rf & 2) {
729
+ const run_r20 = ctx.$implicit;
730
+ const ctx_r0 = i0.ɵɵnextContext(5);
731
+ i0.ɵɵproperty("title", "Click to view suite run - " + i0.ɵɵpipeBind2(1, 5, run_r20.date, "medium"));
732
+ i0.ɵɵadvance(3);
733
+ i0.ɵɵproperty("ngIf", run_r20.tags.length > 0);
734
+ i0.ɵɵadvance(2);
735
+ i0.ɵɵtextInterpolate(ctx_r0.getRelativeTime(run_r20.date));
736
+ i0.ɵɵadvance();
737
+ i0.ɵɵproperty("ngClass", i0.ɵɵpureFunction3(8, _c5, run_r20.passRate >= 80, run_r20.passRate >= 50 && run_r20.passRate < 80, run_r20.passRate < 50));
738
+ i0.ɵɵadvance();
739
+ i0.ɵɵtextInterpolate1(" ", run_r20.passRate.toFixed(0), "% ");
740
+ } }
741
+ function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_tr_28_td_6_ng_container_1_span_2_Template(rf, ctx) { if (rf & 1) {
742
+ i0.ɵɵelementStart(0, "span", 213);
743
+ i0.ɵɵelement(1, "i", 85);
744
+ i0.ɵɵelementEnd();
745
+ } if (rf & 2) {
746
+ const result_r26 = i0.ɵɵnextContext().ngIf;
747
+ const ctx_r0 = i0.ɵɵnextContext(7);
748
+ i0.ɵɵclassProp("cell-skipped-status", result_r26.status === "Skipped" || result_r26.status === "Pending");
749
+ i0.ɵɵproperty("ngClass", "status-" + result_r26.status.toLowerCase())("title", ctx_r0.getStatusTooltip(result_r26.status));
750
+ i0.ɵɵadvance();
751
+ i0.ɵɵclassProp("fa-check", result_r26.status === "Passed")("fa-times", result_r26.status === "Failed")("fa-exclamation", result_r26.status === "Error")("fa-hourglass-end", result_r26.status === "Timeout")("fa-forward", result_r26.status === "Skipped")("fa-spinner", result_r26.status === "Running")("fa-clock", result_r26.status === "Pending");
752
+ } }
753
+ function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_tr_28_td_6_ng_container_1_span_3_Template(rf, ctx) { if (rf & 1) {
754
+ i0.ɵɵelementStart(0, "span", 214);
755
+ i0.ɵɵelement(1, "i", 215);
756
+ i0.ɵɵelementEnd();
757
+ } }
758
+ function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_tr_28_td_6_ng_container_1_span_4_Template(rf, ctx) { if (rf & 1) {
759
+ i0.ɵɵelementStart(0, "span", 216);
760
+ i0.ɵɵelement(1, "i", 100);
761
+ i0.ɵɵelementStart(2, "span", 217);
762
+ i0.ɵɵtext(3);
763
+ i0.ɵɵelementEnd()();
764
+ } if (rf & 2) {
765
+ const result_r26 = i0.ɵɵnextContext().ngIf;
766
+ const ctx_r0 = i0.ɵɵnextContext(7);
767
+ i0.ɵɵclassProp("rating-low", result_r26.humanRating <= 4)("rating-medium", result_r26.humanRating >= 5 && result_r26.humanRating <= 6)("rating-good", result_r26.humanRating >= 7 && result_r26.humanRating <= 8)("rating-excellent", result_r26.humanRating >= 9);
768
+ i0.ɵɵproperty("title", ctx_r0.getHumanTooltip(result_r26.humanRating, result_r26.humanComments));
769
+ i0.ɵɵadvance(3);
770
+ i0.ɵɵtextInterpolate(result_r26.humanRating);
771
+ } }
772
+ function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_tr_28_td_6_ng_container_1_span_5_Template(rf, ctx) { if (rf & 1) {
773
+ i0.ɵɵelementStart(0, "span", 218);
774
+ i0.ɵɵelement(1, "i", 104);
775
+ i0.ɵɵelementStart(2, "span", 219);
776
+ i0.ɵɵtext(3);
777
+ i0.ɵɵelementEnd()();
778
+ } if (rf & 2) {
779
+ const result_r26 = i0.ɵɵnextContext().ngIf;
780
+ i0.ɵɵclassProp("score-low", result_r26.score < 0.5)("score-medium", result_r26.score >= 0.5 && result_r26.score < 0.7)("score-good", result_r26.score >= 0.7 && result_r26.score < 0.85)("score-excellent", result_r26.score >= 0.85);
781
+ i0.ɵɵproperty("title", "Auto Score: " + (result_r26.score * 100).toFixed(0) + "% automated evaluation");
782
+ i0.ɵɵadvance(3);
783
+ i0.ɵɵtextInterpolate((result_r26.score * 100).toFixed(0));
784
+ } }
785
+ function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_tr_28_td_6_ng_container_1_span_6_Template(rf, ctx) { if (rf & 1) {
786
+ i0.ɵɵelementStart(0, "span", 220);
787
+ i0.ɵɵelement(1, "i", 104);
788
+ i0.ɵɵelementEnd();
789
+ } }
790
+ function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_tr_28_td_6_ng_container_1_Template(rf, ctx) { if (rf & 1) {
791
+ i0.ɵɵelementContainerStart(0);
792
+ i0.ɵɵelementStart(1, "div", 207);
793
+ i0.ɵɵtemplate(2, TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_tr_28_td_6_ng_container_1_span_2_Template, 2, 18, "span", 208)(3, TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_tr_28_td_6_ng_container_1_span_3_Template, 2, 0, "span", 209)(4, TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_tr_28_td_6_ng_container_1_span_4_Template, 4, 10, "span", 210)(5, TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_tr_28_td_6_ng_container_1_span_5_Template, 4, 10, "span", 211)(6, TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_tr_28_td_6_ng_container_1_span_6_Template, 2, 0, "span", 212);
794
+ i0.ɵɵelementEnd();
795
+ i0.ɵɵelementContainerEnd();
796
+ } if (rf & 2) {
797
+ const result_r26 = ctx.ngIf;
798
+ const ctx_r0 = i0.ɵɵnextContext(7);
799
+ i0.ɵɵadvance(2);
800
+ i0.ɵɵproperty("ngIf", ctx_r0.evalPreferences.showExecution);
801
+ i0.ɵɵadvance();
802
+ i0.ɵɵproperty("ngIf", ctx_r0.evalPreferences.showHuman && !result_r26.humanRating);
803
+ i0.ɵɵadvance();
804
+ i0.ɵɵproperty("ngIf", ctx_r0.evalPreferences.showHuman && result_r26.humanRating);
805
+ i0.ɵɵadvance();
806
+ i0.ɵɵproperty("ngIf", ctx_r0.evalPreferences.showAuto && result_r26.score != null);
807
+ i0.ɵɵadvance();
808
+ i0.ɵɵproperty("ngIf", ctx_r0.evalPreferences.showAuto && result_r26.score == null);
809
+ } }
810
+ function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_tr_28_td_6_span_2_Template(rf, ctx) { if (rf & 1) {
811
+ i0.ɵɵelementStart(0, "span", 221);
812
+ i0.ɵɵelement(1, "i", 222);
813
+ i0.ɵɵelementEnd();
814
+ } }
815
+ function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_tr_28_td_6_Template(rf, ctx) { if (rf & 1) {
816
+ const _r24 = i0.ɵɵgetCurrentView();
817
+ i0.ɵɵelementStart(0, "td", 205);
818
+ i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_tr_28_td_6_Template_td_click_0_listener($event) { const run_r25 = i0.ɵɵrestoreView(_r24).$implicit; const test_r23 = i0.ɵɵnextContext().$implicit; const ctx_r0 = i0.ɵɵnextContext(5); return i0.ɵɵresetView(ctx_r0.onMatrixCellClick(ctx_r0.getTestResultForRun(run_r25.runId, test_r23.testId), $event)); });
819
+ i0.ɵɵtemplate(1, TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_tr_28_td_6_ng_container_1_Template, 7, 5, "ng-container", 75)(2, TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_tr_28_td_6_span_2_Template, 2, 0, "span", 206);
820
+ i0.ɵɵelementEnd();
821
+ } if (rf & 2) {
822
+ let tmp_11_0;
823
+ const run_r25 = ctx.$implicit;
824
+ const test_r23 = i0.ɵɵnextContext().$implicit;
825
+ const ctx_r0 = i0.ɵɵnextContext(5);
826
+ i0.ɵɵclassProp("clickable", ctx_r0.getTestResultForRun(run_r25.runId, test_r23.testId))("cell-not-run", !ctx_r0.getTestResultForRun(run_r25.runId, test_r23.testId));
827
+ i0.ɵɵproperty("ngClass", ctx_r0.getMatrixCellClass(ctx_r0.getTestResultForRun(run_r25.runId, test_r23.testId)))("title", ((tmp_11_0 = ctx_r0.getTestResultForRun(run_r25.runId, test_r23.testId)) == null ? null : tmp_11_0.status) + " - Click to view test run" || "Not Run");
828
+ i0.ɵɵadvance();
829
+ i0.ɵɵproperty("ngIf", ctx_r0.getTestResultForRun(run_r25.runId, test_r23.testId));
830
+ i0.ɵɵadvance();
831
+ i0.ɵɵproperty("ngIf", !ctx_r0.getTestResultForRun(run_r25.runId, test_r23.testId));
832
+ } }
833
+ function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_tr_28_Template(rf, ctx) { if (rf & 1) {
834
+ const _r22 = i0.ɵɵgetCurrentView();
835
+ i0.ɵɵelementStart(0, "tr", 200);
836
+ i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_tr_28_Template_tr_click_0_listener() { const test_r23 = i0.ɵɵrestoreView(_r22).$implicit; const ctx_r0 = i0.ɵɵnextContext(5); return i0.ɵɵresetView(ctx_r0.selectMatrixRow(test_r23.testId)); });
837
+ i0.ɵɵelementStart(1, "td", 201);
838
+ i0.ɵɵtext(2);
839
+ i0.ɵɵelementEnd();
840
+ i0.ɵɵelementStart(3, "td", 202)(4, "span", 203);
841
+ i0.ɵɵtext(5);
842
+ i0.ɵɵelementEnd()();
843
+ i0.ɵɵtemplate(6, TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_tr_28_td_6_Template, 3, 8, "td", 204);
844
+ i0.ɵɵelement(7, "td", 187);
845
+ i0.ɵɵelementEnd();
846
+ } if (rf & 2) {
847
+ const test_r23 = ctx.$implicit;
848
+ const ctx_r0 = i0.ɵɵnextContext(5);
849
+ i0.ɵɵclassProp("row-selected", ctx_r0.selectedMatrixTestId === test_r23.testId);
850
+ i0.ɵɵadvance(2);
851
+ i0.ɵɵtextInterpolate(test_r23.sequence);
852
+ i0.ɵɵadvance(2);
853
+ i0.ɵɵproperty("title", test_r23.testName);
854
+ i0.ɵɵadvance();
855
+ i0.ɵɵtextInterpolate(test_r23.testName);
856
+ i0.ɵɵadvance();
857
+ i0.ɵɵproperty("ngForOf", ctx_r0.matrixData);
858
+ } }
859
+ function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_td_35_span_2_Template(rf, ctx) { if (rf & 1) {
860
+ i0.ɵɵelementStart(0, "span", 228)(1, "span", 229);
861
+ i0.ɵɵtext(2);
862
+ i0.ɵɵelementEnd()();
863
+ } if (rf & 2) {
864
+ const run_r27 = i0.ɵɵnextContext().$implicit;
865
+ const ctx_r0 = i0.ɵɵnextContext(5);
866
+ i0.ɵɵadvance(2);
867
+ i0.ɵɵtextInterpolate2("", ctx_r0.getRunPassedCount(run_r27), "/", ctx_r0.getRunTotalCount(run_r27), "");
868
+ } }
869
+ function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_td_35_span_3_span_1_Template(rf, ctx) { if (rf & 1) {
870
+ i0.ɵɵelementStart(0, "span", 233);
871
+ i0.ɵɵtext(1);
872
+ i0.ɵɵelementEnd();
873
+ } if (rf & 2) {
874
+ let tmp_8_0;
875
+ const run_r27 = i0.ɵɵnextContext(2).$implicit;
876
+ const ctx_r0 = i0.ɵɵnextContext(5);
877
+ i0.ɵɵadvance();
878
+ i0.ɵɵtextInterpolate((tmp_8_0 = ctx_r0.getRunHumanAvg(run_r27)) == null ? null : tmp_8_0.toFixed(1));
879
+ } }
880
+ function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_td_35_span_3_Template(rf, ctx) { if (rf & 1) {
881
+ i0.ɵɵelementStart(0, "span", 230);
882
+ i0.ɵɵtemplate(1, TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_td_35_span_3_span_1_Template, 2, 1, "span", 231);
883
+ i0.ɵɵelementStart(2, "span", 232);
884
+ i0.ɵɵtext(3);
885
+ i0.ɵɵelementEnd()();
886
+ } if (rf & 2) {
887
+ const run_r27 = i0.ɵɵnextContext().$implicit;
888
+ const ctx_r0 = i0.ɵɵnextContext(5);
889
+ i0.ɵɵadvance();
890
+ i0.ɵɵproperty("ngIf", ctx_r0.getRunHumanAvg(run_r27) != null);
891
+ i0.ɵɵadvance(2);
892
+ i0.ɵɵtextInterpolate1("(", ctx_r0.getRunHumanCount(run_r27), ")");
893
+ } }
894
+ function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_td_35_span_4_span_1_Template(rf, ctx) { if (rf & 1) {
895
+ i0.ɵɵelementStart(0, "span", 233);
896
+ i0.ɵɵtext(1);
897
+ i0.ɵɵelementEnd();
898
+ } if (rf & 2) {
899
+ const run_r27 = i0.ɵɵnextContext(2).$implicit;
900
+ const ctx_r0 = i0.ɵɵnextContext(5);
901
+ i0.ɵɵadvance();
902
+ i0.ɵɵtextInterpolate1("", (ctx_r0.getRunAutoAvg(run_r27) * 100).toFixed(0), "%");
903
+ } }
904
+ function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_td_35_span_4_Template(rf, ctx) { if (rf & 1) {
905
+ i0.ɵɵelementStart(0, "span", 234);
906
+ i0.ɵɵtemplate(1, TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_td_35_span_4_span_1_Template, 2, 1, "span", 231);
907
+ i0.ɵɵelementStart(2, "span", 232);
908
+ i0.ɵɵtext(3);
909
+ i0.ɵɵelementEnd()();
910
+ } if (rf & 2) {
911
+ const run_r27 = i0.ɵɵnextContext().$implicit;
912
+ const ctx_r0 = i0.ɵɵnextContext(5);
913
+ i0.ɵɵadvance();
914
+ i0.ɵɵproperty("ngIf", ctx_r0.getRunAutoAvg(run_r27) != null);
915
+ i0.ɵɵadvance(2);
916
+ i0.ɵɵtextInterpolate1("(", ctx_r0.getRunAutoCount(run_r27), ")");
917
+ } }
918
+ function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_td_35_Template(rf, ctx) { if (rf & 1) {
919
+ i0.ɵɵelementStart(0, "td", 223)(1, "div", 224);
920
+ i0.ɵɵtemplate(2, TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_td_35_span_2_Template, 3, 2, "span", 225)(3, TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_td_35_span_3_Template, 4, 2, "span", 226)(4, TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_td_35_span_4_Template, 4, 2, "span", 227);
921
+ i0.ɵɵelementEnd()();
922
+ } if (rf & 2) {
923
+ const ctx_r0 = i0.ɵɵnextContext(5);
924
+ i0.ɵɵadvance(2);
925
+ i0.ɵɵproperty("ngIf", ctx_r0.evalPreferences.showExecution);
926
+ i0.ɵɵadvance();
927
+ i0.ɵɵproperty("ngIf", ctx_r0.evalPreferences.showHuman);
928
+ i0.ɵɵadvance();
929
+ i0.ɵɵproperty("ngIf", ctx_r0.evalPreferences.showAuto);
930
+ } }
931
+ function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_Template(rf, ctx) { if (rf & 1) {
932
+ const _r17 = i0.ɵɵgetCurrentView();
933
+ i0.ɵɵelementStart(0, "div", 166)(1, "div", 167)(2, "h3");
934
+ i0.ɵɵelement(3, "i", 115);
935
+ i0.ɵɵtext(4, " Test Results Matrix");
936
+ i0.ɵɵelementEnd();
937
+ i0.ɵɵelementStart(5, "div", 168)(6, "div", 169);
938
+ i0.ɵɵelement(7, "i", 170);
939
+ i0.ɵɵelementStart(8, "input", 171);
940
+ i0.ɵɵlistener("input", function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_Template_input_input_8_listener($event) { i0.ɵɵrestoreView(_r17); const ctx_r0 = i0.ɵɵnextContext(4); return i0.ɵɵresetView(ctx_r0.onMatrixFilterInput($event)); });
941
+ i0.ɵɵelementEnd();
942
+ i0.ɵɵtemplate(9, TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_button_9_Template, 2, 0, "button", 172);
943
+ i0.ɵɵelementEnd();
944
+ i0.ɵɵelementStart(10, "span", 173);
945
+ i0.ɵɵtext(11);
946
+ i0.ɵɵelementEnd();
947
+ i0.ɵɵelementStart(12, "button", 174);
948
+ i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_Template_button_click_12_listener() { i0.ɵɵrestoreView(_r17); const ctx_r0 = i0.ɵɵnextContext(4); return i0.ɵɵresetView(ctx_r0.exportMatrixToCSV()); });
949
+ i0.ɵɵelement(13, "i", 175);
950
+ i0.ɵɵtext(14, " Export ");
951
+ i0.ɵɵelementEnd()()();
952
+ i0.ɵɵelementStart(15, "div", 176)(16, "table", 177)(17, "thead")(18, "tr")(19, "th", 178);
953
+ i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_Template_th_click_19_listener() { i0.ɵɵrestoreView(_r17); const ctx_r0 = i0.ɵɵnextContext(4); return i0.ɵɵresetView(ctx_r0.toggleMatrixSort("sequence")); });
954
+ i0.ɵɵtext(20, " # ");
955
+ i0.ɵɵelement(21, "i", 10);
956
+ i0.ɵɵelementEnd();
957
+ i0.ɵɵelementStart(22, "th", 179);
958
+ i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_Template_th_click_22_listener() { i0.ɵɵrestoreView(_r17); const ctx_r0 = i0.ɵɵnextContext(4); return i0.ɵɵresetView(ctx_r0.toggleMatrixSort("name")); });
959
+ i0.ɵɵtext(23, " Test ");
960
+ i0.ɵɵelement(24, "i", 10);
961
+ i0.ɵɵelementEnd();
962
+ i0.ɵɵtemplate(25, TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_th_25_Template, 8, 12, "th", 180);
963
+ i0.ɵɵelement(26, "th", 181);
964
+ i0.ɵɵelementEnd()();
965
+ i0.ɵɵelementStart(27, "tbody");
966
+ i0.ɵɵtemplate(28, TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_tr_28_Template, 8, 6, "tr", 182);
967
+ i0.ɵɵelementEnd();
968
+ i0.ɵɵelementStart(29, "tfoot")(30, "tr", 183);
969
+ i0.ɵɵelement(31, "td", 184);
970
+ i0.ɵɵelementStart(32, "td", 185)(33, "strong");
971
+ i0.ɵɵtext(34, "Totals");
972
+ i0.ɵɵelementEnd()();
973
+ i0.ɵɵtemplate(35, TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_td_35_Template, 5, 3, "td", 186);
974
+ i0.ɵɵelement(36, "td", 187);
975
+ i0.ɵɵelementEnd()()()()();
976
+ } if (rf & 2) {
977
+ const ctx_r0 = i0.ɵɵnextContext(4);
978
+ i0.ɵɵadvance(8);
979
+ i0.ɵɵproperty("value", ctx_r0.matrixTestFilter);
980
+ i0.ɵɵadvance();
981
+ i0.ɵɵproperty("ngIf", ctx_r0.matrixTestFilter);
982
+ i0.ɵɵadvance(2);
983
+ i0.ɵɵtextInterpolate2("", ctx_r0.matrixData.length, " runs \u00B7 ", ctx_r0.getUniqueTestsFromMatrix().length, " tests");
984
+ i0.ɵɵadvance();
985
+ i0.ɵɵproperty("disabled", ctx_r0.matrixData.length === 0);
986
+ i0.ɵɵadvance(9);
987
+ i0.ɵɵproperty("ngClass", ctx_r0.matrixSortBy === "sequence" ? ctx_r0.matrixSortAsc ? "fa-sort-up" : "fa-sort-down" : "fa-sort");
988
+ i0.ɵɵadvance(3);
989
+ i0.ɵɵproperty("ngClass", ctx_r0.matrixSortBy === "name" ? ctx_r0.matrixSortAsc ? "fa-sort-up" : "fa-sort-down" : "fa-sort");
990
+ i0.ɵɵadvance();
991
+ i0.ɵɵproperty("ngForOf", ctx_r0.matrixData);
992
+ i0.ɵɵadvance(3);
993
+ i0.ɵɵproperty("ngForOf", ctx_r0.getUniqueTestsFromMatrix());
994
+ i0.ɵɵadvance(7);
995
+ i0.ɵɵproperty("ngForOf", ctx_r0.matrixData);
996
+ } }
997
+ function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_3_Template(rf, ctx) { if (rf & 1) {
998
+ i0.ɵɵelementStart(0, "div", 77)(1, "div", 78);
999
+ i0.ɵɵelement(2, "i", 115);
1000
+ i0.ɵɵelementEnd();
1001
+ i0.ɵɵelementStart(3, "h4");
1002
+ i0.ɵɵtext(4, "No Matrix Data");
1003
+ i0.ɵɵelementEnd();
1004
+ i0.ɵɵelementStart(5, "p");
1005
+ i0.ɵɵtext(6, "No suite runs match the current filters.");
1006
+ i0.ɵɵelementEnd()();
1007
+ } }
1008
+ function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_Template(rf, ctx) { if (rf & 1) {
1009
+ i0.ɵɵelementContainerStart(0);
1010
+ i0.ɵɵtemplate(1, TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_1_Template, 2, 0, "div", 55)(2, TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_Template, 37, 10, "div", 164)(3, TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_3_Template, 7, 0, "div", 57);
1011
+ i0.ɵɵelementContainerEnd();
1012
+ } if (rf & 2) {
1013
+ const ctx_r0 = i0.ɵɵnextContext(3);
1014
+ i0.ɵɵadvance();
1015
+ i0.ɵɵproperty("ngIf", ctx_r0.loadingMatrix);
1016
+ i0.ɵɵadvance();
1017
+ i0.ɵɵproperty("ngIf", !ctx_r0.loadingMatrix && ctx_r0.matrixLoaded && ctx_r0.matrixData.length > 0);
1018
+ i0.ɵɵadvance();
1019
+ i0.ɵɵproperty("ngIf", !ctx_r0.loadingMatrix && ctx_r0.matrixLoaded && ctx_r0.matrixData.length === 0);
1020
+ } }
1021
+ function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_25_div_1_Template(rf, ctx) { if (rf & 1) {
1022
+ i0.ɵɵelementStart(0, "div", 58);
1023
+ i0.ɵɵelement(1, "mj-loading", 236);
1024
+ i0.ɵɵelementEnd();
1025
+ } }
1026
+ function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_25_div_2_Template(rf, ctx) { if (rf & 1) {
1027
+ i0.ɵɵelementStart(0, "div", 237)(1, "div", 238)(2, "h3");
1028
+ i0.ɵɵelement(3, "i", 116);
1029
+ i0.ɵɵtext(4, " Test Results Flow");
1030
+ i0.ɵɵelementEnd();
1031
+ i0.ɵɵelementStart(5, "div", 239)(6, "span", 240);
1032
+ i0.ɵɵelement(7, "i", 136);
1033
+ i0.ɵɵtext(8, " Passed");
1034
+ i0.ɵɵelementEnd();
1035
+ i0.ɵɵelementStart(9, "span", 241);
1036
+ i0.ɵɵelement(10, "i", 189);
1037
+ i0.ɵɵtext(11, " Failed");
1038
+ i0.ɵɵelementEnd();
1039
+ i0.ɵɵelementStart(12, "span", 242);
1040
+ i0.ɵɵelement(13, "i", 243);
1041
+ i0.ɵɵtext(14, " Error");
1042
+ i0.ɵɵelementEnd();
1043
+ i0.ɵɵelementStart(15, "span", 244);
1044
+ i0.ɵɵelement(16, "i", 245);
1045
+ i0.ɵɵtext(17, " Skipped");
1046
+ i0.ɵɵelementEnd()()();
1047
+ i0.ɵɵelementStart(18, "div", 246);
1048
+ i0.ɵɵelement(19, "div", 247, 0);
1049
+ i0.ɵɵelementEnd();
1050
+ i0.ɵɵelementStart(21, "div", 248);
1051
+ i0.ɵɵelement(22, "i", 42);
1052
+ i0.ɵɵtext(23);
1053
+ i0.ɵɵelementEnd()();
1054
+ } if (rf & 2) {
1055
+ const ctx_r0 = i0.ɵɵnextContext(4);
1056
+ i0.ɵɵadvance(23);
1057
+ i0.ɵɵtextInterpolate1(" Interactive visualization showing test results across ", ctx_r0.matrixData.length, " runs. Hover over elements for details, click nodes to navigate. ");
1058
+ } }
1059
+ function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_25_div_3_Template(rf, ctx) { if (rf & 1) {
1060
+ i0.ɵɵelementStart(0, "div", 77)(1, "div", 78);
1061
+ i0.ɵɵelement(2, "i", 116);
1062
+ i0.ɵɵelementEnd();
1063
+ i0.ɵɵelementStart(3, "h4");
1064
+ i0.ɵɵtext(4, "No Chart Data");
1065
+ i0.ɵɵelementEnd();
1066
+ i0.ɵɵelementStart(5, "p");
1067
+ i0.ɵɵtext(6, "No suite runs match the current filters.");
1068
+ i0.ɵɵelementEnd()();
1069
+ } }
1070
+ function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_25_Template(rf, ctx) { if (rf & 1) {
1071
+ i0.ɵɵelementContainerStart(0);
1072
+ i0.ɵɵtemplate(1, TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_25_div_1_Template, 2, 0, "div", 55)(2, TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_25_div_2_Template, 24, 1, "div", 235)(3, TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_25_div_3_Template, 7, 0, "div", 57);
1073
+ i0.ɵɵelementContainerEnd();
1074
+ } if (rf & 2) {
1075
+ const ctx_r0 = i0.ɵɵnextContext(3);
1076
+ i0.ɵɵadvance();
1077
+ i0.ɵɵproperty("ngIf", ctx_r0.loadingMatrix);
1078
+ i0.ɵɵadvance();
1079
+ i0.ɵɵproperty("ngIf", !ctx_r0.loadingMatrix && ctx_r0.matrixLoaded && ctx_r0.matrixData.length > 0);
1080
+ i0.ɵɵadvance();
1081
+ i0.ɵɵproperty("ngIf", !ctx_r0.loadingMatrix && ctx_r0.matrixLoaded && ctx_r0.matrixData.length === 0);
1082
+ } }
1083
+ function TestSuiteFormComponentExtended_div_49_ng_container_2_Template(rf, ctx) { if (rf & 1) {
1084
+ const _r9 = i0.ɵɵgetCurrentView();
1085
+ i0.ɵɵelementContainerStart(0);
1086
+ i0.ɵɵelementStart(1, "div", 111)(2, "div", 112)(3, "button", 113);
1087
+ i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_div_49_ng_container_2_Template_button_click_3_listener() { i0.ɵɵrestoreView(_r9); const ctx_r0 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r0.setAnalyticsView("summary")); });
1088
+ i0.ɵɵelement(4, "i", 114);
1089
+ i0.ɵɵelementStart(5, "span");
1090
+ i0.ɵɵtext(6, "Summary");
1091
+ i0.ɵɵelementEnd()();
1092
+ i0.ɵɵelementStart(7, "button", 113);
1093
+ i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_div_49_ng_container_2_Template_button_click_7_listener() { i0.ɵɵrestoreView(_r9); const ctx_r0 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r0.setAnalyticsView("matrix")); });
1094
+ i0.ɵɵelement(8, "i", 115);
1095
+ i0.ɵɵelementStart(9, "span");
1096
+ i0.ɵɵtext(10, "Matrix");
1097
+ i0.ɵɵelementEnd()();
1098
+ i0.ɵɵelementStart(11, "button", 113);
1099
+ i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_div_49_ng_container_2_Template_button_click_11_listener() { i0.ɵɵrestoreView(_r9); const ctx_r0 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r0.setAnalyticsView("chart")); });
1100
+ i0.ɵɵelement(12, "i", 116);
1101
+ i0.ɵɵelementStart(13, "span");
1102
+ i0.ɵɵtext(14, "Chart");
1103
+ i0.ɵɵelementEnd()()()();
1104
+ i0.ɵɵelementStart(15, "div", 117)(16, "div", 118);
1105
+ i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_div_49_ng_container_2_Template_div_click_16_listener() { i0.ɵɵrestoreView(_r9); const ctx_r0 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r0.toggleFilters()); });
1106
+ i0.ɵɵelementStart(17, "span", 119);
1107
+ i0.ɵɵelement(18, "i", 120);
1108
+ i0.ɵɵtext(19, " Filters ");
1109
+ i0.ɵɵtemplate(20, TestSuiteFormComponentExtended_div_49_ng_container_2_span_20_Template, 3, 2, "span", 121);
1110
+ i0.ɵɵelementEnd();
1111
+ i0.ɵɵelement(21, "i", 10);
1112
+ i0.ɵɵelementEnd();
1113
+ i0.ɵɵtemplate(22, TestSuiteFormComponentExtended_div_49_ng_container_2_div_22_Template, 14, 9, "div", 122);
1114
+ i0.ɵɵelementEnd();
1115
+ i0.ɵɵtemplate(23, TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_23_Template, 62, 16, "ng-container", 75)(24, TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_Template, 4, 3, "ng-container", 75)(25, TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_25_Template, 4, 3, "ng-container", 75);
1116
+ i0.ɵɵelementContainerEnd();
1117
+ } if (rf & 2) {
1118
+ const ctx_r0 = i0.ɵɵnextContext(2);
1119
+ i0.ɵɵadvance(3);
1120
+ i0.ɵɵclassProp("active", ctx_r0.analyticsView === "summary");
1121
+ i0.ɵɵadvance(4);
1122
+ i0.ɵɵclassProp("active", ctx_r0.analyticsView === "matrix");
1123
+ i0.ɵɵadvance(4);
1124
+ i0.ɵɵclassProp("active", ctx_r0.analyticsView === "chart");
1125
+ i0.ɵɵadvance(4);
1126
+ i0.ɵɵclassProp("collapsed", ctx_r0.filtersCollapsed);
1127
+ i0.ɵɵadvance(5);
1128
+ i0.ɵɵproperty("ngIf", ctx_r0.filtersCollapsed);
1129
+ i0.ɵɵadvance();
1130
+ i0.ɵɵproperty("ngClass", ctx_r0.filtersCollapsed ? "fa-chevron-down" : "fa-chevron-up");
1131
+ i0.ɵɵadvance();
1132
+ i0.ɵɵproperty("ngIf", !ctx_r0.filtersCollapsed);
1133
+ i0.ɵɵadvance();
1134
+ i0.ɵɵproperty("ngIf", ctx_r0.analyticsView === "summary");
1135
+ i0.ɵɵadvance();
1136
+ i0.ɵɵproperty("ngIf", ctx_r0.analyticsView === "matrix");
1137
+ i0.ɵɵadvance();
1138
+ i0.ɵɵproperty("ngIf", ctx_r0.analyticsView === "chart");
1139
+ } }
1140
+ function TestSuiteFormComponentExtended_div_49_div_3_Template(rf, ctx) { if (rf & 1) {
1141
+ const _r28 = i0.ɵɵgetCurrentView();
1142
+ i0.ɵɵelementStart(0, "div", 77)(1, "div", 78);
1143
+ i0.ɵɵelement(2, "i", 26);
1144
+ i0.ɵɵelementEnd();
1145
+ i0.ɵɵelementStart(3, "h4");
1146
+ i0.ɵɵtext(4, "No Analytics Data");
1147
+ i0.ɵɵelementEnd();
1148
+ i0.ɵɵelementStart(5, "p");
1149
+ i0.ɵɵtext(6, "Run this suite to start collecting analytics data.");
1150
+ i0.ɵɵelementEnd();
1151
+ i0.ɵɵelementStart(7, "button", 15);
1152
+ i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_div_49_div_3_Template_button_click_7_listener() { i0.ɵɵrestoreView(_r28); const ctx_r0 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r0.runSuite()); });
1153
+ i0.ɵɵelement(8, "i", 16);
1154
+ i0.ɵɵtext(9, " Run Suite Now ");
1155
+ i0.ɵɵelementEnd()();
1156
+ } }
1157
+ function TestSuiteFormComponentExtended_div_49_Template(rf, ctx) { if (rf & 1) {
1158
+ i0.ɵɵelementStart(0, "div", 109);
1159
+ i0.ɵɵtemplate(1, TestSuiteFormComponentExtended_div_49_div_1_Template, 2, 0, "div", 55)(2, TestSuiteFormComponentExtended_div_49_ng_container_2_Template, 26, 14, "ng-container", 75)(3, TestSuiteFormComponentExtended_div_49_div_3_Template, 10, 0, "div", 57);
1160
+ i0.ɵɵelementEnd();
1161
+ } if (rf & 2) {
1162
+ const ctx_r0 = i0.ɵɵnextContext();
1163
+ i0.ɵɵadvance();
1164
+ i0.ɵɵproperty("ngIf", ctx_r0.loadingAnalytics);
1165
+ i0.ɵɵadvance();
1166
+ i0.ɵɵproperty("ngIf", !ctx_r0.loadingAnalytics && ctx_r0.analyticsLoaded);
1167
+ i0.ɵɵadvance();
1168
+ i0.ɵɵproperty("ngIf", !ctx_r0.loadingAnalytics && ctx_r0.analyticsLoaded && ctx_r0.analyticsData.length === 0);
1169
+ } }
1170
+ function TestSuiteFormComponentExtended_div_50_div_5_div_1_div_8_span_1_Template(rf, ctx) { if (rf & 1) {
1171
+ i0.ɵɵelementStart(0, "span", 268);
1172
+ i0.ɵɵtext(1);
1173
+ i0.ɵɵelementEnd();
1174
+ } if (rf & 2) {
1175
+ const tag_r31 = ctx.$implicit;
1176
+ i0.ɵɵadvance();
1177
+ i0.ɵɵtextInterpolate(tag_r31);
1178
+ } }
1179
+ function TestSuiteFormComponentExtended_div_50_div_5_div_1_div_8_Template(rf, ctx) { if (rf & 1) {
1180
+ i0.ɵɵelementStart(0, "div", 266);
1181
+ i0.ɵɵtemplate(1, TestSuiteFormComponentExtended_div_50_div_5_div_1_div_8_span_1_Template, 2, 1, "span", 267);
1182
+ i0.ɵɵelementEnd();
1183
+ } if (rf & 2) {
1184
+ const run_r30 = i0.ɵɵnextContext().$implicit;
1185
+ const ctx_r0 = i0.ɵɵnextContext(3);
1186
+ i0.ɵɵadvance();
1187
+ i0.ɵɵproperty("ngForOf", ctx_r0.getRunTags(run_r30).slice(0, 2));
1188
+ } }
1189
+ function TestSuiteFormComponentExtended_div_50_div_5_div_1_Template(rf, ctx) { if (rf & 1) {
1190
+ const _r29 = i0.ɵɵgetCurrentView();
1191
+ i0.ɵɵelementStart(0, "div", 260);
1192
+ i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_div_50_div_5_div_1_Template_div_click_0_listener() { const run_r30 = i0.ɵɵrestoreView(_r29).$implicit; const ctx_r0 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r0.selectCompareRunA(run_r30)); });
1193
+ i0.ɵɵelement(1, "div", 261);
1194
+ i0.ɵɵelementStart(2, "div", 262)(3, "div", 263);
1195
+ i0.ɵɵtext(4);
1196
+ i0.ɵɵpipe(5, "date");
1197
+ i0.ɵɵelementEnd();
1198
+ i0.ɵɵelementStart(6, "div", 264);
1199
+ i0.ɵɵtext(7);
1200
+ i0.ɵɵelementEnd()();
1201
+ i0.ɵɵtemplate(8, TestSuiteFormComponentExtended_div_50_div_5_div_1_div_8_Template, 2, 1, "div", 265);
1202
+ i0.ɵɵelementEnd();
1203
+ } if (rf & 2) {
1204
+ const run_r30 = ctx.$implicit;
1205
+ const ctx_r0 = i0.ɵɵnextContext(3);
1206
+ i0.ɵɵclassProp("selected", (ctx_r0.compareRunA == null ? null : ctx_r0.compareRunA.ID) === run_r30.ID);
1207
+ i0.ɵɵadvance();
1208
+ i0.ɵɵstyleProp("background-color", ctx_r0.getRunStatusColor(run_r30.Status));
1209
+ i0.ɵɵadvance(3);
1210
+ i0.ɵɵtextInterpolate(i0.ɵɵpipeBind2(5, 7, run_r30.StartedAt, "short"));
1211
+ i0.ɵɵadvance(3);
1212
+ i0.ɵɵtextInterpolate1("", ctx_r0.getPassRate(run_r30).toFixed(0), "% pass");
1213
+ i0.ɵɵadvance();
1214
+ i0.ɵɵproperty("ngIf", ctx_r0.getRunTags(run_r30).length > 0);
1215
+ } }
1216
+ function TestSuiteFormComponentExtended_div_50_div_5_Template(rf, ctx) { if (rf & 1) {
1217
+ i0.ɵɵelementStart(0, "div", 258);
1218
+ i0.ɵɵtemplate(1, TestSuiteFormComponentExtended_div_50_div_5_div_1_Template, 9, 10, "div", 259);
1219
+ i0.ɵɵelementEnd();
1220
+ } if (rf & 2) {
1221
+ const ctx_r0 = i0.ɵɵnextContext(2);
1222
+ i0.ɵɵadvance();
1223
+ i0.ɵɵproperty("ngForOf", ctx_r0.suiteRuns);
1224
+ } }
1225
+ function TestSuiteFormComponentExtended_div_50_div_6_Template(rf, ctx) { if (rf & 1) {
1226
+ const _r32 = i0.ɵɵgetCurrentView();
1227
+ i0.ɵɵelementStart(0, "div", 269)(1, "div", 270)(2, "span", 271);
1228
+ i0.ɵɵtext(3, "Selected:");
1229
+ i0.ɵɵelementEnd();
1230
+ i0.ɵɵelementStart(4, "button", 272);
1231
+ i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_div_50_div_6_Template_button_click_4_listener() { i0.ɵɵrestoreView(_r32); const ctx_r0 = i0.ɵɵnextContext(2); ctx_r0.compareRunA = null; return i0.ɵɵresetView(ctx_r0.compareResults = []); });
1232
+ i0.ɵɵtext(5, "Clear");
1233
+ i0.ɵɵelementEnd()();
1234
+ i0.ɵɵelementStart(6, "div", 273)(7, "span");
1235
+ i0.ɵɵtext(8);
1236
+ i0.ɵɵpipe(9, "date");
1237
+ i0.ɵɵelementEnd();
1238
+ i0.ɵɵelementStart(10, "span", 274);
1239
+ i0.ɵɵtext(11);
1240
+ i0.ɵɵelementEnd()()();
1241
+ } if (rf & 2) {
1242
+ const ctx_r0 = i0.ɵɵnextContext(2);
1243
+ i0.ɵɵadvance(8);
1244
+ i0.ɵɵtextInterpolate(i0.ɵɵpipeBind2(9, 2, ctx_r0.compareRunA.StartedAt, "medium"));
1245
+ i0.ɵɵadvance(3);
1246
+ i0.ɵɵtextInterpolate1("", ctx_r0.getPassRate(ctx_r0.compareRunA).toFixed(1), "%");
1247
+ } }
1248
+ function TestSuiteFormComponentExtended_div_50_div_12_div_1_div_8_span_1_Template(rf, ctx) { if (rf & 1) {
1249
+ i0.ɵɵelementStart(0, "span", 268);
1250
+ i0.ɵɵtext(1);
1251
+ i0.ɵɵelementEnd();
1252
+ } if (rf & 2) {
1253
+ const tag_r35 = ctx.$implicit;
1254
+ i0.ɵɵadvance();
1255
+ i0.ɵɵtextInterpolate(tag_r35);
1256
+ } }
1257
+ function TestSuiteFormComponentExtended_div_50_div_12_div_1_div_8_Template(rf, ctx) { if (rf & 1) {
1258
+ i0.ɵɵelementStart(0, "div", 266);
1259
+ i0.ɵɵtemplate(1, TestSuiteFormComponentExtended_div_50_div_12_div_1_div_8_span_1_Template, 2, 1, "span", 267);
1260
+ i0.ɵɵelementEnd();
1261
+ } if (rf & 2) {
1262
+ const run_r34 = i0.ɵɵnextContext().$implicit;
1263
+ const ctx_r0 = i0.ɵɵnextContext(3);
1264
+ i0.ɵɵadvance();
1265
+ i0.ɵɵproperty("ngForOf", ctx_r0.getRunTags(run_r34).slice(0, 2));
1266
+ } }
1267
+ function TestSuiteFormComponentExtended_div_50_div_12_div_1_Template(rf, ctx) { if (rf & 1) {
1268
+ const _r33 = i0.ɵɵgetCurrentView();
1269
+ i0.ɵɵelementStart(0, "div", 260);
1270
+ i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_div_50_div_12_div_1_Template_div_click_0_listener() { const run_r34 = i0.ɵɵrestoreView(_r33).$implicit; const ctx_r0 = i0.ɵɵnextContext(3); return i0.ɵɵresetView((ctx_r0.compareRunA == null ? null : ctx_r0.compareRunA.ID) !== run_r34.ID && ctx_r0.selectCompareRunB(run_r34)); });
1271
+ i0.ɵɵelement(1, "div", 261);
1272
+ i0.ɵɵelementStart(2, "div", 262)(3, "div", 263);
1273
+ i0.ɵɵtext(4);
1274
+ i0.ɵɵpipe(5, "date");
1275
+ i0.ɵɵelementEnd();
1276
+ i0.ɵɵelementStart(6, "div", 264);
1277
+ i0.ɵɵtext(7);
1278
+ i0.ɵɵelementEnd()();
1279
+ i0.ɵɵtemplate(8, TestSuiteFormComponentExtended_div_50_div_12_div_1_div_8_Template, 2, 1, "div", 265);
1280
+ i0.ɵɵelementEnd();
1281
+ } if (rf & 2) {
1282
+ const run_r34 = ctx.$implicit;
1283
+ const ctx_r0 = i0.ɵɵnextContext(3);
1284
+ i0.ɵɵclassProp("selected", (ctx_r0.compareRunB == null ? null : ctx_r0.compareRunB.ID) === run_r34.ID)("disabled", (ctx_r0.compareRunA == null ? null : ctx_r0.compareRunA.ID) === run_r34.ID);
1285
+ i0.ɵɵadvance();
1286
+ i0.ɵɵstyleProp("background-color", ctx_r0.getRunStatusColor(run_r34.Status));
1287
+ i0.ɵɵadvance(3);
1288
+ i0.ɵɵtextInterpolate(i0.ɵɵpipeBind2(5, 9, run_r34.StartedAt, "short"));
1289
+ i0.ɵɵadvance(3);
1290
+ i0.ɵɵtextInterpolate1("", ctx_r0.getPassRate(run_r34).toFixed(0), "% pass");
1291
+ i0.ɵɵadvance();
1292
+ i0.ɵɵproperty("ngIf", ctx_r0.getRunTags(run_r34).length > 0);
1293
+ } }
1294
+ function TestSuiteFormComponentExtended_div_50_div_12_Template(rf, ctx) { if (rf & 1) {
1295
+ i0.ɵɵelementStart(0, "div", 258);
1296
+ i0.ɵɵtemplate(1, TestSuiteFormComponentExtended_div_50_div_12_div_1_Template, 9, 12, "div", 275);
1297
+ i0.ɵɵelementEnd();
1298
+ } if (rf & 2) {
1299
+ const ctx_r0 = i0.ɵɵnextContext(2);
1300
+ i0.ɵɵadvance();
1301
+ i0.ɵɵproperty("ngForOf", ctx_r0.suiteRuns);
1302
+ } }
1303
+ function TestSuiteFormComponentExtended_div_50_div_13_Template(rf, ctx) { if (rf & 1) {
1304
+ const _r36 = i0.ɵɵgetCurrentView();
1305
+ i0.ɵɵelementStart(0, "div", 269)(1, "div", 270)(2, "span", 271);
1306
+ i0.ɵɵtext(3, "Selected:");
1307
+ i0.ɵɵelementEnd();
1308
+ i0.ɵɵelementStart(4, "button", 272);
1309
+ i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_div_50_div_13_Template_button_click_4_listener() { i0.ɵɵrestoreView(_r36); const ctx_r0 = i0.ɵɵnextContext(2); ctx_r0.compareRunB = null; return i0.ɵɵresetView(ctx_r0.compareResults = []); });
1310
+ i0.ɵɵtext(5, "Clear");
1311
+ i0.ɵɵelementEnd()();
1312
+ i0.ɵɵelementStart(6, "div", 273)(7, "span");
1313
+ i0.ɵɵtext(8);
1314
+ i0.ɵɵpipe(9, "date");
1315
+ i0.ɵɵelementEnd();
1316
+ i0.ɵɵelementStart(10, "span", 274);
1317
+ i0.ɵɵtext(11);
1318
+ i0.ɵɵelementEnd()()();
1319
+ } if (rf & 2) {
1320
+ const ctx_r0 = i0.ɵɵnextContext(2);
1321
+ i0.ɵɵadvance(8);
1322
+ i0.ɵɵtextInterpolate(i0.ɵɵpipeBind2(9, 2, ctx_r0.compareRunB.StartedAt, "medium"));
1323
+ i0.ɵɵadvance(3);
1324
+ i0.ɵɵtextInterpolate1("", ctx_r0.getPassRate(ctx_r0.compareRunB).toFixed(1), "%");
1325
+ } }
1326
+ function TestSuiteFormComponentExtended_div_50_div_14_tr_45_span_4_Template(rf, ctx) { if (rf & 1) {
1327
+ i0.ɵɵelementStart(0, "span", 155);
1328
+ i0.ɵɵtext(1);
1329
+ i0.ɵɵelementEnd();
1330
+ } if (rf & 2) {
1331
+ const result_r37 = i0.ɵɵnextContext().$implicit;
1332
+ i0.ɵɵproperty("ngClass", "status-" + result_r37.runA.status.toLowerCase());
1333
+ i0.ɵɵadvance();
1334
+ i0.ɵɵtextInterpolate(result_r37.runA.status);
1335
+ } }
1336
+ function TestSuiteFormComponentExtended_div_50_div_14_tr_45_span_5_Template(rf, ctx) { if (rf & 1) {
1337
+ i0.ɵɵelementStart(0, "span", 297);
1338
+ i0.ɵɵtext(1, "N/A");
1339
+ i0.ɵɵelementEnd();
1340
+ } }
1341
+ function TestSuiteFormComponentExtended_div_50_div_14_tr_45_span_7_Template(rf, ctx) { if (rf & 1) {
1342
+ i0.ɵɵelementStart(0, "span", 155);
1343
+ i0.ɵɵtext(1);
1344
+ i0.ɵɵelementEnd();
1345
+ } if (rf & 2) {
1346
+ const result_r37 = i0.ɵɵnextContext().$implicit;
1347
+ i0.ɵɵproperty("ngClass", "status-" + result_r37.runB.status.toLowerCase());
1348
+ i0.ɵɵadvance();
1349
+ i0.ɵɵtextInterpolate(result_r37.runB.status);
1350
+ } }
1351
+ function TestSuiteFormComponentExtended_div_50_div_14_tr_45_span_8_Template(rf, ctx) { if (rf & 1) {
1352
+ i0.ɵɵelementStart(0, "span", 297);
1353
+ i0.ɵɵtext(1, "N/A");
1354
+ i0.ɵɵelementEnd();
1355
+ } }
1356
+ function TestSuiteFormComponentExtended_div_50_div_14_tr_45_span_10_Template(rf, ctx) { if (rf & 1) {
1357
+ i0.ɵɵelementStart(0, "span", 289);
1358
+ i0.ɵɵtext(1);
1359
+ i0.ɵɵelementEnd();
1360
+ } if (rf & 2) {
1361
+ const result_r37 = i0.ɵɵnextContext().$implicit;
1362
+ i0.ɵɵproperty("ngClass", i0.ɵɵpureFunction2(3, _c6, result_r37.scoreDiff > 0, result_r37.scoreDiff < 0));
1363
+ i0.ɵɵadvance();
1364
+ i0.ɵɵtextInterpolate2(" ", result_r37.scoreDiff > 0 ? "+" : "", "", (result_r37.scoreDiff * 100).toFixed(1), "% ");
1365
+ } }
1366
+ function TestSuiteFormComponentExtended_div_50_div_14_tr_45_span_11_Template(rf, ctx) { if (rf & 1) {
1367
+ i0.ɵɵelementStart(0, "span", 298);
1368
+ i0.ɵɵtext(1, "-");
1369
+ i0.ɵɵelementEnd();
1370
+ } }
1371
+ function TestSuiteFormComponentExtended_div_50_div_14_tr_45_span_13_Template(rf, ctx) { if (rf & 1) {
1372
+ i0.ɵɵelementStart(0, "span", 289);
1373
+ i0.ɵɵtext(1);
1374
+ i0.ɵɵelementEnd();
1375
+ } if (rf & 2) {
1376
+ const result_r37 = i0.ɵɵnextContext().$implicit;
1377
+ i0.ɵɵproperty("ngClass", i0.ɵɵpureFunction2(3, _c6, result_r37.durationDiff < 0, result_r37.durationDiff > 0));
1378
+ i0.ɵɵadvance();
1379
+ i0.ɵɵtextInterpolate2(" ", result_r37.durationDiff > 0 ? "+" : "", "", result_r37.durationDiff.toFixed(1), "s ");
1380
+ } }
1381
+ function TestSuiteFormComponentExtended_div_50_div_14_tr_45_span_14_Template(rf, ctx) { if (rf & 1) {
1382
+ i0.ɵɵelementStart(0, "span", 298);
1383
+ i0.ɵɵtext(1, "-");
1384
+ i0.ɵɵelementEnd();
1385
+ } }
1386
+ function TestSuiteFormComponentExtended_div_50_div_14_tr_45_span_16_Template(rf, ctx) { if (rf & 1) {
1387
+ i0.ɵɵelementStart(0, "span", 299);
1388
+ i0.ɵɵelement(1, "i", 300);
1389
+ i0.ɵɵtext(2, " Fixed ");
1390
+ i0.ɵɵelementEnd();
1391
+ } }
1392
+ function TestSuiteFormComponentExtended_div_50_div_14_tr_45_span_17_Template(rf, ctx) { if (rf & 1) {
1393
+ i0.ɵɵelementStart(0, "span", 301);
1394
+ i0.ɵɵelement(1, "i", 302);
1395
+ i0.ɵɵtext(2, " Broke ");
1396
+ i0.ɵɵelementEnd();
1397
+ } }
1398
+ function TestSuiteFormComponentExtended_div_50_div_14_tr_45_span_18_Template(rf, ctx) { if (rf & 1) {
1399
+ i0.ɵɵelementStart(0, "span", 303);
1400
+ i0.ɵɵelement(1, "i", 222);
1401
+ i0.ɵɵelementEnd();
1402
+ } }
1403
+ function TestSuiteFormComponentExtended_div_50_div_14_tr_45_Template(rf, ctx) { if (rf & 1) {
1404
+ i0.ɵɵelementStart(0, "tr", 289)(1, "td", 202);
1405
+ i0.ɵɵtext(2);
1406
+ i0.ɵɵelementEnd();
1407
+ i0.ɵɵelementStart(3, "td");
1408
+ i0.ɵɵtemplate(4, TestSuiteFormComponentExtended_div_50_div_14_tr_45_span_4_Template, 2, 2, "span", 290)(5, TestSuiteFormComponentExtended_div_50_div_14_tr_45_span_5_Template, 2, 0, "span", 291);
1409
+ i0.ɵɵelementEnd();
1410
+ i0.ɵɵelementStart(6, "td");
1411
+ i0.ɵɵtemplate(7, TestSuiteFormComponentExtended_div_50_div_14_tr_45_span_7_Template, 2, 2, "span", 290)(8, TestSuiteFormComponentExtended_div_50_div_14_tr_45_span_8_Template, 2, 0, "span", 291);
1412
+ i0.ɵɵelementEnd();
1413
+ i0.ɵɵelementStart(9, "td");
1414
+ i0.ɵɵtemplate(10, TestSuiteFormComponentExtended_div_50_div_14_tr_45_span_10_Template, 2, 6, "span", 292)(11, TestSuiteFormComponentExtended_div_50_div_14_tr_45_span_11_Template, 2, 0, "span", 293);
1415
+ i0.ɵɵelementEnd();
1416
+ i0.ɵɵelementStart(12, "td");
1417
+ i0.ɵɵtemplate(13, TestSuiteFormComponentExtended_div_50_div_14_tr_45_span_13_Template, 2, 6, "span", 292)(14, TestSuiteFormComponentExtended_div_50_div_14_tr_45_span_14_Template, 2, 0, "span", 293);
1418
+ i0.ɵɵelementEnd();
1419
+ i0.ɵɵelementStart(15, "td");
1420
+ i0.ɵɵtemplate(16, TestSuiteFormComponentExtended_div_50_div_14_tr_45_span_16_Template, 3, 0, "span", 294)(17, TestSuiteFormComponentExtended_div_50_div_14_tr_45_span_17_Template, 3, 0, "span", 295)(18, TestSuiteFormComponentExtended_div_50_div_14_tr_45_span_18_Template, 2, 0, "span", 296);
1421
+ i0.ɵɵelementEnd()();
1422
+ } if (rf & 2) {
1423
+ const result_r37 = ctx.$implicit;
1424
+ i0.ɵɵproperty("ngClass", i0.ɵɵpureFunction2(13, _c8, result_r37.runA && result_r37.runB && result_r37.runA.status !== "Passed" && result_r37.runB.status === "Passed", result_r37.runA && result_r37.runB && result_r37.runA.status === "Passed" && result_r37.runB.status !== "Passed"));
1425
+ i0.ɵɵadvance(2);
1426
+ i0.ɵɵtextInterpolate(result_r37.testName);
1427
+ i0.ɵɵadvance(2);
1428
+ i0.ɵɵproperty("ngIf", result_r37.runA);
1429
+ i0.ɵɵadvance();
1430
+ i0.ɵɵproperty("ngIf", !result_r37.runA);
1431
+ i0.ɵɵadvance(2);
1432
+ i0.ɵɵproperty("ngIf", result_r37.runB);
1433
+ i0.ɵɵadvance();
1434
+ i0.ɵɵproperty("ngIf", !result_r37.runB);
1435
+ i0.ɵɵadvance(2);
1436
+ i0.ɵɵproperty("ngIf", result_r37.scoreDiff != null);
1437
+ i0.ɵɵadvance();
1438
+ i0.ɵɵproperty("ngIf", result_r37.scoreDiff == null);
1439
+ i0.ɵɵadvance(2);
1440
+ i0.ɵɵproperty("ngIf", result_r37.durationDiff != null);
1441
+ i0.ɵɵadvance();
1442
+ i0.ɵɵproperty("ngIf", result_r37.durationDiff == null);
1443
+ i0.ɵɵadvance(2);
1444
+ i0.ɵɵproperty("ngIf", result_r37.runA && result_r37.runB && result_r37.runA.status !== "Passed" && result_r37.runB.status === "Passed");
1445
+ i0.ɵɵadvance();
1446
+ i0.ɵɵproperty("ngIf", result_r37.runA && result_r37.runB && result_r37.runA.status === "Passed" && result_r37.runB.status !== "Passed");
1447
+ i0.ɵɵadvance();
1448
+ i0.ɵɵproperty("ngIf", !result_r37.statusChanged);
1449
+ } }
1450
+ function TestSuiteFormComponentExtended_div_50_div_14_Template(rf, ctx) { if (rf & 1) {
1451
+ i0.ɵɵelementStart(0, "div", 276)(1, "div", 277)(2, "div", 278)(3, "div", 279);
1452
+ i0.ɵɵtext(4, "Pass Rate Change");
51
1453
  i0.ɵɵelementEnd();
52
- i0.ɵɵelementStart(4, "div", 27)(5, "div", 28)(6, "div", 29);
53
- i0.ɵɵtext(7, "Name");
54
- i0.ɵɵelementEnd();
55
- i0.ɵɵelementStart(8, "div", 30);
56
- i0.ɵɵtext(9);
1454
+ i0.ɵɵelementStart(5, "div", 280);
1455
+ i0.ɵɵelement(6, "i", 10);
1456
+ i0.ɵɵtext(7);
57
1457
  i0.ɵɵelementEnd()();
58
- i0.ɵɵelementStart(10, "div", 28)(11, "div", 29);
59
- i0.ɵɵtext(12, "Status");
1458
+ i0.ɵɵelementStart(8, "div", 278)(9, "div", 279);
1459
+ i0.ɵɵtext(10, "Duration Change");
60
1460
  i0.ɵɵelementEnd();
61
- i0.ɵɵelementStart(13, "div", 30);
62
- i0.ɵɵtext(14);
1461
+ i0.ɵɵelementStart(11, "div", 280);
1462
+ i0.ɵɵelement(12, "i", 10);
1463
+ i0.ɵɵtext(13);
63
1464
  i0.ɵɵelementEnd()();
64
- i0.ɵɵelementStart(15, "div", 28)(16, "div", 29);
65
- i0.ɵɵtext(17, "Created");
1465
+ i0.ɵɵelementStart(14, "div", 281)(15, "div", 279);
1466
+ i0.ɵɵtext(16, "Improved");
66
1467
  i0.ɵɵelementEnd();
67
- i0.ɵɵelementStart(18, "div", 30);
68
- i0.ɵɵtext(19);
69
- i0.ɵɵpipe(20, "date");
1468
+ i0.ɵɵelementStart(17, "div", 282);
1469
+ i0.ɵɵtext(18);
70
1470
  i0.ɵɵelementEnd()();
71
- i0.ɵɵelementStart(21, "div", 28)(22, "div", 29);
72
- i0.ɵɵtext(23, "Updated");
1471
+ i0.ɵɵelementStart(19, "div", 283)(20, "div", 279);
1472
+ i0.ɵɵtext(21, "Regressed");
73
1473
  i0.ɵɵelementEnd();
74
- i0.ɵɵelementStart(24, "div", 30);
75
- i0.ɵɵtext(25);
76
- i0.ɵɵpipe(26, "date");
77
- i0.ɵɵelementEnd()()()()();
78
- } if (rf & 2) {
79
- const ctx_r0 = i0.ɵɵnextContext();
80
- i0.ɵɵadvance(9);
81
- i0.ɵɵtextInterpolate(ctx_r0.record.Name);
82
- i0.ɵɵadvance(5);
83
- i0.ɵɵtextInterpolate(ctx_r0.record.Status);
84
- i0.ɵɵadvance(5);
85
- i0.ɵɵtextInterpolate(i0.ɵɵpipeBind2(20, 4, ctx_r0.record.__mj_CreatedAt, "medium"));
86
- i0.ɵɵadvance(6);
87
- i0.ɵɵtextInterpolate(i0.ɵɵpipeBind2(26, 7, ctx_r0.record.__mj_UpdatedAt, "medium"));
88
- } }
89
- function TestSuiteFormComponentExtended_div_31_div_1_div_1_Template(rf, ctx) { if (rf & 1) {
90
- const _r2 = i0.ɵɵgetCurrentView();
91
- i0.ɵɵelementStart(0, "div", 36);
92
- i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_div_31_div_1_div_1_Template_div_click_0_listener() { const test_r3 = i0.ɵɵrestoreView(_r2).$implicit; const ctx_r0 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r0.openTest(test_r3.TestID)); });
93
- i0.ɵɵelementStart(1, "div", 37);
94
- i0.ɵɵtext(2);
1474
+ i0.ɵɵelementStart(22, "div", 282);
1475
+ i0.ɵɵtext(23);
1476
+ i0.ɵɵelementEnd()()();
1477
+ i0.ɵɵelementStart(24, "div", 284)(25, "h3");
1478
+ i0.ɵɵelement(26, "i", 285);
1479
+ i0.ɵɵtext(27, " Test-by-Test Comparison");
95
1480
  i0.ɵɵelementEnd();
96
- i0.ɵɵelementStart(3, "div", 38);
97
- i0.ɵɵelement(4, "i", 16);
1481
+ i0.ɵɵelementStart(28, "div", 286)(29, "table", 287)(30, "thead")(31, "tr")(32, "th");
1482
+ i0.ɵɵtext(33, "Test");
98
1483
  i0.ɵɵelementEnd();
99
- i0.ɵɵelementStart(5, "div", 39)(6, "div", 40);
100
- i0.ɵɵtext(7);
1484
+ i0.ɵɵelementStart(34, "th");
1485
+ i0.ɵɵtext(35, "Run A Status");
101
1486
  i0.ɵɵelementEnd();
102
- i0.ɵɵelementStart(8, "div", 41);
103
- i0.ɵɵtext(9);
104
- i0.ɵɵelementEnd()();
105
- i0.ɵɵelement(10, "i", 42);
1487
+ i0.ɵɵelementStart(36, "th");
1488
+ i0.ɵɵtext(37, "Run B Status");
106
1489
  i0.ɵɵelementEnd();
107
- } if (rf & 2) {
108
- const test_r3 = ctx.$implicit;
109
- i0.ɵɵadvance(2);
110
- i0.ɵɵtextInterpolate(test_r3.Sequence);
111
- i0.ɵɵadvance(5);
112
- i0.ɵɵtextInterpolate(test_r3.Test);
113
- i0.ɵɵadvance(2);
114
- i0.ɵɵtextInterpolate(test_r3.Status);
115
- } }
116
- function TestSuiteFormComponentExtended_div_31_div_1_Template(rf, ctx) { if (rf & 1) {
117
- i0.ɵɵelementStart(0, "div", 34);
118
- i0.ɵɵtemplate(1, TestSuiteFormComponentExtended_div_31_div_1_div_1_Template, 11, 3, "div", 35);
1490
+ i0.ɵɵelementStart(38, "th");
1491
+ i0.ɵɵtext(39, "Score Diff");
1492
+ i0.ɵɵelementEnd();
1493
+ i0.ɵɵelementStart(40, "th");
1494
+ i0.ɵɵtext(41, "Duration Diff");
119
1495
  i0.ɵɵelementEnd();
1496
+ i0.ɵɵelementStart(42, "th");
1497
+ i0.ɵɵtext(43, "Change");
1498
+ i0.ɵɵelementEnd()()();
1499
+ i0.ɵɵelementStart(44, "tbody");
1500
+ i0.ɵɵtemplate(45, TestSuiteFormComponentExtended_div_50_div_14_tr_45_Template, 19, 16, "tr", 288);
1501
+ i0.ɵɵelementEnd()()()()();
120
1502
  } if (rf & 2) {
1503
+ let tmp_4_0;
121
1504
  const ctx_r0 = i0.ɵɵnextContext(2);
1505
+ i0.ɵɵadvance(5);
1506
+ i0.ɵɵproperty("ngClass", i0.ɵɵpureFunction2(10, _c6, ctx_r0.getComparePassRateDiff() > 0, ctx_r0.getComparePassRateDiff() < 0));
122
1507
  i0.ɵɵadvance();
123
- i0.ɵɵproperty("ngForOf", ctx_r0.suiteTests);
124
- } }
125
- function TestSuiteFormComponentExtended_div_31_div_2_Template(rf, ctx) { if (rf & 1) {
126
- i0.ɵɵelementStart(0, "div", 43);
127
- i0.ɵɵelement(1, "i", 44);
128
- i0.ɵɵelementStart(2, "p");
129
- i0.ɵɵtext(3, "No tests in this suite");
130
- i0.ɵɵelementEnd()();
131
- } }
132
- function TestSuiteFormComponentExtended_div_31_Template(rf, ctx) { if (rf & 1) {
133
- i0.ɵɵelementStart(0, "div", 31);
134
- i0.ɵɵtemplate(1, TestSuiteFormComponentExtended_div_31_div_1_Template, 2, 1, "div", 32)(2, TestSuiteFormComponentExtended_div_31_div_2_Template, 4, 0, "div", 33);
135
- i0.ɵɵelementEnd();
136
- } if (rf & 2) {
137
- const ctx_r0 = i0.ɵɵnextContext();
1508
+ i0.ɵɵproperty("ngClass", i0.ɵɵpureFunction3(13, _c4, ctx_r0.getComparePassRateDiff() > 0, ctx_r0.getComparePassRateDiff() < 0, ctx_r0.getComparePassRateDiff() === 0));
1509
+ i0.ɵɵadvance();
1510
+ i0.ɵɵtextInterpolate2(" ", ctx_r0.getComparePassRateDiff() > 0 ? "+" : "", "", (tmp_4_0 = ctx_r0.getComparePassRateDiff()) == null ? null : tmp_4_0.toFixed(1), "% ");
1511
+ i0.ɵɵadvance(4);
1512
+ i0.ɵɵproperty("ngClass", i0.ɵɵpureFunction2(17, _c6, ctx_r0.getCompareDurationDiff() < 0, ctx_r0.getCompareDurationDiff() > 0));
138
1513
  i0.ɵɵadvance();
139
- i0.ɵɵproperty("ngIf", ctx_r0.suiteTests.length > 0);
1514
+ i0.ɵɵproperty("ngClass", i0.ɵɵpureFunction3(20, _c7, ctx_r0.getCompareDurationDiff() < 0, ctx_r0.getCompareDurationDiff() > 0, ctx_r0.getCompareDurationDiff() === 0));
140
1515
  i0.ɵɵadvance();
141
- i0.ɵɵproperty("ngIf", ctx_r0.testsLoaded && ctx_r0.suiteTests.length === 0);
1516
+ i0.ɵɵtextInterpolate1(" ", ctx_r0.formatDuration(ctx_r0.getAbsCompareDurationDiff()), " ");
1517
+ i0.ɵɵadvance(5);
1518
+ i0.ɵɵtextInterpolate(ctx_r0.getCompareImprovedCount());
1519
+ i0.ɵɵadvance(5);
1520
+ i0.ɵɵtextInterpolate(ctx_r0.getCompareRegressedCount());
1521
+ i0.ɵɵadvance(22);
1522
+ i0.ɵɵproperty("ngForOf", ctx_r0.compareResults);
142
1523
  } }
143
- function TestSuiteFormComponentExtended_div_32_div_1_div_1_span_13_Template(rf, ctx) { if (rf & 1) {
144
- i0.ɵɵelementStart(0, "span");
145
- i0.ɵɵtext(1);
1524
+ function TestSuiteFormComponentExtended_div_50_div_15_Template(rf, ctx) { if (rf & 1) {
1525
+ i0.ɵɵelementStart(0, "div", 58);
1526
+ i0.ɵɵelement(1, "mj-loading", 304);
146
1527
  i0.ɵɵelementEnd();
147
- } if (rf & 2) {
148
- const run_r5 = i0.ɵɵnextContext().$implicit;
149
- i0.ɵɵadvance();
150
- i0.ɵɵtextInterpolate2("", run_r5.PassedTests, "/", run_r5.TotalTests, " passed");
151
1528
  } }
152
- function TestSuiteFormComponentExtended_div_32_div_1_div_1_Template(rf, ctx) { if (rf & 1) {
153
- const _r4 = i0.ɵɵgetCurrentView();
154
- i0.ɵɵelementStart(0, "div", 49);
155
- i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_div_32_div_1_div_1_Template_div_click_0_listener() { const run_r5 = i0.ɵɵrestoreView(_r4).$implicit; const ctx_r0 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r0.openSuiteRun(run_r5.ID)); });
156
- i0.ɵɵelementStart(1, "div", 50);
157
- i0.ɵɵelement(2, "i", 51);
1529
+ function TestSuiteFormComponentExtended_div_50_div_16_Template(rf, ctx) { if (rf & 1) {
1530
+ i0.ɵɵelementStart(0, "div", 305)(1, "div", 306);
1531
+ i0.ɵɵelement(2, "i", 27);
158
1532
  i0.ɵɵelementEnd();
159
- i0.ɵɵelementStart(3, "div", 52)(4, "div", 53)(5, "span", 54);
160
- i0.ɵɵtext(6);
1533
+ i0.ɵɵelementStart(3, "h4");
1534
+ i0.ɵɵtext(4, "Select Two Runs to Compare");
161
1535
  i0.ɵɵelementEnd();
162
- i0.ɵɵelementStart(7, "span", 55);
163
- i0.ɵɵtext(8);
1536
+ i0.ɵɵelementStart(5, "p");
1537
+ i0.ɵɵtext(6, "Choose a baseline run (A) and a comparison run (B) from the lists above to see a detailed side-by-side comparison.");
164
1538
  i0.ɵɵelementEnd()();
165
- i0.ɵɵelementStart(9, "div", 56)(10, "span");
166
- i0.ɵɵtext(11);
167
- i0.ɵɵpipe(12, "date");
1539
+ } }
1540
+ function TestSuiteFormComponentExtended_div_50_div_17_Template(rf, ctx) { if (rf & 1) {
1541
+ const _r38 = i0.ɵɵgetCurrentView();
1542
+ i0.ɵɵelementStart(0, "div", 77)(1, "div", 78);
1543
+ i0.ɵɵelement(2, "i", 27);
168
1544
  i0.ɵɵelementEnd();
169
- i0.ɵɵtemplate(13, TestSuiteFormComponentExtended_div_32_div_1_div_1_span_13_Template, 2, 2, "span", 57);
170
- i0.ɵɵelementEnd()();
171
- i0.ɵɵelement(14, "i", 42);
1545
+ i0.ɵɵelementStart(3, "h4");
1546
+ i0.ɵɵtext(4, "Not Enough Runs to Compare");
172
1547
  i0.ɵɵelementEnd();
173
- } if (rf & 2) {
174
- const run_r5 = ctx.$implicit;
175
- i0.ɵɵadvance();
176
- i0.ɵɵstyleProp("background-color", run_r5.Status === "Completed" ? "#4caf50" : run_r5.Status === "Failed" ? "#f44336" : "#2196f3");
177
- i0.ɵɵadvance(5);
178
- i0.ɵɵtextInterpolate(run_r5.ID.substring(0, 8));
179
- i0.ɵɵadvance(2);
180
- i0.ɵɵtextInterpolate(run_r5.Status);
181
- i0.ɵɵadvance(3);
182
- i0.ɵɵtextInterpolate(i0.ɵɵpipeBind2(12, 6, run_r5.StartedAt, "short"));
183
- i0.ɵɵadvance(2);
184
- i0.ɵɵproperty("ngIf", run_r5.TotalTests);
185
- } }
186
- function TestSuiteFormComponentExtended_div_32_div_1_Template(rf, ctx) { if (rf & 1) {
187
- i0.ɵɵelementStart(0, "div", 47);
188
- i0.ɵɵtemplate(1, TestSuiteFormComponentExtended_div_32_div_1_div_1_Template, 15, 9, "div", 48);
1548
+ i0.ɵɵelementStart(5, "p");
1549
+ i0.ɵɵtext(6, "You need at least 2 suite runs to use the comparison feature.");
189
1550
  i0.ɵɵelementEnd();
190
- } if (rf & 2) {
191
- const ctx_r0 = i0.ɵɵnextContext(2);
192
- i0.ɵɵadvance();
193
- i0.ɵɵproperty("ngForOf", ctx_r0.suiteRuns);
194
- } }
195
- function TestSuiteFormComponentExtended_div_32_div_2_Template(rf, ctx) { if (rf & 1) {
196
- i0.ɵɵelementStart(0, "div", 43);
197
- i0.ɵɵelement(1, "i", 44);
198
- i0.ɵɵelementStart(2, "p");
199
- i0.ɵɵtext(3, "No runs for this suite");
1551
+ i0.ɵɵelementStart(7, "button", 15);
1552
+ i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_div_50_div_17_Template_button_click_7_listener() { i0.ɵɵrestoreView(_r38); const ctx_r0 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r0.runSuite()); });
1553
+ i0.ɵɵelement(8, "i", 16);
1554
+ i0.ɵɵtext(9, " Run Suite Now ");
200
1555
  i0.ɵɵelementEnd()();
201
1556
  } }
202
- function TestSuiteFormComponentExtended_div_32_Template(rf, ctx) { if (rf & 1) {
203
- i0.ɵɵelementStart(0, "div", 45);
204
- i0.ɵɵtemplate(1, TestSuiteFormComponentExtended_div_32_div_1_Template, 2, 1, "div", 46)(2, TestSuiteFormComponentExtended_div_32_div_2_Template, 4, 0, "div", 33);
1557
+ function TestSuiteFormComponentExtended_div_50_Template(rf, ctx) { if (rf & 1) {
1558
+ i0.ɵɵelementStart(0, "div", 249)(1, "div", 250)(2, "div", 251)(3, "h4");
1559
+ i0.ɵɵtext(4, "Run A (Baseline)");
1560
+ i0.ɵɵelementEnd();
1561
+ i0.ɵɵtemplate(5, TestSuiteFormComponentExtended_div_50_div_5_Template, 2, 1, "div", 252)(6, TestSuiteFormComponentExtended_div_50_div_6_Template, 12, 5, "div", 253);
1562
+ i0.ɵɵelementEnd();
1563
+ i0.ɵɵelementStart(7, "div", 254);
1564
+ i0.ɵɵelement(8, "i", 255);
1565
+ i0.ɵɵelementEnd();
1566
+ i0.ɵɵelementStart(9, "div", 251)(10, "h4");
1567
+ i0.ɵɵtext(11, "Run B (Compare)");
1568
+ i0.ɵɵelementEnd();
1569
+ i0.ɵɵtemplate(12, TestSuiteFormComponentExtended_div_50_div_12_Template, 2, 1, "div", 252)(13, TestSuiteFormComponentExtended_div_50_div_13_Template, 12, 5, "div", 253);
1570
+ i0.ɵɵelementEnd()();
1571
+ i0.ɵɵtemplate(14, TestSuiteFormComponentExtended_div_50_div_14_Template, 46, 24, "div", 256)(15, TestSuiteFormComponentExtended_div_50_div_15_Template, 2, 0, "div", 55)(16, TestSuiteFormComponentExtended_div_50_div_16_Template, 7, 0, "div", 257)(17, TestSuiteFormComponentExtended_div_50_div_17_Template, 10, 0, "div", 57);
205
1572
  i0.ɵɵelementEnd();
206
1573
  } if (rf & 2) {
207
1574
  const ctx_r0 = i0.ɵɵnextContext();
1575
+ i0.ɵɵadvance(5);
1576
+ i0.ɵɵproperty("ngIf", !ctx_r0.loadingRuns && ctx_r0.suiteRuns.length > 0);
1577
+ i0.ɵɵadvance();
1578
+ i0.ɵɵproperty("ngIf", ctx_r0.compareRunA);
1579
+ i0.ɵɵadvance(6);
1580
+ i0.ɵɵproperty("ngIf", !ctx_r0.loadingRuns && ctx_r0.suiteRuns.length > 0);
1581
+ i0.ɵɵadvance();
1582
+ i0.ɵɵproperty("ngIf", ctx_r0.compareRunB);
208
1583
  i0.ɵɵadvance();
209
- i0.ɵɵproperty("ngIf", ctx_r0.suiteRuns.length > 0);
1584
+ i0.ɵɵproperty("ngIf", ctx_r0.compareRunA && ctx_r0.compareRunB && !ctx_r0.loadingCompare);
210
1585
  i0.ɵɵadvance();
211
- i0.ɵɵproperty("ngIf", ctx_r0.runsLoaded && ctx_r0.suiteRuns.length === 0);
1586
+ i0.ɵɵproperty("ngIf", ctx_r0.loadingCompare);
1587
+ i0.ɵɵadvance();
1588
+ i0.ɵɵproperty("ngIf", !ctx_r0.compareRunA || !ctx_r0.compareRunB);
1589
+ i0.ɵɵadvance();
1590
+ i0.ɵɵproperty("ngIf", ctx_r0.runsLoaded && ctx_r0.suiteRuns.length < 2);
1591
+ } }
1592
+ function TestSuiteFormComponentExtended_div_53_Template(rf, ctx) { if (rf & 1) {
1593
+ const _r39 = i0.ɵɵgetCurrentView();
1594
+ i0.ɵɵelementStart(0, "div", 307)(1, "div", 308);
1595
+ i0.ɵɵelement(2, "i", 35);
1596
+ i0.ɵɵtext(3, " Shortcuts ");
1597
+ i0.ɵɵelementStart(4, "button", 309);
1598
+ i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_div_53_Template_button_click_4_listener() { i0.ɵɵrestoreView(_r39); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.toggleShortcuts()); });
1599
+ i0.ɵɵelement(5, "i", 189);
1600
+ i0.ɵɵelementEnd()();
1601
+ i0.ɵɵelementStart(6, "div", 310)(7, "div", 311)(8, "span");
1602
+ i0.ɵɵtext(9, "Refresh");
1603
+ i0.ɵɵelementEnd();
1604
+ i0.ɵɵelementStart(10, "span", 312)(11, "kbd");
1605
+ i0.ɵɵtext(12, "Cmd");
1606
+ i0.ɵɵelementEnd();
1607
+ i0.ɵɵelementStart(13, "kbd");
1608
+ i0.ɵɵtext(14, "R");
1609
+ i0.ɵɵelementEnd()()();
1610
+ i0.ɵɵelementStart(15, "div", 311)(16, "span");
1611
+ i0.ɵɵtext(17, "Run Suite");
1612
+ i0.ɵɵelementEnd();
1613
+ i0.ɵɵelementStart(18, "span", 312)(19, "kbd");
1614
+ i0.ɵɵtext(20, "Cmd");
1615
+ i0.ɵɵelementEnd();
1616
+ i0.ɵɵelementStart(21, "kbd");
1617
+ i0.ɵɵtext(22, "Enter");
1618
+ i0.ɵɵelementEnd()()();
1619
+ i0.ɵɵelementStart(23, "div", 311)(24, "span");
1620
+ i0.ɵɵtext(25, "Switch Tabs");
1621
+ i0.ɵɵelementEnd();
1622
+ i0.ɵɵelementStart(26, "span", 312)(27, "kbd");
1623
+ i0.ɵɵtext(28, "1");
1624
+ i0.ɵɵelementEnd();
1625
+ i0.ɵɵtext(29, "-");
1626
+ i0.ɵɵelementStart(30, "kbd");
1627
+ i0.ɵɵtext(31, "5");
1628
+ i0.ɵɵelementEnd()()()()();
212
1629
  } }
1630
+ /** Settings key for keyboard shortcuts visibility */
1631
+ const SHORTCUTS_SETTINGS_KEY = '__mj.Testing.ShowKeyboardShortcuts';
213
1632
  let TestSuiteFormComponentExtended = class TestSuiteFormComponentExtended extends TestSuiteFormComponent {
214
- constructor(elementRef, sharedService, router, route, cdr, testingDialogService) {
1633
+ constructor(elementRef, sharedService, router, route, cdr, testingDialogService, evalPrefsService) {
215
1634
  super(elementRef, sharedService, router, route, cdr);
216
1635
  this.router = router;
217
1636
  this.cdr = cdr;
218
1637
  this.testingDialogService = testingDialogService;
1638
+ this.evalPrefsService = evalPrefsService;
219
1639
  this.destroy$ = new Subject();
1640
+ // UI state
220
1641
  this.activeTab = 'overview';
221
1642
  this.loading = false;
1643
+ this.loadingTests = false;
1644
+ this.loadingRuns = false;
1645
+ this.loadingAnalytics = false;
1646
+ this.loadingCompare = false;
222
1647
  this.testsLoaded = false;
223
1648
  this.runsLoaded = false;
1649
+ this.analyticsLoaded = false;
1650
+ this.isRefreshing = false;
1651
+ this.error = null;
1652
+ // Related data
224
1653
  this.suiteTests = [];
225
1654
  this.suiteRuns = [];
1655
+ // Analytics data
1656
+ this.analyticsData = [];
1657
+ this.uniqueTags = [];
1658
+ this.selectedTags = []; // Multi-select: empty array means "All Tags"
1659
+ this.analyticsTimeRange = '30d';
1660
+ this.analyticsView = 'summary';
1661
+ this.matrixData = [];
1662
+ this.loadingMatrix = false;
1663
+ this.matrixLoaded = false;
1664
+ this.chartRendered = false;
1665
+ // Compare data
1666
+ this.compareRunA = null;
1667
+ this.compareRunB = null;
1668
+ this.compareResults = [];
1669
+ this.compareRunATests = [];
1670
+ this.compareRunBTests = [];
1671
+ // Keyboard shortcuts
1672
+ this.keyboardShortcutsEnabled = true;
1673
+ this.showShortcuts = false; // Hidden by default
1674
+ this.shortcutsSettingEntity = null;
1675
+ this.metadata = new Metadata();
1676
+ // Evaluation preferences
1677
+ this.evalPreferences = { showExecution: true, showHuman: true, showAuto: false };
1678
+ // Filter collapse state
1679
+ this.filtersCollapsed = false;
1680
+ // Matrix sorting
1681
+ this.matrixSortBy = 'sequence';
1682
+ this.matrixSortAsc = true;
1683
+ // Matrix row selection
1684
+ this.selectedMatrixTestId = null;
1685
+ // Matrix test name filter
1686
+ this.matrixTestFilter = '';
1687
+ this.matrixFilterSubject$ = new Subject();
226
1688
  }
227
1689
  async ngOnInit() {
228
1690
  await super.ngOnInit();
1691
+ this.loadShortcutsSetting();
1692
+ // Subscribe to evaluation preferences
1693
+ this.evalPrefsService.preferences$
1694
+ .pipe(takeUntil(this.destroy$))
1695
+ .subscribe(prefs => {
1696
+ this.evalPreferences = prefs;
1697
+ this.cdr.markForCheck();
1698
+ // Re-render chart when preferences change (D3 chart needs manual update)
1699
+ if (this.chartRendered && this.analyticsView === 'chart') {
1700
+ this.renderChart();
1701
+ }
1702
+ });
1703
+ // Subscribe to matrix filter with debounce
1704
+ this.matrixFilterSubject$
1705
+ .pipe(debounceTime(300), distinctUntilChanged(), takeUntil(this.destroy$))
1706
+ .subscribe(value => {
1707
+ this.matrixTestFilter = value;
1708
+ this.cdr.markForCheck();
1709
+ });
1710
+ }
1711
+ ngAfterViewInit() {
1712
+ // Initialize any view-dependent logic
229
1713
  }
230
1714
  ngOnDestroy() {
231
1715
  this.destroy$.next();
232
1716
  this.destroy$.complete();
233
1717
  }
1718
+ // Keyboard shortcuts
1719
+ handleKeyboardShortcut(event) {
1720
+ if (!this.keyboardShortcutsEnabled)
1721
+ return;
1722
+ // Cmd/Ctrl + R: Refresh
1723
+ if ((event.metaKey || event.ctrlKey) && event.key === 'r' && !event.shiftKey) {
1724
+ event.preventDefault();
1725
+ this.refresh();
1726
+ return;
1727
+ }
1728
+ // Cmd/Ctrl + Enter: Run suite
1729
+ if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {
1730
+ event.preventDefault();
1731
+ this.runSuite();
1732
+ return;
1733
+ }
1734
+ // Number keys for tabs (1-5)
1735
+ if (!event.metaKey && !event.ctrlKey && !event.altKey) {
1736
+ switch (event.key) {
1737
+ case '1':
1738
+ this.changeTab('overview');
1739
+ break;
1740
+ case '2':
1741
+ this.changeTab('tests');
1742
+ break;
1743
+ case '3':
1744
+ this.changeTab('runs');
1745
+ break;
1746
+ case '4':
1747
+ this.changeTab('analytics');
1748
+ break;
1749
+ case '5':
1750
+ this.changeTab('compare');
1751
+ break;
1752
+ }
1753
+ }
1754
+ }
234
1755
  changeTab(tab) {
235
1756
  this.activeTab = tab;
236
1757
  if (tab === 'tests' && !this.testsLoaded)
237
1758
  this.loadTests();
238
1759
  if (tab === 'runs' && !this.runsLoaded)
239
1760
  this.loadRuns();
1761
+ if (tab === 'analytics' && !this.analyticsLoaded)
1762
+ this.loadAnalytics();
240
1763
  this.cdr.markForCheck();
241
1764
  }
242
1765
  async loadTests() {
243
- const rv = new RunView();
244
- const result = await rv.RunView({
245
- EntityName: 'MJ: Test Suite Tests',
246
- ExtraFilter: `SuiteID='${this.record.ID}'`,
247
- OrderBy: 'Sequence',
248
- ResultType: 'entity_object'
249
- });
250
- if (result.Success)
251
- this.suiteTests = result.Results || [];
252
- this.testsLoaded = true;
1766
+ if (this.testsLoaded)
1767
+ return;
1768
+ this.loadingTests = true;
253
1769
  this.cdr.markForCheck();
1770
+ try {
1771
+ const rv = new RunView();
1772
+ const result = await rv.RunView({
1773
+ EntityName: 'MJ: Test Suite Tests',
1774
+ ExtraFilter: `SuiteID='${this.record.ID}'`,
1775
+ OrderBy: 'Sequence',
1776
+ ResultType: 'entity_object'
1777
+ });
1778
+ if (result.Success)
1779
+ this.suiteTests = result.Results || [];
1780
+ this.testsLoaded = true;
1781
+ }
1782
+ catch (error) {
1783
+ console.error('Error loading tests:', error);
1784
+ SharedService.Instance.CreateSimpleNotification('Failed to load tests', 'error', 3000);
1785
+ }
1786
+ finally {
1787
+ this.loadingTests = false;
1788
+ this.cdr.markForCheck();
1789
+ }
254
1790
  }
255
1791
  async loadRuns() {
256
- const rv = new RunView();
257
- const result = await rv.RunView({
258
- EntityName: 'MJ: Test Suite Runs',
259
- ExtraFilter: `SuiteID='${this.record.ID}'`,
260
- OrderBy: 'StartedAt DESC',
261
- MaxRows: 50,
262
- ResultType: 'entity_object'
263
- });
264
- if (result.Success)
265
- this.suiteRuns = result.Results || [];
266
- this.runsLoaded = true;
1792
+ if (this.runsLoaded)
1793
+ return;
1794
+ this.loadingRuns = true;
267
1795
  this.cdr.markForCheck();
1796
+ try {
1797
+ const rv = new RunView();
1798
+ const result = await rv.RunView({
1799
+ EntityName: 'MJ: Test Suite Runs',
1800
+ ExtraFilter: `SuiteID='${this.record.ID}'`,
1801
+ OrderBy: 'StartedAt DESC',
1802
+ MaxRows: 50,
1803
+ ResultType: 'entity_object'
1804
+ });
1805
+ if (result.Success)
1806
+ this.suiteRuns = result.Results || [];
1807
+ this.runsLoaded = true;
1808
+ }
1809
+ catch (error) {
1810
+ console.error('Error loading runs:', error);
1811
+ SharedService.Instance.CreateSimpleNotification('Failed to load runs', 'error', 3000);
1812
+ }
1813
+ finally {
1814
+ this.loadingRuns = false;
1815
+ this.cdr.markForCheck();
1816
+ }
268
1817
  }
269
1818
  getStatusColor() {
270
- return this.record.Status === 'Active' ? '#4caf50' : '#9e9e9e';
1819
+ switch (this.record.Status) {
1820
+ case 'Active': return '#10b981';
1821
+ case 'Disabled': return '#6b7280';
1822
+ case 'Pending': return '#f59e0b';
1823
+ default: return '#9ca3af';
1824
+ }
1825
+ }
1826
+ getStatusClass() {
1827
+ return `status-${this.record.Status?.toLowerCase() || 'unknown'}`;
1828
+ }
1829
+ getRunStatusColor(status) {
1830
+ switch (status) {
1831
+ case 'Completed': return '#10b981';
1832
+ case 'Failed': return '#ef4444';
1833
+ case 'Running': return '#3b82f6';
1834
+ case 'Pending': return '#8b5cf6';
1835
+ case 'Cancelled': return '#6b7280';
1836
+ default: return '#9ca3af';
1837
+ }
1838
+ }
1839
+ formatTimeout(ms) {
1840
+ if (ms === null || ms === undefined)
1841
+ return 'Default (5 min)';
1842
+ if (ms < 1000)
1843
+ return `${ms}ms`;
1844
+ if (ms < 60000)
1845
+ return `${(ms / 1000).toFixed(1)}s`;
1846
+ if (ms < 3600000) {
1847
+ const mins = Math.floor(ms / 60000);
1848
+ const secs = Math.floor((ms % 60000) / 1000);
1849
+ return secs > 0 ? `${mins}m ${secs}s` : `${mins}m`;
1850
+ }
1851
+ const hours = Math.floor(ms / 3600000);
1852
+ const mins = Math.floor((ms % 3600000) / 60000);
1853
+ return mins > 0 ? `${hours}h ${mins}m` : `${hours}h`;
1854
+ }
1855
+ getRelativeTime(date) {
1856
+ if (!date)
1857
+ return 'N/A';
1858
+ const d = new Date(date);
1859
+ const now = new Date();
1860
+ const diffMs = now.getTime() - d.getTime();
1861
+ const diffMins = Math.floor(diffMs / 60000);
1862
+ const diffHours = Math.floor(diffMs / 3600000);
1863
+ const diffDays = Math.floor(diffMs / 86400000);
1864
+ if (diffMins < 1)
1865
+ return 'Just now';
1866
+ if (diffMins < 60)
1867
+ return `${diffMins}m ago`;
1868
+ if (diffHours < 24)
1869
+ return `${diffHours}h ago`;
1870
+ if (diffDays < 7)
1871
+ return `${diffDays}d ago`;
1872
+ return d.toLocaleDateString();
1873
+ }
1874
+ getPassRate(run) {
1875
+ const total = run.TotalTests || 0;
1876
+ const passed = run.PassedTests || 0;
1877
+ if (total === 0)
1878
+ return 0;
1879
+ return (passed / total) * 100;
271
1880
  }
272
1881
  openTest(testId) {
273
1882
  SharedService.Instance.OpenEntityRecord('MJ: Tests', CompositeKey.FromID(testId));
@@ -280,71 +1889,1563 @@ let TestSuiteFormComponentExtended = class TestSuiteFormComponentExtended extend
280
1889
  this.testingDialogService.OpenSuiteDialog(this.record.ID);
281
1890
  }
282
1891
  }
283
- static { this.ɵfac = function TestSuiteFormComponentExtended_Factory(t) { return new (t || TestSuiteFormComponentExtended)(i0.ɵɵdirectiveInject(i0.ElementRef), i0.ɵɵdirectiveInject(i1.SharedService), i0.ɵɵdirectiveInject(i2.Router), i0.ɵɵdirectiveInject(i2.ActivatedRoute), i0.ɵɵdirectiveInject(i0.ChangeDetectorRef), i0.ɵɵdirectiveInject(i3.TestingDialogService)); }; }
284
- static { this.ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: TestSuiteFormComponentExtended, selectors: [["mj-test-suite-form"]], features: [i0.ɵɵInheritDefinitionFeature], decls: 33, vars: 18, consts: [["kendoDialogContainer", "", 1, "test-suite-form"], [1, "suite-header"], [1, "header-content"], [1, "header-left"], [1, "suite-icon"], [1, "fas", "fa-layer-group"], [1, "suite-info"], [1, "suite-meta"], [1, "status-badge"], [1, "header-actions"], ["kendoButton", "", "icon", "play", 3, "click"], ["class", "suite-description", 4, "ngIf"], [1, "tabs-container"], [1, "tabs"], [1, "tab", 3, "click"], [1, "fas", "fa-th-large"], [1, "fas", "fa-flask"], ["class", "tab-badge", 4, "ngIf"], [1, "fas", "fa-history"], [1, "tab-content"], ["class", "overview-tab", 4, "ngIf"], ["class", "tests-tab", 4, "ngIf"], ["class", "runs-tab", 4, "ngIf"], [1, "suite-description"], [1, "tab-badge"], [1, "overview-tab"], [1, "info-section"], [1, "info-grid"], [1, "info-item"], [1, "info-label"], [1, "info-value"], [1, "tests-tab"], ["class", "tests-list", 4, "ngIf"], ["class", "no-data", 4, "ngIf"], [1, "tests-list"], ["class", "test-item", 3, "click", 4, "ngFor", "ngForOf"], [1, "test-item", 3, "click"], [1, "test-sequence"], [1, "test-icon"], [1, "test-content"], [1, "test-name"], [1, "test-status"], [1, "fas", "fa-chevron-right"], [1, "no-data"], [1, "fas", "fa-inbox"], [1, "runs-tab"], ["class", "runs-list", 4, "ngIf"], [1, "runs-list"], ["class", "run-item", 3, "click", 4, "ngFor", "ngForOf"], [1, "run-item", 3, "click"], [1, "run-icon"], [1, "fas", "fa-play"], [1, "run-content"], [1, "run-header"], [1, "run-id"], [1, "run-status"], [1, "run-meta"], [4, "ngIf"]], template: function TestSuiteFormComponentExtended_Template(rf, ctx) { if (rf & 1) {
285
- i0.ɵɵelementStart(0, "div", 0)(1, "div", 1)(2, "div", 2)(3, "div", 3)(4, "div", 4);
286
- i0.ɵɵelement(5, "i", 5);
1892
+ async refresh() {
1893
+ this.isRefreshing = true;
1894
+ this.cdr.markForCheck();
1895
+ try {
1896
+ await this.record.Load(this.record.ID);
1897
+ // Reset lazy-loaded data
1898
+ if (this.testsLoaded) {
1899
+ this.testsLoaded = false;
1900
+ this.suiteTests = [];
1901
+ await this.loadTests();
1902
+ }
1903
+ if (this.runsLoaded) {
1904
+ this.runsLoaded = false;
1905
+ this.suiteRuns = [];
1906
+ await this.loadRuns();
1907
+ }
1908
+ if (this.analyticsLoaded) {
1909
+ this.analyticsLoaded = false;
1910
+ this.analyticsData = [];
1911
+ // Also reset matrix data so it reloads with fresh data
1912
+ this.matrixLoaded = false;
1913
+ this.matrixData = [];
1914
+ await this.loadAnalytics();
1915
+ // Reload matrix if currently viewing matrix or chart view
1916
+ if (this.analyticsView === 'matrix' || this.analyticsView === 'chart') {
1917
+ await this.loadMatrixData();
1918
+ }
1919
+ }
1920
+ SharedService.Instance.CreateSimpleNotification('Refreshed successfully', 'success', 2000);
1921
+ }
1922
+ catch {
1923
+ SharedService.Instance.CreateSimpleNotification('Failed to refresh', 'error', 3000);
1924
+ }
1925
+ finally {
1926
+ this.isRefreshing = false;
1927
+ this.cdr.markForCheck();
1928
+ }
1929
+ }
1930
+ // ==========================================
1931
+ // Analytics Tab Methods
1932
+ // ==========================================
1933
+ async loadAnalytics() {
1934
+ if (this.analyticsLoaded)
1935
+ return;
1936
+ this.loadingAnalytics = true;
1937
+ this.cdr.markForCheck();
1938
+ try {
1939
+ // Load all runs for analytics (not just recent 50)
1940
+ const rv = new RunView();
1941
+ const result = await rv.RunView({
1942
+ EntityName: 'MJ: Test Suite Runs',
1943
+ ExtraFilter: `SuiteID='${this.record.ID}'`,
1944
+ OrderBy: 'StartedAt DESC',
1945
+ MaxRows: 200,
1946
+ ResultType: 'entity_object'
1947
+ });
1948
+ if (result.Success && result.Results) {
1949
+ // Process runs into analytics data points
1950
+ this.analyticsData = result.Results.map(run => this.runToDataPoint(run));
1951
+ // Extract unique tags
1952
+ this.uniqueTags = TagsHelper.getUniqueTags(result.Results.map(r => r.Tags));
1953
+ // Also populate suiteRuns if not already loaded
1954
+ if (!this.runsLoaded) {
1955
+ this.suiteRuns = result.Results.slice(0, 50);
1956
+ this.runsLoaded = true;
1957
+ }
1958
+ }
1959
+ this.analyticsLoaded = true;
1960
+ }
1961
+ catch (error) {
1962
+ console.error('Error loading analytics:', error);
1963
+ SharedService.Instance.CreateSimpleNotification('Failed to load analytics data', 'error', 3000);
1964
+ }
1965
+ finally {
1966
+ this.loadingAnalytics = false;
1967
+ this.cdr.markForCheck();
1968
+ }
1969
+ }
1970
+ runToDataPoint(run) {
1971
+ const total = run.TotalTests || 0;
1972
+ const passed = run.PassedTests || 0;
1973
+ return {
1974
+ runId: run.ID,
1975
+ date: run.StartedAt ? new Date(run.StartedAt) : new Date(),
1976
+ passRate: total > 0 ? (passed / total) * 100 : 0,
1977
+ totalTests: total,
1978
+ passedTests: passed,
1979
+ failedTests: run.FailedTests || 0,
1980
+ errorTests: run.ErrorTests || 0,
1981
+ skippedTests: run.SkippedTests || 0,
1982
+ duration: run.TotalDurationSeconds || 0,
1983
+ cost: run.TotalCostUSD || 0,
1984
+ tags: TagsHelper.parseTags(run.Tags),
1985
+ status: run.Status || 'Unknown'
1986
+ };
1987
+ }
1988
+ getFilteredAnalyticsData() {
1989
+ let data = this.analyticsData;
1990
+ // Apply time range filter
1991
+ const now = new Date();
1992
+ let cutoffDate = null;
1993
+ switch (this.analyticsTimeRange) {
1994
+ case '7d':
1995
+ cutoffDate = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
1996
+ break;
1997
+ case '30d':
1998
+ cutoffDate = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
1999
+ break;
2000
+ case '90d':
2001
+ cutoffDate = new Date(now.getTime() - 90 * 24 * 60 * 60 * 1000);
2002
+ break;
2003
+ }
2004
+ if (cutoffDate) {
2005
+ data = data.filter(d => d.date >= cutoffDate);
2006
+ }
2007
+ // Apply tag filter (multi-select: empty array means all tags)
2008
+ if (this.selectedTags.length > 0) {
2009
+ data = data.filter(d => this.selectedTags.some(tag => d.tags.includes(tag)));
2010
+ }
2011
+ return data;
2012
+ }
2013
+ setTimeRange(range) {
2014
+ this.analyticsTimeRange = range;
2015
+ // Reload matrix data when time range changes (if currently viewing matrix or chart)
2016
+ if (this.analyticsView === 'matrix' || this.analyticsView === 'chart') {
2017
+ this.reloadMatrixData();
2018
+ }
2019
+ this.cdr.markForCheck();
2020
+ }
2021
+ /**
2022
+ * Toggle a tag in the multi-select filter.
2023
+ * If tag is null, clear all selections (show all tags).
2024
+ */
2025
+ toggleTagFilter(tag) {
2026
+ if (tag === null) {
2027
+ // Clear all - show all tags
2028
+ this.selectedTags = [];
2029
+ }
2030
+ else {
2031
+ // Toggle the tag
2032
+ const index = this.selectedTags.indexOf(tag);
2033
+ if (index >= 0) {
2034
+ this.selectedTags = this.selectedTags.filter(t => t !== tag);
2035
+ }
2036
+ else {
2037
+ this.selectedTags = [...this.selectedTags, tag];
2038
+ }
2039
+ }
2040
+ // Reload matrix data when tag filter changes (if currently viewing matrix or chart)
2041
+ if (this.analyticsView === 'matrix' || this.analyticsView === 'chart') {
2042
+ this.reloadMatrixData();
2043
+ }
2044
+ this.cdr.markForCheck();
2045
+ }
2046
+ /**
2047
+ * Check if a tag is currently selected in the filter
2048
+ */
2049
+ isTagSelected(tag) {
2050
+ return this.selectedTags.includes(tag);
2051
+ }
2052
+ setAnalyticsView(view) {
2053
+ this.analyticsView = view;
2054
+ if ((view === 'matrix' || view === 'chart') && !this.matrixLoaded) {
2055
+ this.loadMatrixData();
2056
+ }
2057
+ // Render chart when switching to chart view
2058
+ if (view === 'chart' && this.matrixLoaded) {
2059
+ setTimeout(() => this.renderChart(), 100);
2060
+ }
2061
+ this.cdr.markForCheck();
2062
+ }
2063
+ /**
2064
+ * Force reload of matrix data (used when filters change)
2065
+ */
2066
+ reloadMatrixData() {
2067
+ this.matrixLoaded = false;
2068
+ this.loadMatrixData();
2069
+ }
2070
+ async loadMatrixData() {
2071
+ if (this.loadingMatrix)
2072
+ return;
2073
+ this.loadingMatrix = true;
2074
+ this.cdr.markForCheck();
2075
+ try {
2076
+ const rv = new RunView();
2077
+ const filteredRuns = this.getFilteredAnalyticsData();
2078
+ // Load test runs for each suite run (limit to most recent 10 for performance)
2079
+ const runsToLoad = filteredRuns.slice(0, 10);
2080
+ const matrixData = [];
2081
+ // Collect all test run IDs to batch load feedbacks
2082
+ const allTestRunIds = [];
2083
+ for (const runData of runsToLoad) {
2084
+ const testRunsResult = await rv.RunView({
2085
+ EntityName: 'MJ: Test Runs',
2086
+ ExtraFilter: `TestSuiteRunID='${runData.runId}'`,
2087
+ OrderBy: 'Sequence',
2088
+ ResultType: 'entity_object'
2089
+ });
2090
+ if (testRunsResult.Success && testRunsResult.Results) {
2091
+ const testResults = new Map();
2092
+ for (const testRun of testRunsResult.Results) {
2093
+ allTestRunIds.push(testRun.ID);
2094
+ testResults.set(testRun.TestID, {
2095
+ testRunId: testRun.ID,
2096
+ testId: testRun.TestID,
2097
+ testName: testRun.Test || 'Unknown',
2098
+ status: testRun.Status,
2099
+ score: testRun.Score,
2100
+ duration: testRun.DurationSeconds,
2101
+ humanRating: null, // Will be populated below
2102
+ humanComments: null, // Will be populated below
2103
+ sequence: testRun.Sequence ?? 0
2104
+ });
2105
+ }
2106
+ matrixData.push({
2107
+ runId: runData.runId,
2108
+ date: runData.date,
2109
+ tags: runData.tags,
2110
+ status: runData.status,
2111
+ passRate: runData.passRate,
2112
+ testResults
2113
+ });
2114
+ }
2115
+ }
2116
+ // Batch load feedbacks for all test runs
2117
+ if (allTestRunIds.length > 0) {
2118
+ const feedbackMap = await this.loadFeedbacksForTestRuns(allTestRunIds);
2119
+ // Apply feedbacks to matrix data
2120
+ for (const run of matrixData) {
2121
+ run.testResults.forEach((cell, _testId) => {
2122
+ const feedback = feedbackMap.get(cell.testRunId);
2123
+ if (feedback) {
2124
+ if (feedback.Rating != null) {
2125
+ cell.humanRating = feedback.Rating;
2126
+ }
2127
+ // Use CorrectionSummary if available (from inline feedback), fallback to Comments
2128
+ const commentText = feedback.CorrectionSummary || feedback.Comments;
2129
+ if (commentText) {
2130
+ cell.humanComments = commentText;
2131
+ }
2132
+ }
2133
+ });
2134
+ }
2135
+ }
2136
+ this.matrixData = matrixData;
2137
+ this.matrixLoaded = true;
2138
+ // Render chart if currently on chart view
2139
+ if (this.analyticsView === 'chart') {
2140
+ setTimeout(() => this.renderChart(), 100);
2141
+ }
2142
+ }
2143
+ catch (error) {
2144
+ console.error('Error loading matrix data:', error);
2145
+ SharedService.Instance.CreateSimpleNotification('Failed to load matrix data', 'error', 3000);
2146
+ }
2147
+ finally {
2148
+ this.loadingMatrix = false;
2149
+ this.cdr.markForCheck();
2150
+ }
2151
+ }
2152
+ /**
2153
+ * Load feedbacks for a batch of test run IDs
2154
+ * Returns a map of testRunId -> TestRunFeedbackEntity
2155
+ */
2156
+ async loadFeedbacksForTestRuns(testRunIds) {
2157
+ const feedbackMap = new Map();
2158
+ if (testRunIds.length === 0)
2159
+ return feedbackMap;
2160
+ try {
2161
+ const rv = new RunView();
2162
+ // Build IN clause for the IDs (batch in chunks to avoid query size limits)
2163
+ const chunkSize = 50;
2164
+ for (let i = 0; i < testRunIds.length; i += chunkSize) {
2165
+ const chunk = testRunIds.slice(i, i + chunkSize);
2166
+ const inClause = chunk.map(id => `'${id}'`).join(',');
2167
+ const result = await rv.RunView({
2168
+ EntityName: 'MJ: Test Run Feedbacks',
2169
+ ExtraFilter: `TestRunID IN (${inClause})`,
2170
+ ResultType: 'entity_object'
2171
+ });
2172
+ if (result.Success && result.Results) {
2173
+ for (const feedback of result.Results) {
2174
+ // If multiple feedbacks exist for same test run, keep the most recent (last one)
2175
+ feedbackMap.set(feedback.TestRunID, feedback);
2176
+ }
2177
+ }
2178
+ }
2179
+ }
2180
+ catch (error) {
2181
+ console.warn('Failed to load feedbacks:', error);
2182
+ }
2183
+ return feedbackMap;
2184
+ }
2185
+ getUniqueTestsFromMatrix() {
2186
+ const testsMap = new Map();
2187
+ for (const runData of this.matrixData) {
2188
+ for (const [testId, testResult] of runData.testResults) {
2189
+ if (!testsMap.has(testId)) {
2190
+ testsMap.set(testId, { testName: testResult.testName, sequence: testResult.sequence });
2191
+ }
2192
+ }
2193
+ }
2194
+ let tests = Array.from(testsMap.entries()).map(([testId, data]) => ({
2195
+ testId,
2196
+ testName: data.testName,
2197
+ sequence: data.sequence
2198
+ }));
2199
+ // Apply test name filter if set
2200
+ if (this.matrixTestFilter.trim()) {
2201
+ const filterLower = this.matrixTestFilter.toLowerCase().trim();
2202
+ tests = tests.filter(t => t.testName.toLowerCase().includes(filterLower));
2203
+ }
2204
+ // Apply sorting
2205
+ if (this.matrixSortBy === 'sequence') {
2206
+ tests = tests.sort((a, b) => this.matrixSortAsc ? a.sequence - b.sequence : b.sequence - a.sequence);
2207
+ }
2208
+ else {
2209
+ tests = tests.sort((a, b) => {
2210
+ const cmp = a.testName.localeCompare(b.testName);
2211
+ return this.matrixSortAsc ? cmp : -cmp;
2212
+ });
2213
+ }
2214
+ return tests;
2215
+ }
2216
+ /**
2217
+ * Toggle matrix sort column
2218
+ */
2219
+ toggleMatrixSort(column) {
2220
+ if (this.matrixSortBy === column) {
2221
+ this.matrixSortAsc = !this.matrixSortAsc;
2222
+ }
2223
+ else {
2224
+ this.matrixSortBy = column;
2225
+ this.matrixSortAsc = true;
2226
+ }
2227
+ this.cdr.markForCheck();
2228
+ }
2229
+ /**
2230
+ * Select/deselect a matrix row for highlighting
2231
+ */
2232
+ selectMatrixRow(testId) {
2233
+ this.selectedMatrixTestId = this.selectedMatrixTestId === testId ? null : testId;
2234
+ this.cdr.markForCheck();
2235
+ }
2236
+ /**
2237
+ * Handle test name filter input - uses Subject for debounce
2238
+ */
2239
+ onMatrixFilterInput(event) {
2240
+ const value = event.target.value;
2241
+ this.matrixFilterSubject$.next(value);
2242
+ }
2243
+ /**
2244
+ * Clear the matrix test name filter
2245
+ */
2246
+ clearMatrixFilter() {
2247
+ this.matrixTestFilter = '';
2248
+ this.matrixFilterSubject$.next('');
2249
+ this.cdr.markForCheck();
2250
+ }
2251
+ getTestResultForRun(runId, testId) {
2252
+ const runData = this.matrixData.find(r => r.runId === runId);
2253
+ if (!runData)
2254
+ return null;
2255
+ return runData.testResults.get(testId) || null;
2256
+ }
2257
+ getMatrixCellClass(result) {
2258
+ if (!result)
2259
+ return 'cell-none cell-not-run';
2260
+ switch (result.status) {
2261
+ case 'Passed': return 'cell-passed';
2262
+ case 'Failed': return 'cell-failed';
2263
+ case 'Error': return 'cell-error';
2264
+ case 'Timeout': return 'cell-timeout';
2265
+ case 'Skipped': return 'cell-skipped cell-not-run';
2266
+ case 'Running': return 'cell-running';
2267
+ default: return 'cell-pending';
2268
+ }
2269
+ }
2270
+ /**
2271
+ * Get descriptive tooltip for execution status
2272
+ */
2273
+ getStatusTooltip(status) {
2274
+ switch (status) {
2275
+ case 'Passed': return 'Status: Passed - Test completed without error';
2276
+ case 'Failed': return 'Status: Failed - Test assertions did not pass';
2277
+ case 'Error': return 'Status: Error - Test encountered an exception';
2278
+ case 'Timeout': return 'Status: Timeout - Test exceeded time limit';
2279
+ case 'Skipped': return 'Status: Skipped - Test was not executed';
2280
+ case 'Running': return 'Status: Running - Test is currently executing';
2281
+ case 'Pending': return 'Status: Pending - Test waiting to run';
2282
+ default: return `Status: ${status}`;
2283
+ }
2284
+ }
2285
+ /**
2286
+ * Get tooltip for human review with rating and optional comments
2287
+ */
2288
+ getHumanTooltip(rating, comments) {
2289
+ let tooltip = `Human Review: ${rating}/10 rating`;
2290
+ if (comments) {
2291
+ const truncated = comments.length > 200 ? comments.substring(0, 200) + '...' : comments;
2292
+ tooltip += `\n\n"${truncated}"`;
2293
+ }
2294
+ return tooltip;
2295
+ }
2296
+ /**
2297
+ * Get the count of enabled evaluation types for matrix cell layout
2298
+ */
2299
+ getEvalCount() {
2300
+ let count = 0;
2301
+ if (this.evalPreferences.showExecution)
2302
+ count++;
2303
+ if (this.evalPreferences.showHuman)
2304
+ count++;
2305
+ if (this.evalPreferences.showAuto)
2306
+ count++;
2307
+ return count;
2308
+ }
2309
+ // ===========================
2310
+ // Matrix Totals Row Methods
2311
+ // ===========================
2312
+ /**
2313
+ * Get count of passed tests for a run
2314
+ */
2315
+ getRunPassedCount(run) {
2316
+ let count = 0;
2317
+ run.testResults.forEach(result => {
2318
+ if (result.status === 'Passed')
2319
+ count++;
2320
+ });
2321
+ return count;
2322
+ }
2323
+ /**
2324
+ * Get total count of tests for a run
2325
+ */
2326
+ getRunTotalCount(run) {
2327
+ return run.testResults.size;
2328
+ }
2329
+ /**
2330
+ * Get average human rating for a run (only from tests that have ratings)
2331
+ */
2332
+ getRunHumanAvg(run) {
2333
+ let sum = 0;
2334
+ let count = 0;
2335
+ run.testResults.forEach(result => {
2336
+ if (result.humanRating != null) {
2337
+ sum += result.humanRating;
2338
+ count++;
2339
+ }
2340
+ });
2341
+ return count > 0 ? sum / count : null;
2342
+ }
2343
+ /**
2344
+ * Get count of tests with human ratings for a run
2345
+ */
2346
+ getRunHumanCount(run) {
2347
+ let count = 0;
2348
+ run.testResults.forEach(result => {
2349
+ if (result.humanRating != null)
2350
+ count++;
2351
+ });
2352
+ return count;
2353
+ }
2354
+ /**
2355
+ * Get average auto score for a run (only from tests that have scores)
2356
+ */
2357
+ getRunAutoAvg(run) {
2358
+ let sum = 0;
2359
+ let count = 0;
2360
+ run.testResults.forEach(result => {
2361
+ if (result.score != null) {
2362
+ sum += result.score;
2363
+ count++;
2364
+ }
2365
+ });
2366
+ return count > 0 ? sum / count : null;
2367
+ }
2368
+ /**
2369
+ * Get count of tests with auto scores for a run
2370
+ */
2371
+ getRunAutoCount(run) {
2372
+ let count = 0;
2373
+ run.testResults.forEach(result => {
2374
+ if (result.score != null)
2375
+ count++;
2376
+ });
2377
+ return count;
2378
+ }
2379
+ /**
2380
+ * Navigate to a test run when clicking a matrix cell
2381
+ */
2382
+ openTestRun(testRunId) {
2383
+ SharedService.Instance.OpenEntityRecord('MJ: Test Runs', CompositeKey.FromID(testRunId));
2384
+ }
2385
+ /**
2386
+ * Handle matrix cell click - navigate to the test run
2387
+ */
2388
+ onMatrixCellClick(result, event) {
2389
+ event.stopPropagation(); // Prevent row click from also firing
2390
+ if (result?.testRunId) {
2391
+ this.openTestRun(result.testRunId);
2392
+ }
2393
+ }
2394
+ // ===========================
2395
+ // D3 Chart Rendering
2396
+ // ===========================
2397
+ /**
2398
+ * Renders an interactive heatmap-style chart showing test results across runs
2399
+ * with trend lines and better visual hierarchy
2400
+ */
2401
+ renderChart() {
2402
+ if (!this.chartContainer?.nativeElement || this.matrixData.length === 0) {
2403
+ return;
2404
+ }
2405
+ const container = this.chartContainer.nativeElement;
2406
+ const tests = this.getUniqueTestsFromMatrix();
2407
+ const runs = this.matrixData;
2408
+ // Dynamic sizing based on content and evaluation preferences
2409
+ const width = container.clientWidth || 900;
2410
+ const evalCount = this.getEvalCount();
2411
+ // Adjust row height and column width based on how many eval types are shown
2412
+ const rowHeight = evalCount >= 3 ? 34 : evalCount === 2 ? 30 : 28;
2413
+ const minColWidth = evalCount >= 3 ? 65 : evalCount === 2 ? 55 : 50;
2414
+ const colWidth = Math.min(90, Math.max(minColWidth, (width - 250) / runs.length));
2415
+ const margin = { top: 80, right: 40, bottom: 20, left: 220 };
2416
+ const chartWidth = runs.length * colWidth;
2417
+ const chartHeight = tests.length * rowHeight;
2418
+ const height = chartHeight + margin.top + margin.bottom;
2419
+ // Update container height
2420
+ container.style.height = `${Math.max(400, height)}px`;
2421
+ // Clear previous chart
2422
+ d3.select(container).selectAll('*').remove();
2423
+ // Status colors with better contrast
2424
+ const statusColors = {
2425
+ 'Passed': '#22c55e',
2426
+ 'Failed': '#ef4444',
2427
+ 'Error': '#f97316',
2428
+ 'Skipped': '#a1a1aa',
2429
+ 'Running': '#3b82f6',
2430
+ 'Pending': '#d1d5db'
2431
+ };
2432
+ // Create SVG
2433
+ const svg = d3.select(container)
2434
+ .append('svg')
2435
+ .attr('width', width)
2436
+ .attr('height', height);
2437
+ // Create tooltip div
2438
+ const tooltip = d3.select(container)
2439
+ .append('div')
2440
+ .attr('class', 'chart-tooltip')
2441
+ .style('position', 'absolute')
2442
+ .style('opacity', 0)
2443
+ .style('background', 'rgba(15, 23, 42, 0.95)')
2444
+ .style('color', 'white')
2445
+ .style('padding', '10px 14px')
2446
+ .style('border-radius', '8px')
2447
+ .style('font-size', '12px')
2448
+ .style('pointer-events', 'none')
2449
+ .style('z-index', '1000')
2450
+ .style('box-shadow', '0 4px 20px rgba(0,0,0,0.3)')
2451
+ .style('max-width', '280px');
2452
+ // Create chart group
2453
+ const chart = svg.append('g')
2454
+ .attr('transform', `translate(${margin.left}, ${margin.top})`);
2455
+ // Add gradient definitions for cells
2456
+ const defs = svg.append('defs');
2457
+ // Create gradient for each status
2458
+ Object.entries(statusColors).forEach(([status, color]) => {
2459
+ const gradient = defs.append('linearGradient')
2460
+ .attr('id', `gradient-${status.toLowerCase()}`)
2461
+ .attr('x1', '0%')
2462
+ .attr('y1', '0%')
2463
+ .attr('x2', '0%')
2464
+ .attr('y2', '100%');
2465
+ gradient.append('stop')
2466
+ .attr('offset', '0%')
2467
+ .attr('stop-color', color)
2468
+ .attr('stop-opacity', 0.95);
2469
+ gradient.append('stop')
2470
+ .attr('offset', '100%')
2471
+ .attr('stop-color', d3.color(color)?.darker(0.3)?.toString() || color)
2472
+ .attr('stop-opacity', 0.95);
2473
+ });
2474
+ // Draw evaluation legend at top-left corner
2475
+ this.renderChartLegend(chart, margin);
2476
+ // Draw column headers (run dates) at top
2477
+ runs.forEach((run, i) => {
2478
+ const x = i * colWidth + colWidth / 2;
2479
+ // Date text
2480
+ chart.append('text')
2481
+ .attr('x', x)
2482
+ .attr('y', -45)
2483
+ .attr('text-anchor', 'middle')
2484
+ .attr('fill', '#475569')
2485
+ .attr('font-size', '10px')
2486
+ .attr('font-weight', '500')
2487
+ .text(this.getRelativeTime(run.date))
2488
+ .style('cursor', 'pointer')
2489
+ .on('click', () => this.openSuiteRun(run.runId));
2490
+ // Pass rate badge
2491
+ const passRateColor = run.passRate >= 80 ? '#22c55e' : run.passRate >= 50 ? '#f97316' : '#ef4444';
2492
+ const badgeWidth = 36;
2493
+ const badgeHeight = 18;
2494
+ chart.append('rect')
2495
+ .attr('x', x - badgeWidth / 2)
2496
+ .attr('y', -35)
2497
+ .attr('width', badgeWidth)
2498
+ .attr('height', badgeHeight)
2499
+ .attr('rx', 9)
2500
+ .attr('fill', passRateColor)
2501
+ .attr('opacity', 0.15);
2502
+ chart.append('text')
2503
+ .attr('x', x)
2504
+ .attr('y', -22)
2505
+ .attr('text-anchor', 'middle')
2506
+ .attr('fill', passRateColor)
2507
+ .attr('font-size', '11px')
2508
+ .attr('font-weight', '700')
2509
+ .text(`${run.passRate.toFixed(0)}%`);
2510
+ // Tags indicator - styled pills
2511
+ if (run.tags.length > 0) {
2512
+ const tagsToShow = run.tags.slice(0, 2);
2513
+ const tagPillWidth = 42;
2514
+ const tagPillHeight = 14;
2515
+ const tagGap = 4;
2516
+ const totalTagsWidth = tagsToShow.length * tagPillWidth + (tagsToShow.length - 1) * tagGap;
2517
+ const tagStartX = x - totalTagsWidth / 2;
2518
+ tagsToShow.forEach((tag, tagIndex) => {
2519
+ const tagX = tagStartX + tagIndex * (tagPillWidth + tagGap);
2520
+ const tagY = -68;
2521
+ // Pill background with gradient
2522
+ chart.append('rect')
2523
+ .attr('x', tagX)
2524
+ .attr('y', tagY)
2525
+ .attr('width', tagPillWidth)
2526
+ .attr('height', tagPillHeight)
2527
+ .attr('rx', 7)
2528
+ .attr('fill', '#dbeafe')
2529
+ .attr('stroke', '#93c5fd')
2530
+ .attr('stroke-width', 1)
2531
+ .style('cursor', 'pointer')
2532
+ .on('click', () => this.openSuiteRun(run.runId));
2533
+ // Tag text
2534
+ chart.append('text')
2535
+ .attr('x', tagX + tagPillWidth / 2)
2536
+ .attr('y', tagY + tagPillHeight / 2 + 3)
2537
+ .attr('text-anchor', 'middle')
2538
+ .attr('fill', '#1d4ed8')
2539
+ .attr('font-size', '8px')
2540
+ .attr('font-weight', '600')
2541
+ .text(tag.length > 8 ? tag.substring(0, 7) + '…' : tag)
2542
+ .style('cursor', 'pointer')
2543
+ .on('click', () => this.openSuiteRun(run.runId));
2544
+ });
2545
+ // Show +N indicator if more tags
2546
+ if (run.tags.length > 2) {
2547
+ const moreX = tagStartX + tagsToShow.length * (tagPillWidth + tagGap);
2548
+ chart.append('text')
2549
+ .attr('x', moreX)
2550
+ .attr('y', -68 + tagPillHeight / 2 + 3)
2551
+ .attr('text-anchor', 'start')
2552
+ .attr('fill', '#64748b')
2553
+ .attr('font-size', '8px')
2554
+ .attr('font-weight', '500')
2555
+ .text(`+${run.tags.length - 2}`);
2556
+ }
2557
+ }
2558
+ });
2559
+ // Draw row labels (test names) on left
2560
+ tests.forEach((test, i) => {
2561
+ const y = i * rowHeight + rowHeight / 2;
2562
+ chart.append('text')
2563
+ .attr('x', -12)
2564
+ .attr('y', y + 4)
2565
+ .attr('text-anchor', 'end')
2566
+ .attr('fill', '#334155')
2567
+ .attr('font-size', '11px')
2568
+ .text(test.testName.length > 28 ? test.testName.substring(0, 28) + '...' : test.testName)
2569
+ .style('cursor', 'pointer')
2570
+ .on('click', () => {
2571
+ SharedService.Instance.OpenEntityRecord('MJ: Tests', CompositeKey.FromID(test.testId));
2572
+ })
2573
+ .on('mouseover', function () {
2574
+ d3.select(this).attr('fill', '#3b82f6').attr('font-weight', '600');
2575
+ })
2576
+ .on('mouseout', function () {
2577
+ d3.select(this).attr('fill', '#334155').attr('font-weight', 'normal');
2578
+ });
2579
+ });
2580
+ // Draw grid lines
2581
+ tests.forEach((_, i) => {
2582
+ chart.append('line')
2583
+ .attr('x1', 0)
2584
+ .attr('y1', (i + 1) * rowHeight)
2585
+ .attr('x2', chartWidth)
2586
+ .attr('y2', (i + 1) * rowHeight)
2587
+ .attr('stroke', '#e2e8f0')
2588
+ .attr('stroke-width', 1);
2589
+ });
2590
+ runs.forEach((_, i) => {
2591
+ chart.append('line')
2592
+ .attr('x1', (i + 1) * colWidth)
2593
+ .attr('y1', 0)
2594
+ .attr('x2', (i + 1) * colWidth)
2595
+ .attr('y2', chartHeight)
2596
+ .attr('stroke', '#e2e8f0')
2597
+ .attr('stroke-width', 1);
2598
+ });
2599
+ // Draw cells with evaluation toggle support
2600
+ const cellPadding = 3;
2601
+ runs.forEach((run, runIndex) => {
2602
+ tests.forEach((test, testIndex) => {
2603
+ const result = run.testResults.get(test.testId);
2604
+ const x = runIndex * colWidth + cellPadding;
2605
+ const y = testIndex * rowHeight + cellPadding;
2606
+ const cellWidth = colWidth - cellPadding * 2;
2607
+ const cellHeight = rowHeight - cellPadding * 2;
2608
+ if (!result) {
2609
+ // Empty cell indicator
2610
+ chart.append('rect')
2611
+ .attr('x', x)
2612
+ .attr('y', y)
2613
+ .attr('width', cellWidth)
2614
+ .attr('height', cellHeight)
2615
+ .attr('rx', 4)
2616
+ .attr('fill', '#f8fafc')
2617
+ .attr('stroke', '#e2e8f0')
2618
+ .attr('stroke-width', 1);
2619
+ chart.append('text')
2620
+ .attr('x', x + cellWidth / 2)
2621
+ .attr('y', y + cellHeight / 2 + 4)
2622
+ .attr('text-anchor', 'middle')
2623
+ .attr('fill', '#cbd5e1')
2624
+ .attr('font-size', '12px')
2625
+ .text('—');
2626
+ return;
2627
+ }
2628
+ // Build tooltip content based on evaluation preferences
2629
+ const tooltipParts = [];
2630
+ if (this.evalPreferences.showExecution) {
2631
+ tooltipParts.push(`<div style="display:inline-block; padding:2px 8px; border-radius:4px; background:${statusColors[result.status]}; color:white; font-size:11px; font-weight:600">${result.status}</div>`);
2632
+ }
2633
+ if (this.evalPreferences.showHuman) {
2634
+ tooltipParts.push(`<div style="margin-top:4px"><span style="color:#f59e0b">👤</span> <strong>Human:</strong> <span style="color:#94a3b8">Needs review</span></div>`);
2635
+ }
2636
+ if (this.evalPreferences.showAuto && result.score != null) {
2637
+ tooltipParts.push(`<div style="margin-top:4px"><span style="color:#3b82f6">🤖</span> <strong>Auto:</strong> ${(result.score * 100).toFixed(1)}%</div>`);
2638
+ }
2639
+ const durationText = result.duration != null ? `<div><strong>Duration:</strong> ${result.duration.toFixed(2)}s</div>` : '';
2640
+ const cellGroup = chart.append('g')
2641
+ .attr('class', 'result-cell')
2642
+ .style('cursor', 'pointer')
2643
+ .on('click', () => this.openTestRun(result.testRunId))
2644
+ .on('mouseover', (event) => {
2645
+ d3.select(event.currentTarget).select('rect.cell-bg')
2646
+ .attr('stroke', '#1e40af')
2647
+ .attr('stroke-width', 2);
2648
+ tooltip
2649
+ .style('opacity', 1)
2650
+ .html(`
2651
+ <div style="font-weight:600; margin-bottom:6px; color:#f1f5f9">${result.testName}</div>
2652
+ ${tooltipParts.join('')}
2653
+ ${durationText}
2654
+ <div style="margin-top:6px; color:#94a3b8; font-size:10px">${this.getRelativeTime(run.date)} • Click to view</div>
2655
+ `)
2656
+ .style('left', `${event.offsetX + 15}px`)
2657
+ .style('top', `${event.offsetY - 10}px`);
2658
+ })
2659
+ .on('mouseout', (event) => {
2660
+ d3.select(event.currentTarget).select('rect.cell-bg')
2661
+ .attr('stroke', 'none')
2662
+ .attr('stroke-width', 0);
2663
+ tooltip.style('opacity', 0);
2664
+ });
2665
+ // Determine cell background color based on eval preferences
2666
+ let cellBgColor = '#f1f5f9';
2667
+ let cellBgGradient = '';
2668
+ if (this.evalPreferences.showExecution) {
2669
+ // Use status-based gradient
2670
+ cellBgGradient = `url(#gradient-${result.status.toLowerCase()})`;
2671
+ }
2672
+ else if (this.evalPreferences.showAuto && result.score != null) {
2673
+ // Use score-based color
2674
+ const scoreColor = result.score >= 0.8 ? '#22c55e' : result.score >= 0.5 ? '#f97316' : '#ef4444';
2675
+ cellBgColor = scoreColor;
2676
+ }
2677
+ else {
2678
+ // Neutral background when only Human is selected
2679
+ cellBgColor = '#fef3c7';
2680
+ }
2681
+ // Cell background
2682
+ cellGroup.append('rect')
2683
+ .attr('class', 'cell-bg')
2684
+ .attr('x', x)
2685
+ .attr('y', y)
2686
+ .attr('width', cellWidth)
2687
+ .attr('height', cellHeight)
2688
+ .attr('rx', 4)
2689
+ .attr('fill', cellBgGradient || cellBgColor)
2690
+ .attr('stroke', 'none')
2691
+ .attr('stroke-width', 0)
2692
+ .style('transition', 'all 0.15s ease');
2693
+ // Calculate icon positions based on how many eval types are shown
2694
+ // With wider cells, we can use larger icons and better spacing
2695
+ const iconSize = evalCount === 1 ? 14 : evalCount === 2 ? 12 : 10;
2696
+ const iconSpacing = evalCount === 1 ? 0 : evalCount === 2 ? 16 : 14;
2697
+ const startX = x + cellWidth / 2 - ((evalCount - 1) * iconSpacing) / 2;
2698
+ let iconIndex = 0;
2699
+ // Status icon (execution)
2700
+ if (this.evalPreferences.showExecution) {
2701
+ const iconText = {
2702
+ 'Passed': '✓',
2703
+ 'Failed': '✕',
2704
+ 'Error': '!',
2705
+ 'Skipped': '»',
2706
+ 'Running': '●',
2707
+ 'Pending': '○'
2708
+ };
2709
+ const iconX = startX + iconIndex * iconSpacing;
2710
+ // If status is NOT the only thing shown, add a small circular bg
2711
+ if (evalCount > 1) {
2712
+ cellGroup.append('circle')
2713
+ .attr('cx', iconX)
2714
+ .attr('cy', y + cellHeight / 2)
2715
+ .attr('r', iconSize / 2 + 3)
2716
+ .attr('fill', statusColors[result.status])
2717
+ .attr('opacity', 0.9);
2718
+ }
2719
+ cellGroup.append('text')
2720
+ .attr('x', iconX)
2721
+ .attr('y', y + cellHeight / 2 + iconSize / 3)
2722
+ .attr('text-anchor', 'middle')
2723
+ .attr('fill', 'white')
2724
+ .attr('font-size', `${iconSize}px`)
2725
+ .attr('font-weight', 'bold')
2726
+ .attr('font-family', 'system-ui, -apple-system, sans-serif')
2727
+ .text(iconText[result.status] || '?');
2728
+ iconIndex++;
2729
+ }
2730
+ // Human icon
2731
+ if (this.evalPreferences.showHuman) {
2732
+ const iconX = startX + iconIndex * iconSpacing;
2733
+ // Human evaluation indicator (clock icon for pending)
2734
+ cellGroup.append('circle')
2735
+ .attr('cx', iconX)
2736
+ .attr('cy', y + cellHeight / 2)
2737
+ .attr('r', iconSize / 2 + 3)
2738
+ .attr('fill', '#fef3c7')
2739
+ .attr('stroke', '#f59e0b')
2740
+ .attr('stroke-width', 1);
2741
+ cellGroup.append('text')
2742
+ .attr('x', iconX)
2743
+ .attr('y', y + cellHeight / 2 + iconSize / 3)
2744
+ .attr('text-anchor', 'middle')
2745
+ .attr('fill', '#d97706')
2746
+ .attr('font-size', `${iconSize - 2}px`)
2747
+ .attr('font-weight', '500')
2748
+ .text('⏱');
2749
+ iconIndex++;
2750
+ }
2751
+ // Auto score icon/indicator
2752
+ if (this.evalPreferences.showAuto) {
2753
+ const iconX = startX + iconIndex * iconSpacing;
2754
+ if (result.score != null) {
2755
+ const scorePercent = Math.round(result.score * 100);
2756
+ const scoreColor = result.score >= 0.8 ? '#22c55e' : result.score >= 0.5 ? '#f97316' : '#ef4444';
2757
+ // Score pill background
2758
+ if (evalCount > 1) {
2759
+ cellGroup.append('rect')
2760
+ .attr('x', iconX - 10)
2761
+ .attr('y', y + cellHeight / 2 - iconSize / 2 - 1)
2762
+ .attr('width', 20)
2763
+ .attr('height', iconSize + 2)
2764
+ .attr('rx', (iconSize + 2) / 2)
2765
+ .attr('fill', scoreColor)
2766
+ .attr('opacity', 0.9);
2767
+ }
2768
+ cellGroup.append('text')
2769
+ .attr('x', iconX)
2770
+ .attr('y', y + cellHeight / 2 + iconSize / 3)
2771
+ .attr('text-anchor', 'middle')
2772
+ .attr('fill', evalCount > 1 ? 'white' : 'white')
2773
+ .attr('font-size', `${iconSize - 1}px`)
2774
+ .attr('font-weight', '700')
2775
+ .text(`${scorePercent}`);
2776
+ }
2777
+ else {
2778
+ // No auto score available
2779
+ cellGroup.append('circle')
2780
+ .attr('cx', iconX)
2781
+ .attr('cy', y + cellHeight / 2)
2782
+ .attr('r', iconSize / 2 + 2)
2783
+ .attr('fill', '#e2e8f0')
2784
+ .attr('stroke', '#94a3b8')
2785
+ .attr('stroke-width', 1);
2786
+ cellGroup.append('text')
2787
+ .attr('x', iconX)
2788
+ .attr('y', y + cellHeight / 2 + iconSize / 3)
2789
+ .attr('text-anchor', 'middle')
2790
+ .attr('fill', '#94a3b8')
2791
+ .attr('font-size', `${iconSize - 2}px`)
2792
+ .text('—');
2793
+ }
2794
+ }
2795
+ });
2796
+ });
2797
+ // Draw trend line for pass rate across runs
2798
+ if (runs.length > 1) {
2799
+ const trendLineY = chartHeight + 50;
2800
+ const trendHeight = 40;
2801
+ // Trend line label
2802
+ chart.append('text')
2803
+ .attr('x', -12)
2804
+ .attr('y', trendLineY + trendHeight / 2)
2805
+ .attr('text-anchor', 'end')
2806
+ .attr('fill', '#64748b')
2807
+ .attr('font-size', '10px')
2808
+ .attr('font-weight', '500')
2809
+ .text('Pass Rate Trend');
2810
+ // Create trend line path
2811
+ const lineGenerator = d3.line()
2812
+ .x((_, i) => i * colWidth + colWidth / 2)
2813
+ .y(d => trendLineY + trendHeight - (d.passRate / 100) * trendHeight)
2814
+ .curve(d3.curveMonotoneX);
2815
+ // Draw area under line
2816
+ const areaGenerator = d3.area()
2817
+ .x((_, i) => i * colWidth + colWidth / 2)
2818
+ .y0(trendLineY + trendHeight)
2819
+ .y1(d => trendLineY + trendHeight - (d.passRate / 100) * trendHeight)
2820
+ .curve(d3.curveMonotoneX);
2821
+ chart.append('path')
2822
+ .datum(runs)
2823
+ .attr('d', areaGenerator)
2824
+ .attr('fill', 'url(#trendGradient)')
2825
+ .attr('opacity', 0.3);
2826
+ // Create gradient for trend area
2827
+ const trendGradient = defs.append('linearGradient')
2828
+ .attr('id', 'trendGradient')
2829
+ .attr('x1', '0%')
2830
+ .attr('y1', '0%')
2831
+ .attr('x2', '0%')
2832
+ .attr('y2', '100%');
2833
+ trendGradient.append('stop')
2834
+ .attr('offset', '0%')
2835
+ .attr('stop-color', '#3b82f6')
2836
+ .attr('stop-opacity', 0.4);
2837
+ trendGradient.append('stop')
2838
+ .attr('offset', '100%')
2839
+ .attr('stop-color', '#3b82f6')
2840
+ .attr('stop-opacity', 0);
2841
+ chart.append('path')
2842
+ .datum(runs)
2843
+ .attr('d', lineGenerator)
2844
+ .attr('fill', 'none')
2845
+ .attr('stroke', '#3b82f6')
2846
+ .attr('stroke-width', 2.5)
2847
+ .attr('stroke-linecap', 'round');
2848
+ // Draw dots on trend line
2849
+ runs.forEach((run, i) => {
2850
+ const cx = i * colWidth + colWidth / 2;
2851
+ const cy = trendLineY + trendHeight - (run.passRate / 100) * trendHeight;
2852
+ chart.append('circle')
2853
+ .attr('cx', cx)
2854
+ .attr('cy', cy)
2855
+ .attr('r', 4)
2856
+ .attr('fill', 'white')
2857
+ .attr('stroke', '#3b82f6')
2858
+ .attr('stroke-width', 2);
2859
+ });
2860
+ // Update container height for trend line
2861
+ container.style.height = `${Math.max(400, height + 80)}px`;
2862
+ svg.attr('height', height + 80);
2863
+ }
2864
+ this.chartRendered = true;
2865
+ }
2866
+ /**
2867
+ * Renders a legend showing which evaluation types are currently displayed
2868
+ */
2869
+ renderChartLegend(chart, margin) {
2870
+ const legendGroup = chart.append('g')
2871
+ .attr('class', 'eval-legend')
2872
+ .attr('transform', `translate(${-margin.left + 10}, ${-margin.top + 15})`);
2873
+ // Legend title
2874
+ legendGroup.append('text')
2875
+ .attr('x', 0)
2876
+ .attr('y', 0)
2877
+ .attr('fill', '#64748b')
2878
+ .attr('font-size', '9px')
2879
+ .attr('font-weight', '600')
2880
+ .attr('text-transform', 'uppercase')
2881
+ .text('SHOWING:');
2882
+ let xOffset = 55;
2883
+ // Status indicator
2884
+ if (this.evalPreferences.showExecution) {
2885
+ const statusGroup = legendGroup.append('g')
2886
+ .attr('transform', `translate(${xOffset}, -4)`);
2887
+ statusGroup.append('circle')
2888
+ .attr('cx', 6)
2889
+ .attr('cy', 0)
2890
+ .attr('r', 6)
2891
+ .attr('fill', '#22c55e');
2892
+ statusGroup.append('text')
2893
+ .attr('x', 6)
2894
+ .attr('y', 4)
2895
+ .attr('text-anchor', 'middle')
2896
+ .attr('fill', 'white')
2897
+ .attr('font-size', '8px')
2898
+ .attr('font-weight', 'bold')
2899
+ .text('✓');
2900
+ statusGroup.append('text')
2901
+ .attr('x', 16)
2902
+ .attr('y', 3)
2903
+ .attr('fill', '#475569')
2904
+ .attr('font-size', '10px')
2905
+ .attr('font-weight', '500')
2906
+ .text('Status');
2907
+ xOffset += 60;
2908
+ }
2909
+ // Human indicator
2910
+ if (this.evalPreferences.showHuman) {
2911
+ const humanGroup = legendGroup.append('g')
2912
+ .attr('transform', `translate(${xOffset}, -4)`);
2913
+ humanGroup.append('circle')
2914
+ .attr('cx', 6)
2915
+ .attr('cy', 0)
2916
+ .attr('r', 6)
2917
+ .attr('fill', '#fef3c7')
2918
+ .attr('stroke', '#f59e0b')
2919
+ .attr('stroke-width', 1);
2920
+ humanGroup.append('text')
2921
+ .attr('x', 6)
2922
+ .attr('y', 3)
2923
+ .attr('text-anchor', 'middle')
2924
+ .attr('fill', '#d97706')
2925
+ .attr('font-size', '7px')
2926
+ .text('⏱');
2927
+ humanGroup.append('text')
2928
+ .attr('x', 16)
2929
+ .attr('y', 3)
2930
+ .attr('fill', '#475569')
2931
+ .attr('font-size', '10px')
2932
+ .attr('font-weight', '500')
2933
+ .text('Human');
2934
+ xOffset += 60;
2935
+ }
2936
+ // Auto indicator
2937
+ if (this.evalPreferences.showAuto) {
2938
+ const autoGroup = legendGroup.append('g')
2939
+ .attr('transform', `translate(${xOffset}, -4)`);
2940
+ autoGroup.append('rect')
2941
+ .attr('x', 0)
2942
+ .attr('y', -6)
2943
+ .attr('width', 18)
2944
+ .attr('height', 12)
2945
+ .attr('rx', 6)
2946
+ .attr('fill', '#3b82f6');
2947
+ autoGroup.append('text')
2948
+ .attr('x', 9)
2949
+ .attr('y', 3)
2950
+ .attr('text-anchor', 'middle')
2951
+ .attr('fill', 'white')
2952
+ .attr('font-size', '7px')
2953
+ .attr('font-weight', '700')
2954
+ .text('%');
2955
+ autoGroup.append('text')
2956
+ .attr('x', 24)
2957
+ .attr('y', 3)
2958
+ .attr('fill', '#475569')
2959
+ .attr('font-size', '10px')
2960
+ .attr('font-weight', '500')
2961
+ .text('Auto');
2962
+ }
2963
+ }
2964
+ getAveragePassRate() {
2965
+ const data = this.getFilteredAnalyticsData();
2966
+ if (data.length === 0)
2967
+ return 0;
2968
+ return data.reduce((sum, d) => sum + d.passRate, 0) / data.length;
2969
+ }
2970
+ getTotalRuns() {
2971
+ return this.getFilteredAnalyticsData().length;
2972
+ }
2973
+ getAverageDuration() {
2974
+ const data = this.getFilteredAnalyticsData();
2975
+ if (data.length === 0)
2976
+ return 0;
2977
+ return data.reduce((sum, d) => sum + d.duration, 0) / data.length;
2978
+ }
2979
+ getTotalCost() {
2980
+ return this.getFilteredAnalyticsData().reduce((sum, d) => sum + d.cost, 0);
2981
+ }
2982
+ getPassRateTrend() {
2983
+ const data = this.getFilteredAnalyticsData();
2984
+ if (data.length < 2)
2985
+ return { direction: 'stable', value: 0 };
2986
+ // Compare recent half to older half
2987
+ const midpoint = Math.floor(data.length / 2);
2988
+ const recentData = data.slice(0, midpoint);
2989
+ const olderData = data.slice(midpoint);
2990
+ const recentAvg = recentData.reduce((s, d) => s + d.passRate, 0) / recentData.length;
2991
+ const olderAvg = olderData.reduce((s, d) => s + d.passRate, 0) / olderData.length;
2992
+ const diff = recentAvg - olderAvg;
2993
+ if (Math.abs(diff) < 1)
2994
+ return { direction: 'stable', value: diff };
2995
+ return { direction: diff > 0 ? 'up' : 'down', value: Math.abs(diff) };
2996
+ }
2997
+ formatDuration(seconds) {
2998
+ if (seconds < 60)
2999
+ return `${seconds.toFixed(1)}s`;
3000
+ if (seconds < 3600) {
3001
+ const mins = Math.floor(seconds / 60);
3002
+ const secs = Math.floor(seconds % 60);
3003
+ return secs > 0 ? `${mins}m ${secs}s` : `${mins}m`;
3004
+ }
3005
+ const hours = Math.floor(seconds / 3600);
3006
+ const mins = Math.floor((seconds % 3600) / 60);
3007
+ return mins > 0 ? `${hours}h ${mins}m` : `${hours}h`;
3008
+ }
3009
+ formatCost(cost) {
3010
+ if (cost < 0.01)
3011
+ return `$${cost.toFixed(6)}`;
3012
+ if (cost < 1)
3013
+ return `$${cost.toFixed(4)}`;
3014
+ return `$${cost.toFixed(2)}`;
3015
+ }
3016
+ // ==========================================
3017
+ // Compare Tab Methods
3018
+ // ==========================================
3019
+ async selectCompareRunA(run) {
3020
+ this.compareRunA = run;
3021
+ await this.loadCompareData();
3022
+ }
3023
+ async selectCompareRunB(run) {
3024
+ this.compareRunB = run;
3025
+ await this.loadCompareData();
3026
+ }
3027
+ clearCompareSelection() {
3028
+ this.compareRunA = null;
3029
+ this.compareRunB = null;
3030
+ this.compareResults = [];
3031
+ this.compareRunATests = [];
3032
+ this.compareRunBTests = [];
3033
+ this.cdr.markForCheck();
3034
+ }
3035
+ async loadCompareData() {
3036
+ if (!this.compareRunA || !this.compareRunB) {
3037
+ this.compareResults = [];
3038
+ this.cdr.markForCheck();
3039
+ return;
3040
+ }
3041
+ this.loadingCompare = true;
3042
+ this.cdr.markForCheck();
3043
+ try {
3044
+ const rv = new RunView();
3045
+ // Load test runs for both suite runs in parallel
3046
+ const [resultA, resultB] = await Promise.all([
3047
+ rv.RunView({
3048
+ EntityName: 'MJ: Test Runs',
3049
+ ExtraFilter: `TestSuiteRunID='${this.compareRunA.ID}'`,
3050
+ OrderBy: 'Sequence ASC',
3051
+ ResultType: 'entity_object'
3052
+ }),
3053
+ rv.RunView({
3054
+ EntityName: 'MJ: Test Runs',
3055
+ ExtraFilter: `TestSuiteRunID='${this.compareRunB.ID}'`,
3056
+ OrderBy: 'Sequence ASC',
3057
+ ResultType: 'entity_object'
3058
+ })
3059
+ ]);
3060
+ this.compareRunATests = resultA.Success ? resultA.Results || [] : [];
3061
+ this.compareRunBTests = resultB.Success ? resultB.Results || [] : [];
3062
+ // Build comparison results
3063
+ this.compareResults = this.buildComparisonResults(this.compareRunATests, this.compareRunBTests);
3064
+ }
3065
+ catch (error) {
3066
+ console.error('Error loading comparison data:', error);
3067
+ SharedService.Instance.CreateSimpleNotification('Failed to load comparison data', 'error', 3000);
3068
+ }
3069
+ finally {
3070
+ this.loadingCompare = false;
3071
+ this.cdr.markForCheck();
3072
+ }
3073
+ }
3074
+ buildComparisonResults(testsA, testsB) {
3075
+ const results = [];
3076
+ const testMap = new Map();
3077
+ // Map all tests from run A
3078
+ for (const test of testsA) {
3079
+ testMap.set(test.TestID, { a: test, name: test.Test || 'Unknown Test' });
3080
+ }
3081
+ // Map all tests from run B
3082
+ for (const test of testsB) {
3083
+ const existing = testMap.get(test.TestID);
3084
+ if (existing) {
3085
+ existing.b = test;
3086
+ }
3087
+ else {
3088
+ testMap.set(test.TestID, { b: test, name: test.Test || 'Unknown Test' });
3089
+ }
3090
+ }
3091
+ // Build comparison array
3092
+ for (const [testId, { a, b, name }] of testMap) {
3093
+ const comparison = {
3094
+ testId,
3095
+ testName: name,
3096
+ runA: a ? {
3097
+ status: a.Status || 'Unknown',
3098
+ score: a.Score,
3099
+ duration: a.DurationSeconds,
3100
+ cost: a.CostUSD
3101
+ } : null,
3102
+ runB: b ? {
3103
+ status: b.Status || 'Unknown',
3104
+ score: b.Score,
3105
+ duration: b.DurationSeconds,
3106
+ cost: b.CostUSD
3107
+ } : null,
3108
+ scoreDiff: (a?.Score != null && b?.Score != null) ? b.Score - a.Score : null,
3109
+ durationDiff: (a?.DurationSeconds != null && b?.DurationSeconds != null) ? b.DurationSeconds - a.DurationSeconds : null,
3110
+ statusChanged: (a?.Status || 'none') !== (b?.Status || 'none')
3111
+ };
3112
+ results.push(comparison);
3113
+ }
3114
+ return results;
3115
+ }
3116
+ getComparePassRateDiff() {
3117
+ if (!this.compareRunA || !this.compareRunB)
3118
+ return null;
3119
+ const rateA = this.getPassRate(this.compareRunA);
3120
+ const rateB = this.getPassRate(this.compareRunB);
3121
+ return rateB - rateA;
3122
+ }
3123
+ getCompareDurationDiff() {
3124
+ if (!this.compareRunA || !this.compareRunB)
3125
+ return null;
3126
+ const durA = this.compareRunA.TotalDurationSeconds || 0;
3127
+ const durB = this.compareRunB.TotalDurationSeconds || 0;
3128
+ return durB - durA;
3129
+ }
3130
+ getAbsCompareDurationDiff() {
3131
+ const diff = this.getCompareDurationDiff();
3132
+ return diff != null ? Math.abs(diff) : 0;
3133
+ }
3134
+ getCompareCostDiff() {
3135
+ if (!this.compareRunA || !this.compareRunB)
3136
+ return null;
3137
+ const costA = this.compareRunA.TotalCostUSD || 0;
3138
+ const costB = this.compareRunB.TotalCostUSD || 0;
3139
+ return costB - costA;
3140
+ }
3141
+ getCompareImprovedCount() {
3142
+ return this.compareResults.filter(r => r.runA && r.runB &&
3143
+ r.runA.status !== 'Passed' && r.runB.status === 'Passed').length;
3144
+ }
3145
+ getCompareRegressedCount() {
3146
+ return this.compareResults.filter(r => r.runA && r.runB &&
3147
+ r.runA.status === 'Passed' && r.runB.status !== 'Passed').length;
3148
+ }
3149
+ // ==========================================
3150
+ // Excel Export Methods
3151
+ // ==========================================
3152
+ async exportToExcel() {
3153
+ try {
3154
+ // Ensure runs are loaded
3155
+ if (!this.runsLoaded)
3156
+ await this.loadRuns();
3157
+ const data = this.suiteRuns.map(run => ({
3158
+ 'Run ID': run.ID,
3159
+ 'Status': run.Status,
3160
+ 'Started At': run.StartedAt ? new Date(run.StartedAt).toLocaleString() : 'N/A',
3161
+ 'Completed At': run.CompletedAt ? new Date(run.CompletedAt).toLocaleString() : 'N/A',
3162
+ 'Duration (s)': run.TotalDurationSeconds?.toFixed(2) || 'N/A',
3163
+ 'Total Tests': run.TotalTests || 0,
3164
+ 'Passed': run.PassedTests || 0,
3165
+ 'Failed': run.FailedTests || 0,
3166
+ 'Errors': run.ErrorTests || 0,
3167
+ 'Skipped': run.SkippedTests || 0,
3168
+ 'Pass Rate (%)': run.TotalTests ? ((run.PassedTests || 0) / run.TotalTests * 100).toFixed(1) : 'N/A',
3169
+ 'Total Cost ($)': run.TotalCostUSD?.toFixed(6) || 'N/A',
3170
+ 'Tags': TagsHelper.parseTags(run.Tags).join(', ') || 'None',
3171
+ 'Environment': run.Environment || 'N/A',
3172
+ 'Trigger Type': run.TriggerType || 'N/A',
3173
+ 'Run By': run.RunByUser || 'N/A'
3174
+ }));
3175
+ this.downloadAsCSV(data, `${this.record.Name}_runs_export.csv`);
3176
+ SharedService.Instance.CreateSimpleNotification('Export successful', 'success', 2000);
3177
+ }
3178
+ catch (error) {
3179
+ console.error('Export failed:', error);
3180
+ SharedService.Instance.CreateSimpleNotification('Export failed', 'error', 3000);
3181
+ }
3182
+ }
3183
+ downloadAsCSV(data, filename) {
3184
+ if (data.length === 0)
3185
+ return;
3186
+ const headers = Object.keys(data[0]);
3187
+ const csvContent = [
3188
+ headers.join(','),
3189
+ ...data.map(row => headers.map(h => {
3190
+ const val = row[h];
3191
+ // Escape quotes and wrap in quotes if contains comma
3192
+ const strVal = String(val);
3193
+ if (strVal.includes(',') || strVal.includes('"') || strVal.includes('\n')) {
3194
+ return `"${strVal.replace(/"/g, '""')}"`;
3195
+ }
3196
+ return strVal;
3197
+ }).join(','))
3198
+ ].join('\n');
3199
+ const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
3200
+ const link = document.createElement('a');
3201
+ link.href = URL.createObjectURL(blob);
3202
+ link.download = filename;
3203
+ link.click();
3204
+ URL.revokeObjectURL(link.href);
3205
+ }
3206
+ getRunTags(run) {
3207
+ return TagsHelper.parseTags(run.Tags);
3208
+ }
3209
+ /**
3210
+ * Export matrix view data to CSV
3211
+ */
3212
+ exportMatrixToCSV() {
3213
+ if (this.matrixData.length === 0) {
3214
+ SharedService.Instance.CreateSimpleNotification('No data to export', 'warning', 2000);
3215
+ return;
3216
+ }
3217
+ try {
3218
+ const tests = this.getUniqueTestsFromMatrix();
3219
+ const runs = this.matrixData;
3220
+ // Build CSV rows
3221
+ const rows = [];
3222
+ for (const test of tests) {
3223
+ const row = {
3224
+ 'Seq': test.sequence,
3225
+ 'Test Name': test.testName
3226
+ };
3227
+ // Add column for each run
3228
+ for (const run of runs) {
3229
+ const result = run.testResults.get(test.testId);
3230
+ const runLabel = run.tags.length > 0
3231
+ ? `${run.tags.slice(0, 2).join('/')} (${this.getRelativeTime(run.date)})`
3232
+ : this.getRelativeTime(run.date);
3233
+ if (result) {
3234
+ // Build cell value based on what's shown
3235
+ const parts = [];
3236
+ if (this.evalPreferences.showExecution)
3237
+ parts.push(result.status);
3238
+ if (this.evalPreferences.showHuman)
3239
+ parts.push(result.humanRating != null ? `H:${result.humanRating}` : 'H:-');
3240
+ if (this.evalPreferences.showAuto)
3241
+ parts.push(result.score != null ? `A:${Math.round(result.score * 100)}%` : 'A:-');
3242
+ row[runLabel] = parts.join(' | ');
3243
+ }
3244
+ else {
3245
+ row[runLabel] = 'Not Run';
3246
+ }
3247
+ }
3248
+ rows.push(row);
3249
+ }
3250
+ this.downloadAsCSV(rows, `${this.record.Name}_matrix_export.csv`);
3251
+ SharedService.Instance.CreateSimpleNotification('Matrix exported successfully', 'success', 2000);
3252
+ }
3253
+ catch (error) {
3254
+ console.error('Matrix export failed:', error);
3255
+ SharedService.Instance.CreateSimpleNotification('Export failed', 'error', 3000);
3256
+ }
3257
+ }
3258
+ // ==========================================
3259
+ // Keyboard Shortcuts Settings
3260
+ // ==========================================
3261
+ /**
3262
+ * Load keyboard shortcuts visibility setting from user settings
3263
+ */
3264
+ loadShortcutsSetting() {
3265
+ try {
3266
+ const engine = UserInfoEngine.Instance;
3267
+ const setting = engine.UserSettings.find(s => s.Setting === SHORTCUTS_SETTINGS_KEY);
3268
+ if (setting) {
3269
+ this.shortcutsSettingEntity = setting;
3270
+ this.showShortcuts = setting.Value === 'true';
3271
+ }
3272
+ else {
3273
+ // Default to hidden
3274
+ this.showShortcuts = false;
3275
+ }
3276
+ this.cdr.markForCheck();
3277
+ }
3278
+ catch (error) {
3279
+ console.warn('Failed to load shortcuts setting:', error);
3280
+ }
3281
+ }
3282
+ /**
3283
+ * Toggle analytics filters visibility
3284
+ */
3285
+ toggleFilters() {
3286
+ this.filtersCollapsed = !this.filtersCollapsed;
3287
+ this.cdr.markForCheck();
3288
+ }
3289
+ /**
3290
+ * Toggle keyboard shortcuts visibility and save preference
3291
+ */
3292
+ async toggleShortcuts() {
3293
+ this.showShortcuts = !this.showShortcuts;
3294
+ this.cdr.markForCheck();
3295
+ try {
3296
+ const userId = this.metadata.CurrentUser?.ID;
3297
+ if (!userId)
3298
+ return;
3299
+ if (!this.shortcutsSettingEntity) {
3300
+ const engine = UserInfoEngine.Instance;
3301
+ const setting = engine.UserSettings.find(s => s.Setting === SHORTCUTS_SETTINGS_KEY);
3302
+ if (setting) {
3303
+ this.shortcutsSettingEntity = setting;
3304
+ }
3305
+ else {
3306
+ this.shortcutsSettingEntity = await this.metadata.GetEntityObject('MJ: User Settings');
3307
+ this.shortcutsSettingEntity.UserID = userId;
3308
+ this.shortcutsSettingEntity.Setting = SHORTCUTS_SETTINGS_KEY;
3309
+ }
3310
+ }
3311
+ this.shortcutsSettingEntity.Value = this.showShortcuts ? 'true' : 'false';
3312
+ await this.shortcutsSettingEntity.Save();
3313
+ }
3314
+ catch (error) {
3315
+ console.warn('Failed to save shortcuts setting:', error);
3316
+ }
3317
+ }
3318
+ static { this.ɵfac = function TestSuiteFormComponentExtended_Factory(t) { return new (t || TestSuiteFormComponentExtended)(i0.ɵɵdirectiveInject(i0.ElementRef), i0.ɵɵdirectiveInject(i1.SharedService), i0.ɵɵdirectiveInject(i2.Router), i0.ɵɵdirectiveInject(i2.ActivatedRoute), i0.ɵɵdirectiveInject(i0.ChangeDetectorRef), i0.ɵɵdirectiveInject(i3.TestingDialogService), i0.ɵɵdirectiveInject(i3.EvaluationPreferencesService)); }; }
3319
+ static { this.ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: TestSuiteFormComponentExtended, selectors: [["mj-test-suite-form"]], viewQuery: function TestSuiteFormComponentExtended_Query(rf, ctx) { if (rf & 1) {
3320
+ i0.ɵɵviewQuery(_c0, 5);
3321
+ } if (rf & 2) {
3322
+ let _t;
3323
+ i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.chartContainer = _t.first);
3324
+ } }, hostBindings: function TestSuiteFormComponentExtended_HostBindings(rf, ctx) { if (rf & 1) {
3325
+ i0.ɵɵlistener("keydown", function TestSuiteFormComponentExtended_keydown_HostBindingHandler($event) { return ctx.handleKeyboardShortcut($event); }, false, i0.ɵɵresolveDocument);
3326
+ } }, features: [i0.ɵɵInheritDefinitionFeature], decls: 54, vars: 35, consts: [["chartContainer", ""], ["kendoDialogContainer", "", 1, "test-suite-form"], [1, "suite-header"], [1, "header-content"], [1, "header-left"], [1, "suite-icon"], [1, "fas", "fa-layer-group"], [1, "suite-info"], [1, "suite-meta"], [1, "status-badge", 3, "ngClass"], [1, "fas", 3, "ngClass"], ["class", "test-count", 4, "ngIf"], [1, "header-actions"], ["kendoButton", "", "title", "Export to CSV", 3, "click"], [1, "fas", "fa-file-excel"], ["kendoButton", "", "themeColor", "primary", 3, "click"], [1, "fas", "fa-play"], ["kendoButton", "", 3, "click", "disabled"], ["class", "suite-description", 4, "ngIf"], [1, "tabs-container"], ["role", "tablist", 1, "tabs"], ["role", "tab", 1, "tab", 3, "click"], [1, "fas", "fa-th-large"], [1, "fas", "fa-flask"], ["class", "tab-badge", 4, "ngIf"], [1, "fas", "fa-history"], [1, "fas", "fa-chart-line"], [1, "fas", "fa-balance-scale"], [1, "tab-content"], ["class", "overview-tab", 4, "ngIf"], ["class", "tests-tab", 4, "ngIf"], ["class", "runs-tab", 4, "ngIf"], ["class", "analytics-tab", 4, "ngIf"], ["class", "compare-tab", 4, "ngIf"], [1, "shortcuts-toggle", 3, "click", "title"], [1, "fas", "fa-keyboard"], ["class", "keyboard-shortcuts", 4, "ngIf"], [1, "test-count"], [1, "suite-description"], [1, "tab-badge"], [1, "overview-tab"], [1, "info-section"], [1, "fas", "fa-info-circle"], [1, "info-grid"], [1, "info-item"], [1, "info-label"], [1, "info-value"], [1, "status-badge-inline", 3, "ngClass"], [1, "config-section"], [1, "fas", "fa-cogs"], [1, "config-grid"], [1, "config-item"], ["type", "number", "placeholder", "Default: 300000 (5 min)", 1, "config-input", 3, "ngModelChange", "ngModel"], [1, "config-hint"], [1, "tests-tab"], ["class", "loading-state", 4, "ngIf"], ["class", "tests-list", 4, "ngIf"], ["class", "empty-state", 4, "ngIf"], [1, "loading-state"], [1, "skeleton-list"], ["class", "skeleton-card", 4, "ngFor", "ngForOf"], [1, "skeleton-card"], [1, "skeleton-sequence"], [1, "skeleton-icon"], [1, "skeleton-content"], [1, "skeleton-line", "wide"], [1, "skeleton-line", "narrow"], [1, "tests-list"], ["class", "test-item", 3, "click", 4, "ngFor", "ngForOf"], [1, "test-item", 3, "click"], [1, "test-sequence"], [1, "test-icon"], [1, "test-content"], [1, "test-name"], [1, "test-status"], [4, "ngIf"], [1, "fas", "fa-chevron-right"], [1, "empty-state"], [1, "empty-icon"], [1, "runs-tab"], ["class", "runs-list", 4, "ngIf"], [1, "runs-list"], ["class", "run-item", 3, "click", 4, "ngFor", "ngForOf"], [1, "run-item", 3, "click"], [1, "run-icon"], [1, "fas"], [1, "run-content"], [1, "run-header"], [1, "run-id"], [1, "run-status"], [1, "run-meta"], [1, "fas", "fa-calendar"], [1, "run-eval-metrics"], ["class", "eval-metric status", 3, "class", 4, "ngIf"], ["class", "eval-metric human", "title", "Human evaluation", 4, "ngIf"], ["class", "eval-metric auto", "title", "Auto score (pass rate)", 3, "high", "medium", "low", 4, "ngIf"], ["class", "run-tags", 4, "ngIf"], [1, "fas", "fa-check-circle"], [1, "eval-metric", "status"], ["title", "Human evaluation", 1, "eval-metric", "human"], [1, "fas", "fa-user"], [1, "eval-pending"], [1, "fas", "fa-clock"], ["title", "Auto score (pass rate)", 1, "eval-metric", "auto"], [1, "fas", "fa-robot"], [1, "run-tags"], ["class", "tag-chip", 4, "ngFor", "ngForOf"], [1, "tag-chip"], [1, "fas", "fa-play-circle"], [1, "analytics-tab"], ["text", "Loading analytics data..."], [1, "analytics-subnav"], [1, "subnav-tabs"], [1, "subnav-tab", 3, "click"], [1, "fas", "fa-chart-bar"], [1, "fas", "fa-th"], [1, "fas", "fa-project-diagram"], [1, "analytics-filters"], [1, "filters-header", 3, "click"], [1, "filters-title"], [1, "fas", "fa-filter"], ["class", "filter-summary", 4, "ngIf"], ["class", "filters-content", 4, "ngIf"], [1, "filter-summary"], [1, "filters-content"], [1, "filter-group"], [1, "filter-buttons"], [1, "filter-btn", 3, "click"], ["class", "filter-group", 4, "ngIf"], ["class", "filter-hint", 4, "ngIf"], [1, "filter-buttons", "tag-filters"], [1, "filter-btn", "tag-btn", "all-tags-btn", 3, "click"], ["class", "filter-btn tag-btn", 3, "active", "click", 4, "ngFor", "ngForOf"], [1, "filter-hint"], [1, "filter-btn", "tag-btn", 3, "click"], ["class", "fas fa-check", 4, "ngIf"], [1, "fas", "fa-check"], [1, "analytics-kpis"], [1, "kpi-card"], [1, "kpi-icon"], [1, "kpi-content"], [1, "kpi-value"], [1, "kpi-label"], [1, "kpi-icon", "success"], [1, "kpi-trend", 3, "ngClass"], [1, "kpi-icon", "info"], [1, "kpi-icon", "warning"], [1, "fas", "fa-dollar-sign"], [1, "analytics-table-section"], [1, "fas", "fa-table"], [1, "analytics-table-wrapper"], [1, "analytics-table"], ["class", "clickable-row", 3, "click", 4, "ngFor", "ngForOf"], ["class", "empty-state small", 4, "ngIf"], [1, "clickable-row", 3, "click"], [1, "status-chip", 3, "ngClass"], [1, "pass-rate-cell"], [1, "pass-rate-bar", 3, "ngClass"], [1, "tag-cell"], ["class", "tag-chip-table", 4, "ngFor", "ngForOf"], ["class", "tag-more", 4, "ngIf"], [1, "tag-chip-table"], [1, "tag-more"], [1, "empty-state", "small"], ["class", "matrix-section", 4, "ngIf"], ["text", "Loading test matrix..."], [1, "matrix-section"], [1, "matrix-header"], [1, "matrix-header-right"], [1, "matrix-filter-input"], [1, "fas", "fa-search"], ["type", "text", "placeholder", "Filter tests...", 1, "filter-input", 3, "input", "value"], ["class", "clear-filter-btn", "title", "Clear filter", 3, "click", 4, "ngIf"], [1, "matrix-run-count"], ["kendoButton", "", "title", "Export matrix to CSV", 3, "click", "disabled"], [1, "fas", "fa-download"], [1, "matrix-scroll-container"], [1, "test-matrix"], ["title", "Sort by sequence", 1, "seq-header", 3, "click"], ["title", "Sort by name", 1, "test-name-header", 3, "click"], ["class", "run-header", 3, "title", "click", 4, "ngFor", "ngForOf"], [1, "spacer-header"], [3, "row-selected", "click", 4, "ngFor", "ngForOf"], [1, "totals-row"], [1, "seq-cell", "totals-label"], [1, "test-name-cell", "totals-label"], ["class", "result-cell totals-cell", 4, "ngFor", "ngForOf"], [1, "spacer-cell"], ["title", "Clear filter", 1, "clear-filter-btn", 3, "click"], [1, "fas", "fa-times"], [1, "run-header", 3, "click", "title"], [1, "run-header-content"], ["class", "run-tags-header", 4, "ngIf"], [1, "run-date"], [1, "run-pass-rate", 3, "ngClass"], [1, "run-tags-header"], ["class", "tag-chip-header", 4, "ngFor", "ngForOf"], ["class", "tag-more-header", 4, "ngIf"], [1, "tag-chip-header"], [1, "tag-more-header"], [3, "click"], [1, "seq-cell"], [1, "test-name-cell"], [1, "test-name", 3, "title"], ["class", "result-cell", 3, "ngClass", "clickable", "cell-not-run", "title", "click", 4, "ngFor", "ngForOf"], [1, "result-cell", 3, "click", "ngClass", "title"], ["class", "cell-not-run-indicator", 4, "ngIf"], [1, "cell-eval-stack"], ["class", "cell-status", 3, "ngClass", "cell-skipped-status", "title", 4, "ngIf"], ["class", "cell-human no-feedback", "title", "Human Review: No rating submitted yet", 4, "ngIf"], ["class", "cell-human has-feedback", 3, "rating-low", "rating-medium", "rating-good", "rating-excellent", "title", 4, "ngIf"], ["class", "cell-auto has-score", 3, "score-low", "score-medium", "score-good", "score-excellent", "title", 4, "ngIf"], ["class", "cell-auto no-score", "title", "Auto Score: No automated score available", 4, "ngIf"], [1, "cell-status", 3, "ngClass", "title"], ["title", "Human Review: No rating submitted yet", 1, "cell-human", "no-feedback"], [1, "fas", "fa-user-slash"], [1, "cell-human", "has-feedback", 3, "title"], [1, "rating-value"], [1, "cell-auto", "has-score", 3, "title"], [1, "score-value"], ["title", "Auto Score: No automated score available", 1, "cell-auto", "no-score"], [1, "cell-not-run-indicator"], [1, "fas", "fa-minus"], [1, "result-cell", "totals-cell"], [1, "cell-eval-stack", "totals-stack"], ["class", "totals-status", 4, "ngIf"], ["class", "totals-human", 4, "ngIf"], ["class", "totals-auto", 4, "ngIf"], [1, "totals-status"], [1, "pass-count"], [1, "totals-human"], ["class", "avg-label", 4, "ngIf"], [1, "count-label"], [1, "avg-label"], [1, "totals-auto"], ["class", "chart-section", 4, "ngIf"], ["text", "Loading chart data..."], [1, "chart-section"], [1, "chart-header"], [1, "chart-legend"], [1, "legend-item", "chart-passed"], [1, "legend-item", "chart-failed"], [1, "legend-item", "chart-error"], [1, "fas", "fa-exclamation"], [1, "legend-item", "chart-skipped"], [1, "fas", "fa-forward"], [1, "chart-container"], [1, "d3-chart"], [1, "chart-info"], [1, "compare-tab"], [1, "compare-selection"], [1, "compare-run-selector"], ["class", "run-selector-list", 4, "ngIf"], ["class", "selected-run-preview", 4, "ngIf"], [1, "compare-vs"], [1, "fas", "fa-exchange-alt"], ["class", "compare-results", 4, "ngIf"], ["class", "compare-empty", 4, "ngIf"], [1, "run-selector-list"], ["class", "run-selector-item", 3, "selected", "click", 4, "ngFor", "ngForOf"], [1, "run-selector-item", 3, "click"], [1, "selector-status"], [1, "selector-content"], [1, "selector-date"], [1, "selector-rate"], ["class", "selector-tags", 4, "ngIf"], [1, "selector-tags"], ["class", "tag-mini", 4, "ngFor", "ngForOf"], [1, "tag-mini"], [1, "selected-run-preview"], [1, "preview-header"], [1, "preview-label"], [1, "clear-btn", 3, "click"], [1, "preview-details"], [1, "preview-rate"], ["class", "run-selector-item", 3, "selected", "disabled", "click", 4, "ngFor", "ngForOf"], [1, "compare-results"], [1, "compare-summary"], [1, "compare-summary-card"], [1, "summary-label"], [1, "summary-value", 3, "ngClass"], [1, "compare-summary-card", "improved"], [1, "summary-value"], [1, "compare-summary-card", "regressed"], [1, "compare-table-section"], [1, "fas", "fa-list"], [1, "compare-table-wrapper"], [1, "compare-table"], [3, "ngClass", 4, "ngFor", "ngForOf"], [3, "ngClass"], ["class", "status-chip", 3, "ngClass", 4, "ngIf"], ["class", "status-chip status-missing", 4, "ngIf"], [3, "ngClass", 4, "ngIf"], ["class", "muted", 4, "ngIf"], ["class", "change-indicator improved", 4, "ngIf"], ["class", "change-indicator regressed", 4, "ngIf"], ["class", "change-indicator unchanged", 4, "ngIf"], [1, "status-chip", "status-missing"], [1, "muted"], [1, "change-indicator", "improved"], [1, "fas", "fa-arrow-up"], [1, "change-indicator", "regressed"], [1, "fas", "fa-arrow-down"], [1, "change-indicator", "unchanged"], ["text", "Loading comparison data..."], [1, "compare-empty"], [1, "compare-empty-icon"], [1, "keyboard-shortcuts"], [1, "shortcuts-header"], ["title", "Hide shortcuts", 1, "shortcuts-close", 3, "click"], [1, "shortcut-list"], [1, "shortcut-item"], [1, "shortcut-keys"]], template: function TestSuiteFormComponentExtended_Template(rf, ctx) { if (rf & 1) {
3327
+ i0.ɵɵelementStart(0, "div", 1)(1, "div", 2)(2, "div", 3)(3, "div", 4)(4, "div", 5);
3328
+ i0.ɵɵelement(5, "i", 6);
287
3329
  i0.ɵɵelementEnd();
288
- i0.ɵɵelementStart(6, "div", 6)(7, "h1");
3330
+ i0.ɵɵelementStart(6, "div", 7)(7, "h1");
289
3331
  i0.ɵɵtext(8);
290
3332
  i0.ɵɵelementEnd();
291
- i0.ɵɵelementStart(9, "div", 7)(10, "span", 8);
292
- i0.ɵɵtext(11);
293
- i0.ɵɵelementEnd()()()();
294
- i0.ɵɵelementStart(12, "div", 9)(13, "button", 10);
295
- i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_Template_button_click_13_listener() { return ctx.runSuite(); });
296
- i0.ɵɵtext(14, "Run Suite");
3333
+ i0.ɵɵelementStart(9, "div", 8)(10, "span", 9);
3334
+ i0.ɵɵelement(11, "i", 10);
3335
+ i0.ɵɵtext(12);
3336
+ i0.ɵɵelementEnd();
3337
+ i0.ɵɵtemplate(13, TestSuiteFormComponentExtended_span_13_Template, 3, 1, "span", 11);
297
3338
  i0.ɵɵelementEnd()()();
298
- i0.ɵɵtemplate(15, TestSuiteFormComponentExtended_div_15_Template, 3, 1, "div", 11);
3339
+ i0.ɵɵelementStart(14, "div", 12);
3340
+ i0.ɵɵelement(15, "app-evaluation-mode-toggle");
3341
+ i0.ɵɵelementStart(16, "button", 13);
3342
+ i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_Template_button_click_16_listener() { return ctx.exportToExcel(); });
3343
+ i0.ɵɵelement(17, "i", 14);
3344
+ i0.ɵɵtext(18, " Export ");
299
3345
  i0.ɵɵelementEnd();
300
- i0.ɵɵelementStart(16, "div", 12)(17, "div", 13)(18, "button", 14);
301
- i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_Template_button_click_18_listener() { return ctx.changeTab("overview"); });
302
- i0.ɵɵelement(19, "i", 15);
303
- i0.ɵɵtext(20, " Overview ");
3346
+ i0.ɵɵelementStart(19, "button", 15);
3347
+ i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_Template_button_click_19_listener() { return ctx.runSuite(); });
3348
+ i0.ɵɵelement(20, "i", 16);
3349
+ i0.ɵɵtext(21, " Run Suite ");
304
3350
  i0.ɵɵelementEnd();
305
- i0.ɵɵelementStart(21, "button", 14);
306
- i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_Template_button_click_21_listener() { return ctx.changeTab("tests"); });
307
- i0.ɵɵelement(22, "i", 16);
308
- i0.ɵɵtext(23, " Tests ");
309
- i0.ɵɵtemplate(24, TestSuiteFormComponentExtended_span_24_Template, 2, 1, "span", 17);
3351
+ i0.ɵɵelementStart(22, "button", 17);
3352
+ i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_Template_button_click_22_listener() { return ctx.refresh(); });
3353
+ i0.ɵɵelement(23, "i", 10);
3354
+ i0.ɵɵtext(24);
3355
+ i0.ɵɵelementEnd()()();
3356
+ i0.ɵɵtemplate(25, TestSuiteFormComponentExtended_div_25_Template, 3, 1, "div", 18);
3357
+ i0.ɵɵelementEnd();
3358
+ i0.ɵɵelementStart(26, "div", 19)(27, "div", 20)(28, "button", 21);
3359
+ i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_Template_button_click_28_listener() { return ctx.changeTab("overview"); });
3360
+ i0.ɵɵelement(29, "i", 22);
3361
+ i0.ɵɵtext(30, " Overview ");
3362
+ i0.ɵɵelementEnd();
3363
+ i0.ɵɵelementStart(31, "button", 21);
3364
+ i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_Template_button_click_31_listener() { return ctx.changeTab("tests"); });
3365
+ i0.ɵɵelement(32, "i", 23);
3366
+ i0.ɵɵtext(33, " Tests ");
3367
+ i0.ɵɵtemplate(34, TestSuiteFormComponentExtended_span_34_Template, 2, 1, "span", 24);
3368
+ i0.ɵɵelementEnd();
3369
+ i0.ɵɵelementStart(35, "button", 21);
3370
+ i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_Template_button_click_35_listener() { return ctx.changeTab("runs"); });
3371
+ i0.ɵɵelement(36, "i", 25);
3372
+ i0.ɵɵtext(37, " Runs ");
3373
+ i0.ɵɵtemplate(38, TestSuiteFormComponentExtended_span_38_Template, 2, 1, "span", 24);
310
3374
  i0.ɵɵelementEnd();
311
- i0.ɵɵelementStart(25, "button", 14);
312
- i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_Template_button_click_25_listener() { return ctx.changeTab("runs"); });
313
- i0.ɵɵelement(26, "i", 18);
314
- i0.ɵɵtext(27, " Runs ");
315
- i0.ɵɵtemplate(28, TestSuiteFormComponentExtended_span_28_Template, 2, 1, "span", 17);
3375
+ i0.ɵɵelementStart(39, "button", 21);
3376
+ i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_Template_button_click_39_listener() { return ctx.changeTab("analytics"); });
3377
+ i0.ɵɵelement(40, "i", 26);
3378
+ i0.ɵɵtext(41, " Analytics ");
3379
+ i0.ɵɵelementEnd();
3380
+ i0.ɵɵelementStart(42, "button", 21);
3381
+ i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_Template_button_click_42_listener() { return ctx.changeTab("compare"); });
3382
+ i0.ɵɵelement(43, "i", 27);
3383
+ i0.ɵɵtext(44, " Compare ");
316
3384
  i0.ɵɵelementEnd()()();
317
- i0.ɵɵelementStart(29, "div", 19);
318
- i0.ɵɵtemplate(30, TestSuiteFormComponentExtended_div_30_Template, 27, 10, "div", 20)(31, TestSuiteFormComponentExtended_div_31_Template, 3, 2, "div", 21)(32, TestSuiteFormComponentExtended_div_32_Template, 3, 2, "div", 22);
319
- i0.ɵɵelementEnd()();
3385
+ i0.ɵɵelementStart(45, "div", 28);
3386
+ i0.ɵɵtemplate(46, TestSuiteFormComponentExtended_div_46_Template, 45, 13, "div", 29)(47, TestSuiteFormComponentExtended_div_47_Template, 4, 3, "div", 30)(48, TestSuiteFormComponentExtended_div_48_Template, 4, 3, "div", 31)(49, TestSuiteFormComponentExtended_div_49_Template, 4, 3, "div", 32)(50, TestSuiteFormComponentExtended_div_50_Template, 18, 8, "div", 33);
3387
+ i0.ɵɵelementEnd();
3388
+ i0.ɵɵelementStart(51, "button", 34);
3389
+ i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_Template_button_click_51_listener() { return ctx.toggleShortcuts(); });
3390
+ i0.ɵɵelement(52, "i", 35);
3391
+ i0.ɵɵelementEnd();
3392
+ i0.ɵɵtemplate(53, TestSuiteFormComponentExtended_div_53_Template, 32, 0, "div", 36);
3393
+ i0.ɵɵelementEnd();
320
3394
  } if (rf & 2) {
321
3395
  i0.ɵɵadvance(4);
322
3396
  i0.ɵɵstyleProp("background-color", ctx.getStatusColor());
323
3397
  i0.ɵɵadvance(4);
324
3398
  i0.ɵɵtextInterpolate(ctx.record.Name);
325
3399
  i0.ɵɵadvance(2);
326
- i0.ɵɵstyleProp("background-color", ctx.getStatusColor());
3400
+ i0.ɵɵproperty("ngClass", ctx.getStatusClass());
3401
+ i0.ɵɵadvance();
3402
+ i0.ɵɵproperty("ngClass", ctx.record.Status === "Active" ? "fa-circle-check" : "fa-circle-pause");
327
3403
  i0.ɵɵadvance();
328
3404
  i0.ɵɵtextInterpolate1(" ", ctx.record.Status, " ");
329
- i0.ɵɵadvance(4);
3405
+ i0.ɵɵadvance();
3406
+ i0.ɵɵproperty("ngIf", ctx.testsLoaded);
3407
+ i0.ɵɵadvance(9);
3408
+ i0.ɵɵproperty("disabled", ctx.isRefreshing);
3409
+ i0.ɵɵadvance();
3410
+ i0.ɵɵproperty("ngClass", ctx.isRefreshing ? "fa-sync fa-spin" : "fa-sync");
3411
+ i0.ɵɵadvance();
3412
+ i0.ɵɵtextInterpolate1(" ", ctx.isRefreshing ? "Refreshing..." : "Refresh", " ");
3413
+ i0.ɵɵadvance();
330
3414
  i0.ɵɵproperty("ngIf", ctx.record.Description);
331
3415
  i0.ɵɵadvance(3);
332
3416
  i0.ɵɵclassProp("active", ctx.activeTab === "overview");
3417
+ i0.ɵɵattribute("aria-selected", ctx.activeTab === "overview");
333
3418
  i0.ɵɵadvance(3);
334
3419
  i0.ɵɵclassProp("active", ctx.activeTab === "tests");
3420
+ i0.ɵɵattribute("aria-selected", ctx.activeTab === "tests");
335
3421
  i0.ɵɵadvance(3);
336
3422
  i0.ɵɵproperty("ngIf", ctx.testsLoaded);
337
3423
  i0.ɵɵadvance();
338
3424
  i0.ɵɵclassProp("active", ctx.activeTab === "runs");
3425
+ i0.ɵɵattribute("aria-selected", ctx.activeTab === "runs");
339
3426
  i0.ɵɵadvance(3);
340
3427
  i0.ɵɵproperty("ngIf", ctx.runsLoaded);
341
- i0.ɵɵadvance(2);
3428
+ i0.ɵɵadvance();
3429
+ i0.ɵɵclassProp("active", ctx.activeTab === "analytics");
3430
+ i0.ɵɵattribute("aria-selected", ctx.activeTab === "analytics");
3431
+ i0.ɵɵadvance(3);
3432
+ i0.ɵɵclassProp("active", ctx.activeTab === "compare");
3433
+ i0.ɵɵattribute("aria-selected", ctx.activeTab === "compare");
3434
+ i0.ɵɵadvance(4);
342
3435
  i0.ɵɵproperty("ngIf", ctx.activeTab === "overview");
343
3436
  i0.ɵɵadvance();
344
3437
  i0.ɵɵproperty("ngIf", ctx.activeTab === "tests");
345
3438
  i0.ɵɵadvance();
346
3439
  i0.ɵɵproperty("ngIf", ctx.activeTab === "runs");
347
- } }, dependencies: [i4.NgForOf, i4.NgIf, i5.DialogContainerDirective, i6.ButtonComponent, i4.DatePipe], styles: [".test-suite-form[_ngcontent-%COMP%] { display: flex; flex-direction: column; height: 100%; background: #f8f9fa; }\n.suite-header[_ngcontent-%COMP%] { background: white; border-bottom: 1px solid #e0e0e0; padding: 20px; }\n.header-content[_ngcontent-%COMP%] { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 16px; }\n.header-left[_ngcontent-%COMP%] { display: flex; gap: 16px; }\n.suite-icon[_ngcontent-%COMP%] { width: 56px; height: 56px; border-radius: 12px; display: flex; align-items: center; justify-content: center; color: white; font-size: 24px; }\n.suite-info[_ngcontent-%COMP%] h1[_ngcontent-%COMP%] { margin: 0 0 8px 0; font-size: 24px; font-weight: 600; color: #333; }\n.suite-meta[_ngcontent-%COMP%] { display: flex; align-items: center; gap: 12px; }\n.status-badge[_ngcontent-%COMP%] { display: inline-flex; align-items: center; gap: 6px; padding: 4px 12px; border-radius: 12px; color: white; font-size: 12px; font-weight: 600; }\n.suite-description[_ngcontent-%COMP%] { padding: 16px; background: #f8f9fa; border-radius: 8px; }\n.suite-description[_ngcontent-%COMP%] p[_ngcontent-%COMP%] { margin: 0; color: #666; line-height: 1.5; }\n.tabs-container[_ngcontent-%COMP%] { background: white; border-bottom: 1px solid #e0e0e0; }\n.tabs[_ngcontent-%COMP%] { display: flex; padding: 0 20px; overflow-x: auto; }\n.tab[_ngcontent-%COMP%] { display: flex; align-items: center; gap: 8px; padding: 16px 20px; border: none; background: transparent; border-bottom: 3px solid transparent; color: #666; font-size: 14px; font-weight: 500; cursor: pointer; transition: all 0.2s ease; }\n.tab[_ngcontent-%COMP%]:hover { color: #2196f3; background: rgba(33, 150, 243, 0.05); }\n.tab.active[_ngcontent-%COMP%] { color: #2196f3; border-bottom-color: #2196f3; }\n.tab-badge[_ngcontent-%COMP%] { background: #e0e0e0; color: #666; padding: 2px 8px; border-radius: 10px; font-size: 11px; font-weight: 600; }\n.tab.active[_ngcontent-%COMP%] .tab-badge[_ngcontent-%COMP%] { background: #e3f2fd; color: #2196f3; }\n.tab-content[_ngcontent-%COMP%] { flex: 1; overflow-y: auto; padding: 20px; }\n.info-section[_ngcontent-%COMP%] { background: white; border-radius: 12px; padding: 20px; }\n.info-section[_ngcontent-%COMP%] h3[_ngcontent-%COMP%] { margin: 0 0 16px 0; font-size: 18px; font-weight: 600; color: #333; }\n.info-grid[_ngcontent-%COMP%] { display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px; }\n.info-item[_ngcontent-%COMP%] { display: flex; flex-direction: column; gap: 4px; }\n.info-label[_ngcontent-%COMP%] { font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; color: #999; }\n.info-value[_ngcontent-%COMP%] { font-size: 14px; color: #333; word-wrap: break-word; }\n.tests-list[_ngcontent-%COMP%], .runs-list[_ngcontent-%COMP%] { display: flex; flex-direction: column; gap: 12px; }\n.test-item[_ngcontent-%COMP%], .run-item[_ngcontent-%COMP%] { display: flex; align-items: center; gap: 12px; padding: 16px; background: white; border: 2px solid transparent; border-radius: 8px; cursor: pointer; transition: all 0.2s ease; }\n.test-item[_ngcontent-%COMP%]:hover, .run-item[_ngcontent-%COMP%]:hover { background: #e3f2fd; border-color: #90caf9; }\n.test-sequence[_ngcontent-%COMP%] { width: 32px; height: 32px; display: flex; align-items: center; justify-content: center; background: #f0f0f0; border-radius: 50%; font-size: 14px; font-weight: 700; color: #666; flex-shrink: 0; }\n.test-icon[_ngcontent-%COMP%], .run-icon[_ngcontent-%COMP%] { width: 40px; height: 40px; display: flex; align-items: center; justify-content: center; border-radius: 8px; color: white; font-size: 18px; flex-shrink: 0; background: #2196f3; }\n.test-content[_ngcontent-%COMP%], .run-content[_ngcontent-%COMP%] { flex: 1; }\n.test-name[_ngcontent-%COMP%], .run-header[_ngcontent-%COMP%] { font-size: 14px; font-weight: 600; color: #333; margin-bottom: 4px; }\n.test-status[_ngcontent-%COMP%], .run-meta[_ngcontent-%COMP%] { font-size: 12px; color: #666; }\n.run-meta[_ngcontent-%COMP%] { display: flex; gap: 12px; }\n.no-data[_ngcontent-%COMP%] { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 60px 20px; color: #999; text-align: center; background: white; border-radius: 12px; }\n.no-data[_ngcontent-%COMP%] i[_ngcontent-%COMP%] { font-size: 48px; margin-bottom: 16px; opacity: 0.3; }\n.no-data[_ngcontent-%COMP%] p[_ngcontent-%COMP%] { margin: 0; font-size: 14px; }"], changeDetection: 0 }); }
3440
+ i0.ɵɵadvance();
3441
+ i0.ɵɵproperty("ngIf", ctx.activeTab === "analytics");
3442
+ i0.ɵɵadvance();
3443
+ i0.ɵɵproperty("ngIf", ctx.activeTab === "compare");
3444
+ i0.ɵɵadvance();
3445
+ i0.ɵɵproperty("title", ctx.showShortcuts ? "Hide keyboard shortcuts" : "Show keyboard shortcuts");
3446
+ i0.ɵɵadvance(2);
3447
+ i0.ɵɵproperty("ngIf", ctx.showShortcuts);
3448
+ } }, dependencies: [i4.NgClass, i4.NgForOf, i4.NgIf, i5.DefaultValueAccessor, i5.NumberValueAccessor, i5.NgControlStatus, i5.NgModel, i6.DialogContainerDirective, i7.ButtonComponent, i3.EvaluationModeToggleComponent, i8.LoadingComponent, i4.DatePipe], styles: ["\n\n\n\n\n\n\n[_nghost-%COMP%] {\n --test-primary: #2563eb;\n --test-primary-light: #3b82f6;\n --test-success: #10b981;\n --test-error: #ef4444;\n --test-warning: #f59e0b;\n --test-disabled: #6b7280;\n --test-bg: #f8fafc;\n --test-surface: #ffffff;\n --test-border: #e2e8f0;\n --test-text: #1e293b;\n --test-text-secondary: #64748b;\n --test-text-muted: #94a3b8;\n --test-radius-sm: 6px;\n --test-radius-md: 10px;\n --test-radius-lg: 16px;\n --test-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);\n --test-shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);\n --test-shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);\n --test-transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n display: block;\n height: 100%;\n}\n\n.test-suite-form[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n height: 100%;\n background: var(--test-bg);\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n}\n\n\n\n.suite-header[_ngcontent-%COMP%] {\n background: var(--test-surface);\n border-bottom: 1px solid var(--test-border);\n padding: 20px;\n}\n\n.header-content[_ngcontent-%COMP%] {\n display: flex;\n justify-content: space-between;\n align-items: flex-start;\n margin-bottom: 16px;\n gap: 16px;\n}\n\n.header-left[_ngcontent-%COMP%] {\n display: flex;\n gap: 16px;\n flex: 1;\n min-width: 0;\n}\n\n.suite-icon[_ngcontent-%COMP%] {\n width: 56px;\n height: 56px;\n border-radius: var(--test-radius-md);\n display: flex;\n align-items: center;\n justify-content: center;\n color: white;\n font-size: 24px;\n flex-shrink: 0;\n box-shadow: var(--test-shadow-md);\n transition: var(--test-transition);\n}\n\n.suite-icon[_ngcontent-%COMP%]:hover { transform: scale(1.05); }\n\n.suite-info[_ngcontent-%COMP%] { flex: 1; min-width: 0; }\n\n.suite-info[_ngcontent-%COMP%] h1[_ngcontent-%COMP%] {\n margin: 0 0 8px 0;\n font-size: clamp(18px, 4vw, 24px);\n font-weight: 700;\n color: var(--test-text);\n word-wrap: break-word;\n}\n\n.suite-meta[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 12px;\n flex-wrap: wrap;\n}\n\n.status-badge[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 6px 14px;\n border-radius: 20px;\n color: white;\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n}\n\n.status-badge.status-active[_ngcontent-%COMP%] { background: linear-gradient(135deg, var(--test-success) 0%, #059669 100%); }\n.status-badge.status-disabled[_ngcontent-%COMP%] { background: linear-gradient(135deg, var(--test-disabled) 0%, #4b5563 100%); }\n.status-badge.status-pending[_ngcontent-%COMP%] { background: linear-gradient(135deg, var(--test-warning) 0%, #d97706 100%); }\n\n.status-badge-inline[_ngcontent-%COMP%] {\n display: inline-flex;\n padding: 2px 10px;\n border-radius: 10px;\n color: white;\n font-size: 11px;\n font-weight: 600;\n}\n\n.status-badge-inline.status-active[_ngcontent-%COMP%] { background: var(--test-success); }\n.status-badge-inline.status-disabled[_ngcontent-%COMP%] { background: var(--test-disabled); }\n.status-badge-inline.status-pending[_ngcontent-%COMP%] { background: var(--test-warning); }\n\n.test-count[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--test-text-secondary);\n padding: 4px 10px;\n background: var(--test-bg);\n border-radius: var(--test-radius-sm);\n}\n\n.header-actions[_ngcontent-%COMP%] {\n display: flex;\n gap: 8px;\n flex-shrink: 0;\n}\n\n.suite-description[_ngcontent-%COMP%] {\n padding: 16px;\n background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);\n border-radius: var(--test-radius-md);\n border: 1px solid var(--test-border);\n}\n\n.suite-description[_ngcontent-%COMP%] p[_ngcontent-%COMP%] {\n margin: 0;\n color: var(--test-text-secondary);\n line-height: 1.6;\n font-size: 14px;\n}\n\n\n\n.tabs-container[_ngcontent-%COMP%] {\n background: var(--test-surface);\n border-bottom: 1px solid var(--test-border);\n position: sticky;\n top: 0;\n z-index: 10;\n}\n\n.tabs[_ngcontent-%COMP%] {\n display: flex;\n padding: 0 20px;\n overflow-x: auto;\n scrollbar-width: none;\n}\n\n.tabs[_ngcontent-%COMP%]::-webkit-scrollbar { display: none; }\n\n.tab[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 14px 18px;\n border: none;\n background: transparent;\n border-bottom: 3px solid transparent;\n color: var(--test-text-secondary);\n font-size: 14px;\n font-weight: 500;\n cursor: pointer;\n transition: var(--test-transition);\n white-space: nowrap;\n}\n\n.tab[_ngcontent-%COMP%]:hover {\n color: var(--test-primary);\n background: rgba(37, 99, 235, 0.05);\n}\n\n.tab.active[_ngcontent-%COMP%] {\n color: var(--test-primary);\n border-bottom-color: var(--test-primary);\n font-weight: 600;\n}\n\n.tab-badge[_ngcontent-%COMP%] {\n background: var(--test-border);\n color: var(--test-text-secondary);\n padding: 2px 8px;\n border-radius: 10px;\n font-size: 11px;\n font-weight: 600;\n}\n\n.tab.active[_ngcontent-%COMP%] .tab-badge[_ngcontent-%COMP%] {\n background: rgba(37, 99, 235, 0.15);\n color: var(--test-primary);\n}\n\n.tab-shortcut[_ngcontent-%COMP%] {\n font-size: 10px;\n color: var(--test-text-muted);\n background: var(--test-bg);\n padding: 2px 6px;\n border-radius: 4px;\n font-weight: 600;\n}\n\n\n\n.tab-content[_ngcontent-%COMP%] {\n flex: 1;\n overflow-y: auto;\n padding: 20px;\n}\n\n\n\n.overview-tab[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 20px;\n animation: _ngcontent-%COMP%_fadeIn 0.3s ease-out;\n}\n\n@keyframes _ngcontent-%COMP%_fadeIn {\n from { opacity: 0; transform: translateY(10px); }\n to { opacity: 1; transform: translateY(0); }\n}\n\n.info-section[_ngcontent-%COMP%], .config-section[_ngcontent-%COMP%] {\n background: var(--test-surface);\n border-radius: var(--test-radius-lg);\n padding: 24px;\n box-shadow: var(--test-shadow-sm);\n}\n\n.info-section[_ngcontent-%COMP%] h3[_ngcontent-%COMP%], .config-section[_ngcontent-%COMP%] h3[_ngcontent-%COMP%] {\n margin: 0 0 20px 0;\n font-size: 18px;\n font-weight: 700;\n color: var(--test-text);\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.info-section[_ngcontent-%COMP%] h3[_ngcontent-%COMP%] i[_ngcontent-%COMP%], .config-section[_ngcontent-%COMP%] h3[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--test-primary);\n}\n\n.info-grid[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\n gap: 16px;\n}\n\n.info-item[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.info-label[_ngcontent-%COMP%] {\n font-size: 11px;\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n color: var(--test-text-muted);\n}\n\n.info-value[_ngcontent-%COMP%] {\n font-size: 14px;\n color: var(--test-text);\n font-weight: 500;\n}\n\n.config-grid[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));\n gap: 20px;\n}\n\n.config-item[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.config-item[_ngcontent-%COMP%] label[_ngcontent-%COMP%] {\n font-size: 12px;\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n color: var(--test-text-muted);\n}\n\n.config-input[_ngcontent-%COMP%] {\n padding: 10px 14px;\n border: 1px solid var(--test-border);\n border-radius: var(--test-radius-sm);\n font-size: 14px;\n transition: var(--test-transition);\n}\n\n.config-input[_ngcontent-%COMP%]:focus {\n outline: none;\n border-color: var(--test-primary);\n box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);\n}\n\n.config-hint[_ngcontent-%COMP%] {\n font-size: 11px;\n color: var(--test-text-muted);\n}\n\n\n\n.tests-tab[_ngcontent-%COMP%], .runs-tab[_ngcontent-%COMP%] {\n animation: _ngcontent-%COMP%_fadeIn 0.3s ease-out;\n}\n\n.loading-state[_ngcontent-%COMP%] { padding: 0; }\n\n.skeleton-list[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 12px;\n}\n\n.skeleton-card[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 14px;\n padding: 16px;\n background: var(--test-surface);\n border-radius: var(--test-radius-md);\n border: 1px solid var(--test-border);\n}\n\n.skeleton-sequence[_ngcontent-%COMP%] {\n width: 32px;\n height: 32px;\n border-radius: 50%;\n background: linear-gradient(90deg, #e2e8f0 25%, #f1f5f9 50%, #e2e8f0 75%);\n background-size: 200% 100%;\n animation: _ngcontent-%COMP%_shimmer 1.5s infinite;\n}\n\n.skeleton-icon[_ngcontent-%COMP%] {\n width: 40px;\n height: 40px;\n border-radius: var(--test-radius-md);\n background: linear-gradient(90deg, #e2e8f0 25%, #f1f5f9 50%, #e2e8f0 75%);\n background-size: 200% 100%;\n animation: _ngcontent-%COMP%_shimmer 1.5s infinite;\n}\n\n.skeleton-content[_ngcontent-%COMP%] {\n flex: 1;\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n.skeleton-line[_ngcontent-%COMP%] {\n height: 14px;\n border-radius: 4px;\n background: linear-gradient(90deg, #e2e8f0 25%, #f1f5f9 50%, #e2e8f0 75%);\n background-size: 200% 100%;\n animation: _ngcontent-%COMP%_shimmer 1.5s infinite;\n}\n\n.skeleton-line.wide[_ngcontent-%COMP%] { width: 70%; }\n.skeleton-line.narrow[_ngcontent-%COMP%] { width: 40%; }\n\n@keyframes _ngcontent-%COMP%_shimmer {\n 0% { background-position: 200% 0; }\n 100% { background-position: -200% 0; }\n}\n\n.tests-list[_ngcontent-%COMP%], .runs-list[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n.test-item[_ngcontent-%COMP%], .run-item[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 14px;\n padding: 16px;\n background: var(--test-surface);\n border: 1px solid var(--test-border);\n border-radius: var(--test-radius-md);\n cursor: pointer;\n transition: var(--test-transition);\n}\n\n.test-item[_ngcontent-%COMP%]:hover, .run-item[_ngcontent-%COMP%]:hover {\n background: rgba(37, 99, 235, 0.05);\n border-color: var(--test-primary-light);\n transform: translateX(4px);\n}\n\n.test-sequence[_ngcontent-%COMP%] {\n width: 32px;\n height: 32px;\n display: flex;\n align-items: center;\n justify-content: center;\n background: var(--test-bg);\n border-radius: 50%;\n font-size: 14px;\n font-weight: 700;\n color: var(--test-text-secondary);\n flex-shrink: 0;\n}\n\n.test-icon[_ngcontent-%COMP%], .run-icon[_ngcontent-%COMP%] {\n width: 40px;\n height: 40px;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: var(--test-radius-md);\n color: white;\n font-size: 18px;\n flex-shrink: 0;\n box-shadow: var(--test-shadow-sm);\n}\n\n.test-icon[_ngcontent-%COMP%] {\n background: linear-gradient(135deg, var(--test-primary) 0%, #1d4ed8 100%);\n}\n\n.test-content[_ngcontent-%COMP%], .run-content[_ngcontent-%COMP%] { flex: 1; min-width: 0; }\n\n.test-name[_ngcontent-%COMP%], .run-header[_ngcontent-%COMP%] {\n font-size: 14px;\n font-weight: 600;\n color: var(--test-text);\n margin-bottom: 4px;\n}\n\n.run-header[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 12px;\n}\n\n.run-id[_ngcontent-%COMP%] { font-weight: 600; }\n\n.run-status[_ngcontent-%COMP%] {\n font-size: 12px;\n font-weight: 700;\n text-transform: uppercase;\n}\n\n.test-status[_ngcontent-%COMP%], .run-meta[_ngcontent-%COMP%] {\n display: flex;\n gap: 12px;\n font-size: 12px;\n color: var(--test-text-secondary);\n}\n\n.test-status[_ngcontent-%COMP%] span[_ngcontent-%COMP%], .run-meta[_ngcontent-%COMP%] span[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n}\n\n.test-item[_ngcontent-%COMP%] > i[_ngcontent-%COMP%], .run-item[_ngcontent-%COMP%] > i[_ngcontent-%COMP%] {\n color: var(--test-text-muted);\n font-size: 14px;\n transition: var(--test-transition);\n}\n\n.test-item[_ngcontent-%COMP%]:hover > i[_ngcontent-%COMP%], .run-item[_ngcontent-%COMP%]:hover > i[_ngcontent-%COMP%] {\n color: var(--test-primary);\n transform: translateX(2px);\n}\n\n\n\n.empty-state[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 60px 24px;\n text-align: center;\n background: var(--test-surface);\n border-radius: var(--test-radius-lg);\n}\n\n.empty-icon[_ngcontent-%COMP%] {\n width: 80px;\n height: 80px;\n display: flex;\n align-items: center;\n justify-content: center;\n background: var(--test-bg);\n border-radius: 50%;\n margin-bottom: 20px;\n}\n\n.empty-icon[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 36px;\n color: var(--test-text-muted);\n}\n\n.empty-state[_ngcontent-%COMP%] h4[_ngcontent-%COMP%] {\n margin: 0 0 8px 0;\n font-size: 18px;\n font-weight: 600;\n color: var(--test-text);\n}\n\n.empty-state[_ngcontent-%COMP%] p[_ngcontent-%COMP%] {\n margin: 0 0 20px 0;\n font-size: 14px;\n color: var(--test-text-secondary);\n max-width: 300px;\n}\n\n\n\n\n\n.shortcuts-toggle[_ngcontent-%COMP%] {\n position: fixed;\n bottom: 20px;\n right: 20px;\n width: 36px;\n height: 36px;\n border-radius: 50%;\n background: var(--test-surface);\n border: 1px solid var(--test-border);\n box-shadow: var(--test-shadow-md);\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--test-text-secondary);\n font-size: 14px;\n z-index: 99;\n transition: var(--test-transition);\n opacity: 0.7;\n}\n\n.shortcuts-toggle[_ngcontent-%COMP%]:hover {\n opacity: 1;\n transform: scale(1.1);\n color: var(--test-primary);\n border-color: var(--test-primary);\n}\n\n.keyboard-shortcuts[_ngcontent-%COMP%] {\n position: fixed;\n bottom: 20px;\n right: 20px;\n background: var(--test-surface);\n border: 1px solid var(--test-border);\n border-radius: var(--test-radius-md);\n padding: 12px 16px;\n box-shadow: var(--test-shadow-lg);\n font-size: 12px;\n z-index: 100;\n max-width: 260px;\n}\n\n.shortcuts-header[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 6px;\n margin-bottom: 10px;\n padding-bottom: 8px;\n border-bottom: 1px solid var(--test-border);\n font-weight: 600;\n color: var(--test-text);\n}\n\n.shortcuts-close[_ngcontent-%COMP%] {\n margin-left: auto;\n background: none;\n border: none;\n cursor: pointer;\n color: var(--test-text-muted);\n font-size: 12px;\n padding: 2px 4px;\n border-radius: 4px;\n transition: var(--test-transition);\n}\n\n.shortcuts-close[_ngcontent-%COMP%]:hover {\n color: var(--test-text);\n background: var(--test-border);\n}\n\n.shortcut-list[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.shortcut-item[_ngcontent-%COMP%] {\n display: flex;\n justify-content: space-between;\n align-items: center;\n color: var(--test-text-secondary);\n}\n\n.shortcut-keys[_ngcontent-%COMP%] {\n display: flex;\n gap: 4px;\n}\n\n.shortcut-keys[_ngcontent-%COMP%] kbd[_ngcontent-%COMP%] {\n background: var(--test-bg);\n border: 1px solid var(--test-border);\n border-radius: 4px;\n padding: 2px 6px;\n font-size: 11px;\n color: var(--test-text);\n}\n\n\n\n@media (max-width: 1024px) {\n .keyboard-shortcuts[_ngcontent-%COMP%], .shortcuts-toggle[_ngcontent-%COMP%] { display: none; }\n}\n\n@media (max-width: 768px) {\n .suite-header[_ngcontent-%COMP%] { padding: 16px; }\n\n .header-content[_ngcontent-%COMP%] {\n flex-direction: column;\n gap: 16px;\n }\n\n .header-actions[_ngcontent-%COMP%] {\n width: 100%;\n justify-content: stretch;\n }\n\n .header-actions[_ngcontent-%COMP%] button[_ngcontent-%COMP%] { flex: 1; }\n\n .tab-shortcut[_ngcontent-%COMP%] { display: none; }\n\n .info-grid[_ngcontent-%COMP%] { grid-template-columns: 1fr; }\n\n .test-item[_ngcontent-%COMP%], .run-item[_ngcontent-%COMP%] { padding: 14px; }\n}\n\n@media (max-width: 480px) {\n .suite-icon[_ngcontent-%COMP%] {\n width: 40px;\n height: 40px;\n font-size: 18px;\n }\n\n .suite-info[_ngcontent-%COMP%] h1[_ngcontent-%COMP%] { font-size: 16px; }\n\n .tab-badge[_ngcontent-%COMP%] { display: none; }\n\n .test-sequence[_ngcontent-%COMP%] { display: none; }\n}\n\n@media (hover: none) and (pointer: coarse) {\n .test-item[_ngcontent-%COMP%]:active, .run-item[_ngcontent-%COMP%]:active {\n background: rgba(37, 99, 235, 0.1);\n transform: scale(0.98);\n }\n\n .tab[_ngcontent-%COMP%] { min-height: 48px; }\n .test-item[_ngcontent-%COMP%], .run-item[_ngcontent-%COMP%] { min-height: 64px; }\n}\n\n@media (prefers-reduced-motion: reduce) {\n *[_ngcontent-%COMP%], *[_ngcontent-%COMP%]::before, *[_ngcontent-%COMP%]::after {\n animation-duration: 0.01ms !important;\n transition-duration: 0.01ms !important;\n }\n}\n\n@media print {\n .header-actions[_ngcontent-%COMP%], .tabs-container[_ngcontent-%COMP%], .keyboard-shortcuts[_ngcontent-%COMP%] {\n display: none !important;\n }\n}\n\n\n\n\n\n.run-tags[_ngcontent-%COMP%] {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n margin-top: 8px;\n}\n\n.tag-chip[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n padding: 3px 10px;\n background: linear-gradient(135deg, #eff6ff 0%, #dbeafe 100%);\n border: 1px solid #bfdbfe;\n border-radius: 12px;\n font-size: 11px;\n font-weight: 500;\n color: #1d4ed8;\n}\n\n.tag-mini[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n padding: 2px 6px;\n background: #f1f5f9;\n border-radius: 4px;\n font-size: 10px;\n font-weight: 500;\n color: #64748b;\n}\n\n.tag-more[_ngcontent-%COMP%] {\n font-size: 10px;\n color: #94a3b8;\n font-weight: 500;\n}\n\n\n\n.run-eval-metrics[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n margin-top: 8px;\n}\n\n.eval-metric[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 3px 8px;\n border-radius: 6px;\n font-size: 11px;\n font-weight: 500;\n}\n\n.eval-metric.status[_ngcontent-%COMP%] {\n background: #f1f5f9;\n color: #64748b;\n}\n\n.eval-metric.status.status-completed[_ngcontent-%COMP%] {\n background: #dcfce7;\n color: #166534;\n}\n\n.eval-metric.status.status-failed[_ngcontent-%COMP%] {\n background: #fee2e2;\n color: #991b1b;\n}\n\n.eval-metric.status.status-running[_ngcontent-%COMP%] {\n background: #dbeafe;\n color: #1d4ed8;\n}\n\n.eval-metric.status.status-pending[_ngcontent-%COMP%] {\n background: #fef3c7;\n color: #92400e;\n}\n\n.eval-metric.human[_ngcontent-%COMP%] {\n background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);\n border: 1px solid #f59e0b;\n color: #92400e;\n}\n\n.eval-metric.human[_ngcontent-%COMP%] .eval-pending[_ngcontent-%COMP%] {\n font-size: 9px;\n color: #d97706;\n}\n\n.eval-metric.auto[_ngcontent-%COMP%] {\n background: linear-gradient(135deg, #dbeafe 0%, #bfdbfe 100%);\n border: 1px solid #93c5fd;\n color: #1d4ed8;\n}\n\n.eval-metric.auto.high[_ngcontent-%COMP%] {\n background: linear-gradient(135deg, #dcfce7 0%, #bbf7d0 100%);\n border: 1px solid #86efac;\n color: #166534;\n}\n\n.eval-metric.auto.medium[_ngcontent-%COMP%] {\n background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);\n border: 1px solid #fcd34d;\n color: #92400e;\n}\n\n.eval-metric.auto.low[_ngcontent-%COMP%] {\n background: linear-gradient(135deg, #fee2e2 0%, #fecaca 100%);\n border: 1px solid #fca5a5;\n color: #991b1b;\n}\n\n.tag-cell[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 4px;\n flex-wrap: wrap;\n}\n\n.tag-chip-table[_ngcontent-%COMP%] {\n display: inline-block;\n padding: 3px 8px;\n background: linear-gradient(135deg, #dbeafe 0%, #bfdbfe 100%);\n border: 1px solid #93c5fd;\n color: #1d4ed8;\n border-radius: 10px;\n font-size: 10px;\n font-weight: 600;\n white-space: nowrap;\n max-width: 80px;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n\n\n\n\n.analytics-tab[_ngcontent-%COMP%] {\n animation: _ngcontent-%COMP%_fadeIn 0.3s ease-out;\n}\n\n\n\n.analytics-filters[_ngcontent-%COMP%] {\n background: var(--test-surface);\n border-radius: var(--test-radius-lg);\n margin-bottom: 16px;\n box-shadow: var(--test-shadow-sm);\n overflow: hidden;\n}\n\n.analytics-filters.collapsed[_ngcontent-%COMP%] {\n margin-bottom: 12px;\n}\n\n.filters-header[_ngcontent-%COMP%] {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 12px 16px;\n cursor: pointer;\n user-select: none;\n transition: background 0.15s ease;\n}\n\n.filters-header[_ngcontent-%COMP%]:hover {\n background: var(--test-bg);\n}\n\n.filters-title[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 13px;\n font-weight: 600;\n color: var(--test-text-secondary);\n}\n\n.filters-title[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--test-primary);\n}\n\n.filter-summary[_ngcontent-%COMP%] {\n font-weight: 400;\n color: var(--test-text-muted);\n margin-left: 8px;\n}\n\n.filters-header[_ngcontent-%COMP%] > i[_ngcontent-%COMP%] {\n color: var(--test-text-muted);\n font-size: 12px;\n}\n\n.filters-content[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 16px;\n padding: 12px 16px 16px 16px;\n border-top: 1px solid var(--test-border);\n}\n\n.filter-group[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n.filter-group[_ngcontent-%COMP%] label[_ngcontent-%COMP%] {\n font-size: 11px;\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n color: var(--test-text-muted);\n}\n\n.filter-buttons[_ngcontent-%COMP%] {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n}\n\n.filter-btn[_ngcontent-%COMP%] {\n padding: 8px 16px;\n border: 1px solid var(--test-border);\n border-radius: var(--test-radius-sm);\n background: var(--test-surface);\n color: var(--test-text-secondary);\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n transition: var(--test-transition);\n}\n\n.filter-btn[_ngcontent-%COMP%]:hover {\n border-color: var(--test-primary);\n color: var(--test-primary);\n background: rgba(37, 99, 235, 0.05);\n}\n\n.filter-btn.active[_ngcontent-%COMP%] {\n background: var(--test-primary);\n border-color: var(--test-primary);\n color: white;\n}\n\n.filter-btn.tag-btn[_ngcontent-%COMP%] {\n padding: 6px 12px;\n font-size: 12px;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n}\n\n.filter-btn.tag-btn[_ngcontent-%COMP%] i.fa-check[_ngcontent-%COMP%] {\n font-size: 10px;\n}\n\n.filter-btn.tag-btn.all-tags-btn[_ngcontent-%COMP%] {\n font-weight: 600;\n}\n\n.filter-btn.tag-btn.all-tags-btn[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 11px;\n}\n\n.filter-hint[_ngcontent-%COMP%] {\n font-weight: 400;\n font-size: 10px;\n color: var(--test-primary);\n text-transform: none;\n letter-spacing: normal;\n}\n\n.tag-filters[_ngcontent-%COMP%] {\n max-height: 120px;\n overflow-y: auto;\n}\n\n\n\n.analytics-kpis[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\n gap: 16px;\n margin-bottom: 24px;\n}\n\n.kpi-card[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 16px;\n background: var(--test-surface);\n border-radius: var(--test-radius-lg);\n padding: 20px;\n box-shadow: var(--test-shadow-sm);\n transition: var(--test-transition);\n}\n\n.kpi-card[_ngcontent-%COMP%]:hover {\n transform: translateY(-2px);\n box-shadow: var(--test-shadow-md);\n}\n\n.kpi-icon[_ngcontent-%COMP%] {\n width: 48px;\n height: 48px;\n display: flex;\n align-items: center;\n justify-content: center;\n background: #eff6ff;\n border-radius: var(--test-radius-md);\n color: var(--test-primary);\n font-size: 20px;\n}\n\n.kpi-icon.success[_ngcontent-%COMP%] {\n background: #ecfdf5;\n color: var(--test-success);\n}\n\n.kpi-icon.info[_ngcontent-%COMP%] {\n background: #f0f9ff;\n color: #0ea5e9;\n}\n\n.kpi-icon.warning[_ngcontent-%COMP%] {\n background: #fffbeb;\n color: var(--test-warning);\n}\n\n.kpi-content[_ngcontent-%COMP%] {\n flex: 1;\n min-width: 0;\n}\n\n.kpi-value[_ngcontent-%COMP%] {\n font-size: 24px;\n font-weight: 700;\n color: var(--test-text);\n line-height: 1.2;\n}\n\n.kpi-label[_ngcontent-%COMP%] {\n font-size: 12px;\n color: var(--test-text-secondary);\n margin-top: 2px;\n}\n\n.kpi-trend[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n font-size: 12px;\n font-weight: 600;\n margin-top: 4px;\n padding: 2px 8px;\n border-radius: 10px;\n background: #f1f5f9;\n color: var(--test-text-secondary);\n}\n\n.kpi-trend.trend-up[_ngcontent-%COMP%] {\n background: #ecfdf5;\n color: var(--test-success);\n}\n\n.kpi-trend.trend-down[_ngcontent-%COMP%] {\n background: #fef2f2;\n color: var(--test-error);\n}\n\n\n\n.analytics-table-section[_ngcontent-%COMP%] {\n background: var(--test-surface);\n border-radius: var(--test-radius-lg);\n padding: 24px;\n box-shadow: var(--test-shadow-sm);\n}\n\n.analytics-table-section[_ngcontent-%COMP%] h3[_ngcontent-%COMP%] {\n margin: 0 0 16px 0;\n font-size: 16px;\n font-weight: 600;\n color: var(--test-text);\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.analytics-table-section[_ngcontent-%COMP%] h3[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--test-primary);\n}\n\n.analytics-table-wrapper[_ngcontent-%COMP%] {\n overflow-x: auto;\n margin: 0 -24px;\n padding: 0 24px;\n}\n\n.analytics-table[_ngcontent-%COMP%] {\n width: 100%;\n border-collapse: collapse;\n font-size: 13px;\n}\n\n.analytics-table[_ngcontent-%COMP%] th[_ngcontent-%COMP%] {\n text-align: left;\n padding: 12px 16px;\n background: #f8fafc;\n border-bottom: 2px solid var(--test-border);\n font-weight: 600;\n color: var(--test-text-secondary);\n text-transform: uppercase;\n font-size: 11px;\n letter-spacing: 0.5px;\n white-space: nowrap;\n}\n\n.analytics-table[_ngcontent-%COMP%] td[_ngcontent-%COMP%] {\n padding: 14px 16px;\n border-bottom: 1px solid var(--test-border);\n color: var(--test-text);\n}\n\n.analytics-table[_ngcontent-%COMP%] tbody[_ngcontent-%COMP%] tr.clickable-row[_ngcontent-%COMP%] {\n cursor: pointer;\n transition: var(--test-transition);\n}\n\n.analytics-table[_ngcontent-%COMP%] tbody[_ngcontent-%COMP%] tr.clickable-row[_ngcontent-%COMP%]:hover {\n background: rgba(37, 99, 235, 0.05);\n}\n\n.status-chip[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n padding: 4px 10px;\n border-radius: 12px;\n font-size: 11px;\n font-weight: 600;\n text-transform: uppercase;\n}\n\n.status-chip.status-completed[_ngcontent-%COMP%] { background: #ecfdf5; color: var(--test-success); }\n.status-chip.status-passed[_ngcontent-%COMP%] { background: #ecfdf5; color: var(--test-success); }\n.status-chip.status-failed[_ngcontent-%COMP%] { background: #fef2f2; color: var(--test-error); }\n.status-chip.status-error[_ngcontent-%COMP%] { background: #fffbeb; color: var(--test-warning); }\n.status-chip.status-running[_ngcontent-%COMP%] { background: #eff6ff; color: var(--test-primary); }\n.status-chip.status-pending[_ngcontent-%COMP%] { background: #f5f3ff; color: #8b5cf6; }\n.status-chip.status-cancelled[_ngcontent-%COMP%] { background: #f1f5f9; color: var(--test-disabled); }\n.status-chip.status-missing[_ngcontent-%COMP%] { background: #f1f5f9; color: var(--test-text-muted); }\n.status-chip.status-timeout[_ngcontent-%COMP%] { background: #fffbeb; color: var(--test-warning); }\n.status-chip.status-skipped[_ngcontent-%COMP%] { background: #f1f5f9; color: var(--test-disabled); }\n\n.pass-rate-cell[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 100px;\n}\n\n.pass-rate-bar[_ngcontent-%COMP%] {\n height: 6px;\n border-radius: 3px;\n background: var(--test-success);\n transition: width 0.3s ease;\n max-width: 60px;\n}\n\n.pass-rate-bar.medium[_ngcontent-%COMP%] { background: var(--test-warning); }\n.pass-rate-bar.low[_ngcontent-%COMP%] { background: var(--test-error); }\n\n\n\n.analytics-subnav[_ngcontent-%COMP%] {\n background: var(--test-surface);\n border-radius: var(--test-radius-lg);\n padding: 8px;\n margin-bottom: 20px;\n box-shadow: var(--test-shadow-sm);\n display: inline-flex;\n}\n\n.subnav-tabs[_ngcontent-%COMP%] {\n display: flex;\n gap: 4px;\n background: var(--test-bg);\n border-radius: var(--test-radius-md);\n padding: 4px;\n}\n\n.subnav-tab[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px 20px;\n border: none;\n border-radius: var(--test-radius-sm);\n background: transparent;\n color: var(--test-text-secondary);\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n transition: var(--test-transition);\n}\n\n.subnav-tab[_ngcontent-%COMP%]:hover {\n color: var(--test-primary);\n background: rgba(37, 99, 235, 0.05);\n}\n\n.subnav-tab.active[_ngcontent-%COMP%] {\n background: var(--test-surface);\n color: var(--test-primary);\n box-shadow: var(--test-shadow-sm);\n}\n\n.subnav-tab[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 14px;\n}\n\n\n\n.matrix-section[_ngcontent-%COMP%] {\n background: var(--test-surface);\n border-radius: var(--test-radius-lg);\n padding: 16px;\n box-shadow: var(--test-shadow-sm);\n display: flex;\n flex-direction: column;\n max-height: calc(100vh - 280px);\n min-height: 300px;\n}\n\n.matrix-header[_ngcontent-%COMP%] {\n display: flex;\n justify-content: space-between;\n align-items: center;\n flex-wrap: wrap;\n gap: 12px;\n margin-bottom: 12px;\n padding-bottom: 12px;\n border-bottom: 1px solid var(--test-border);\n flex-shrink: 0;\n}\n\n.matrix-header-right[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 12px;\n}\n\n.matrix-header[_ngcontent-%COMP%] h3[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 10px;\n margin: 0;\n font-size: 15px;\n font-weight: 600;\n color: var(--test-text);\n}\n\n.matrix-header[_ngcontent-%COMP%] h3[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--test-primary);\n}\n\n.matrix-run-count[_ngcontent-%COMP%] {\n font-size: 12px;\n color: var(--test-text-muted);\n font-weight: 500;\n}\n\n\n\n.matrix-filter-input[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n background: #f8fafc;\n border: 1px solid var(--test-border);\n border-radius: 6px;\n padding: 4px 10px;\n min-width: 180px;\n}\n\n.matrix-filter-input[_ngcontent-%COMP%] i.fa-search[_ngcontent-%COMP%] {\n color: #94a3b8;\n font-size: 12px;\n}\n\n.matrix-filter-input[_ngcontent-%COMP%] .filter-input[_ngcontent-%COMP%] {\n border: none;\n background: transparent;\n outline: none;\n font-size: 12px;\n color: var(--test-text);\n width: 100%;\n}\n\n.matrix-filter-input[_ngcontent-%COMP%] .filter-input[_ngcontent-%COMP%]::placeholder {\n color: #94a3b8;\n}\n\n.clear-filter-btn[_ngcontent-%COMP%] {\n border: none;\n background: none;\n padding: 2px 4px;\n cursor: pointer;\n color: #94a3b8;\n font-size: 10px;\n border-radius: 3px;\n transition: all 0.15s ease;\n}\n\n.clear-filter-btn[_ngcontent-%COMP%]:hover {\n background: #e2e8f0;\n color: #64748b;\n}\n\n\n\n.matrix-scroll-container[_ngcontent-%COMP%] {\n flex: 1;\n overflow: auto;\n border: 1px solid var(--test-border);\n border-radius: var(--test-radius-sm);\n}\n\n.test-matrix[_ngcontent-%COMP%] {\n display: table;\n width: 100%;\n border-collapse: collapse;\n font-size: 13px;\n}\n\n.test-matrix[_ngcontent-%COMP%] thead[_ngcontent-%COMP%] {\n display: table-header-group;\n}\n\n.test-matrix[_ngcontent-%COMP%] thead[_ngcontent-%COMP%] tr[_ngcontent-%COMP%] {\n display: table-row;\n}\n\n.test-matrix[_ngcontent-%COMP%] tbody[_ngcontent-%COMP%] {\n display: table-row-group;\n}\n\n.test-matrix[_ngcontent-%COMP%] tbody[_ngcontent-%COMP%] tr[_ngcontent-%COMP%] {\n display: table-row;\n cursor: pointer;\n transition: background-color 0.15s ease;\n}\n\n.test-matrix[_ngcontent-%COMP%] tbody[_ngcontent-%COMP%] tr[_ngcontent-%COMP%]:hover {\n background-color: rgba(59, 130, 246, 0.05);\n}\n\n.test-matrix[_ngcontent-%COMP%] tbody[_ngcontent-%COMP%] tr.row-selected[_ngcontent-%COMP%] {\n background-color: rgba(59, 130, 246, 0.12) !important;\n}\n\n.test-matrix[_ngcontent-%COMP%] tbody[_ngcontent-%COMP%] tr.row-selected[_ngcontent-%COMP%] td[_ngcontent-%COMP%] {\n border-top: 1px solid rgba(59, 130, 246, 0.3);\n border-bottom: 1px solid rgba(59, 130, 246, 0.3);\n}\n\n.test-matrix[_ngcontent-%COMP%] tbody[_ngcontent-%COMP%] tr.row-selected[_ngcontent-%COMP%] .seq-cell[_ngcontent-%COMP%], \n.test-matrix[_ngcontent-%COMP%] tbody[_ngcontent-%COMP%] tr.row-selected[_ngcontent-%COMP%] .test-name-cell[_ngcontent-%COMP%] {\n background-color: rgba(59, 130, 246, 0.12) !important;\n}\n\n.test-matrix[_ngcontent-%COMP%] tfoot[_ngcontent-%COMP%] {\n display: table-footer-group;\n}\n\n.test-matrix[_ngcontent-%COMP%] th[_ngcontent-%COMP%], \n.test-matrix[_ngcontent-%COMP%] td[_ngcontent-%COMP%] {\n display: table-cell;\n border: 1px solid var(--test-border);\n padding: 8px 12px;\n text-align: center;\n vertical-align: middle;\n}\n\n.test-matrix[_ngcontent-%COMP%] th[_ngcontent-%COMP%] {\n background: var(--test-bg);\n font-weight: 600;\n font-size: 11px;\n color: var(--test-text-secondary);\n text-transform: uppercase;\n letter-spacing: 0.5px;\n position: sticky;\n top: 0;\n z-index: 10;\n border-bottom: 3px solid #475569 !important;\n}\n\n\n\n.test-matrix[_ngcontent-%COMP%] .seq-header[_ngcontent-%COMP%], \n.test-matrix[_ngcontent-%COMP%] .seq-cell[_ngcontent-%COMP%] {\n width: 36px;\n min-width: 36px;\n max-width: 36px;\n text-align: center;\n position: sticky;\n left: 0;\n font-size: 11px;\n color: #64748b;\n border-right: 1px solid var(--test-border);\n padding: 6px 4px !important;\n}\n\n\n\n.test-matrix[_ngcontent-%COMP%] .seq-header[_ngcontent-%COMP%] {\n cursor: pointer;\n font-weight: 600;\n background: var(--test-bg);\n z-index: 12; \n\n top: 0;\n}\n\n\n\n.test-matrix[_ngcontent-%COMP%] .seq-cell[_ngcontent-%COMP%] {\n background: var(--test-surface);\n z-index: 2;\n}\n\n.test-matrix[_ngcontent-%COMP%] .seq-header[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 9px;\n margin-left: 2px;\n opacity: 0.6;\n}\n\n.test-matrix[_ngcontent-%COMP%] .seq-header[_ngcontent-%COMP%]:hover {\n background: #f1f5f9;\n}\n\n.test-matrix[_ngcontent-%COMP%] .test-name-header[_ngcontent-%COMP%] {\n text-align: left;\n min-width: 150px;\n max-width: 500px;\n width: auto;\n position: sticky;\n left: 36px;\n background: var(--test-bg);\n z-index: 11;\n border-right: 2px solid var(--test-border);\n cursor: pointer;\n}\n\n.test-matrix[_ngcontent-%COMP%] .test-name-header[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 9px;\n margin-left: 4px;\n opacity: 0.6;\n}\n\n.test-matrix[_ngcontent-%COMP%] .test-name-header[_ngcontent-%COMP%]:hover {\n background: #f1f5f9;\n}\n\n.test-matrix[_ngcontent-%COMP%] .run-header[_ngcontent-%COMP%] {\n min-width: 120px;\n width: 120px;\n cursor: pointer;\n transition: var(--test-transition);\n}\n\n\n\n.test-matrix[_ngcontent-%COMP%] .spacer-header[_ngcontent-%COMP%], \n.test-matrix[_ngcontent-%COMP%] .spacer-cell[_ngcontent-%COMP%] {\n width: 100%;\n min-width: 20px;\n background: var(--test-bg);\n border: none;\n}\n\n.test-matrix[_ngcontent-%COMP%] .spacer-cell[_ngcontent-%COMP%] {\n background: var(--test-surface);\n}\n\n.test-matrix[_ngcontent-%COMP%] .run-header[_ngcontent-%COMP%]:hover {\n background: rgba(37, 99, 235, 0.05);\n}\n\n.run-header-content[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 4px;\n}\n\n.run-date[_ngcontent-%COMP%] {\n font-size: 12px;\n font-weight: 600;\n color: var(--test-text);\n}\n\n.run-pass-rate[_ngcontent-%COMP%] {\n font-size: 11px;\n font-weight: 700;\n padding: 2px 8px;\n border-radius: 10px;\n}\n\n.run-pass-rate.high[_ngcontent-%COMP%] {\n background: #ecfdf5;\n color: var(--test-success);\n}\n\n.run-pass-rate.medium[_ngcontent-%COMP%] {\n background: #fffbeb;\n color: var(--test-warning);\n}\n\n.run-pass-rate.low[_ngcontent-%COMP%] {\n background: #fef2f2;\n color: var(--test-error);\n}\n\n.run-tags[_ngcontent-%COMP%] {\n display: flex;\n gap: 4px;\n}\n\n.tag-tiny[_ngcontent-%COMP%] {\n font-size: 9px;\n padding: 2px 6px;\n background: #eff6ff;\n color: var(--test-primary);\n border-radius: 8px;\n white-space: nowrap;\n max-width: 60px;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n\n\n.run-tags-header[_ngcontent-%COMP%] {\n display: flex;\n flex-wrap: wrap;\n gap: 3px;\n justify-content: center;\n margin-bottom: 4px;\n}\n\n.tag-chip-header[_ngcontent-%COMP%] {\n display: inline-block;\n padding: 3px 8px;\n background: linear-gradient(135deg, #dbeafe 0%, #bfdbfe 100%);\n border: 1px solid #93c5fd;\n color: #1d4ed8;\n border-radius: 10px;\n font-size: 10px;\n font-weight: 600;\n white-space: nowrap;\n max-width: 70px;\n overflow: hidden;\n text-overflow: ellipsis;\n text-transform: none;\n letter-spacing: normal;\n}\n\n.tag-more-header[_ngcontent-%COMP%] {\n font-size: 9px;\n color: var(--test-text-secondary);\n padding: 2px 4px;\n}\n\n.test-matrix[_ngcontent-%COMP%] .test-name-cell[_ngcontent-%COMP%] {\n text-align: left;\n min-width: 150px;\n max-width: 500px;\n width: auto;\n position: sticky;\n left: 36px;\n background: var(--test-surface);\n z-index: 2;\n border-right: 2px solid var(--test-border);\n padding: 6px 10px !important;\n}\n\n.test-name[_ngcontent-%COMP%] {\n display: block;\n white-space: nowrap;\n font-weight: 500;\n color: var(--test-text);\n font-size: 12px;\n}\n\n.result-cell[_ngcontent-%COMP%] {\n position: relative;\n min-width: 120px;\n width: 120px;\n transition: var(--test-transition);\n}\n\n.result-cell.clickable[_ngcontent-%COMP%] {\n cursor: pointer;\n}\n\n.result-cell.clickable[_ngcontent-%COMP%]:hover {\n transform: scale(1.1);\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);\n z-index: 5;\n}\n\n.result-cell[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 14px;\n}\n\n\n\n.result-cell.cell-passed[_ngcontent-%COMP%], \n.result-cell.cell-failed[_ngcontent-%COMP%], \n.result-cell.cell-error[_ngcontent-%COMP%], \n.result-cell.cell-timeout[_ngcontent-%COMP%], \n.result-cell.cell-running[_ngcontent-%COMP%], \n.result-cell.cell-pending[_ngcontent-%COMP%] {\n background: #ffffff;\n color: var(--test-text);\n}\n\n\n\n.result-cell.cell-skipped[_ngcontent-%COMP%] {\n background: repeating-linear-gradient(\n 45deg,\n #f8fafc,\n #f8fafc 4px,\n #e2e8f0 4px,\n #e2e8f0 8px\n );\n color: var(--test-disabled);\n}\n\n.result-cell.cell-none[_ngcontent-%COMP%] {\n background: #f8fafc;\n color: var(--test-text-muted);\n}\n\n.cell-score[_ngcontent-%COMP%] {\n display: block;\n font-size: 10px;\n font-weight: 600;\n margin-top: 2px;\n}\n\n\n\n.cell-eval-stack[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n flex-wrap: nowrap;\n}\n\n.result-cell.multi-eval[_ngcontent-%COMP%] {\n min-width: 120px;\n width: auto;\n}\n\n\n\n.cell-status.status-timeout[_ngcontent-%COMP%] {\n background: #fef3c7;\n color: #d97706;\n}\n\n.cell-status[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 22px;\n height: 22px;\n border-radius: 50%;\n font-size: 11px;\n}\n\n.cell-status.status-passed[_ngcontent-%COMP%] {\n background: #dcfce7;\n color: #166534;\n}\n\n.cell-status.status-failed[_ngcontent-%COMP%] {\n background: #fee2e2;\n color: #991b1b;\n}\n\n.cell-status.status-error[_ngcontent-%COMP%] {\n background: #fef3c7;\n color: #92400e;\n}\n\n.cell-status.status-skipped[_ngcontent-%COMP%] {\n background: #f1f5f9;\n color: #64748b;\n}\n\n.cell-status.status-running[_ngcontent-%COMP%] {\n background: #dbeafe;\n color: #1d4ed8;\n}\n\n.cell-status.status-pending[_ngcontent-%COMP%] {\n background: #f3e8ff;\n color: #7c3aed;\n}\n\n.cell-human[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 22px;\n height: 22px;\n border-radius: 50%;\n background: #fef3c7;\n color: #d97706;\n font-size: 10px;\n}\n\n.cell-auto[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n padding: 2px 6px;\n border-radius: 4px;\n font-size: 10px;\n font-weight: 600;\n background: #dbeafe;\n color: #1d4ed8;\n}\n\n.cell-auto.high[_ngcontent-%COMP%] {\n background: #dcfce7;\n color: #166534;\n}\n\n.cell-auto.medium[_ngcontent-%COMP%] {\n background: #fef3c7;\n color: #92400e;\n}\n\n.cell-auto.low[_ngcontent-%COMP%] {\n background: #fee2e2;\n color: #991b1b;\n}\n\n.cell-none-indicator[_ngcontent-%COMP%] {\n color: var(--test-text-muted);\n opacity: 0.5;\n}\n\n.matrix-info[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 12px;\n color: var(--test-text-muted);\n padding: 12px;\n background: var(--test-bg);\n border-radius: var(--test-radius-sm);\n}\n\n.matrix-info[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--test-primary);\n}\n\n\n\n\n\n.chart-section[_ngcontent-%COMP%] {\n background: var(--test-surface);\n border-radius: var(--test-radius-lg);\n padding: 24px;\n box-shadow: var(--test-shadow-sm);\n}\n\n.chart-header[_ngcontent-%COMP%] {\n display: flex;\n justify-content: space-between;\n align-items: center;\n flex-wrap: wrap;\n gap: 16px;\n margin-bottom: 20px;\n padding-bottom: 16px;\n border-bottom: 1px solid var(--test-border);\n}\n\n.chart-header[_ngcontent-%COMP%] h3[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 10px;\n margin: 0;\n font-size: 16px;\n font-weight: 600;\n color: var(--test-text);\n}\n\n.chart-header[_ngcontent-%COMP%] h3[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--test-primary);\n}\n\n.chart-legend[_ngcontent-%COMP%] {\n display: flex;\n gap: 16px;\n flex-wrap: wrap;\n}\n\n.chart-legend[_ngcontent-%COMP%] .legend-item[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 12px;\n font-weight: 500;\n padding: 4px 10px;\n border-radius: 12px;\n}\n\n.chart-legend[_ngcontent-%COMP%] .legend-item.chart-passed[_ngcontent-%COMP%] {\n background: #ecfdf5;\n color: var(--test-success);\n}\n\n.chart-legend[_ngcontent-%COMP%] .legend-item.chart-failed[_ngcontent-%COMP%] {\n background: #fef2f2;\n color: var(--test-error);\n}\n\n.chart-legend[_ngcontent-%COMP%] .legend-item.chart-error[_ngcontent-%COMP%] {\n background: #fffbeb;\n color: var(--test-warning);\n}\n\n.chart-legend[_ngcontent-%COMP%] .legend-item.chart-skipped[_ngcontent-%COMP%] {\n background: #f1f5f9;\n color: var(--test-disabled);\n}\n\n.chart-container[_ngcontent-%COMP%] {\n min-height: 500px;\n position: relative;\n overflow: hidden;\n background: linear-gradient(135deg, #fafbff 0%, #f8fafc 100%);\n border-radius: var(--test-radius-md);\n border: 1px solid var(--test-border);\n}\n\n.d3-chart[_ngcontent-%COMP%] {\n width: 100%;\n height: 500px;\n}\n\n.d3-chart[_ngcontent-%COMP%] svg[_ngcontent-%COMP%] {\n width: 100%;\n height: 100%;\n}\n\n\n\n.d3-chart[_ngcontent-%COMP%] .node[_ngcontent-%COMP%] {\n cursor: pointer;\n transition: transform 0.2s ease;\n}\n\n.d3-chart[_ngcontent-%COMP%] .node[_ngcontent-%COMP%]:hover {\n transform: scale(1.05);\n}\n\n.d3-chart[_ngcontent-%COMP%] .node-label[_ngcontent-%COMP%] {\n font-size: 11px;\n font-weight: 500;\n fill: var(--test-text);\n pointer-events: none;\n}\n\n.d3-chart[_ngcontent-%COMP%] .link[_ngcontent-%COMP%] {\n fill: none;\n stroke-opacity: 0.4;\n transition: stroke-opacity 0.2s ease;\n}\n\n.d3-chart[_ngcontent-%COMP%] .link[_ngcontent-%COMP%]:hover {\n stroke-opacity: 0.8;\n}\n\n.d3-chart[_ngcontent-%COMP%] .tooltip[_ngcontent-%COMP%] {\n position: absolute;\n padding: 10px 14px;\n background: rgba(30, 41, 59, 0.95);\n color: white;\n border-radius: 8px;\n font-size: 12px;\n pointer-events: none;\n z-index: 100;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);\n max-width: 250px;\n}\n\n.d3-chart[_ngcontent-%COMP%] .tooltip-title[_ngcontent-%COMP%] {\n font-weight: 600;\n margin-bottom: 4px;\n}\n\n.d3-chart[_ngcontent-%COMP%] .tooltip-value[_ngcontent-%COMP%] {\n opacity: 0.8;\n}\n\n.chart-info[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 12px;\n color: var(--test-text-muted);\n padding: 12px;\n background: var(--test-bg);\n border-radius: var(--test-radius-sm);\n margin-top: 16px;\n}\n\n.chart-info[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--test-primary);\n}\n\n\n\n\n\n.compare-tab[_ngcontent-%COMP%] {\n animation: _ngcontent-%COMP%_fadeIn 0.3s ease-out;\n}\n\n.compare-selection[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: 1fr auto 1fr;\n gap: 24px;\n align-items: flex-start;\n margin-bottom: 24px;\n}\n\n.compare-run-selector[_ngcontent-%COMP%] {\n background: var(--test-surface);\n border-radius: var(--test-radius-lg);\n padding: 20px;\n box-shadow: var(--test-shadow-sm);\n}\n\n.compare-run-selector[_ngcontent-%COMP%] h4[_ngcontent-%COMP%] {\n margin: 0 0 16px 0;\n font-size: 14px;\n font-weight: 600;\n color: var(--test-text);\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.run-selector-list[_ngcontent-%COMP%] {\n max-height: 200px;\n overflow-y: auto;\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-bottom: 12px;\n}\n\n.run-selector-item[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 10px 12px;\n border: 1px solid var(--test-border);\n border-radius: var(--test-radius-sm);\n cursor: pointer;\n transition: var(--test-transition);\n}\n\n.run-selector-item[_ngcontent-%COMP%]:hover {\n border-color: var(--test-primary);\n background: rgba(37, 99, 235, 0.05);\n}\n\n.run-selector-item.selected[_ngcontent-%COMP%] {\n border-color: var(--test-primary);\n background: #eff6ff;\n}\n\n.run-selector-item.disabled[_ngcontent-%COMP%] {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.selector-status[_ngcontent-%COMP%] {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n flex-shrink: 0;\n}\n\n.selector-content[_ngcontent-%COMP%] {\n flex: 1;\n min-width: 0;\n}\n\n.selector-date[_ngcontent-%COMP%] {\n font-size: 12px;\n font-weight: 500;\n color: var(--test-text);\n}\n\n.selector-rate[_ngcontent-%COMP%] {\n font-size: 11px;\n color: var(--test-text-secondary);\n}\n\n.selector-tags[_ngcontent-%COMP%] {\n display: flex;\n gap: 4px;\n flex-wrap: wrap;\n}\n\n.selected-run-preview[_ngcontent-%COMP%] {\n padding: 12px;\n background: #f8fafc;\n border-radius: var(--test-radius-sm);\n border: 1px solid var(--test-border);\n}\n\n.preview-header[_ngcontent-%COMP%] {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 8px;\n}\n\n.preview-label[_ngcontent-%COMP%] {\n font-size: 11px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--test-text-muted);\n}\n\n.clear-btn[_ngcontent-%COMP%] {\n padding: 4px 8px;\n border: none;\n background: transparent;\n color: var(--test-error);\n font-size: 11px;\n font-weight: 500;\n cursor: pointer;\n}\n\n.clear-btn[_ngcontent-%COMP%]:hover { text-decoration: underline; }\n\n.preview-details[_ngcontent-%COMP%] {\n display: flex;\n justify-content: space-between;\n font-size: 12px;\n color: var(--test-text);\n}\n\n.preview-rate[_ngcontent-%COMP%] {\n font-weight: 600;\n color: var(--test-success);\n}\n\n.compare-vs[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 40px;\n height: 40px;\n background: var(--test-bg);\n border-radius: 50%;\n color: var(--test-text-muted);\n font-size: 16px;\n align-self: center;\n margin-top: 60px;\n}\n\n\n\n.compare-results[_ngcontent-%COMP%] {\n animation: _ngcontent-%COMP%_fadeIn 0.3s ease-out;\n}\n\n.compare-summary[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));\n gap: 16px;\n margin-bottom: 24px;\n}\n\n.compare-summary-card[_ngcontent-%COMP%] {\n background: var(--test-surface);\n border-radius: var(--test-radius-lg);\n padding: 20px;\n box-shadow: var(--test-shadow-sm);\n text-align: center;\n}\n\n.compare-summary-card[_ngcontent-%COMP%] .summary-label[_ngcontent-%COMP%] {\n font-size: 11px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--test-text-muted);\n margin-bottom: 8px;\n}\n\n.compare-summary-card[_ngcontent-%COMP%] .summary-value[_ngcontent-%COMP%] {\n font-size: 24px;\n font-weight: 700;\n color: var(--test-text);\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n}\n\n.compare-summary-card[_ngcontent-%COMP%] .summary-value.positive[_ngcontent-%COMP%] { color: var(--test-success); }\n.compare-summary-card[_ngcontent-%COMP%] .summary-value.negative[_ngcontent-%COMP%] { color: var(--test-error); }\n\n.compare-summary-card.improved[_ngcontent-%COMP%] {\n border-left: 4px solid var(--test-success);\n}\n\n.compare-summary-card.improved[_ngcontent-%COMP%] .summary-value[_ngcontent-%COMP%] { color: var(--test-success); }\n\n.compare-summary-card.regressed[_ngcontent-%COMP%] {\n border-left: 4px solid var(--test-error);\n}\n\n.compare-summary-card.regressed[_ngcontent-%COMP%] .summary-value[_ngcontent-%COMP%] { color: var(--test-error); }\n\n\n\n.compare-table-section[_ngcontent-%COMP%] {\n background: var(--test-surface);\n border-radius: var(--test-radius-lg);\n padding: 24px;\n box-shadow: var(--test-shadow-sm);\n}\n\n.compare-table-section[_ngcontent-%COMP%] h3[_ngcontent-%COMP%] {\n margin: 0 0 16px 0;\n font-size: 16px;\n font-weight: 600;\n color: var(--test-text);\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.compare-table-section[_ngcontent-%COMP%] h3[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--test-primary);\n}\n\n.compare-table-wrapper[_ngcontent-%COMP%] {\n overflow-x: auto;\n}\n\n.compare-table[_ngcontent-%COMP%] {\n width: 100%;\n border-collapse: collapse;\n font-size: 13px;\n}\n\n.compare-table[_ngcontent-%COMP%] th[_ngcontent-%COMP%] {\n text-align: left;\n padding: 12px 16px;\n background: #f8fafc;\n border-bottom: 2px solid var(--test-border);\n font-weight: 600;\n color: var(--test-text-secondary);\n text-transform: uppercase;\n font-size: 11px;\n letter-spacing: 0.5px;\n white-space: nowrap;\n}\n\n.compare-table[_ngcontent-%COMP%] td[_ngcontent-%COMP%] {\n padding: 14px 16px;\n border-bottom: 1px solid var(--test-border);\n color: var(--test-text);\n}\n\n.compare-table[_ngcontent-%COMP%] tbody[_ngcontent-%COMP%] tr.improved[_ngcontent-%COMP%] {\n background: rgba(16, 185, 129, 0.05);\n}\n\n.compare-table[_ngcontent-%COMP%] tbody[_ngcontent-%COMP%] tr.regressed[_ngcontent-%COMP%] {\n background: rgba(239, 68, 68, 0.05);\n}\n\n.test-name-cell[_ngcontent-%COMP%] {\n font-weight: 500;\n max-width: 200px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.positive[_ngcontent-%COMP%] { color: var(--test-success); }\n.negative[_ngcontent-%COMP%] { color: var(--test-error); }\n.muted[_ngcontent-%COMP%] { color: var(--test-text-muted); }\n\n.change-indicator[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 4px 8px;\n border-radius: 10px;\n font-size: 11px;\n font-weight: 600;\n}\n\n.change-indicator.improved[_ngcontent-%COMP%] {\n background: #ecfdf5;\n color: var(--test-success);\n}\n\n.change-indicator.regressed[_ngcontent-%COMP%] {\n background: #fef2f2;\n color: var(--test-error);\n}\n\n.change-indicator.unchanged[_ngcontent-%COMP%] {\n background: #f1f5f9;\n color: var(--test-text-muted);\n}\n\n\n\n.compare-empty[_ngcontent-%COMP%] {\n text-align: center;\n padding: 60px 24px;\n background: var(--test-surface);\n border-radius: var(--test-radius-lg);\n margin-top: 24px;\n}\n\n.compare-empty-icon[_ngcontent-%COMP%] {\n width: 80px;\n height: 80px;\n display: flex;\n align-items: center;\n justify-content: center;\n background: var(--test-bg);\n border-radius: 50%;\n margin: 0 auto 20px;\n}\n\n.compare-empty-icon[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 32px;\n color: var(--test-text-muted);\n}\n\n.compare-empty[_ngcontent-%COMP%] h4[_ngcontent-%COMP%] {\n margin: 0 0 8px 0;\n font-size: 18px;\n font-weight: 600;\n color: var(--test-text);\n}\n\n.compare-empty[_ngcontent-%COMP%] p[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 14px;\n color: var(--test-text-secondary);\n max-width: 400px;\n margin: 0 auto;\n}\n\n\n\n.empty-state.small[_ngcontent-%COMP%] {\n padding: 32px 16px;\n}\n\n.empty-state.small[_ngcontent-%COMP%] p[_ngcontent-%COMP%] {\n margin: 0;\n}\n\n\n\n\n\n@media (max-width: 1024px) {\n .compare-selection[_ngcontent-%COMP%] {\n grid-template-columns: 1fr;\n gap: 16px;\n }\n\n .compare-vs[_ngcontent-%COMP%] {\n margin: 0;\n align-self: center;\n justify-self: center;\n }\n\n .analytics-kpis[_ngcontent-%COMP%] {\n grid-template-columns: repeat(2, 1fr);\n }\n}\n\n@media (max-width: 768px) {\n .filter-buttons[_ngcontent-%COMP%] {\n flex-direction: column;\n }\n\n .filter-btn[_ngcontent-%COMP%] {\n width: 100%;\n text-align: center;\n }\n\n .analytics-kpis[_ngcontent-%COMP%] {\n grid-template-columns: 1fr;\n }\n\n .kpi-card[_ngcontent-%COMP%] {\n padding: 16px;\n }\n\n .kpi-value[_ngcontent-%COMP%] {\n font-size: 20px;\n }\n\n .compare-summary[_ngcontent-%COMP%] {\n grid-template-columns: repeat(2, 1fr);\n }\n\n .analytics-table-wrapper[_ngcontent-%COMP%], \n .compare-table-wrapper[_ngcontent-%COMP%] {\n margin: 0 -20px;\n padding: 0 20px;\n }\n\n .run-selector-list[_ngcontent-%COMP%] {\n max-height: 150px;\n }\n}\n\n@media (max-width: 480px) {\n .compare-summary[_ngcontent-%COMP%] {\n grid-template-columns: 1fr;\n }\n\n .compare-summary-card[_ngcontent-%COMP%] .summary-value[_ngcontent-%COMP%] {\n font-size: 20px;\n }\n\n .analytics-table[_ngcontent-%COMP%] th[_ngcontent-%COMP%], \n .analytics-table[_ngcontent-%COMP%] td[_ngcontent-%COMP%], \n .compare-table[_ngcontent-%COMP%] th[_ngcontent-%COMP%], \n .compare-table[_ngcontent-%COMP%] td[_ngcontent-%COMP%] {\n padding: 10px 12px;\n font-size: 12px;\n }\n}\n\n\n\n\n\n\n\n\n.cell-human[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 3px;\n min-width: 22px;\n height: 22px;\n border-radius: 50%;\n font-size: 10px;\n}\n\n.cell-human.no-feedback[_ngcontent-%COMP%] {\n background: #f1f5f9;\n color: #94a3b8;\n}\n\n.cell-human.no-feedback[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 11px;\n}\n\n.cell-human.has-feedback[_ngcontent-%COMP%] {\n padding: 0 6px;\n border-radius: 12px;\n min-width: 36px;\n}\n\n.cell-human.has-feedback[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 9px;\n}\n\n.cell-human.has-feedback[_ngcontent-%COMP%] .rating-value[_ngcontent-%COMP%] {\n font-weight: 700;\n font-size: 11px;\n}\n\n\n\n.cell-human.rating-low[_ngcontent-%COMP%] {\n background: #fee2e2;\n color: #dc2626;\n}\n\n.cell-human.rating-medium[_ngcontent-%COMP%] {\n background: #fef3c7;\n color: #d97706;\n}\n\n.cell-human.rating-good[_ngcontent-%COMP%] {\n background: #d1fae5;\n color: #059669;\n}\n\n.cell-human.rating-excellent[_ngcontent-%COMP%] {\n background: #dcfce7;\n color: #16a34a;\n}\n\n\n\n.cell-auto[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 3px;\n min-width: 22px;\n height: 22px;\n border-radius: 50%;\n font-size: 10px;\n}\n\n.cell-auto.no-score[_ngcontent-%COMP%] {\n background: #f1f5f9;\n color: #94a3b8;\n}\n\n.cell-auto.no-score[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 11px;\n}\n\n.cell-auto.has-score[_ngcontent-%COMP%] {\n padding: 0 6px;\n border-radius: 12px;\n min-width: 36px;\n}\n\n.cell-auto.has-score[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 9px;\n}\n\n.cell-auto.has-score[_ngcontent-%COMP%] .score-value[_ngcontent-%COMP%] {\n font-weight: 700;\n font-size: 10px;\n}\n\n\n\n.cell-auto.score-low[_ngcontent-%COMP%] {\n background: #fee2e2;\n color: #dc2626;\n}\n\n.cell-auto.score-medium[_ngcontent-%COMP%] {\n background: #fef3c7;\n color: #d97706;\n}\n\n.cell-auto.score-good[_ngcontent-%COMP%] {\n background: #d1fae5;\n color: #059669;\n}\n\n.cell-auto.score-excellent[_ngcontent-%COMP%] {\n background: #dcfce7;\n color: #16a34a;\n}\n\n\n\n.cell-status[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 22px;\n height: 22px;\n border-radius: 50%;\n font-size: 11px;\n}\n\n.cell-status.status-passed[_ngcontent-%COMP%] {\n background: #dcfce7;\n color: #16a34a;\n}\n\n.cell-status.status-failed[_ngcontent-%COMP%] {\n background: #fee2e2;\n color: #dc2626;\n}\n\n.cell-status.status-error[_ngcontent-%COMP%] {\n background: #fef3c7;\n color: #d97706;\n}\n\n.cell-status.status-timeout[_ngcontent-%COMP%] {\n background: #fef3c7;\n color: #d97706;\n}\n\n.cell-status.status-skipped[_ngcontent-%COMP%], \n.cell-status.status-pending[_ngcontent-%COMP%] {\n background: #f1f5f9;\n color: #64748b;\n}\n\n.cell-status.status-running[_ngcontent-%COMP%] {\n background: #dbeafe;\n color: #2563eb;\n}\n\n\n\n.result-cell.cell-not-run[_ngcontent-%COMP%] {\n background: repeating-linear-gradient(\n 45deg,\n #f8fafc,\n #f8fafc 4px,\n #e2e8f0 4px,\n #e2e8f0 8px\n );\n color: #94a3b8;\n}\n\n.result-cell.cell-not-run[_ngcontent-%COMP%] .cell-eval-stack[_ngcontent-%COMP%] {\n opacity: 0.6;\n}\n\n.cell-not-run-indicator[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 22px;\n height: 22px;\n color: #94a3b8;\n font-size: 11px;\n}\n\n\n\n\n\n.test-matrix[_ngcontent-%COMP%] tfoot[_ngcontent-%COMP%] {\n position: sticky;\n bottom: 0;\n z-index: 2;\n}\n\n.totals-row[_ngcontent-%COMP%] {\n background: linear-gradient(to bottom, #f8fafc, #f1f5f9);\n border-top: 2px solid var(--test-border);\n}\n\n.totals-row[_ngcontent-%COMP%] td[_ngcontent-%COMP%] {\n padding: 10px 12px;\n font-weight: 600;\n}\n\n.totals-row[_ngcontent-%COMP%] .totals-label[_ngcontent-%COMP%] {\n background: linear-gradient(to bottom, #f8fafc, #f1f5f9);\n font-size: 12px;\n color: var(--test-text-secondary);\n text-transform: uppercase;\n letter-spacing: 0.5px;\n}\n\n.totals-row[_ngcontent-%COMP%] .totals-cell[_ngcontent-%COMP%] {\n background: linear-gradient(to bottom, #f8fafc, #f1f5f9);\n}\n\n.totals-stack[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 4px;\n}\n\n.totals-status[_ngcontent-%COMP%], \n.totals-human[_ngcontent-%COMP%], \n.totals-auto[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 4px;\n font-size: 11px;\n padding: 2px 8px;\n border-radius: 10px;\n white-space: nowrap;\n}\n\n.totals-status[_ngcontent-%COMP%] {\n background: #e0f2fe;\n color: #0369a1;\n}\n\n.totals-status[_ngcontent-%COMP%] .pass-count[_ngcontent-%COMP%] {\n font-weight: 700;\n}\n\n.totals-human[_ngcontent-%COMP%] {\n background: #fef3c7;\n color: #92400e;\n}\n\n.totals-human[_ngcontent-%COMP%] .avg-label[_ngcontent-%COMP%] {\n font-weight: 700;\n}\n\n.totals-human[_ngcontent-%COMP%] .count-label[_ngcontent-%COMP%] {\n font-size: 10px;\n opacity: 0.8;\n}\n\n.totals-auto[_ngcontent-%COMP%] {\n background: #dbeafe;\n color: #1e40af;\n}\n\n.totals-auto[_ngcontent-%COMP%] .avg-label[_ngcontent-%COMP%] {\n font-weight: 700;\n}\n\n.totals-auto[_ngcontent-%COMP%] .count-label[_ngcontent-%COMP%] {\n font-size: 10px;\n opacity: 0.8;\n}"], changeDetection: 0 }); }
348
3449
  };
349
3450
  TestSuiteFormComponentExtended = __decorate([
350
3451
  RegisterClass(BaseFormComponent, 'MJ: Test Suites')
@@ -352,9 +3453,15 @@ TestSuiteFormComponentExtended = __decorate([
352
3453
  export { TestSuiteFormComponentExtended };
353
3454
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(TestSuiteFormComponentExtended, [{
354
3455
  type: Component,
355
- args: [{ selector: 'mj-test-suite-form', changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"test-suite-form\" kendoDialogContainer>\n <div class=\"suite-header\">\n <div class=\"header-content\">\n <div class=\"header-left\">\n <div class=\"suite-icon\" [style.background-color]=\"getStatusColor()\">\n <i class=\"fas fa-layer-group\"></i>\n </div>\n <div class=\"suite-info\">\n <h1>{{ record.Name }}</h1>\n <div class=\"suite-meta\">\n <span class=\"status-badge\" [style.background-color]=\"getStatusColor()\">\n {{ record.Status }}\n </span>\n </div>\n </div>\n </div>\n <div class=\"header-actions\">\n <button kendoButton (click)=\"runSuite()\" icon=\"play\">Run Suite</button>\n </div>\n </div>\n <div class=\"suite-description\" *ngIf=\"record.Description\">\n <p>{{ record.Description }}</p>\n </div>\n </div>\n\n <div class=\"tabs-container\">\n <div class=\"tabs\">\n <button class=\"tab\" [class.active]=\"activeTab === 'overview'\" (click)=\"changeTab('overview')\">\n <i class=\"fas fa-th-large\"></i> Overview\n </button>\n <button class=\"tab\" [class.active]=\"activeTab === 'tests'\" (click)=\"changeTab('tests')\">\n <i class=\"fas fa-flask\"></i> Tests\n <span class=\"tab-badge\" *ngIf=\"testsLoaded\">{{ suiteTests.length }}</span>\n </button>\n <button class=\"tab\" [class.active]=\"activeTab === 'runs'\" (click)=\"changeTab('runs')\">\n <i class=\"fas fa-history\"></i> Runs\n <span class=\"tab-badge\" *ngIf=\"runsLoaded\">{{ suiteRuns.length }}</span>\n </button>\n </div>\n </div>\n\n <div class=\"tab-content\">\n <div class=\"overview-tab\" *ngIf=\"activeTab === 'overview'\">\n <div class=\"info-section\">\n <h3>Suite Information</h3>\n <div class=\"info-grid\">\n <div class=\"info-item\">\n <div class=\"info-label\">Name</div>\n <div class=\"info-value\">{{ record.Name }}</div>\n </div>\n <div class=\"info-item\">\n <div class=\"info-label\">Status</div>\n <div class=\"info-value\">{{ record.Status }}</div>\n </div>\n <div class=\"info-item\">\n <div class=\"info-label\">Created</div>\n <div class=\"info-value\">{{ record.__mj_CreatedAt | date:'medium' }}</div>\n </div>\n <div class=\"info-item\">\n <div class=\"info-label\">Updated</div>\n <div class=\"info-value\">{{ record.__mj_UpdatedAt | date:'medium' }}</div>\n </div>\n </div>\n </div>\n </div>\n\n <div class=\"tests-tab\" *ngIf=\"activeTab === 'tests'\">\n <div class=\"tests-list\" *ngIf=\"suiteTests.length > 0\">\n <div class=\"test-item\" *ngFor=\"let test of suiteTests\" (click)=\"openTest(test.TestID)\">\n <div class=\"test-sequence\">{{ test.Sequence }}</div>\n <div class=\"test-icon\"><i class=\"fas fa-flask\"></i></div>\n <div class=\"test-content\">\n <div class=\"test-name\">{{ test.Test }}</div>\n <div class=\"test-status\">{{ test.Status }}</div>\n </div>\n <i class=\"fas fa-chevron-right\"></i>\n </div>\n </div>\n <div class=\"no-data\" *ngIf=\"testsLoaded && suiteTests.length === 0\">\n <i class=\"fas fa-inbox\"></i>\n <p>No tests in this suite</p>\n </div>\n </div>\n\n <div class=\"runs-tab\" *ngIf=\"activeTab === 'runs'\">\n <div class=\"runs-list\" *ngIf=\"suiteRuns.length > 0\">\n <div class=\"run-item\" *ngFor=\"let run of suiteRuns\" (click)=\"openSuiteRun(run.ID)\">\n <div class=\"run-icon\" [style.background-color]=\"run.Status === 'Completed' ? '#4caf50' : run.Status === 'Failed' ? '#f44336' : '#2196f3'\">\n <i class=\"fas fa-play\"></i>\n </div>\n <div class=\"run-content\">\n <div class=\"run-header\">\n <span class=\"run-id\">{{ run.ID.substring(0, 8) }}</span>\n <span class=\"run-status\">{{ run.Status }}</span>\n </div>\n <div class=\"run-meta\">\n <span>{{ run.StartedAt | date:'short' }}</span>\n <span *ngIf=\"run.TotalTests\">{{ run.PassedTests }}/{{ run.TotalTests }} passed</span>\n </div>\n </div>\n <i class=\"fas fa-chevron-right\"></i>\n </div>\n </div>\n <div class=\"no-data\" *ngIf=\"runsLoaded && suiteRuns.length === 0\">\n <i class=\"fas fa-inbox\"></i>\n <p>No runs for this suite</p>\n </div>\n </div>\n </div>\n</div>\n", styles: [".test-suite-form { display: flex; flex-direction: column; height: 100%; background: #f8f9fa; }\n.suite-header { background: white; border-bottom: 1px solid #e0e0e0; padding: 20px; }\n.header-content { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 16px; }\n.header-left { display: flex; gap: 16px; }\n.suite-icon { width: 56px; height: 56px; border-radius: 12px; display: flex; align-items: center; justify-content: center; color: white; font-size: 24px; }\n.suite-info h1 { margin: 0 0 8px 0; font-size: 24px; font-weight: 600; color: #333; }\n.suite-meta { display: flex; align-items: center; gap: 12px; }\n.status-badge { display: inline-flex; align-items: center; gap: 6px; padding: 4px 12px; border-radius: 12px; color: white; font-size: 12px; font-weight: 600; }\n.suite-description { padding: 16px; background: #f8f9fa; border-radius: 8px; }\n.suite-description p { margin: 0; color: #666; line-height: 1.5; }\n.tabs-container { background: white; border-bottom: 1px solid #e0e0e0; }\n.tabs { display: flex; padding: 0 20px; overflow-x: auto; }\n.tab { display: flex; align-items: center; gap: 8px; padding: 16px 20px; border: none; background: transparent; border-bottom: 3px solid transparent; color: #666; font-size: 14px; font-weight: 500; cursor: pointer; transition: all 0.2s ease; }\n.tab:hover { color: #2196f3; background: rgba(33, 150, 243, 0.05); }\n.tab.active { color: #2196f3; border-bottom-color: #2196f3; }\n.tab-badge { background: #e0e0e0; color: #666; padding: 2px 8px; border-radius: 10px; font-size: 11px; font-weight: 600; }\n.tab.active .tab-badge { background: #e3f2fd; color: #2196f3; }\n.tab-content { flex: 1; overflow-y: auto; padding: 20px; }\n.info-section { background: white; border-radius: 12px; padding: 20px; }\n.info-section h3 { margin: 0 0 16px 0; font-size: 18px; font-weight: 600; color: #333; }\n.info-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px; }\n.info-item { display: flex; flex-direction: column; gap: 4px; }\n.info-label { font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; color: #999; }\n.info-value { font-size: 14px; color: #333; word-wrap: break-word; }\n.tests-list, .runs-list { display: flex; flex-direction: column; gap: 12px; }\n.test-item, .run-item { display: flex; align-items: center; gap: 12px; padding: 16px; background: white; border: 2px solid transparent; border-radius: 8px; cursor: pointer; transition: all 0.2s ease; }\n.test-item:hover, .run-item:hover { background: #e3f2fd; border-color: #90caf9; }\n.test-sequence { width: 32px; height: 32px; display: flex; align-items: center; justify-content: center; background: #f0f0f0; border-radius: 50%; font-size: 14px; font-weight: 700; color: #666; flex-shrink: 0; }\n.test-icon, .run-icon { width: 40px; height: 40px; display: flex; align-items: center; justify-content: center; border-radius: 8px; color: white; font-size: 18px; flex-shrink: 0; background: #2196f3; }\n.test-content, .run-content { flex: 1; }\n.test-name, .run-header { font-size: 14px; font-weight: 600; color: #333; margin-bottom: 4px; }\n.test-status, .run-meta { font-size: 12px; color: #666; }\n.run-meta { display: flex; gap: 12px; }\n.no-data { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 60px 20px; color: #999; text-align: center; background: white; border-radius: 12px; }\n.no-data i { font-size: 48px; margin-bottom: 16px; opacity: 0.3; }\n.no-data p { margin: 0; font-size: 14px; }\n"] }]
356
- }], () => [{ type: i0.ElementRef }, { type: i1.SharedService }, { type: i2.Router }, { type: i2.ActivatedRoute }, { type: i0.ChangeDetectorRef }, { type: i3.TestingDialogService }], null); })();
357
- (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(TestSuiteFormComponentExtended, { className: "TestSuiteFormComponentExtended", filePath: "src/lib/custom/Tests/test-suite-form.component.ts", lineNumber: 19 }); })();
3456
+ args: [{ selector: 'mj-test-suite-form', changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"test-suite-form\" kendoDialogContainer>\n <!-- Header Section -->\n <div class=\"suite-header\">\n <div class=\"header-content\">\n <div class=\"header-left\">\n <div class=\"suite-icon\" [style.background-color]=\"getStatusColor()\">\n <i class=\"fas fa-layer-group\"></i>\n </div>\n <div class=\"suite-info\">\n <h1>{{ record.Name }}</h1>\n <div class=\"suite-meta\">\n <span class=\"status-badge\" [ngClass]=\"getStatusClass()\">\n <i class=\"fas\" [ngClass]=\"record.Status === 'Active' ? 'fa-circle-check' : 'fa-circle-pause'\"></i>\n {{ record.Status }}\n </span>\n <span class=\"test-count\" *ngIf=\"testsLoaded\">\n <i class=\"fas fa-flask\"></i>\n {{ suiteTests.length }} tests\n </span>\n </div>\n </div>\n </div>\n <div class=\"header-actions\">\n <app-evaluation-mode-toggle></app-evaluation-mode-toggle>\n <button kendoButton (click)=\"exportToExcel()\" title=\"Export to CSV\">\n <i class=\"fas fa-file-excel\"></i> Export\n </button>\n <button kendoButton (click)=\"runSuite()\" themeColor=\"primary\">\n <i class=\"fas fa-play\"></i> Run Suite\n </button>\n <button kendoButton (click)=\"refresh()\" [disabled]=\"isRefreshing\">\n <i class=\"fas\" [ngClass]=\"isRefreshing ? 'fa-sync fa-spin' : 'fa-sync'\"></i>\n {{ isRefreshing ? 'Refreshing...' : 'Refresh' }}\n </button>\n </div>\n </div>\n <div class=\"suite-description\" *ngIf=\"record.Description\">\n <p>{{ record.Description }}</p>\n </div>\n </div>\n\n <!-- Tabs -->\n <div class=\"tabs-container\">\n <div class=\"tabs\" role=\"tablist\">\n <button class=\"tab\"\n [class.active]=\"activeTab === 'overview'\"\n (click)=\"changeTab('overview')\"\n role=\"tab\"\n [attr.aria-selected]=\"activeTab === 'overview'\">\n <i class=\"fas fa-th-large\"></i> Overview\n </button>\n <button class=\"tab\"\n [class.active]=\"activeTab === 'tests'\"\n (click)=\"changeTab('tests')\"\n role=\"tab\"\n [attr.aria-selected]=\"activeTab === 'tests'\">\n <i class=\"fas fa-flask\"></i> Tests\n <span class=\"tab-badge\" *ngIf=\"testsLoaded\">{{ suiteTests.length }}</span>\n </button>\n <button class=\"tab\"\n [class.active]=\"activeTab === 'runs'\"\n (click)=\"changeTab('runs')\"\n role=\"tab\"\n [attr.aria-selected]=\"activeTab === 'runs'\">\n <i class=\"fas fa-history\"></i> Runs\n <span class=\"tab-badge\" *ngIf=\"runsLoaded\">{{ suiteRuns.length }}</span>\n </button>\n <button class=\"tab\"\n [class.active]=\"activeTab === 'analytics'\"\n (click)=\"changeTab('analytics')\"\n role=\"tab\"\n [attr.aria-selected]=\"activeTab === 'analytics'\">\n <i class=\"fas fa-chart-line\"></i> Analytics\n </button>\n <button class=\"tab\"\n [class.active]=\"activeTab === 'compare'\"\n (click)=\"changeTab('compare')\"\n role=\"tab\"\n [attr.aria-selected]=\"activeTab === 'compare'\">\n <i class=\"fas fa-balance-scale\"></i> Compare\n </button>\n </div>\n </div>\n\n <!-- Tab Content -->\n <div class=\"tab-content\">\n <!-- Overview Tab -->\n <div class=\"overview-tab\" *ngIf=\"activeTab === 'overview'\">\n <div class=\"info-section\">\n <h3><i class=\"fas fa-info-circle\"></i> Suite Information</h3>\n <div class=\"info-grid\">\n <div class=\"info-item\">\n <div class=\"info-label\">Name</div>\n <div class=\"info-value\">{{ record.Name }}</div>\n </div>\n <div class=\"info-item\">\n <div class=\"info-label\">Status</div>\n <div class=\"info-value\">\n <span class=\"status-badge-inline\" [ngClass]=\"getStatusClass()\">{{ record.Status }}</span>\n </div>\n </div>\n <div class=\"info-item\">\n <div class=\"info-label\">Created</div>\n <div class=\"info-value\">{{ record.__mj_CreatedAt | date:'medium' }}</div>\n </div>\n <div class=\"info-item\">\n <div class=\"info-label\">Updated</div>\n <div class=\"info-value\">{{ record.__mj_UpdatedAt | date:'medium' }}</div>\n </div>\n <div class=\"info-item\">\n <div class=\"info-label\">Max Execution Time</div>\n <div class=\"info-value\">{{ formatTimeout(record.MaxExecutionTimeMS) }}</div>\n </div>\n </div>\n </div>\n\n <div class=\"config-section\">\n <h3><i class=\"fas fa-cogs\"></i> Execution Settings</h3>\n <div class=\"config-grid\">\n <div class=\"config-item\">\n <label>Max Execution Time (ms)</label>\n <input type=\"number\" [(ngModel)]=\"record.MaxExecutionTimeMS\" class=\"config-input\" placeholder=\"Default: 300000 (5 min)\" />\n <span class=\"config-hint\">Default timeout for tests in this suite</span>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Tests Tab -->\n <div class=\"tests-tab\" *ngIf=\"activeTab === 'tests'\">\n <!-- Loading State -->\n <div class=\"loading-state\" *ngIf=\"loadingTests\">\n <div class=\"skeleton-list\">\n <div class=\"skeleton-card\" *ngFor=\"let i of [1,2,3,4,5]\">\n <div class=\"skeleton-sequence\"></div>\n <div class=\"skeleton-icon\"></div>\n <div class=\"skeleton-content\">\n <div class=\"skeleton-line wide\"></div>\n <div class=\"skeleton-line narrow\"></div>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Tests List -->\n <div class=\"tests-list\" *ngIf=\"!loadingTests && suiteTests.length > 0\">\n <div class=\"test-item\" *ngFor=\"let test of suiteTests\" (click)=\"openTest(test.TestID)\">\n <div class=\"test-sequence\">{{ test.Sequence }}</div>\n <div class=\"test-icon\"><i class=\"fas fa-flask\"></i></div>\n <div class=\"test-content\">\n <div class=\"test-name\">{{ test.Test }}</div>\n <div class=\"test-status\">\n <span *ngIf=\"test.Status\"><i class=\"fas fa-info-circle\"></i> {{ test.Status }}</span>\n </div>\n </div>\n <i class=\"fas fa-chevron-right\"></i>\n </div>\n </div>\n\n <!-- Empty State -->\n <div class=\"empty-state\" *ngIf=\"testsLoaded && !loadingTests && suiteTests.length === 0\">\n <div class=\"empty-icon\">\n <i class=\"fas fa-flask\"></i>\n </div>\n <h4>No Tests in Suite</h4>\n <p>Add tests to this suite to start running them together.</p>\n </div>\n </div>\n\n <!-- Runs Tab -->\n <div class=\"runs-tab\" *ngIf=\"activeTab === 'runs'\">\n <!-- Loading State -->\n <div class=\"loading-state\" *ngIf=\"loadingRuns\">\n <div class=\"skeleton-list\">\n <div class=\"skeleton-card\" *ngFor=\"let i of [1,2,3]\">\n <div class=\"skeleton-icon\"></div>\n <div class=\"skeleton-content\">\n <div class=\"skeleton-line wide\"></div>\n <div class=\"skeleton-line narrow\"></div>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Runs List -->\n <div class=\"runs-list\" *ngIf=\"!loadingRuns && suiteRuns.length > 0\">\n <div class=\"run-item\" *ngFor=\"let run of suiteRuns\" (click)=\"openSuiteRun(run.ID)\">\n <div class=\"run-icon\" [style.background-color]=\"getRunStatusColor(run.Status)\">\n <i class=\"fas\"\n [class.fa-check]=\"run.Status === 'Completed'\"\n [class.fa-times]=\"run.Status === 'Failed'\"\n [class.fa-spinner]=\"run.Status === 'Running'\"\n [class.fa-clock]=\"run.Status === 'Pending'\"\n [class.fa-ban]=\"run.Status === 'Cancelled'\"></i>\n </div>\n <div class=\"run-content\">\n <div class=\"run-header\">\n <span class=\"run-id\">Run #{{ run.ID.substring(0, 8) }}</span>\n <span class=\"run-status\" [style.color]=\"getRunStatusColor(run.Status)\">{{ run.Status }}</span>\n </div>\n <div class=\"run-meta\">\n <span><i class=\"fas fa-calendar\"></i> {{ getRelativeTime(run.StartedAt) }}</span>\n <span *ngIf=\"run.TotalTests\">\n <i class=\"fas fa-check-circle\"></i> {{ run.PassedTests }}/{{ run.TotalTests }}\n ({{ getPassRate(run).toFixed(0) }}%)\n </span>\n </div>\n <!-- Evaluation metrics row -->\n <div class=\"run-eval-metrics\">\n <!-- Status badge -->\n <span class=\"eval-metric status\" *ngIf=\"evalPreferences.showExecution\" [class]=\"'status-' + run.Status.toLowerCase()\">\n <i class=\"fas\"\n [class.fa-circle-check]=\"run.Status === 'Completed'\"\n [class.fa-circle-xmark]=\"run.Status === 'Failed'\"\n [class.fa-spinner]=\"run.Status === 'Running'\"\n [class.fa-clock]=\"run.Status === 'Pending'\"\n [class.fa-ban]=\"run.Status === 'Cancelled'\"></i>\n </span>\n <!-- Human score (placeholder - need avg from suite run) -->\n <span class=\"eval-metric human\" *ngIf=\"evalPreferences.showHuman\" title=\"Human evaluation\">\n <i class=\"fas fa-user\"></i>\n <span class=\"eval-pending\"><i class=\"fas fa-clock\"></i></span>\n </span>\n <!-- Auto score (pass rate as proxy) -->\n <span class=\"eval-metric auto\" *ngIf=\"evalPreferences.showAuto && run.TotalTests\"\n [class.high]=\"getPassRate(run) >= 80\"\n [class.medium]=\"getPassRate(run) >= 50 && getPassRate(run) < 80\"\n [class.low]=\"getPassRate(run) < 50\"\n title=\"Auto score (pass rate)\">\n <i class=\"fas fa-robot\"></i>\n <span>{{ getPassRate(run).toFixed(0) }}%</span>\n </span>\n </div>\n <!-- Tags display -->\n <div class=\"run-tags\" *ngIf=\"getRunTags(run).length > 0\">\n <span class=\"tag-chip\" *ngFor=\"let tag of getRunTags(run)\">{{ tag }}</span>\n </div>\n </div>\n <i class=\"fas fa-chevron-right\"></i>\n </div>\n </div>\n\n <!-- Empty State -->\n <div class=\"empty-state\" *ngIf=\"runsLoaded && !loadingRuns && suiteRuns.length === 0\">\n <div class=\"empty-icon\">\n <i class=\"fas fa-play-circle\"></i>\n </div>\n <h4>No Suite Runs Yet</h4>\n <p>Run this suite to see execution history and results here.</p>\n <button kendoButton (click)=\"runSuite()\" themeColor=\"primary\">\n <i class=\"fas fa-play\"></i> Run Suite Now\n </button>\n </div>\n </div>\n\n <!-- Analytics Tab -->\n <div class=\"analytics-tab\" *ngIf=\"activeTab === 'analytics'\">\n <!-- Loading State -->\n <div class=\"loading-state\" *ngIf=\"loadingAnalytics\">\n <mj-loading text=\"Loading analytics data...\"></mj-loading>\n </div>\n\n <ng-container *ngIf=\"!loadingAnalytics && analyticsLoaded\">\n <!-- View Toggle Sub-nav -->\n <div class=\"analytics-subnav\">\n <div class=\"subnav-tabs\">\n <button class=\"subnav-tab\"\n [class.active]=\"analyticsView === 'summary'\"\n (click)=\"setAnalyticsView('summary')\">\n <i class=\"fas fa-chart-bar\"></i>\n <span>Summary</span>\n </button>\n <button class=\"subnav-tab\"\n [class.active]=\"analyticsView === 'matrix'\"\n (click)=\"setAnalyticsView('matrix')\">\n <i class=\"fas fa-th\"></i>\n <span>Matrix</span>\n </button>\n <button class=\"subnav-tab\"\n [class.active]=\"analyticsView === 'chart'\"\n (click)=\"setAnalyticsView('chart')\">\n <i class=\"fas fa-project-diagram\"></i>\n <span>Chart</span>\n </button>\n </div>\n </div>\n\n <!-- Collapsible Filters (shared by both views) -->\n <div class=\"analytics-filters\" [class.collapsed]=\"filtersCollapsed\">\n <div class=\"filters-header\" (click)=\"toggleFilters()\">\n <span class=\"filters-title\">\n <i class=\"fas fa-filter\"></i>\n Filters\n <span class=\"filter-summary\" *ngIf=\"filtersCollapsed\">\n {{ analyticsTimeRange === 'all' ? 'All Time' : analyticsTimeRange }}\n <span *ngIf=\"selectedTags.length > 0\"> \u00B7 {{ selectedTags.length }} tags</span>\n </span>\n </span>\n <i class=\"fas\" [ngClass]=\"filtersCollapsed ? 'fa-chevron-down' : 'fa-chevron-up'\"></i>\n </div>\n <div class=\"filters-content\" *ngIf=\"!filtersCollapsed\">\n <div class=\"filter-group\">\n <label>Time Range</label>\n <div class=\"filter-buttons\">\n <button class=\"filter-btn\" [class.active]=\"analyticsTimeRange === '7d'\" (click)=\"setTimeRange('7d')\">7 Days</button>\n <button class=\"filter-btn\" [class.active]=\"analyticsTimeRange === '30d'\" (click)=\"setTimeRange('30d')\">30 Days</button>\n <button class=\"filter-btn\" [class.active]=\"analyticsTimeRange === '90d'\" (click)=\"setTimeRange('90d')\">90 Days</button>\n <button class=\"filter-btn\" [class.active]=\"analyticsTimeRange === 'all'\" (click)=\"setTimeRange('all')\">All Time</button>\n </div>\n </div>\n <div class=\"filter-group\" *ngIf=\"uniqueTags.length > 0\">\n <label>Filter by Tag <span class=\"filter-hint\" *ngIf=\"selectedTags.length > 0\">({{ selectedTags.length }} selected)</span></label>\n <div class=\"filter-buttons tag-filters\">\n <button class=\"filter-btn tag-btn all-tags-btn\"\n [class.active]=\"selectedTags.length === 0\"\n (click)=\"toggleTagFilter(null)\">\n <i class=\"fas fa-layer-group\"></i> All Tags\n </button>\n <button class=\"filter-btn tag-btn\"\n *ngFor=\"let tag of uniqueTags\"\n [class.active]=\"isTagSelected(tag)\"\n (click)=\"toggleTagFilter(tag)\">\n <i class=\"fas fa-check\" *ngIf=\"isTagSelected(tag)\"></i>\n {{ tag }}\n </button>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Summary View -->\n <ng-container *ngIf=\"analyticsView === 'summary'\">\n <!-- KPI Cards -->\n <div class=\"analytics-kpis\">\n <div class=\"kpi-card\">\n <div class=\"kpi-icon\"><i class=\"fas fa-play-circle\"></i></div>\n <div class=\"kpi-content\">\n <div class=\"kpi-value\">{{ getTotalRuns() }}</div>\n <div class=\"kpi-label\">Total Runs</div>\n </div>\n </div>\n <div class=\"kpi-card\">\n <div class=\"kpi-icon success\"><i class=\"fas fa-check-circle\"></i></div>\n <div class=\"kpi-content\">\n <div class=\"kpi-value\">{{ getAveragePassRate().toFixed(1) }}%</div>\n <div class=\"kpi-label\">Avg Pass Rate</div>\n <div class=\"kpi-trend\" [ngClass]=\"{'trend-up': getPassRateTrend().direction === 'up', 'trend-down': getPassRateTrend().direction === 'down'}\">\n <i class=\"fas\" [ngClass]=\"{'fa-arrow-up': getPassRateTrend().direction === 'up', 'fa-arrow-down': getPassRateTrend().direction === 'down', 'fa-minus': getPassRateTrend().direction === 'stable'}\"></i>\n {{ getPassRateTrend().value.toFixed(1) }}%\n </div>\n </div>\n </div>\n <div class=\"kpi-card\">\n <div class=\"kpi-icon info\"><i class=\"fas fa-clock\"></i></div>\n <div class=\"kpi-content\">\n <div class=\"kpi-value\">{{ formatDuration(getAverageDuration()) }}</div>\n <div class=\"kpi-label\">Avg Duration</div>\n </div>\n </div>\n <div class=\"kpi-card\">\n <div class=\"kpi-icon warning\"><i class=\"fas fa-dollar-sign\"></i></div>\n <div class=\"kpi-content\">\n <div class=\"kpi-value\">{{ formatCost(getTotalCost()) }}</div>\n <div class=\"kpi-label\">Total Cost</div>\n </div>\n </div>\n </div>\n\n <!-- Runs Table -->\n <div class=\"analytics-table-section\">\n <h3><i class=\"fas fa-table\"></i> Run History</h3>\n <div class=\"analytics-table-wrapper\">\n <table class=\"analytics-table\">\n <thead>\n <tr>\n <th>Date</th>\n <th>Status</th>\n <th>Pass Rate</th>\n <th>Tests</th>\n <th>Duration</th>\n <th>Cost</th>\n <th>Tags</th>\n </tr>\n </thead>\n <tbody>\n <tr *ngFor=\"let dp of getFilteredAnalyticsData()\" (click)=\"openSuiteRun(dp.runId)\" class=\"clickable-row\">\n <td>{{ dp.date | date:'short' }}</td>\n <td>\n <span class=\"status-chip\" [ngClass]=\"'status-' + dp.status.toLowerCase()\">{{ dp.status }}</span>\n </td>\n <td>\n <div class=\"pass-rate-cell\">\n <div class=\"pass-rate-bar\" [style.width.%]=\"dp.passRate\" [ngClass]=\"{'high': dp.passRate >= 80, 'medium': dp.passRate >= 50 && dp.passRate < 80, 'low': dp.passRate < 50}\"></div>\n <span>{{ dp.passRate.toFixed(0) }}%</span>\n </div>\n </td>\n <td>{{ dp.passedTests }}/{{ dp.totalTests }}</td>\n <td>{{ formatDuration(dp.duration) }}</td>\n <td>{{ formatCost(dp.cost) }}</td>\n <td>\n <div class=\"tag-cell\">\n <span class=\"tag-chip-table\" *ngFor=\"let tag of dp.tags.slice(0, 2)\">{{ tag }}</span>\n <span class=\"tag-more\" *ngIf=\"dp.tags.length > 2\">+{{ dp.tags.length - 2 }}</span>\n </div>\n </td>\n </tr>\n </tbody>\n </table>\n </div>\n <div class=\"empty-state small\" *ngIf=\"getFilteredAnalyticsData().length === 0\">\n <p>No runs match the current filters.</p>\n </div>\n </div>\n </ng-container>\n\n <!-- Matrix View -->\n <ng-container *ngIf=\"analyticsView === 'matrix'\">\n <!-- Loading Matrix -->\n <div class=\"loading-state\" *ngIf=\"loadingMatrix\">\n <mj-loading text=\"Loading test matrix...\"></mj-loading>\n </div>\n\n <!-- Matrix Content -->\n <div class=\"matrix-section\" *ngIf=\"!loadingMatrix && matrixLoaded && matrixData.length > 0\">\n <div class=\"matrix-header\">\n <h3><i class=\"fas fa-th\"></i> Test Results Matrix</h3>\n <div class=\"matrix-header-right\">\n <div class=\"matrix-filter-input\">\n <i class=\"fas fa-search\"></i>\n <input type=\"text\"\n placeholder=\"Filter tests...\"\n [value]=\"matrixTestFilter\"\n (input)=\"onMatrixFilterInput($event)\"\n class=\"filter-input\">\n <button class=\"clear-filter-btn\" *ngIf=\"matrixTestFilter\" (click)=\"clearMatrixFilter()\" title=\"Clear filter\">\n <i class=\"fas fa-times\"></i>\n </button>\n </div>\n <span class=\"matrix-run-count\">{{ matrixData.length }} runs \u00B7 {{ getUniqueTestsFromMatrix().length }} tests</span>\n <button kendoButton (click)=\"exportMatrixToCSV()\" [disabled]=\"matrixData.length === 0\" title=\"Export matrix to CSV\">\n <i class=\"fas fa-download\"></i> Export\n </button>\n </div>\n </div>\n\n <div class=\"matrix-scroll-container\">\n <table class=\"test-matrix\">\n <thead>\n <tr>\n <th class=\"seq-header\" (click)=\"toggleMatrixSort('sequence')\" title=\"Sort by sequence\">\n #\n <i class=\"fas\" [ngClass]=\"matrixSortBy === 'sequence' ? (matrixSortAsc ? 'fa-sort-up' : 'fa-sort-down') : 'fa-sort'\"></i>\n </th>\n <th class=\"test-name-header\" (click)=\"toggleMatrixSort('name')\" title=\"Sort by name\">\n Test\n <i class=\"fas\" [ngClass]=\"matrixSortBy === 'name' ? (matrixSortAsc ? 'fa-sort-up' : 'fa-sort-down') : 'fa-sort'\"></i>\n </th>\n <th class=\"run-header\" *ngFor=\"let run of matrixData\" (click)=\"openSuiteRun(run.runId)\" [title]=\"'Click to view suite run - ' + (run.date | date:'medium')\">\n <div class=\"run-header-content\">\n <div class=\"run-tags-header\" *ngIf=\"run.tags.length > 0\">\n <span class=\"tag-chip-header\" *ngFor=\"let tag of run.tags.slice(0, 2)\">{{ tag }}</span>\n <span class=\"tag-more-header\" *ngIf=\"run.tags.length > 2\">+{{ run.tags.length - 2 }}</span>\n </div>\n <div class=\"run-date\">{{ getRelativeTime(run.date) }}</div>\n <div class=\"run-pass-rate\" [ngClass]=\"{'high': run.passRate >= 80, 'medium': run.passRate >= 50 && run.passRate < 80, 'low': run.passRate < 50}\">\n {{ run.passRate.toFixed(0) }}%\n </div>\n </div>\n </th>\n <th class=\"spacer-header\"></th>\n </tr>\n </thead>\n <tbody>\n <tr *ngFor=\"let test of getUniqueTestsFromMatrix()\"\n [class.row-selected]=\"selectedMatrixTestId === test.testId\"\n (click)=\"selectMatrixRow(test.testId)\">\n <td class=\"seq-cell\">{{ test.sequence }}</td>\n <td class=\"test-name-cell\">\n <span class=\"test-name\" [title]=\"test.testName\">{{ test.testName }}</span>\n </td>\n <td class=\"result-cell\"\n *ngFor=\"let run of matrixData\"\n [ngClass]=\"getMatrixCellClass(getTestResultForRun(run.runId, test.testId))\"\n [class.clickable]=\"getTestResultForRun(run.runId, test.testId)\"\n [class.cell-not-run]=\"!getTestResultForRun(run.runId, test.testId)\"\n [title]=\"getTestResultForRun(run.runId, test.testId)?.status + ' - Click to view test run' || 'Not Run'\"\n (click)=\"onMatrixCellClick(getTestResultForRun(run.runId, test.testId), $event)\">\n <ng-container *ngIf=\"getTestResultForRun(run.runId, test.testId) as result\">\n <div class=\"cell-eval-stack\">\n <!-- Status indicator -->\n <span class=\"cell-status\" *ngIf=\"evalPreferences.showExecution\"\n [ngClass]=\"'status-' + result.status.toLowerCase()\"\n [class.cell-skipped-status]=\"result.status === 'Skipped' || result.status === 'Pending'\"\n [title]=\"getStatusTooltip(result.status)\">\n <i class=\"fas\"\n [class.fa-check]=\"result.status === 'Passed'\"\n [class.fa-times]=\"result.status === 'Failed'\"\n [class.fa-exclamation]=\"result.status === 'Error'\"\n [class.fa-hourglass-end]=\"result.status === 'Timeout'\"\n [class.fa-forward]=\"result.status === 'Skipped'\"\n [class.fa-spinner]=\"result.status === 'Running'\"\n [class.fa-clock]=\"result.status === 'Pending'\"></i>\n </span>\n <!-- Human score - slashed icon if no feedback, colored by rating if has feedback -->\n <span class=\"cell-human no-feedback\" *ngIf=\"evalPreferences.showHuman && !result.humanRating\"\n title=\"Human Review: No rating submitted yet\">\n <i class=\"fas fa-user-slash\"></i>\n </span>\n <span class=\"cell-human has-feedback\" *ngIf=\"evalPreferences.showHuman && result.humanRating\"\n [class.rating-low]=\"result.humanRating <= 4\"\n [class.rating-medium]=\"result.humanRating >= 5 && result.humanRating <= 6\"\n [class.rating-good]=\"result.humanRating >= 7 && result.humanRating <= 8\"\n [class.rating-excellent]=\"result.humanRating >= 9\"\n [title]=\"getHumanTooltip(result.humanRating, result.humanComments)\">\n <i class=\"fas fa-user\"></i>\n <span class=\"rating-value\">{{ result.humanRating }}</span>\n </span>\n <!-- Auto score - colored by percentage -->\n <span class=\"cell-auto has-score\" *ngIf=\"evalPreferences.showAuto && result.score != null\"\n [class.score-low]=\"result.score < 0.5\"\n [class.score-medium]=\"result.score >= 0.5 && result.score < 0.7\"\n [class.score-good]=\"result.score >= 0.7 && result.score < 0.85\"\n [class.score-excellent]=\"result.score >= 0.85\"\n [title]=\"'Auto Score: ' + (result.score * 100).toFixed(0) + '% automated evaluation'\">\n <i class=\"fas fa-robot\"></i>\n <span class=\"score-value\">{{ (result.score * 100).toFixed(0) }}</span>\n </span>\n <span class=\"cell-auto no-score\" *ngIf=\"evalPreferences.showAuto && result.score == null\"\n title=\"Auto Score: No automated score available\">\n <i class=\"fas fa-robot\"></i>\n </span>\n </div>\n </ng-container>\n <span class=\"cell-not-run-indicator\" *ngIf=\"!getTestResultForRun(run.runId, test.testId)\">\n <i class=\"fas fa-minus\"></i>\n </span>\n </td>\n <td class=\"spacer-cell\"></td>\n </tr>\n </tbody>\n <!-- Footer row with totals -->\n <tfoot>\n <tr class=\"totals-row\">\n <td class=\"seq-cell totals-label\"></td>\n <td class=\"test-name-cell totals-label\">\n <strong>Totals</strong>\n </td>\n <td class=\"result-cell totals-cell\" *ngFor=\"let run of matrixData\">\n <div class=\"cell-eval-stack totals-stack\">\n <!-- Status totals -->\n <span class=\"totals-status\" *ngIf=\"evalPreferences.showExecution\">\n <span class=\"pass-count\">{{ getRunPassedCount(run) }}/{{ getRunTotalCount(run) }}</span>\n </span>\n <!-- Human totals -->\n <span class=\"totals-human\" *ngIf=\"evalPreferences.showHuman\">\n <span class=\"avg-label\" *ngIf=\"getRunHumanAvg(run) != null\">{{ getRunHumanAvg(run)?.toFixed(1) }}</span>\n <span class=\"count-label\">({{ getRunHumanCount(run) }})</span>\n </span>\n <!-- Auto totals -->\n <span class=\"totals-auto\" *ngIf=\"evalPreferences.showAuto\">\n <span class=\"avg-label\" *ngIf=\"getRunAutoAvg(run) != null\">{{ (getRunAutoAvg(run)! * 100).toFixed(0) }}%</span>\n <span class=\"count-label\">({{ getRunAutoCount(run) }})</span>\n </span>\n </div>\n </td>\n <td class=\"spacer-cell\"></td>\n </tr>\n </tfoot>\n </table>\n </div>\n </div>\n\n <!-- Empty Matrix State -->\n <div class=\"empty-state\" *ngIf=\"!loadingMatrix && matrixLoaded && matrixData.length === 0\">\n <div class=\"empty-icon\">\n <i class=\"fas fa-th\"></i>\n </div>\n <h4>No Matrix Data</h4>\n <p>No suite runs match the current filters.</p>\n </div>\n </ng-container>\n\n <!-- Chart View -->\n <ng-container *ngIf=\"analyticsView === 'chart'\">\n <!-- Loading Chart -->\n <div class=\"loading-state\" *ngIf=\"loadingMatrix\">\n <mj-loading text=\"Loading chart data...\"></mj-loading>\n </div>\n\n <!-- Chart Content -->\n <div class=\"chart-section\" *ngIf=\"!loadingMatrix && matrixLoaded && matrixData.length > 0\">\n <div class=\"chart-header\">\n <h3><i class=\"fas fa-project-diagram\"></i> Test Results Flow</h3>\n <div class=\"chart-legend\">\n <span class=\"legend-item chart-passed\"><i class=\"fas fa-check\"></i> Passed</span>\n <span class=\"legend-item chart-failed\"><i class=\"fas fa-times\"></i> Failed</span>\n <span class=\"legend-item chart-error\"><i class=\"fas fa-exclamation\"></i> Error</span>\n <span class=\"legend-item chart-skipped\"><i class=\"fas fa-forward\"></i> Skipped</span>\n </div>\n </div>\n\n <div class=\"chart-container\">\n <div #chartContainer class=\"d3-chart\"></div>\n </div>\n\n <div class=\"chart-info\">\n <i class=\"fas fa-info-circle\"></i>\n Interactive visualization showing test results across {{ matrixData.length }} runs.\n Hover over elements for details, click nodes to navigate.\n </div>\n </div>\n\n <!-- Empty Chart State -->\n <div class=\"empty-state\" *ngIf=\"!loadingMatrix && matrixLoaded && matrixData.length === 0\">\n <div class=\"empty-icon\">\n <i class=\"fas fa-project-diagram\"></i>\n </div>\n <h4>No Chart Data</h4>\n <p>No suite runs match the current filters.</p>\n </div>\n </ng-container>\n </ng-container>\n\n <!-- Empty State -->\n <div class=\"empty-state\" *ngIf=\"!loadingAnalytics && analyticsLoaded && analyticsData.length === 0\">\n <div class=\"empty-icon\">\n <i class=\"fas fa-chart-line\"></i>\n </div>\n <h4>No Analytics Data</h4>\n <p>Run this suite to start collecting analytics data.</p>\n <button kendoButton (click)=\"runSuite()\" themeColor=\"primary\">\n <i class=\"fas fa-play\"></i> Run Suite Now\n </button>\n </div>\n </div>\n\n <!-- Compare Tab -->\n <div class=\"compare-tab\" *ngIf=\"activeTab === 'compare'\">\n <!-- Run Selection -->\n <div class=\"compare-selection\">\n <div class=\"compare-run-selector\">\n <h4>Run A (Baseline)</h4>\n <div class=\"run-selector-list\" *ngIf=\"!loadingRuns && suiteRuns.length > 0\">\n <div class=\"run-selector-item\"\n *ngFor=\"let run of suiteRuns\"\n [class.selected]=\"compareRunA?.ID === run.ID\"\n (click)=\"selectCompareRunA(run)\">\n <div class=\"selector-status\" [style.background-color]=\"getRunStatusColor(run.Status)\"></div>\n <div class=\"selector-content\">\n <div class=\"selector-date\">{{ run.StartedAt | date:'short' }}</div>\n <div class=\"selector-rate\">{{ getPassRate(run).toFixed(0) }}% pass</div>\n </div>\n <div class=\"selector-tags\" *ngIf=\"getRunTags(run).length > 0\">\n <span class=\"tag-mini\" *ngFor=\"let tag of getRunTags(run).slice(0, 2)\">{{ tag }}</span>\n </div>\n </div>\n </div>\n <div class=\"selected-run-preview\" *ngIf=\"compareRunA\">\n <div class=\"preview-header\">\n <span class=\"preview-label\">Selected:</span>\n <button class=\"clear-btn\" (click)=\"compareRunA = null; compareResults = []\">Clear</button>\n </div>\n <div class=\"preview-details\">\n <span>{{ compareRunA.StartedAt | date:'medium' }}</span>\n <span class=\"preview-rate\">{{ getPassRate(compareRunA).toFixed(1) }}%</span>\n </div>\n </div>\n </div>\n\n <div class=\"compare-vs\"><i class=\"fas fa-exchange-alt\"></i></div>\n\n <div class=\"compare-run-selector\">\n <h4>Run B (Compare)</h4>\n <div class=\"run-selector-list\" *ngIf=\"!loadingRuns && suiteRuns.length > 0\">\n <div class=\"run-selector-item\"\n *ngFor=\"let run of suiteRuns\"\n [class.selected]=\"compareRunB?.ID === run.ID\"\n [class.disabled]=\"compareRunA?.ID === run.ID\"\n (click)=\"compareRunA?.ID !== run.ID && selectCompareRunB(run)\">\n <div class=\"selector-status\" [style.background-color]=\"getRunStatusColor(run.Status)\"></div>\n <div class=\"selector-content\">\n <div class=\"selector-date\">{{ run.StartedAt | date:'short' }}</div>\n <div class=\"selector-rate\">{{ getPassRate(run).toFixed(0) }}% pass</div>\n </div>\n <div class=\"selector-tags\" *ngIf=\"getRunTags(run).length > 0\">\n <span class=\"tag-mini\" *ngFor=\"let tag of getRunTags(run).slice(0, 2)\">{{ tag }}</span>\n </div>\n </div>\n </div>\n <div class=\"selected-run-preview\" *ngIf=\"compareRunB\">\n <div class=\"preview-header\">\n <span class=\"preview-label\">Selected:</span>\n <button class=\"clear-btn\" (click)=\"compareRunB = null; compareResults = []\">Clear</button>\n </div>\n <div class=\"preview-details\">\n <span>{{ compareRunB.StartedAt | date:'medium' }}</span>\n <span class=\"preview-rate\">{{ getPassRate(compareRunB).toFixed(1) }}%</span>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Comparison Results -->\n <div class=\"compare-results\" *ngIf=\"compareRunA && compareRunB && !loadingCompare\">\n <!-- Summary Cards -->\n <div class=\"compare-summary\">\n <div class=\"compare-summary-card\">\n <div class=\"summary-label\">Pass Rate Change</div>\n <div class=\"summary-value\" [ngClass]=\"{'positive': getComparePassRateDiff()! > 0, 'negative': getComparePassRateDiff()! < 0}\">\n <i class=\"fas\" [ngClass]=\"{'fa-arrow-up': getComparePassRateDiff()! > 0, 'fa-arrow-down': getComparePassRateDiff()! < 0, 'fa-minus': getComparePassRateDiff() === 0}\"></i>\n {{ getComparePassRateDiff()! > 0 ? '+' : '' }}{{ getComparePassRateDiff()?.toFixed(1) }}%\n </div>\n </div>\n <div class=\"compare-summary-card\">\n <div class=\"summary-label\">Duration Change</div>\n <div class=\"summary-value\" [ngClass]=\"{'positive': getCompareDurationDiff()! < 0, 'negative': getCompareDurationDiff()! > 0}\">\n <i class=\"fas\" [ngClass]=\"{'fa-arrow-down': getCompareDurationDiff()! < 0, 'fa-arrow-up': getCompareDurationDiff()! > 0, 'fa-minus': getCompareDurationDiff() === 0}\"></i>\n {{ formatDuration(getAbsCompareDurationDiff()) }}\n </div>\n </div>\n <div class=\"compare-summary-card improved\">\n <div class=\"summary-label\">Improved</div>\n <div class=\"summary-value\">{{ getCompareImprovedCount() }}</div>\n </div>\n <div class=\"compare-summary-card regressed\">\n <div class=\"summary-label\">Regressed</div>\n <div class=\"summary-value\">{{ getCompareRegressedCount() }}</div>\n </div>\n </div>\n\n <!-- Detailed Comparison Table -->\n <div class=\"compare-table-section\">\n <h3><i class=\"fas fa-list\"></i> Test-by-Test Comparison</h3>\n <div class=\"compare-table-wrapper\">\n <table class=\"compare-table\">\n <thead>\n <tr>\n <th>Test</th>\n <th>Run A Status</th>\n <th>Run B Status</th>\n <th>Score Diff</th>\n <th>Duration Diff</th>\n <th>Change</th>\n </tr>\n </thead>\n <tbody>\n <tr *ngFor=\"let result of compareResults\" [ngClass]=\"{'improved': result.runA && result.runB && result.runA.status !== 'Passed' && result.runB.status === 'Passed', 'regressed': result.runA && result.runB && result.runA.status === 'Passed' && result.runB.status !== 'Passed'}\">\n <td class=\"test-name-cell\">{{ result.testName }}</td>\n <td>\n <span class=\"status-chip\" *ngIf=\"result.runA\" [ngClass]=\"'status-' + result.runA.status.toLowerCase()\">{{ result.runA.status }}</span>\n <span class=\"status-chip status-missing\" *ngIf=\"!result.runA\">N/A</span>\n </td>\n <td>\n <span class=\"status-chip\" *ngIf=\"result.runB\" [ngClass]=\"'status-' + result.runB.status.toLowerCase()\">{{ result.runB.status }}</span>\n <span class=\"status-chip status-missing\" *ngIf=\"!result.runB\">N/A</span>\n </td>\n <td>\n <span *ngIf=\"result.scoreDiff != null\" [ngClass]=\"{'positive': result.scoreDiff > 0, 'negative': result.scoreDiff < 0}\">\n {{ result.scoreDiff > 0 ? '+' : '' }}{{ (result.scoreDiff * 100).toFixed(1) }}%\n </span>\n <span *ngIf=\"result.scoreDiff == null\" class=\"muted\">-</span>\n </td>\n <td>\n <span *ngIf=\"result.durationDiff != null\" [ngClass]=\"{'positive': result.durationDiff < 0, 'negative': result.durationDiff > 0}\">\n {{ result.durationDiff > 0 ? '+' : '' }}{{ result.durationDiff.toFixed(1) }}s\n </span>\n <span *ngIf=\"result.durationDiff == null\" class=\"muted\">-</span>\n </td>\n <td>\n <span class=\"change-indicator improved\" *ngIf=\"result.runA && result.runB && result.runA.status !== 'Passed' && result.runB.status === 'Passed'\">\n <i class=\"fas fa-arrow-up\"></i> Fixed\n </span>\n <span class=\"change-indicator regressed\" *ngIf=\"result.runA && result.runB && result.runA.status === 'Passed' && result.runB.status !== 'Passed'\">\n <i class=\"fas fa-arrow-down\"></i> Broke\n </span>\n <span class=\"change-indicator unchanged\" *ngIf=\"!result.statusChanged\">\n <i class=\"fas fa-minus\"></i>\n </span>\n </td>\n </tr>\n </tbody>\n </table>\n </div>\n </div>\n </div>\n\n <!-- Loading State for Compare -->\n <div class=\"loading-state\" *ngIf=\"loadingCompare\">\n <mj-loading text=\"Loading comparison data...\"></mj-loading>\n </div>\n\n <!-- Empty State -->\n <div class=\"compare-empty\" *ngIf=\"!compareRunA || !compareRunB\">\n <div class=\"compare-empty-icon\">\n <i class=\"fas fa-balance-scale\"></i>\n </div>\n <h4>Select Two Runs to Compare</h4>\n <p>Choose a baseline run (A) and a comparison run (B) from the lists above to see a detailed side-by-side comparison.</p>\n </div>\n\n <!-- No Runs State -->\n <div class=\"empty-state\" *ngIf=\"runsLoaded && suiteRuns.length < 2\">\n <div class=\"empty-icon\">\n <i class=\"fas fa-balance-scale\"></i>\n </div>\n <h4>Not Enough Runs to Compare</h4>\n <p>You need at least 2 suite runs to use the comparison feature.</p>\n <button kendoButton (click)=\"runSuite()\" themeColor=\"primary\">\n <i class=\"fas fa-play\"></i> Run Suite Now\n </button>\n </div>\n </div>\n </div>\n\n <!-- Keyboard Shortcuts Toggle Button -->\n <button class=\"shortcuts-toggle\" (click)=\"toggleShortcuts()\" [title]=\"showShortcuts ? 'Hide keyboard shortcuts' : 'Show keyboard shortcuts'\">\n <i class=\"fas fa-keyboard\"></i>\n </button>\n\n <!-- Keyboard Shortcuts Hint (Desktop Only) -->\n <div class=\"keyboard-shortcuts\" *ngIf=\"showShortcuts\">\n <div class=\"shortcuts-header\">\n <i class=\"fas fa-keyboard\"></i>\n Shortcuts\n <button class=\"shortcuts-close\" (click)=\"toggleShortcuts()\" title=\"Hide shortcuts\">\n <i class=\"fas fa-times\"></i>\n </button>\n </div>\n <div class=\"shortcut-list\">\n <div class=\"shortcut-item\">\n <span>Refresh</span>\n <span class=\"shortcut-keys\"><kbd>Cmd</kbd><kbd>R</kbd></span>\n </div>\n <div class=\"shortcut-item\">\n <span>Run Suite</span>\n <span class=\"shortcut-keys\"><kbd>Cmd</kbd><kbd>Enter</kbd></span>\n </div>\n <div class=\"shortcut-item\">\n <span>Switch Tabs</span>\n <span class=\"shortcut-keys\"><kbd>1</kbd>-<kbd>5</kbd></span>\n </div>\n </div>\n </div>\n</div>\n", styles: ["/* ===========================\n Test Suite Form - World-Class UX\n =========================== */\n\n/* CSS Custom Properties for Theming - using :host for Angular encapsulation */\n:host {\n --test-primary: #2563eb;\n --test-primary-light: #3b82f6;\n --test-success: #10b981;\n --test-error: #ef4444;\n --test-warning: #f59e0b;\n --test-disabled: #6b7280;\n --test-bg: #f8fafc;\n --test-surface: #ffffff;\n --test-border: #e2e8f0;\n --test-text: #1e293b;\n --test-text-secondary: #64748b;\n --test-text-muted: #94a3b8;\n --test-radius-sm: 6px;\n --test-radius-md: 10px;\n --test-radius-lg: 16px;\n --test-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);\n --test-shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);\n --test-shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);\n --test-transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n display: block;\n height: 100%;\n}\n\n.test-suite-form {\n display: flex;\n flex-direction: column;\n height: 100%;\n background: var(--test-bg);\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n}\n\n/* Header */\n.suite-header {\n background: var(--test-surface);\n border-bottom: 1px solid var(--test-border);\n padding: 20px;\n}\n\n.header-content {\n display: flex;\n justify-content: space-between;\n align-items: flex-start;\n margin-bottom: 16px;\n gap: 16px;\n}\n\n.header-left {\n display: flex;\n gap: 16px;\n flex: 1;\n min-width: 0;\n}\n\n.suite-icon {\n width: 56px;\n height: 56px;\n border-radius: var(--test-radius-md);\n display: flex;\n align-items: center;\n justify-content: center;\n color: white;\n font-size: 24px;\n flex-shrink: 0;\n box-shadow: var(--test-shadow-md);\n transition: var(--test-transition);\n}\n\n.suite-icon:hover { transform: scale(1.05); }\n\n.suite-info { flex: 1; min-width: 0; }\n\n.suite-info h1 {\n margin: 0 0 8px 0;\n font-size: clamp(18px, 4vw, 24px);\n font-weight: 700;\n color: var(--test-text);\n word-wrap: break-word;\n}\n\n.suite-meta {\n display: flex;\n align-items: center;\n gap: 12px;\n flex-wrap: wrap;\n}\n\n.status-badge {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 6px 14px;\n border-radius: 20px;\n color: white;\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n}\n\n.status-badge.status-active { background: linear-gradient(135deg, var(--test-success) 0%, #059669 100%); }\n.status-badge.status-disabled { background: linear-gradient(135deg, var(--test-disabled) 0%, #4b5563 100%); }\n.status-badge.status-pending { background: linear-gradient(135deg, var(--test-warning) 0%, #d97706 100%); }\n\n.status-badge-inline {\n display: inline-flex;\n padding: 2px 10px;\n border-radius: 10px;\n color: white;\n font-size: 11px;\n font-weight: 600;\n}\n\n.status-badge-inline.status-active { background: var(--test-success); }\n.status-badge-inline.status-disabled { background: var(--test-disabled); }\n.status-badge-inline.status-pending { background: var(--test-warning); }\n\n.test-count {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--test-text-secondary);\n padding: 4px 10px;\n background: var(--test-bg);\n border-radius: var(--test-radius-sm);\n}\n\n.header-actions {\n display: flex;\n gap: 8px;\n flex-shrink: 0;\n}\n\n.suite-description {\n padding: 16px;\n background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);\n border-radius: var(--test-radius-md);\n border: 1px solid var(--test-border);\n}\n\n.suite-description p {\n margin: 0;\n color: var(--test-text-secondary);\n line-height: 1.6;\n font-size: 14px;\n}\n\n/* Tabs */\n.tabs-container {\n background: var(--test-surface);\n border-bottom: 1px solid var(--test-border);\n position: sticky;\n top: 0;\n z-index: 10;\n}\n\n.tabs {\n display: flex;\n padding: 0 20px;\n overflow-x: auto;\n scrollbar-width: none;\n}\n\n.tabs::-webkit-scrollbar { display: none; }\n\n.tab {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 14px 18px;\n border: none;\n background: transparent;\n border-bottom: 3px solid transparent;\n color: var(--test-text-secondary);\n font-size: 14px;\n font-weight: 500;\n cursor: pointer;\n transition: var(--test-transition);\n white-space: nowrap;\n}\n\n.tab:hover {\n color: var(--test-primary);\n background: rgba(37, 99, 235, 0.05);\n}\n\n.tab.active {\n color: var(--test-primary);\n border-bottom-color: var(--test-primary);\n font-weight: 600;\n}\n\n.tab-badge {\n background: var(--test-border);\n color: var(--test-text-secondary);\n padding: 2px 8px;\n border-radius: 10px;\n font-size: 11px;\n font-weight: 600;\n}\n\n.tab.active .tab-badge {\n background: rgba(37, 99, 235, 0.15);\n color: var(--test-primary);\n}\n\n.tab-shortcut {\n font-size: 10px;\n color: var(--test-text-muted);\n background: var(--test-bg);\n padding: 2px 6px;\n border-radius: 4px;\n font-weight: 600;\n}\n\n/* Tab Content */\n.tab-content {\n flex: 1;\n overflow-y: auto;\n padding: 20px;\n}\n\n/* Overview Tab */\n.overview-tab {\n display: flex;\n flex-direction: column;\n gap: 20px;\n animation: fadeIn 0.3s ease-out;\n}\n\n@keyframes fadeIn {\n from { opacity: 0; transform: translateY(10px); }\n to { opacity: 1; transform: translateY(0); }\n}\n\n.info-section, .config-section {\n background: var(--test-surface);\n border-radius: var(--test-radius-lg);\n padding: 24px;\n box-shadow: var(--test-shadow-sm);\n}\n\n.info-section h3, .config-section h3 {\n margin: 0 0 20px 0;\n font-size: 18px;\n font-weight: 700;\n color: var(--test-text);\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.info-section h3 i, .config-section h3 i {\n color: var(--test-primary);\n}\n\n.info-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\n gap: 16px;\n}\n\n.info-item {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.info-label {\n font-size: 11px;\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n color: var(--test-text-muted);\n}\n\n.info-value {\n font-size: 14px;\n color: var(--test-text);\n font-weight: 500;\n}\n\n.config-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));\n gap: 20px;\n}\n\n.config-item {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.config-item label {\n font-size: 12px;\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n color: var(--test-text-muted);\n}\n\n.config-input {\n padding: 10px 14px;\n border: 1px solid var(--test-border);\n border-radius: var(--test-radius-sm);\n font-size: 14px;\n transition: var(--test-transition);\n}\n\n.config-input:focus {\n outline: none;\n border-color: var(--test-primary);\n box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);\n}\n\n.config-hint {\n font-size: 11px;\n color: var(--test-text-muted);\n}\n\n/* Tests Tab */\n.tests-tab, .runs-tab {\n animation: fadeIn 0.3s ease-out;\n}\n\n.loading-state { padding: 0; }\n\n.skeleton-list {\n display: flex;\n flex-direction: column;\n gap: 12px;\n}\n\n.skeleton-card {\n display: flex;\n align-items: center;\n gap: 14px;\n padding: 16px;\n background: var(--test-surface);\n border-radius: var(--test-radius-md);\n border: 1px solid var(--test-border);\n}\n\n.skeleton-sequence {\n width: 32px;\n height: 32px;\n border-radius: 50%;\n background: linear-gradient(90deg, #e2e8f0 25%, #f1f5f9 50%, #e2e8f0 75%);\n background-size: 200% 100%;\n animation: shimmer 1.5s infinite;\n}\n\n.skeleton-icon {\n width: 40px;\n height: 40px;\n border-radius: var(--test-radius-md);\n background: linear-gradient(90deg, #e2e8f0 25%, #f1f5f9 50%, #e2e8f0 75%);\n background-size: 200% 100%;\n animation: shimmer 1.5s infinite;\n}\n\n.skeleton-content {\n flex: 1;\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n.skeleton-line {\n height: 14px;\n border-radius: 4px;\n background: linear-gradient(90deg, #e2e8f0 25%, #f1f5f9 50%, #e2e8f0 75%);\n background-size: 200% 100%;\n animation: shimmer 1.5s infinite;\n}\n\n.skeleton-line.wide { width: 70%; }\n.skeleton-line.narrow { width: 40%; }\n\n@keyframes shimmer {\n 0% { background-position: 200% 0; }\n 100% { background-position: -200% 0; }\n}\n\n.tests-list, .runs-list {\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n.test-item, .run-item {\n display: flex;\n align-items: center;\n gap: 14px;\n padding: 16px;\n background: var(--test-surface);\n border: 1px solid var(--test-border);\n border-radius: var(--test-radius-md);\n cursor: pointer;\n transition: var(--test-transition);\n}\n\n.test-item:hover, .run-item:hover {\n background: rgba(37, 99, 235, 0.05);\n border-color: var(--test-primary-light);\n transform: translateX(4px);\n}\n\n.test-sequence {\n width: 32px;\n height: 32px;\n display: flex;\n align-items: center;\n justify-content: center;\n background: var(--test-bg);\n border-radius: 50%;\n font-size: 14px;\n font-weight: 700;\n color: var(--test-text-secondary);\n flex-shrink: 0;\n}\n\n.test-icon, .run-icon {\n width: 40px;\n height: 40px;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: var(--test-radius-md);\n color: white;\n font-size: 18px;\n flex-shrink: 0;\n box-shadow: var(--test-shadow-sm);\n}\n\n.test-icon {\n background: linear-gradient(135deg, var(--test-primary) 0%, #1d4ed8 100%);\n}\n\n.test-content, .run-content { flex: 1; min-width: 0; }\n\n.test-name, .run-header {\n font-size: 14px;\n font-weight: 600;\n color: var(--test-text);\n margin-bottom: 4px;\n}\n\n.run-header {\n display: flex;\n align-items: center;\n gap: 12px;\n}\n\n.run-id { font-weight: 600; }\n\n.run-status {\n font-size: 12px;\n font-weight: 700;\n text-transform: uppercase;\n}\n\n.test-status, .run-meta {\n display: flex;\n gap: 12px;\n font-size: 12px;\n color: var(--test-text-secondary);\n}\n\n.test-status span, .run-meta span {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n}\n\n.test-item > i, .run-item > i {\n color: var(--test-text-muted);\n font-size: 14px;\n transition: var(--test-transition);\n}\n\n.test-item:hover > i, .run-item:hover > i {\n color: var(--test-primary);\n transform: translateX(2px);\n}\n\n/* Empty States */\n.empty-state {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 60px 24px;\n text-align: center;\n background: var(--test-surface);\n border-radius: var(--test-radius-lg);\n}\n\n.empty-icon {\n width: 80px;\n height: 80px;\n display: flex;\n align-items: center;\n justify-content: center;\n background: var(--test-bg);\n border-radius: 50%;\n margin-bottom: 20px;\n}\n\n.empty-icon i {\n font-size: 36px;\n color: var(--test-text-muted);\n}\n\n.empty-state h4 {\n margin: 0 0 8px 0;\n font-size: 18px;\n font-weight: 600;\n color: var(--test-text);\n}\n\n.empty-state p {\n margin: 0 0 20px 0;\n font-size: 14px;\n color: var(--test-text-secondary);\n max-width: 300px;\n}\n\n/* Keyboard Shortcuts */\n/* Keyboard shortcuts toggle button - visible when shortcuts are hidden */\n.shortcuts-toggle {\n position: fixed;\n bottom: 20px;\n right: 20px;\n width: 36px;\n height: 36px;\n border-radius: 50%;\n background: var(--test-surface);\n border: 1px solid var(--test-border);\n box-shadow: var(--test-shadow-md);\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--test-text-secondary);\n font-size: 14px;\n z-index: 99;\n transition: var(--test-transition);\n opacity: 0.7;\n}\n\n.shortcuts-toggle:hover {\n opacity: 1;\n transform: scale(1.1);\n color: var(--test-primary);\n border-color: var(--test-primary);\n}\n\n.keyboard-shortcuts {\n position: fixed;\n bottom: 20px;\n right: 20px;\n background: var(--test-surface);\n border: 1px solid var(--test-border);\n border-radius: var(--test-radius-md);\n padding: 12px 16px;\n box-shadow: var(--test-shadow-lg);\n font-size: 12px;\n z-index: 100;\n max-width: 260px;\n}\n\n.shortcuts-header {\n display: flex;\n align-items: center;\n gap: 6px;\n margin-bottom: 10px;\n padding-bottom: 8px;\n border-bottom: 1px solid var(--test-border);\n font-weight: 600;\n color: var(--test-text);\n}\n\n.shortcuts-close {\n margin-left: auto;\n background: none;\n border: none;\n cursor: pointer;\n color: var(--test-text-muted);\n font-size: 12px;\n padding: 2px 4px;\n border-radius: 4px;\n transition: var(--test-transition);\n}\n\n.shortcuts-close:hover {\n color: var(--test-text);\n background: var(--test-border);\n}\n\n.shortcut-list {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.shortcut-item {\n display: flex;\n justify-content: space-between;\n align-items: center;\n color: var(--test-text-secondary);\n}\n\n.shortcut-keys {\n display: flex;\n gap: 4px;\n}\n\n.shortcut-keys kbd {\n background: var(--test-bg);\n border: 1px solid var(--test-border);\n border-radius: 4px;\n padding: 2px 6px;\n font-size: 11px;\n color: var(--test-text);\n}\n\n/* Responsive */\n@media (max-width: 1024px) {\n .keyboard-shortcuts, .shortcuts-toggle { display: none; }\n}\n\n@media (max-width: 768px) {\n .suite-header { padding: 16px; }\n\n .header-content {\n flex-direction: column;\n gap: 16px;\n }\n\n .header-actions {\n width: 100%;\n justify-content: stretch;\n }\n\n .header-actions button { flex: 1; }\n\n .tab-shortcut { display: none; }\n\n .info-grid { grid-template-columns: 1fr; }\n\n .test-item, .run-item { padding: 14px; }\n}\n\n@media (max-width: 480px) {\n .suite-icon {\n width: 40px;\n height: 40px;\n font-size: 18px;\n }\n\n .suite-info h1 { font-size: 16px; }\n\n .tab-badge { display: none; }\n\n .test-sequence { display: none; }\n}\n\n@media (hover: none) and (pointer: coarse) {\n .test-item:active, .run-item:active {\n background: rgba(37, 99, 235, 0.1);\n transform: scale(0.98);\n }\n\n .tab { min-height: 48px; }\n .test-item, .run-item { min-height: 64px; }\n}\n\n@media (prefers-reduced-motion: reduce) {\n *, *::before, *::after {\n animation-duration: 0.01ms !important;\n transition-duration: 0.01ms !important;\n }\n}\n\n@media print {\n .header-actions, .tabs-container, .keyboard-shortcuts {\n display: none !important;\n }\n}\n\n/* ===========================\n Tags UI\n =========================== */\n.run-tags {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n margin-top: 8px;\n}\n\n.tag-chip {\n display: inline-flex;\n align-items: center;\n padding: 3px 10px;\n background: linear-gradient(135deg, #eff6ff 0%, #dbeafe 100%);\n border: 1px solid #bfdbfe;\n border-radius: 12px;\n font-size: 11px;\n font-weight: 500;\n color: #1d4ed8;\n}\n\n.tag-mini {\n display: inline-flex;\n align-items: center;\n padding: 2px 6px;\n background: #f1f5f9;\n border-radius: 4px;\n font-size: 10px;\n font-weight: 500;\n color: #64748b;\n}\n\n.tag-more {\n font-size: 10px;\n color: #94a3b8;\n font-weight: 500;\n}\n\n/* Evaluation metrics row for Runs list */\n.run-eval-metrics {\n display: flex;\n align-items: center;\n gap: 8px;\n margin-top: 8px;\n}\n\n.eval-metric {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 3px 8px;\n border-radius: 6px;\n font-size: 11px;\n font-weight: 500;\n}\n\n.eval-metric.status {\n background: #f1f5f9;\n color: #64748b;\n}\n\n.eval-metric.status.status-completed {\n background: #dcfce7;\n color: #166534;\n}\n\n.eval-metric.status.status-failed {\n background: #fee2e2;\n color: #991b1b;\n}\n\n.eval-metric.status.status-running {\n background: #dbeafe;\n color: #1d4ed8;\n}\n\n.eval-metric.status.status-pending {\n background: #fef3c7;\n color: #92400e;\n}\n\n.eval-metric.human {\n background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);\n border: 1px solid #f59e0b;\n color: #92400e;\n}\n\n.eval-metric.human .eval-pending {\n font-size: 9px;\n color: #d97706;\n}\n\n.eval-metric.auto {\n background: linear-gradient(135deg, #dbeafe 0%, #bfdbfe 100%);\n border: 1px solid #93c5fd;\n color: #1d4ed8;\n}\n\n.eval-metric.auto.high {\n background: linear-gradient(135deg, #dcfce7 0%, #bbf7d0 100%);\n border: 1px solid #86efac;\n color: #166534;\n}\n\n.eval-metric.auto.medium {\n background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);\n border: 1px solid #fcd34d;\n color: #92400e;\n}\n\n.eval-metric.auto.low {\n background: linear-gradient(135deg, #fee2e2 0%, #fecaca 100%);\n border: 1px solid #fca5a5;\n color: #991b1b;\n}\n\n.tag-cell {\n display: flex;\n align-items: center;\n gap: 4px;\n flex-wrap: wrap;\n}\n\n.tag-chip-table {\n display: inline-block;\n padding: 3px 8px;\n background: linear-gradient(135deg, #dbeafe 0%, #bfdbfe 100%);\n border: 1px solid #93c5fd;\n color: #1d4ed8;\n border-radius: 10px;\n font-size: 10px;\n font-weight: 600;\n white-space: nowrap;\n max-width: 80px;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* ===========================\n Analytics Tab\n =========================== */\n.analytics-tab {\n animation: fadeIn 0.3s ease-out;\n}\n\n/* Collapsible filters */\n.analytics-filters {\n background: var(--test-surface);\n border-radius: var(--test-radius-lg);\n margin-bottom: 16px;\n box-shadow: var(--test-shadow-sm);\n overflow: hidden;\n}\n\n.analytics-filters.collapsed {\n margin-bottom: 12px;\n}\n\n.filters-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 12px 16px;\n cursor: pointer;\n user-select: none;\n transition: background 0.15s ease;\n}\n\n.filters-header:hover {\n background: var(--test-bg);\n}\n\n.filters-title {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 13px;\n font-weight: 600;\n color: var(--test-text-secondary);\n}\n\n.filters-title i {\n color: var(--test-primary);\n}\n\n.filter-summary {\n font-weight: 400;\n color: var(--test-text-muted);\n margin-left: 8px;\n}\n\n.filters-header > i {\n color: var(--test-text-muted);\n font-size: 12px;\n}\n\n.filters-content {\n display: flex;\n flex-direction: column;\n gap: 16px;\n padding: 12px 16px 16px 16px;\n border-top: 1px solid var(--test-border);\n}\n\n.filter-group {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n.filter-group label {\n font-size: 11px;\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n color: var(--test-text-muted);\n}\n\n.filter-buttons {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n}\n\n.filter-btn {\n padding: 8px 16px;\n border: 1px solid var(--test-border);\n border-radius: var(--test-radius-sm);\n background: var(--test-surface);\n color: var(--test-text-secondary);\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n transition: var(--test-transition);\n}\n\n.filter-btn:hover {\n border-color: var(--test-primary);\n color: var(--test-primary);\n background: rgba(37, 99, 235, 0.05);\n}\n\n.filter-btn.active {\n background: var(--test-primary);\n border-color: var(--test-primary);\n color: white;\n}\n\n.filter-btn.tag-btn {\n padding: 6px 12px;\n font-size: 12px;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n}\n\n.filter-btn.tag-btn i.fa-check {\n font-size: 10px;\n}\n\n.filter-btn.tag-btn.all-tags-btn {\n font-weight: 600;\n}\n\n.filter-btn.tag-btn.all-tags-btn i {\n font-size: 11px;\n}\n\n.filter-hint {\n font-weight: 400;\n font-size: 10px;\n color: var(--test-primary);\n text-transform: none;\n letter-spacing: normal;\n}\n\n.tag-filters {\n max-height: 120px;\n overflow-y: auto;\n}\n\n/* KPI Cards */\n.analytics-kpis {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\n gap: 16px;\n margin-bottom: 24px;\n}\n\n.kpi-card {\n display: flex;\n align-items: center;\n gap: 16px;\n background: var(--test-surface);\n border-radius: var(--test-radius-lg);\n padding: 20px;\n box-shadow: var(--test-shadow-sm);\n transition: var(--test-transition);\n}\n\n.kpi-card:hover {\n transform: translateY(-2px);\n box-shadow: var(--test-shadow-md);\n}\n\n.kpi-icon {\n width: 48px;\n height: 48px;\n display: flex;\n align-items: center;\n justify-content: center;\n background: #eff6ff;\n border-radius: var(--test-radius-md);\n color: var(--test-primary);\n font-size: 20px;\n}\n\n.kpi-icon.success {\n background: #ecfdf5;\n color: var(--test-success);\n}\n\n.kpi-icon.info {\n background: #f0f9ff;\n color: #0ea5e9;\n}\n\n.kpi-icon.warning {\n background: #fffbeb;\n color: var(--test-warning);\n}\n\n.kpi-content {\n flex: 1;\n min-width: 0;\n}\n\n.kpi-value {\n font-size: 24px;\n font-weight: 700;\n color: var(--test-text);\n line-height: 1.2;\n}\n\n.kpi-label {\n font-size: 12px;\n color: var(--test-text-secondary);\n margin-top: 2px;\n}\n\n.kpi-trend {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n font-size: 12px;\n font-weight: 600;\n margin-top: 4px;\n padding: 2px 8px;\n border-radius: 10px;\n background: #f1f5f9;\n color: var(--test-text-secondary);\n}\n\n.kpi-trend.trend-up {\n background: #ecfdf5;\n color: var(--test-success);\n}\n\n.kpi-trend.trend-down {\n background: #fef2f2;\n color: var(--test-error);\n}\n\n/* Analytics Table */\n.analytics-table-section {\n background: var(--test-surface);\n border-radius: var(--test-radius-lg);\n padding: 24px;\n box-shadow: var(--test-shadow-sm);\n}\n\n.analytics-table-section h3 {\n margin: 0 0 16px 0;\n font-size: 16px;\n font-weight: 600;\n color: var(--test-text);\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.analytics-table-section h3 i {\n color: var(--test-primary);\n}\n\n.analytics-table-wrapper {\n overflow-x: auto;\n margin: 0 -24px;\n padding: 0 24px;\n}\n\n.analytics-table {\n width: 100%;\n border-collapse: collapse;\n font-size: 13px;\n}\n\n.analytics-table th {\n text-align: left;\n padding: 12px 16px;\n background: #f8fafc;\n border-bottom: 2px solid var(--test-border);\n font-weight: 600;\n color: var(--test-text-secondary);\n text-transform: uppercase;\n font-size: 11px;\n letter-spacing: 0.5px;\n white-space: nowrap;\n}\n\n.analytics-table td {\n padding: 14px 16px;\n border-bottom: 1px solid var(--test-border);\n color: var(--test-text);\n}\n\n.analytics-table tbody tr.clickable-row {\n cursor: pointer;\n transition: var(--test-transition);\n}\n\n.analytics-table tbody tr.clickable-row:hover {\n background: rgba(37, 99, 235, 0.05);\n}\n\n.status-chip {\n display: inline-flex;\n align-items: center;\n padding: 4px 10px;\n border-radius: 12px;\n font-size: 11px;\n font-weight: 600;\n text-transform: uppercase;\n}\n\n.status-chip.status-completed { background: #ecfdf5; color: var(--test-success); }\n.status-chip.status-passed { background: #ecfdf5; color: var(--test-success); }\n.status-chip.status-failed { background: #fef2f2; color: var(--test-error); }\n.status-chip.status-error { background: #fffbeb; color: var(--test-warning); }\n.status-chip.status-running { background: #eff6ff; color: var(--test-primary); }\n.status-chip.status-pending { background: #f5f3ff; color: #8b5cf6; }\n.status-chip.status-cancelled { background: #f1f5f9; color: var(--test-disabled); }\n.status-chip.status-missing { background: #f1f5f9; color: var(--test-text-muted); }\n.status-chip.status-timeout { background: #fffbeb; color: var(--test-warning); }\n.status-chip.status-skipped { background: #f1f5f9; color: var(--test-disabled); }\n\n.pass-rate-cell {\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 100px;\n}\n\n.pass-rate-bar {\n height: 6px;\n border-radius: 3px;\n background: var(--test-success);\n transition: width 0.3s ease;\n max-width: 60px;\n}\n\n.pass-rate-bar.medium { background: var(--test-warning); }\n.pass-rate-bar.low { background: var(--test-error); }\n\n/* Analytics Sub-navigation */\n.analytics-subnav {\n background: var(--test-surface);\n border-radius: var(--test-radius-lg);\n padding: 8px;\n margin-bottom: 20px;\n box-shadow: var(--test-shadow-sm);\n display: inline-flex;\n}\n\n.subnav-tabs {\n display: flex;\n gap: 4px;\n background: var(--test-bg);\n border-radius: var(--test-radius-md);\n padding: 4px;\n}\n\n.subnav-tab {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px 20px;\n border: none;\n border-radius: var(--test-radius-sm);\n background: transparent;\n color: var(--test-text-secondary);\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n transition: var(--test-transition);\n}\n\n.subnav-tab:hover {\n color: var(--test-primary);\n background: rgba(37, 99, 235, 0.05);\n}\n\n.subnav-tab.active {\n background: var(--test-surface);\n color: var(--test-primary);\n box-shadow: var(--test-shadow-sm);\n}\n\n.subnav-tab i {\n font-size: 14px;\n}\n\n/* Matrix View */\n.matrix-section {\n background: var(--test-surface);\n border-radius: var(--test-radius-lg);\n padding: 16px;\n box-shadow: var(--test-shadow-sm);\n display: flex;\n flex-direction: column;\n max-height: calc(100vh - 280px);\n min-height: 300px;\n}\n\n.matrix-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n flex-wrap: wrap;\n gap: 12px;\n margin-bottom: 12px;\n padding-bottom: 12px;\n border-bottom: 1px solid var(--test-border);\n flex-shrink: 0;\n}\n\n.matrix-header-right {\n display: flex;\n align-items: center;\n gap: 12px;\n}\n\n.matrix-header h3 {\n display: flex;\n align-items: center;\n gap: 10px;\n margin: 0;\n font-size: 15px;\n font-weight: 600;\n color: var(--test-text);\n}\n\n.matrix-header h3 i {\n color: var(--test-primary);\n}\n\n.matrix-run-count {\n font-size: 12px;\n color: var(--test-text-muted);\n font-weight: 500;\n}\n\n/* Matrix filter input */\n.matrix-filter-input {\n display: flex;\n align-items: center;\n gap: 8px;\n background: #f8fafc;\n border: 1px solid var(--test-border);\n border-radius: 6px;\n padding: 4px 10px;\n min-width: 180px;\n}\n\n.matrix-filter-input i.fa-search {\n color: #94a3b8;\n font-size: 12px;\n}\n\n.matrix-filter-input .filter-input {\n border: none;\n background: transparent;\n outline: none;\n font-size: 12px;\n color: var(--test-text);\n width: 100%;\n}\n\n.matrix-filter-input .filter-input::placeholder {\n color: #94a3b8;\n}\n\n.clear-filter-btn {\n border: none;\n background: none;\n padding: 2px 4px;\n cursor: pointer;\n color: #94a3b8;\n font-size: 10px;\n border-radius: 3px;\n transition: all 0.15s ease;\n}\n\n.clear-filter-btn:hover {\n background: #e2e8f0;\n color: #64748b;\n}\n\n/* Scrollable matrix container with fixed height */\n.matrix-scroll-container {\n flex: 1;\n overflow: auto;\n border: 1px solid var(--test-border);\n border-radius: var(--test-radius-sm);\n}\n\n.test-matrix {\n display: table;\n width: 100%;\n border-collapse: collapse;\n font-size: 13px;\n}\n\n.test-matrix thead {\n display: table-header-group;\n}\n\n.test-matrix thead tr {\n display: table-row;\n}\n\n.test-matrix tbody {\n display: table-row-group;\n}\n\n.test-matrix tbody tr {\n display: table-row;\n cursor: pointer;\n transition: background-color 0.15s ease;\n}\n\n.test-matrix tbody tr:hover {\n background-color: rgba(59, 130, 246, 0.05);\n}\n\n.test-matrix tbody tr.row-selected {\n background-color: rgba(59, 130, 246, 0.12) !important;\n}\n\n.test-matrix tbody tr.row-selected td {\n border-top: 1px solid rgba(59, 130, 246, 0.3);\n border-bottom: 1px solid rgba(59, 130, 246, 0.3);\n}\n\n.test-matrix tbody tr.row-selected .seq-cell,\n.test-matrix tbody tr.row-selected .test-name-cell {\n background-color: rgba(59, 130, 246, 0.12) !important;\n}\n\n.test-matrix tfoot {\n display: table-footer-group;\n}\n\n.test-matrix th,\n.test-matrix td {\n display: table-cell;\n border: 1px solid var(--test-border);\n padding: 8px 12px;\n text-align: center;\n vertical-align: middle;\n}\n\n.test-matrix th {\n background: var(--test-bg);\n font-weight: 600;\n font-size: 11px;\n color: var(--test-text-secondary);\n text-transform: uppercase;\n letter-spacing: 0.5px;\n position: sticky;\n top: 0;\n z-index: 10;\n border-bottom: 3px solid #475569 !important;\n}\n\n/* Sequence column - shared styles */\n.test-matrix .seq-header,\n.test-matrix .seq-cell {\n width: 36px;\n min-width: 36px;\n max-width: 36px;\n text-align: center;\n position: sticky;\n left: 0;\n font-size: 11px;\n color: #64748b;\n border-right: 1px solid var(--test-border);\n padding: 6px 4px !important;\n}\n\n/* Seq header - sticky top AND left, highest z-index */\n.test-matrix .seq-header {\n cursor: pointer;\n font-weight: 600;\n background: var(--test-bg);\n z-index: 12; /* Higher than other headers */\n top: 0;\n}\n\n/* Seq body cells - sticky left only, lower z-index */\n.test-matrix .seq-cell {\n background: var(--test-surface);\n z-index: 2;\n}\n\n.test-matrix .seq-header i {\n font-size: 9px;\n margin-left: 2px;\n opacity: 0.6;\n}\n\n.test-matrix .seq-header:hover {\n background: #f1f5f9;\n}\n\n.test-matrix .test-name-header {\n text-align: left;\n min-width: 150px;\n max-width: 500px;\n width: auto;\n position: sticky;\n left: 36px;\n background: var(--test-bg);\n z-index: 11;\n border-right: 2px solid var(--test-border);\n cursor: pointer;\n}\n\n.test-matrix .test-name-header i {\n font-size: 9px;\n margin-left: 4px;\n opacity: 0.6;\n}\n\n.test-matrix .test-name-header:hover {\n background: #f1f5f9;\n}\n\n.test-matrix .run-header {\n min-width: 120px;\n width: 120px;\n cursor: pointer;\n transition: var(--test-transition);\n}\n\n/* Spacer column absorbs extra width */\n.test-matrix .spacer-header,\n.test-matrix .spacer-cell {\n width: 100%;\n min-width: 20px;\n background: var(--test-bg);\n border: none;\n}\n\n.test-matrix .spacer-cell {\n background: var(--test-surface);\n}\n\n.test-matrix .run-header:hover {\n background: rgba(37, 99, 235, 0.05);\n}\n\n.run-header-content {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 4px;\n}\n\n.run-date {\n font-size: 12px;\n font-weight: 600;\n color: var(--test-text);\n}\n\n.run-pass-rate {\n font-size: 11px;\n font-weight: 700;\n padding: 2px 8px;\n border-radius: 10px;\n}\n\n.run-pass-rate.high {\n background: #ecfdf5;\n color: var(--test-success);\n}\n\n.run-pass-rate.medium {\n background: #fffbeb;\n color: var(--test-warning);\n}\n\n.run-pass-rate.low {\n background: #fef2f2;\n color: var(--test-error);\n}\n\n.run-tags {\n display: flex;\n gap: 4px;\n}\n\n.tag-tiny {\n font-size: 9px;\n padding: 2px 6px;\n background: #eff6ff;\n color: var(--test-primary);\n border-radius: 8px;\n white-space: nowrap;\n max-width: 60px;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* Matrix column header tags - emphasized */\n.run-tags-header {\n display: flex;\n flex-wrap: wrap;\n gap: 3px;\n justify-content: center;\n margin-bottom: 4px;\n}\n\n.tag-chip-header {\n display: inline-block;\n padding: 3px 8px;\n background: linear-gradient(135deg, #dbeafe 0%, #bfdbfe 100%);\n border: 1px solid #93c5fd;\n color: #1d4ed8;\n border-radius: 10px;\n font-size: 10px;\n font-weight: 600;\n white-space: nowrap;\n max-width: 70px;\n overflow: hidden;\n text-overflow: ellipsis;\n text-transform: none;\n letter-spacing: normal;\n}\n\n.tag-more-header {\n font-size: 9px;\n color: var(--test-text-secondary);\n padding: 2px 4px;\n}\n\n.test-matrix .test-name-cell {\n text-align: left;\n min-width: 150px;\n max-width: 500px;\n width: auto;\n position: sticky;\n left: 36px;\n background: var(--test-surface);\n z-index: 2;\n border-right: 2px solid var(--test-border);\n padding: 6px 10px !important;\n}\n\n.test-name {\n display: block;\n white-space: nowrap;\n font-weight: 500;\n color: var(--test-text);\n font-size: 12px;\n}\n\n.result-cell {\n position: relative;\n min-width: 120px;\n width: 120px;\n transition: var(--test-transition);\n}\n\n.result-cell.clickable {\n cursor: pointer;\n}\n\n.result-cell.clickable:hover {\n transform: scale(1.1);\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);\n z-index: 5;\n}\n\n.result-cell i {\n font-size: 14px;\n}\n\n/* Completed cells have neutral white background - pills tell the story */\n.result-cell.cell-passed,\n.result-cell.cell-failed,\n.result-cell.cell-error,\n.result-cell.cell-timeout,\n.result-cell.cell-running,\n.result-cell.cell-pending {\n background: #ffffff;\n color: var(--test-text);\n}\n\n/* Skipped/not-run cells get hatched background */\n.result-cell.cell-skipped {\n background: repeating-linear-gradient(\n 45deg,\n #f8fafc,\n #f8fafc 4px,\n #e2e8f0 4px,\n #e2e8f0 8px\n );\n color: var(--test-disabled);\n}\n\n.result-cell.cell-none {\n background: #f8fafc;\n color: var(--test-text-muted);\n}\n\n.cell-score {\n display: block;\n font-size: 10px;\n font-weight: 600;\n margin-top: 2px;\n}\n\n/* Matrix cell evaluation stack - shows multiple eval types horizontally */\n.cell-eval-stack {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n flex-wrap: nowrap;\n}\n\n.result-cell.multi-eval {\n min-width: 120px;\n width: auto;\n}\n\n/* Add timeout status icon */\n.cell-status.status-timeout {\n background: #fef3c7;\n color: #d97706;\n}\n\n.cell-status {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 22px;\n height: 22px;\n border-radius: 50%;\n font-size: 11px;\n}\n\n.cell-status.status-passed {\n background: #dcfce7;\n color: #166534;\n}\n\n.cell-status.status-failed {\n background: #fee2e2;\n color: #991b1b;\n}\n\n.cell-status.status-error {\n background: #fef3c7;\n color: #92400e;\n}\n\n.cell-status.status-skipped {\n background: #f1f5f9;\n color: #64748b;\n}\n\n.cell-status.status-running {\n background: #dbeafe;\n color: #1d4ed8;\n}\n\n.cell-status.status-pending {\n background: #f3e8ff;\n color: #7c3aed;\n}\n\n.cell-human {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 22px;\n height: 22px;\n border-radius: 50%;\n background: #fef3c7;\n color: #d97706;\n font-size: 10px;\n}\n\n.cell-auto {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n padding: 2px 6px;\n border-radius: 4px;\n font-size: 10px;\n font-weight: 600;\n background: #dbeafe;\n color: #1d4ed8;\n}\n\n.cell-auto.high {\n background: #dcfce7;\n color: #166534;\n}\n\n.cell-auto.medium {\n background: #fef3c7;\n color: #92400e;\n}\n\n.cell-auto.low {\n background: #fee2e2;\n color: #991b1b;\n}\n\n.cell-none-indicator {\n color: var(--test-text-muted);\n opacity: 0.5;\n}\n\n.matrix-info {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 12px;\n color: var(--test-text-muted);\n padding: 12px;\n background: var(--test-bg);\n border-radius: var(--test-radius-sm);\n}\n\n.matrix-info i {\n color: var(--test-primary);\n}\n\n/* ===========================\n Chart View\n =========================== */\n.chart-section {\n background: var(--test-surface);\n border-radius: var(--test-radius-lg);\n padding: 24px;\n box-shadow: var(--test-shadow-sm);\n}\n\n.chart-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n flex-wrap: wrap;\n gap: 16px;\n margin-bottom: 20px;\n padding-bottom: 16px;\n border-bottom: 1px solid var(--test-border);\n}\n\n.chart-header h3 {\n display: flex;\n align-items: center;\n gap: 10px;\n margin: 0;\n font-size: 16px;\n font-weight: 600;\n color: var(--test-text);\n}\n\n.chart-header h3 i {\n color: var(--test-primary);\n}\n\n.chart-legend {\n display: flex;\n gap: 16px;\n flex-wrap: wrap;\n}\n\n.chart-legend .legend-item {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 12px;\n font-weight: 500;\n padding: 4px 10px;\n border-radius: 12px;\n}\n\n.chart-legend .legend-item.chart-passed {\n background: #ecfdf5;\n color: var(--test-success);\n}\n\n.chart-legend .legend-item.chart-failed {\n background: #fef2f2;\n color: var(--test-error);\n}\n\n.chart-legend .legend-item.chart-error {\n background: #fffbeb;\n color: var(--test-warning);\n}\n\n.chart-legend .legend-item.chart-skipped {\n background: #f1f5f9;\n color: var(--test-disabled);\n}\n\n.chart-container {\n min-height: 500px;\n position: relative;\n overflow: hidden;\n background: linear-gradient(135deg, #fafbff 0%, #f8fafc 100%);\n border-radius: var(--test-radius-md);\n border: 1px solid var(--test-border);\n}\n\n.d3-chart {\n width: 100%;\n height: 500px;\n}\n\n.d3-chart svg {\n width: 100%;\n height: 100%;\n}\n\n/* D3 Chart Node Styles */\n.d3-chart .node {\n cursor: pointer;\n transition: transform 0.2s ease;\n}\n\n.d3-chart .node:hover {\n transform: scale(1.05);\n}\n\n.d3-chart .node-label {\n font-size: 11px;\n font-weight: 500;\n fill: var(--test-text);\n pointer-events: none;\n}\n\n.d3-chart .link {\n fill: none;\n stroke-opacity: 0.4;\n transition: stroke-opacity 0.2s ease;\n}\n\n.d3-chart .link:hover {\n stroke-opacity: 0.8;\n}\n\n.d3-chart .tooltip {\n position: absolute;\n padding: 10px 14px;\n background: rgba(30, 41, 59, 0.95);\n color: white;\n border-radius: 8px;\n font-size: 12px;\n pointer-events: none;\n z-index: 100;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);\n max-width: 250px;\n}\n\n.d3-chart .tooltip-title {\n font-weight: 600;\n margin-bottom: 4px;\n}\n\n.d3-chart .tooltip-value {\n opacity: 0.8;\n}\n\n.chart-info {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 12px;\n color: var(--test-text-muted);\n padding: 12px;\n background: var(--test-bg);\n border-radius: var(--test-radius-sm);\n margin-top: 16px;\n}\n\n.chart-info i {\n color: var(--test-primary);\n}\n\n/* ===========================\n Compare Tab\n =========================== */\n.compare-tab {\n animation: fadeIn 0.3s ease-out;\n}\n\n.compare-selection {\n display: grid;\n grid-template-columns: 1fr auto 1fr;\n gap: 24px;\n align-items: flex-start;\n margin-bottom: 24px;\n}\n\n.compare-run-selector {\n background: var(--test-surface);\n border-radius: var(--test-radius-lg);\n padding: 20px;\n box-shadow: var(--test-shadow-sm);\n}\n\n.compare-run-selector h4 {\n margin: 0 0 16px 0;\n font-size: 14px;\n font-weight: 600;\n color: var(--test-text);\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.run-selector-list {\n max-height: 200px;\n overflow-y: auto;\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-bottom: 12px;\n}\n\n.run-selector-item {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 10px 12px;\n border: 1px solid var(--test-border);\n border-radius: var(--test-radius-sm);\n cursor: pointer;\n transition: var(--test-transition);\n}\n\n.run-selector-item:hover {\n border-color: var(--test-primary);\n background: rgba(37, 99, 235, 0.05);\n}\n\n.run-selector-item.selected {\n border-color: var(--test-primary);\n background: #eff6ff;\n}\n\n.run-selector-item.disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.selector-status {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n flex-shrink: 0;\n}\n\n.selector-content {\n flex: 1;\n min-width: 0;\n}\n\n.selector-date {\n font-size: 12px;\n font-weight: 500;\n color: var(--test-text);\n}\n\n.selector-rate {\n font-size: 11px;\n color: var(--test-text-secondary);\n}\n\n.selector-tags {\n display: flex;\n gap: 4px;\n flex-wrap: wrap;\n}\n\n.selected-run-preview {\n padding: 12px;\n background: #f8fafc;\n border-radius: var(--test-radius-sm);\n border: 1px solid var(--test-border);\n}\n\n.preview-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 8px;\n}\n\n.preview-label {\n font-size: 11px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--test-text-muted);\n}\n\n.clear-btn {\n padding: 4px 8px;\n border: none;\n background: transparent;\n color: var(--test-error);\n font-size: 11px;\n font-weight: 500;\n cursor: pointer;\n}\n\n.clear-btn:hover { text-decoration: underline; }\n\n.preview-details {\n display: flex;\n justify-content: space-between;\n font-size: 12px;\n color: var(--test-text);\n}\n\n.preview-rate {\n font-weight: 600;\n color: var(--test-success);\n}\n\n.compare-vs {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 40px;\n height: 40px;\n background: var(--test-bg);\n border-radius: 50%;\n color: var(--test-text-muted);\n font-size: 16px;\n align-self: center;\n margin-top: 60px;\n}\n\n/* Compare Results */\n.compare-results {\n animation: fadeIn 0.3s ease-out;\n}\n\n.compare-summary {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));\n gap: 16px;\n margin-bottom: 24px;\n}\n\n.compare-summary-card {\n background: var(--test-surface);\n border-radius: var(--test-radius-lg);\n padding: 20px;\n box-shadow: var(--test-shadow-sm);\n text-align: center;\n}\n\n.compare-summary-card .summary-label {\n font-size: 11px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--test-text-muted);\n margin-bottom: 8px;\n}\n\n.compare-summary-card .summary-value {\n font-size: 24px;\n font-weight: 700;\n color: var(--test-text);\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n}\n\n.compare-summary-card .summary-value.positive { color: var(--test-success); }\n.compare-summary-card .summary-value.negative { color: var(--test-error); }\n\n.compare-summary-card.improved {\n border-left: 4px solid var(--test-success);\n}\n\n.compare-summary-card.improved .summary-value { color: var(--test-success); }\n\n.compare-summary-card.regressed {\n border-left: 4px solid var(--test-error);\n}\n\n.compare-summary-card.regressed .summary-value { color: var(--test-error); }\n\n/* Compare Table */\n.compare-table-section {\n background: var(--test-surface);\n border-radius: var(--test-radius-lg);\n padding: 24px;\n box-shadow: var(--test-shadow-sm);\n}\n\n.compare-table-section h3 {\n margin: 0 0 16px 0;\n font-size: 16px;\n font-weight: 600;\n color: var(--test-text);\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.compare-table-section h3 i {\n color: var(--test-primary);\n}\n\n.compare-table-wrapper {\n overflow-x: auto;\n}\n\n.compare-table {\n width: 100%;\n border-collapse: collapse;\n font-size: 13px;\n}\n\n.compare-table th {\n text-align: left;\n padding: 12px 16px;\n background: #f8fafc;\n border-bottom: 2px solid var(--test-border);\n font-weight: 600;\n color: var(--test-text-secondary);\n text-transform: uppercase;\n font-size: 11px;\n letter-spacing: 0.5px;\n white-space: nowrap;\n}\n\n.compare-table td {\n padding: 14px 16px;\n border-bottom: 1px solid var(--test-border);\n color: var(--test-text);\n}\n\n.compare-table tbody tr.improved {\n background: rgba(16, 185, 129, 0.05);\n}\n\n.compare-table tbody tr.regressed {\n background: rgba(239, 68, 68, 0.05);\n}\n\n.test-name-cell {\n font-weight: 500;\n max-width: 200px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.positive { color: var(--test-success); }\n.negative { color: var(--test-error); }\n.muted { color: var(--test-text-muted); }\n\n.change-indicator {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 4px 8px;\n border-radius: 10px;\n font-size: 11px;\n font-weight: 600;\n}\n\n.change-indicator.improved {\n background: #ecfdf5;\n color: var(--test-success);\n}\n\n.change-indicator.regressed {\n background: #fef2f2;\n color: var(--test-error);\n}\n\n.change-indicator.unchanged {\n background: #f1f5f9;\n color: var(--test-text-muted);\n}\n\n/* Compare Empty State */\n.compare-empty {\n text-align: center;\n padding: 60px 24px;\n background: var(--test-surface);\n border-radius: var(--test-radius-lg);\n margin-top: 24px;\n}\n\n.compare-empty-icon {\n width: 80px;\n height: 80px;\n display: flex;\n align-items: center;\n justify-content: center;\n background: var(--test-bg);\n border-radius: 50%;\n margin: 0 auto 20px;\n}\n\n.compare-empty-icon i {\n font-size: 32px;\n color: var(--test-text-muted);\n}\n\n.compare-empty h4 {\n margin: 0 0 8px 0;\n font-size: 18px;\n font-weight: 600;\n color: var(--test-text);\n}\n\n.compare-empty p {\n margin: 0;\n font-size: 14px;\n color: var(--test-text-secondary);\n max-width: 400px;\n margin: 0 auto;\n}\n\n/* Small empty state variant */\n.empty-state.small {\n padding: 32px 16px;\n}\n\n.empty-state.small p {\n margin: 0;\n}\n\n/* ===========================\n Responsive Analytics/Compare\n =========================== */\n@media (max-width: 1024px) {\n .compare-selection {\n grid-template-columns: 1fr;\n gap: 16px;\n }\n\n .compare-vs {\n margin: 0;\n align-self: center;\n justify-self: center;\n }\n\n .analytics-kpis {\n grid-template-columns: repeat(2, 1fr);\n }\n}\n\n@media (max-width: 768px) {\n .filter-buttons {\n flex-direction: column;\n }\n\n .filter-btn {\n width: 100%;\n text-align: center;\n }\n\n .analytics-kpis {\n grid-template-columns: 1fr;\n }\n\n .kpi-card {\n padding: 16px;\n }\n\n .kpi-value {\n font-size: 20px;\n }\n\n .compare-summary {\n grid-template-columns: repeat(2, 1fr);\n }\n\n .analytics-table-wrapper,\n .compare-table-wrapper {\n margin: 0 -20px;\n padding: 0 20px;\n }\n\n .run-selector-list {\n max-height: 150px;\n }\n}\n\n@media (max-width: 480px) {\n .compare-summary {\n grid-template-columns: 1fr;\n }\n\n .compare-summary-card .summary-value {\n font-size: 20px;\n }\n\n .analytics-table th,\n .analytics-table td,\n .compare-table th,\n .compare-table td {\n padding: 10px 12px;\n font-size: 12px;\n }\n}\n\n/* ===========================\n Matrix Evaluation Indicators\n =========================== */\n\n/* Human Feedback Indicators */\n.cell-human {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 3px;\n min-width: 22px;\n height: 22px;\n border-radius: 50%;\n font-size: 10px;\n}\n\n.cell-human.no-feedback {\n background: #f1f5f9;\n color: #94a3b8;\n}\n\n.cell-human.no-feedback i {\n font-size: 11px;\n}\n\n.cell-human.has-feedback {\n padding: 0 6px;\n border-radius: 12px;\n min-width: 36px;\n}\n\n.cell-human.has-feedback i {\n font-size: 9px;\n}\n\n.cell-human.has-feedback .rating-value {\n font-weight: 700;\n font-size: 11px;\n}\n\n/* Human rating color coding: red \u22644, yellow 5-6, light-green 7-8, green 9-10 */\n.cell-human.rating-low {\n background: #fee2e2;\n color: #dc2626;\n}\n\n.cell-human.rating-medium {\n background: #fef3c7;\n color: #d97706;\n}\n\n.cell-human.rating-good {\n background: #d1fae5;\n color: #059669;\n}\n\n.cell-human.rating-excellent {\n background: #dcfce7;\n color: #16a34a;\n}\n\n/* Auto Score Indicators */\n.cell-auto {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 3px;\n min-width: 22px;\n height: 22px;\n border-radius: 50%;\n font-size: 10px;\n}\n\n.cell-auto.no-score {\n background: #f1f5f9;\n color: #94a3b8;\n}\n\n.cell-auto.no-score i {\n font-size: 11px;\n}\n\n.cell-auto.has-score {\n padding: 0 6px;\n border-radius: 12px;\n min-width: 36px;\n}\n\n.cell-auto.has-score i {\n font-size: 9px;\n}\n\n.cell-auto.has-score .score-value {\n font-weight: 700;\n font-size: 10px;\n}\n\n/* Auto score color coding (0-100%): red <50, yellow 50-69, light-green 70-84, green 85+ */\n.cell-auto.score-low {\n background: #fee2e2;\n color: #dc2626;\n}\n\n.cell-auto.score-medium {\n background: #fef3c7;\n color: #d97706;\n}\n\n.cell-auto.score-good {\n background: #d1fae5;\n color: #059669;\n}\n\n.cell-auto.score-excellent {\n background: #dcfce7;\n color: #16a34a;\n}\n\n/* Status indicators in matrix cells */\n.cell-status {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 22px;\n height: 22px;\n border-radius: 50%;\n font-size: 11px;\n}\n\n.cell-status.status-passed {\n background: #dcfce7;\n color: #16a34a;\n}\n\n.cell-status.status-failed {\n background: #fee2e2;\n color: #dc2626;\n}\n\n.cell-status.status-error {\n background: #fef3c7;\n color: #d97706;\n}\n\n.cell-status.status-timeout {\n background: #fef3c7;\n color: #d97706;\n}\n\n.cell-status.status-skipped,\n.cell-status.status-pending {\n background: #f1f5f9;\n color: #64748b;\n}\n\n.cell-status.status-running {\n background: #dbeafe;\n color: #2563eb;\n}\n\n/* Not-run / Skipped cells with hatch pattern */\n.result-cell.cell-not-run {\n background: repeating-linear-gradient(\n 45deg,\n #f8fafc,\n #f8fafc 4px,\n #e2e8f0 4px,\n #e2e8f0 8px\n );\n color: #94a3b8;\n}\n\n.result-cell.cell-not-run .cell-eval-stack {\n opacity: 0.6;\n}\n\n.cell-not-run-indicator {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 22px;\n height: 22px;\n color: #94a3b8;\n font-size: 11px;\n}\n\n/* ===========================\n Matrix Totals Footer Row\n =========================== */\n.test-matrix tfoot {\n position: sticky;\n bottom: 0;\n z-index: 2;\n}\n\n.totals-row {\n background: linear-gradient(to bottom, #f8fafc, #f1f5f9);\n border-top: 2px solid var(--test-border);\n}\n\n.totals-row td {\n padding: 10px 12px;\n font-weight: 600;\n}\n\n.totals-row .totals-label {\n background: linear-gradient(to bottom, #f8fafc, #f1f5f9);\n font-size: 12px;\n color: var(--test-text-secondary);\n text-transform: uppercase;\n letter-spacing: 0.5px;\n}\n\n.totals-row .totals-cell {\n background: linear-gradient(to bottom, #f8fafc, #f1f5f9);\n}\n\n.totals-stack {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 4px;\n}\n\n.totals-status,\n.totals-human,\n.totals-auto {\n display: flex;\n align-items: center;\n gap: 4px;\n font-size: 11px;\n padding: 2px 8px;\n border-radius: 10px;\n white-space: nowrap;\n}\n\n.totals-status {\n background: #e0f2fe;\n color: #0369a1;\n}\n\n.totals-status .pass-count {\n font-weight: 700;\n}\n\n.totals-human {\n background: #fef3c7;\n color: #92400e;\n}\n\n.totals-human .avg-label {\n font-weight: 700;\n}\n\n.totals-human .count-label {\n font-size: 10px;\n opacity: 0.8;\n}\n\n.totals-auto {\n background: #dbeafe;\n color: #1e40af;\n}\n\n.totals-auto .avg-label {\n font-weight: 700;\n}\n\n.totals-auto .count-label {\n font-size: 10px;\n opacity: 0.8;\n}\n"] }]
3457
+ }], () => [{ type: i0.ElementRef }, { type: i1.SharedService }, { type: i2.Router }, { type: i2.ActivatedRoute }, { type: i0.ChangeDetectorRef }, { type: i3.TestingDialogService }, { type: i3.EvaluationPreferencesService }], { chartContainer: [{
3458
+ type: ViewChild,
3459
+ args: ['chartContainer']
3460
+ }], handleKeyboardShortcut: [{
3461
+ type: HostListener,
3462
+ args: ['document:keydown', ['$event']]
3463
+ }] }); })();
3464
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(TestSuiteFormComponentExtended, { className: "TestSuiteFormComponentExtended", filePath: "src/lib/custom/Tests/test-suite-form.component.ts", lineNumber: 30 }); })();
358
3465
  export function LoadTestSuiteFormComponentExtended() { }
359
3466
  LoadTestSuiteFormComponentExtended();
360
3467
  //# sourceMappingURL=test-suite-form.component.js.map