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

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 +96 -9
  6. package/dist/lib/custom/Tests/test-form.component.d.ts.map +1 -1
  7. package/dist/lib/custom/Tests/test-form.component.js +1529 -277
  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 +50 -9
  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 +1079 -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 +228 -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 +3309 -201
  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 +88 -3
  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 +1976 -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,1814 @@ 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, viewContainerRef) {
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;
1639
+ this.viewContainerRef = viewContainerRef;
219
1640
  this.destroy$ = new Subject();
1641
+ // UI state
220
1642
  this.activeTab = 'overview';
221
1643
  this.loading = false;
1644
+ this.loadingTests = false;
1645
+ this.loadingRuns = false;
1646
+ this.loadingAnalytics = false;
1647
+ this.loadingCompare = false;
222
1648
  this.testsLoaded = false;
223
1649
  this.runsLoaded = false;
1650
+ this.analyticsLoaded = false;
1651
+ this.isRefreshing = false;
1652
+ this.error = null;
1653
+ // Related data
224
1654
  this.suiteTests = [];
225
1655
  this.suiteRuns = [];
1656
+ // Analytics data
1657
+ this.analyticsData = [];
1658
+ this.uniqueTags = [];
1659
+ this.selectedTags = []; // Multi-select: empty array means "All Tags"
1660
+ this.analyticsTimeRange = '30d';
1661
+ this.analyticsView = 'summary';
1662
+ this.matrixData = [];
1663
+ this.loadingMatrix = false;
1664
+ this.matrixLoaded = false;
1665
+ this.chartRendered = false;
1666
+ // Compare data
1667
+ this.compareRunA = null;
1668
+ this.compareRunB = null;
1669
+ this.compareResults = [];
1670
+ this.compareRunATests = [];
1671
+ this.compareRunBTests = [];
1672
+ // Keyboard shortcuts
1673
+ this.keyboardShortcutsEnabled = true;
1674
+ this.showShortcuts = false; // Hidden by default
1675
+ this.shortcutsSettingEntity = null;
1676
+ this.metadata = new Metadata();
1677
+ // Evaluation preferences
1678
+ this.evalPreferences = { showExecution: true, showHuman: true, showAuto: false };
1679
+ // Filter collapse state
1680
+ this.filtersCollapsed = false;
1681
+ // Matrix sorting
1682
+ this.matrixSortBy = 'sequence';
1683
+ this.matrixSortAsc = true;
1684
+ // Matrix row selection
1685
+ this.selectedMatrixTestId = null;
1686
+ // Matrix test name filter
1687
+ this.matrixTestFilter = '';
1688
+ this.matrixFilterSubject$ = new Subject();
226
1689
  }
227
1690
  async ngOnInit() {
228
1691
  await super.ngOnInit();
1692
+ this.loadShortcutsSetting();
1693
+ // Subscribe to evaluation preferences
1694
+ this.evalPrefsService.preferences$
1695
+ .pipe(takeUntil(this.destroy$))
1696
+ .subscribe(prefs => {
1697
+ this.evalPreferences = prefs;
1698
+ this.cdr.markForCheck();
1699
+ // Re-render chart when preferences change (D3 chart needs manual update)
1700
+ if (this.chartRendered && this.analyticsView === 'chart') {
1701
+ this.renderChart();
1702
+ }
1703
+ });
1704
+ // Subscribe to matrix filter with debounce
1705
+ this.matrixFilterSubject$
1706
+ .pipe(debounceTime(300), distinctUntilChanged(), takeUntil(this.destroy$))
1707
+ .subscribe(value => {
1708
+ this.matrixTestFilter = value;
1709
+ this.cdr.markForCheck();
1710
+ });
1711
+ }
1712
+ ngAfterViewInit() {
1713
+ // Initialize any view-dependent logic
229
1714
  }
230
1715
  ngOnDestroy() {
231
1716
  this.destroy$.next();
232
1717
  this.destroy$.complete();
233
1718
  }
1719
+ // Keyboard shortcuts
1720
+ handleKeyboardShortcut(event) {
1721
+ if (!this.keyboardShortcutsEnabled)
1722
+ return;
1723
+ // Cmd/Ctrl + R: Refresh
1724
+ if ((event.metaKey || event.ctrlKey) && event.key === 'r' && !event.shiftKey) {
1725
+ event.preventDefault();
1726
+ this.refresh();
1727
+ return;
1728
+ }
1729
+ // Cmd/Ctrl + Enter: Run suite
1730
+ if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {
1731
+ event.preventDefault();
1732
+ this.runSuite();
1733
+ return;
1734
+ }
1735
+ // Number keys for tabs (1-5)
1736
+ if (!event.metaKey && !event.ctrlKey && !event.altKey) {
1737
+ switch (event.key) {
1738
+ case '1':
1739
+ this.changeTab('overview');
1740
+ break;
1741
+ case '2':
1742
+ this.changeTab('tests');
1743
+ break;
1744
+ case '3':
1745
+ this.changeTab('runs');
1746
+ break;
1747
+ case '4':
1748
+ this.changeTab('analytics');
1749
+ break;
1750
+ case '5':
1751
+ this.changeTab('compare');
1752
+ break;
1753
+ }
1754
+ }
1755
+ }
234
1756
  changeTab(tab) {
235
1757
  this.activeTab = tab;
236
1758
  if (tab === 'tests' && !this.testsLoaded)
237
1759
  this.loadTests();
238
1760
  if (tab === 'runs' && !this.runsLoaded)
239
1761
  this.loadRuns();
1762
+ if (tab === 'analytics' && !this.analyticsLoaded)
1763
+ this.loadAnalytics();
240
1764
  this.cdr.markForCheck();
241
1765
  }
242
1766
  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;
1767
+ if (this.testsLoaded)
1768
+ return;
1769
+ this.loadingTests = true;
253
1770
  this.cdr.markForCheck();
1771
+ try {
1772
+ const rv = new RunView();
1773
+ const result = await rv.RunView({
1774
+ EntityName: 'MJ: Test Suite Tests',
1775
+ ExtraFilter: `SuiteID='${this.record.ID}'`,
1776
+ OrderBy: 'Sequence',
1777
+ ResultType: 'entity_object'
1778
+ });
1779
+ if (result.Success)
1780
+ this.suiteTests = result.Results || [];
1781
+ this.testsLoaded = true;
1782
+ }
1783
+ catch (error) {
1784
+ console.error('Error loading tests:', error);
1785
+ SharedService.Instance.CreateSimpleNotification('Failed to load tests', 'error', 3000);
1786
+ }
1787
+ finally {
1788
+ this.loadingTests = false;
1789
+ this.cdr.markForCheck();
1790
+ }
254
1791
  }
255
1792
  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;
1793
+ if (this.runsLoaded)
1794
+ return;
1795
+ this.loadingRuns = true;
267
1796
  this.cdr.markForCheck();
1797
+ try {
1798
+ const rv = new RunView();
1799
+ const result = await rv.RunView({
1800
+ EntityName: 'MJ: Test Suite Runs',
1801
+ ExtraFilter: `SuiteID='${this.record.ID}'`,
1802
+ OrderBy: 'StartedAt DESC',
1803
+ MaxRows: 50,
1804
+ ResultType: 'entity_object'
1805
+ });
1806
+ if (result.Success)
1807
+ this.suiteRuns = result.Results || [];
1808
+ this.runsLoaded = true;
1809
+ }
1810
+ catch (error) {
1811
+ console.error('Error loading runs:', error);
1812
+ SharedService.Instance.CreateSimpleNotification('Failed to load runs', 'error', 3000);
1813
+ }
1814
+ finally {
1815
+ this.loadingRuns = false;
1816
+ this.cdr.markForCheck();
1817
+ }
268
1818
  }
269
1819
  getStatusColor() {
270
- return this.record.Status === 'Active' ? '#4caf50' : '#9e9e9e';
1820
+ switch (this.record.Status) {
1821
+ case 'Active': return '#10b981';
1822
+ case 'Disabled': return '#6b7280';
1823
+ case 'Pending': return '#f59e0b';
1824
+ default: return '#9ca3af';
1825
+ }
1826
+ }
1827
+ getStatusClass() {
1828
+ return `status-${this.record.Status?.toLowerCase() || 'unknown'}`;
1829
+ }
1830
+ getRunStatusColor(status) {
1831
+ switch (status) {
1832
+ case 'Completed': return '#10b981';
1833
+ case 'Failed': return '#ef4444';
1834
+ case 'Running': return '#3b82f6';
1835
+ case 'Pending': return '#8b5cf6';
1836
+ case 'Cancelled': return '#6b7280';
1837
+ default: return '#9ca3af';
1838
+ }
1839
+ }
1840
+ formatTimeout(ms) {
1841
+ if (ms === null || ms === undefined)
1842
+ return 'Default (5 min)';
1843
+ if (ms < 1000)
1844
+ return `${ms}ms`;
1845
+ if (ms < 60000)
1846
+ return `${(ms / 1000).toFixed(1)}s`;
1847
+ if (ms < 3600000) {
1848
+ const mins = Math.floor(ms / 60000);
1849
+ const secs = Math.floor((ms % 60000) / 1000);
1850
+ return secs > 0 ? `${mins}m ${secs}s` : `${mins}m`;
1851
+ }
1852
+ const hours = Math.floor(ms / 3600000);
1853
+ const mins = Math.floor((ms % 3600000) / 60000);
1854
+ return mins > 0 ? `${hours}h ${mins}m` : `${hours}h`;
1855
+ }
1856
+ getRelativeTime(date) {
1857
+ if (!date)
1858
+ return 'N/A';
1859
+ const d = new Date(date);
1860
+ const now = new Date();
1861
+ const diffMs = now.getTime() - d.getTime();
1862
+ const diffMins = Math.floor(diffMs / 60000);
1863
+ const diffHours = Math.floor(diffMs / 3600000);
1864
+ const diffDays = Math.floor(diffMs / 86400000);
1865
+ if (diffMins < 1)
1866
+ return 'Just now';
1867
+ if (diffMins < 60)
1868
+ return `${diffMins}m ago`;
1869
+ if (diffHours < 24)
1870
+ return `${diffHours}h ago`;
1871
+ if (diffDays < 7)
1872
+ return `${diffDays}d ago`;
1873
+ return d.toLocaleDateString();
1874
+ }
1875
+ getPassRate(run) {
1876
+ const total = run.TotalTests || 0;
1877
+ const passed = run.PassedTests || 0;
1878
+ if (total === 0)
1879
+ return 0;
1880
+ return (passed / total) * 100;
271
1881
  }
272
1882
  openTest(testId) {
273
1883
  SharedService.Instance.OpenEntityRecord('MJ: Tests', CompositeKey.FromID(testId));
@@ -277,74 +1887,1566 @@ let TestSuiteFormComponentExtended = class TestSuiteFormComponentExtended extend
277
1887
  }
278
1888
  async runSuite() {
279
1889
  if (this.record?.ID) {
280
- this.testingDialogService.OpenSuiteDialog(this.record.ID);
1890
+ this.testingDialogService.OpenSuiteDialog(this.record.ID, this.viewContainerRef);
1891
+ }
1892
+ }
1893
+ async refresh() {
1894
+ this.isRefreshing = true;
1895
+ this.cdr.markForCheck();
1896
+ try {
1897
+ await this.record.Load(this.record.ID);
1898
+ // Reset lazy-loaded data
1899
+ if (this.testsLoaded) {
1900
+ this.testsLoaded = false;
1901
+ this.suiteTests = [];
1902
+ await this.loadTests();
1903
+ }
1904
+ if (this.runsLoaded) {
1905
+ this.runsLoaded = false;
1906
+ this.suiteRuns = [];
1907
+ await this.loadRuns();
1908
+ }
1909
+ if (this.analyticsLoaded) {
1910
+ this.analyticsLoaded = false;
1911
+ this.analyticsData = [];
1912
+ // Also reset matrix data so it reloads with fresh data
1913
+ this.matrixLoaded = false;
1914
+ this.matrixData = [];
1915
+ await this.loadAnalytics();
1916
+ // Reload matrix if currently viewing matrix or chart view
1917
+ if (this.analyticsView === 'matrix' || this.analyticsView === 'chart') {
1918
+ await this.loadMatrixData();
1919
+ }
1920
+ }
1921
+ SharedService.Instance.CreateSimpleNotification('Refreshed successfully', 'success', 2000);
1922
+ }
1923
+ catch {
1924
+ SharedService.Instance.CreateSimpleNotification('Failed to refresh', 'error', 3000);
1925
+ }
1926
+ finally {
1927
+ this.isRefreshing = false;
1928
+ this.cdr.markForCheck();
1929
+ }
1930
+ }
1931
+ // ==========================================
1932
+ // Analytics Tab Methods
1933
+ // ==========================================
1934
+ async loadAnalytics() {
1935
+ if (this.analyticsLoaded)
1936
+ return;
1937
+ this.loadingAnalytics = true;
1938
+ this.cdr.markForCheck();
1939
+ try {
1940
+ // Load all runs for analytics (not just recent 50)
1941
+ const rv = new RunView();
1942
+ const result = await rv.RunView({
1943
+ EntityName: 'MJ: Test Suite Runs',
1944
+ ExtraFilter: `SuiteID='${this.record.ID}'`,
1945
+ OrderBy: 'StartedAt DESC',
1946
+ MaxRows: 200,
1947
+ ResultType: 'entity_object'
1948
+ });
1949
+ if (result.Success && result.Results) {
1950
+ // Process runs into analytics data points
1951
+ this.analyticsData = result.Results.map(run => this.runToDataPoint(run));
1952
+ // Extract unique tags
1953
+ this.uniqueTags = TagsHelper.getUniqueTags(result.Results.map(r => r.Tags));
1954
+ // Also populate suiteRuns if not already loaded
1955
+ if (!this.runsLoaded) {
1956
+ this.suiteRuns = result.Results.slice(0, 50);
1957
+ this.runsLoaded = true;
1958
+ }
1959
+ }
1960
+ this.analyticsLoaded = true;
1961
+ }
1962
+ catch (error) {
1963
+ console.error('Error loading analytics:', error);
1964
+ SharedService.Instance.CreateSimpleNotification('Failed to load analytics data', 'error', 3000);
1965
+ }
1966
+ finally {
1967
+ this.loadingAnalytics = false;
1968
+ this.cdr.markForCheck();
1969
+ }
1970
+ }
1971
+ runToDataPoint(run) {
1972
+ const total = run.TotalTests || 0;
1973
+ const passed = run.PassedTests || 0;
1974
+ return {
1975
+ runId: run.ID,
1976
+ date: run.StartedAt ? new Date(run.StartedAt) : new Date(),
1977
+ passRate: total > 0 ? (passed / total) * 100 : 0,
1978
+ totalTests: total,
1979
+ passedTests: passed,
1980
+ failedTests: run.FailedTests || 0,
1981
+ errorTests: run.ErrorTests || 0,
1982
+ skippedTests: run.SkippedTests || 0,
1983
+ duration: run.TotalDurationSeconds || 0,
1984
+ cost: run.TotalCostUSD || 0,
1985
+ tags: TagsHelper.parseTags(run.Tags),
1986
+ status: run.Status || 'Unknown'
1987
+ };
1988
+ }
1989
+ getFilteredAnalyticsData() {
1990
+ let data = this.analyticsData;
1991
+ // Apply time range filter
1992
+ const now = new Date();
1993
+ let cutoffDate = null;
1994
+ switch (this.analyticsTimeRange) {
1995
+ case '7d':
1996
+ cutoffDate = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
1997
+ break;
1998
+ case '30d':
1999
+ cutoffDate = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
2000
+ break;
2001
+ case '90d':
2002
+ cutoffDate = new Date(now.getTime() - 90 * 24 * 60 * 60 * 1000);
2003
+ break;
2004
+ }
2005
+ if (cutoffDate) {
2006
+ data = data.filter(d => d.date >= cutoffDate);
2007
+ }
2008
+ // Apply tag filter (multi-select: empty array means all tags)
2009
+ if (this.selectedTags.length > 0) {
2010
+ data = data.filter(d => this.selectedTags.some(tag => d.tags.includes(tag)));
2011
+ }
2012
+ return data;
2013
+ }
2014
+ setTimeRange(range) {
2015
+ this.analyticsTimeRange = range;
2016
+ // Reload matrix data when time range changes (if currently viewing matrix or chart)
2017
+ if (this.analyticsView === 'matrix' || this.analyticsView === 'chart') {
2018
+ this.reloadMatrixData();
2019
+ }
2020
+ this.cdr.markForCheck();
2021
+ }
2022
+ /**
2023
+ * Toggle a tag in the multi-select filter.
2024
+ * If tag is null, clear all selections (show all tags).
2025
+ */
2026
+ toggleTagFilter(tag) {
2027
+ if (tag === null) {
2028
+ // Clear all - show all tags
2029
+ this.selectedTags = [];
2030
+ }
2031
+ else {
2032
+ // Toggle the tag
2033
+ const index = this.selectedTags.indexOf(tag);
2034
+ if (index >= 0) {
2035
+ this.selectedTags = this.selectedTags.filter(t => t !== tag);
2036
+ }
2037
+ else {
2038
+ this.selectedTags = [...this.selectedTags, tag];
2039
+ }
2040
+ }
2041
+ // Reload matrix data when tag filter changes (if currently viewing matrix or chart)
2042
+ if (this.analyticsView === 'matrix' || this.analyticsView === 'chart') {
2043
+ this.reloadMatrixData();
2044
+ }
2045
+ this.cdr.markForCheck();
2046
+ }
2047
+ /**
2048
+ * Check if a tag is currently selected in the filter
2049
+ */
2050
+ isTagSelected(tag) {
2051
+ return this.selectedTags.includes(tag);
2052
+ }
2053
+ setAnalyticsView(view) {
2054
+ this.analyticsView = view;
2055
+ if ((view === 'matrix' || view === 'chart') && !this.matrixLoaded) {
2056
+ this.loadMatrixData();
2057
+ }
2058
+ // Render chart when switching to chart view
2059
+ if (view === 'chart' && this.matrixLoaded) {
2060
+ setTimeout(() => this.renderChart(), 100);
2061
+ }
2062
+ this.cdr.markForCheck();
2063
+ }
2064
+ /**
2065
+ * Force reload of matrix data (used when filters change)
2066
+ */
2067
+ reloadMatrixData() {
2068
+ this.matrixLoaded = false;
2069
+ this.loadMatrixData();
2070
+ }
2071
+ async loadMatrixData() {
2072
+ if (this.loadingMatrix)
2073
+ return;
2074
+ this.loadingMatrix = true;
2075
+ this.cdr.markForCheck();
2076
+ try {
2077
+ const rv = new RunView();
2078
+ const filteredRuns = this.getFilteredAnalyticsData();
2079
+ // Load test runs for each suite run (limit to most recent 10 for performance)
2080
+ const runsToLoad = filteredRuns.slice(0, 10);
2081
+ const matrixData = [];
2082
+ // Collect all test run IDs to batch load feedbacks
2083
+ const allTestRunIds = [];
2084
+ for (const runData of runsToLoad) {
2085
+ const testRunsResult = await rv.RunView({
2086
+ EntityName: 'MJ: Test Runs',
2087
+ ExtraFilter: `TestSuiteRunID='${runData.runId}'`,
2088
+ OrderBy: 'Sequence',
2089
+ ResultType: 'entity_object'
2090
+ });
2091
+ if (testRunsResult.Success && testRunsResult.Results) {
2092
+ const testResults = new Map();
2093
+ for (const testRun of testRunsResult.Results) {
2094
+ allTestRunIds.push(testRun.ID);
2095
+ testResults.set(testRun.TestID, {
2096
+ testRunId: testRun.ID,
2097
+ testId: testRun.TestID,
2098
+ testName: testRun.Test || 'Unknown',
2099
+ status: testRun.Status,
2100
+ score: testRun.Score,
2101
+ duration: testRun.DurationSeconds,
2102
+ humanRating: null, // Will be populated below
2103
+ humanComments: null, // Will be populated below
2104
+ sequence: testRun.Sequence ?? 0
2105
+ });
2106
+ }
2107
+ matrixData.push({
2108
+ runId: runData.runId,
2109
+ date: runData.date,
2110
+ tags: runData.tags,
2111
+ status: runData.status,
2112
+ passRate: runData.passRate,
2113
+ testResults
2114
+ });
2115
+ }
2116
+ }
2117
+ // Batch load feedbacks for all test runs
2118
+ if (allTestRunIds.length > 0) {
2119
+ const feedbackMap = await this.loadFeedbacksForTestRuns(allTestRunIds);
2120
+ // Apply feedbacks to matrix data
2121
+ for (const run of matrixData) {
2122
+ run.testResults.forEach((cell, _testId) => {
2123
+ const feedback = feedbackMap.get(cell.testRunId);
2124
+ if (feedback) {
2125
+ if (feedback.Rating != null) {
2126
+ cell.humanRating = feedback.Rating;
2127
+ }
2128
+ // Use CorrectionSummary if available (from inline feedback), fallback to Comments
2129
+ const commentText = feedback.CorrectionSummary || feedback.Comments;
2130
+ if (commentText) {
2131
+ cell.humanComments = commentText;
2132
+ }
2133
+ }
2134
+ });
2135
+ }
2136
+ }
2137
+ this.matrixData = matrixData;
2138
+ this.matrixLoaded = true;
2139
+ // Render chart if currently on chart view
2140
+ if (this.analyticsView === 'chart') {
2141
+ setTimeout(() => this.renderChart(), 100);
2142
+ }
2143
+ }
2144
+ catch (error) {
2145
+ console.error('Error loading matrix data:', error);
2146
+ SharedService.Instance.CreateSimpleNotification('Failed to load matrix data', 'error', 3000);
2147
+ }
2148
+ finally {
2149
+ this.loadingMatrix = false;
2150
+ this.cdr.markForCheck();
2151
+ }
2152
+ }
2153
+ /**
2154
+ * Load feedbacks for a batch of test run IDs
2155
+ * Returns a map of testRunId -> TestRunFeedbackEntity
2156
+ */
2157
+ async loadFeedbacksForTestRuns(testRunIds) {
2158
+ const feedbackMap = new Map();
2159
+ if (testRunIds.length === 0)
2160
+ return feedbackMap;
2161
+ try {
2162
+ const rv = new RunView();
2163
+ // Build IN clause for the IDs (batch in chunks to avoid query size limits)
2164
+ const chunkSize = 50;
2165
+ for (let i = 0; i < testRunIds.length; i += chunkSize) {
2166
+ const chunk = testRunIds.slice(i, i + chunkSize);
2167
+ const inClause = chunk.map(id => `'${id}'`).join(',');
2168
+ const result = await rv.RunView({
2169
+ EntityName: 'MJ: Test Run Feedbacks',
2170
+ ExtraFilter: `TestRunID IN (${inClause})`,
2171
+ ResultType: 'entity_object'
2172
+ });
2173
+ if (result.Success && result.Results) {
2174
+ for (const feedback of result.Results) {
2175
+ // If multiple feedbacks exist for same test run, keep the most recent (last one)
2176
+ feedbackMap.set(feedback.TestRunID, feedback);
2177
+ }
2178
+ }
2179
+ }
2180
+ }
2181
+ catch (error) {
2182
+ console.warn('Failed to load feedbacks:', error);
2183
+ }
2184
+ return feedbackMap;
2185
+ }
2186
+ getUniqueTestsFromMatrix() {
2187
+ const testsMap = new Map();
2188
+ for (const runData of this.matrixData) {
2189
+ for (const [testId, testResult] of runData.testResults) {
2190
+ if (!testsMap.has(testId)) {
2191
+ testsMap.set(testId, { testName: testResult.testName, sequence: testResult.sequence });
2192
+ }
2193
+ }
2194
+ }
2195
+ let tests = Array.from(testsMap.entries()).map(([testId, data]) => ({
2196
+ testId,
2197
+ testName: data.testName,
2198
+ sequence: data.sequence
2199
+ }));
2200
+ // Apply test name filter if set
2201
+ if (this.matrixTestFilter.trim()) {
2202
+ const filterLower = this.matrixTestFilter.toLowerCase().trim();
2203
+ tests = tests.filter(t => t.testName.toLowerCase().includes(filterLower));
2204
+ }
2205
+ // Apply sorting
2206
+ if (this.matrixSortBy === 'sequence') {
2207
+ tests = tests.sort((a, b) => this.matrixSortAsc ? a.sequence - b.sequence : b.sequence - a.sequence);
2208
+ }
2209
+ else {
2210
+ tests = tests.sort((a, b) => {
2211
+ const cmp = a.testName.localeCompare(b.testName);
2212
+ return this.matrixSortAsc ? cmp : -cmp;
2213
+ });
2214
+ }
2215
+ return tests;
2216
+ }
2217
+ /**
2218
+ * Toggle matrix sort column
2219
+ */
2220
+ toggleMatrixSort(column) {
2221
+ if (this.matrixSortBy === column) {
2222
+ this.matrixSortAsc = !this.matrixSortAsc;
2223
+ }
2224
+ else {
2225
+ this.matrixSortBy = column;
2226
+ this.matrixSortAsc = true;
2227
+ }
2228
+ this.cdr.markForCheck();
2229
+ }
2230
+ /**
2231
+ * Select/deselect a matrix row for highlighting
2232
+ */
2233
+ selectMatrixRow(testId) {
2234
+ this.selectedMatrixTestId = this.selectedMatrixTestId === testId ? null : testId;
2235
+ this.cdr.markForCheck();
2236
+ }
2237
+ /**
2238
+ * Handle test name filter input - uses Subject for debounce
2239
+ */
2240
+ onMatrixFilterInput(event) {
2241
+ const value = event.target.value;
2242
+ this.matrixFilterSubject$.next(value);
2243
+ }
2244
+ /**
2245
+ * Clear the matrix test name filter
2246
+ */
2247
+ clearMatrixFilter() {
2248
+ this.matrixTestFilter = '';
2249
+ this.matrixFilterSubject$.next('');
2250
+ this.cdr.markForCheck();
2251
+ }
2252
+ getTestResultForRun(runId, testId) {
2253
+ const runData = this.matrixData.find(r => r.runId === runId);
2254
+ if (!runData)
2255
+ return null;
2256
+ return runData.testResults.get(testId) || null;
2257
+ }
2258
+ getMatrixCellClass(result) {
2259
+ if (!result)
2260
+ return 'cell-none cell-not-run';
2261
+ switch (result.status) {
2262
+ case 'Passed': return 'cell-passed';
2263
+ case 'Failed': return 'cell-failed';
2264
+ case 'Error': return 'cell-error';
2265
+ case 'Timeout': return 'cell-timeout';
2266
+ case 'Skipped': return 'cell-skipped cell-not-run';
2267
+ case 'Running': return 'cell-running';
2268
+ default: return 'cell-pending';
2269
+ }
2270
+ }
2271
+ /**
2272
+ * Get descriptive tooltip for execution status
2273
+ */
2274
+ getStatusTooltip(status) {
2275
+ switch (status) {
2276
+ case 'Passed': return 'Status: Passed - Test completed without error';
2277
+ case 'Failed': return 'Status: Failed - Test assertions did not pass';
2278
+ case 'Error': return 'Status: Error - Test encountered an exception';
2279
+ case 'Timeout': return 'Status: Timeout - Test exceeded time limit';
2280
+ case 'Skipped': return 'Status: Skipped - Test was not executed';
2281
+ case 'Running': return 'Status: Running - Test is currently executing';
2282
+ case 'Pending': return 'Status: Pending - Test waiting to run';
2283
+ default: return `Status: ${status}`;
2284
+ }
2285
+ }
2286
+ /**
2287
+ * Get tooltip for human review with rating and optional comments
2288
+ */
2289
+ getHumanTooltip(rating, comments) {
2290
+ let tooltip = `Human Review: ${rating}/10 rating`;
2291
+ if (comments) {
2292
+ const truncated = comments.length > 200 ? comments.substring(0, 200) + '...' : comments;
2293
+ tooltip += `\n\n"${truncated}"`;
2294
+ }
2295
+ return tooltip;
2296
+ }
2297
+ /**
2298
+ * Get the count of enabled evaluation types for matrix cell layout
2299
+ */
2300
+ getEvalCount() {
2301
+ let count = 0;
2302
+ if (this.evalPreferences.showExecution)
2303
+ count++;
2304
+ if (this.evalPreferences.showHuman)
2305
+ count++;
2306
+ if (this.evalPreferences.showAuto)
2307
+ count++;
2308
+ return count;
2309
+ }
2310
+ // ===========================
2311
+ // Matrix Totals Row Methods
2312
+ // ===========================
2313
+ /**
2314
+ * Get count of passed tests for a run
2315
+ */
2316
+ getRunPassedCount(run) {
2317
+ let count = 0;
2318
+ run.testResults.forEach(result => {
2319
+ if (result.status === 'Passed')
2320
+ count++;
2321
+ });
2322
+ return count;
2323
+ }
2324
+ /**
2325
+ * Get total count of tests for a run
2326
+ */
2327
+ getRunTotalCount(run) {
2328
+ return run.testResults.size;
2329
+ }
2330
+ /**
2331
+ * Get average human rating for a run (only from tests that have ratings)
2332
+ */
2333
+ getRunHumanAvg(run) {
2334
+ let sum = 0;
2335
+ let count = 0;
2336
+ run.testResults.forEach(result => {
2337
+ if (result.humanRating != null) {
2338
+ sum += result.humanRating;
2339
+ count++;
2340
+ }
2341
+ });
2342
+ return count > 0 ? sum / count : null;
2343
+ }
2344
+ /**
2345
+ * Get count of tests with human ratings for a run
2346
+ */
2347
+ getRunHumanCount(run) {
2348
+ let count = 0;
2349
+ run.testResults.forEach(result => {
2350
+ if (result.humanRating != null)
2351
+ count++;
2352
+ });
2353
+ return count;
2354
+ }
2355
+ /**
2356
+ * Get average auto score for a run (only from tests that have scores)
2357
+ */
2358
+ getRunAutoAvg(run) {
2359
+ let sum = 0;
2360
+ let count = 0;
2361
+ run.testResults.forEach(result => {
2362
+ if (result.score != null) {
2363
+ sum += result.score;
2364
+ count++;
2365
+ }
2366
+ });
2367
+ return count > 0 ? sum / count : null;
2368
+ }
2369
+ /**
2370
+ * Get count of tests with auto scores for a run
2371
+ */
2372
+ getRunAutoCount(run) {
2373
+ let count = 0;
2374
+ run.testResults.forEach(result => {
2375
+ if (result.score != null)
2376
+ count++;
2377
+ });
2378
+ return count;
2379
+ }
2380
+ /**
2381
+ * Navigate to a test run when clicking a matrix cell
2382
+ */
2383
+ openTestRun(testRunId) {
2384
+ SharedService.Instance.OpenEntityRecord('MJ: Test Runs', CompositeKey.FromID(testRunId));
2385
+ }
2386
+ /**
2387
+ * Handle matrix cell click - navigate to the test run
2388
+ */
2389
+ onMatrixCellClick(result, event) {
2390
+ event.stopPropagation(); // Prevent row click from also firing
2391
+ if (result?.testRunId) {
2392
+ this.openTestRun(result.testRunId);
2393
+ }
2394
+ }
2395
+ // ===========================
2396
+ // D3 Chart Rendering
2397
+ // ===========================
2398
+ /**
2399
+ * Renders an interactive heatmap-style chart showing test results across runs
2400
+ * with trend lines and better visual hierarchy
2401
+ */
2402
+ renderChart() {
2403
+ if (!this.chartContainer?.nativeElement || this.matrixData.length === 0) {
2404
+ return;
2405
+ }
2406
+ const container = this.chartContainer.nativeElement;
2407
+ const tests = this.getUniqueTestsFromMatrix();
2408
+ const runs = this.matrixData;
2409
+ // Dynamic sizing based on content and evaluation preferences
2410
+ const width = container.clientWidth || 900;
2411
+ const evalCount = this.getEvalCount();
2412
+ // Adjust row height and column width based on how many eval types are shown
2413
+ const rowHeight = evalCount >= 3 ? 34 : evalCount === 2 ? 30 : 28;
2414
+ const minColWidth = evalCount >= 3 ? 65 : evalCount === 2 ? 55 : 50;
2415
+ const colWidth = Math.min(90, Math.max(minColWidth, (width - 250) / runs.length));
2416
+ const margin = { top: 80, right: 40, bottom: 20, left: 220 };
2417
+ const chartWidth = runs.length * colWidth;
2418
+ const chartHeight = tests.length * rowHeight;
2419
+ const height = chartHeight + margin.top + margin.bottom;
2420
+ // Update container height
2421
+ container.style.height = `${Math.max(400, height)}px`;
2422
+ // Clear previous chart
2423
+ d3.select(container).selectAll('*').remove();
2424
+ // Status colors with better contrast
2425
+ const statusColors = {
2426
+ 'Passed': '#22c55e',
2427
+ 'Failed': '#ef4444',
2428
+ 'Error': '#f97316',
2429
+ 'Skipped': '#a1a1aa',
2430
+ 'Running': '#3b82f6',
2431
+ 'Pending': '#d1d5db'
2432
+ };
2433
+ // Create SVG
2434
+ const svg = d3.select(container)
2435
+ .append('svg')
2436
+ .attr('width', width)
2437
+ .attr('height', height);
2438
+ // Create tooltip div
2439
+ const tooltip = d3.select(container)
2440
+ .append('div')
2441
+ .attr('class', 'chart-tooltip')
2442
+ .style('position', 'absolute')
2443
+ .style('opacity', 0)
2444
+ .style('background', 'rgba(15, 23, 42, 0.95)')
2445
+ .style('color', 'white')
2446
+ .style('padding', '10px 14px')
2447
+ .style('border-radius', '8px')
2448
+ .style('font-size', '12px')
2449
+ .style('pointer-events', 'none')
2450
+ .style('z-index', '1000')
2451
+ .style('box-shadow', '0 4px 20px rgba(0,0,0,0.3)')
2452
+ .style('max-width', '280px');
2453
+ // Create chart group
2454
+ const chart = svg.append('g')
2455
+ .attr('transform', `translate(${margin.left}, ${margin.top})`);
2456
+ // Add gradient definitions for cells
2457
+ const defs = svg.append('defs');
2458
+ // Create gradient for each status
2459
+ Object.entries(statusColors).forEach(([status, color]) => {
2460
+ const gradient = defs.append('linearGradient')
2461
+ .attr('id', `gradient-${status.toLowerCase()}`)
2462
+ .attr('x1', '0%')
2463
+ .attr('y1', '0%')
2464
+ .attr('x2', '0%')
2465
+ .attr('y2', '100%');
2466
+ gradient.append('stop')
2467
+ .attr('offset', '0%')
2468
+ .attr('stop-color', color)
2469
+ .attr('stop-opacity', 0.95);
2470
+ gradient.append('stop')
2471
+ .attr('offset', '100%')
2472
+ .attr('stop-color', d3.color(color)?.darker(0.3)?.toString() || color)
2473
+ .attr('stop-opacity', 0.95);
2474
+ });
2475
+ // Draw evaluation legend at top-left corner
2476
+ this.renderChartLegend(chart, margin);
2477
+ // Draw column headers (run dates) at top
2478
+ runs.forEach((run, i) => {
2479
+ const x = i * colWidth + colWidth / 2;
2480
+ // Date text
2481
+ chart.append('text')
2482
+ .attr('x', x)
2483
+ .attr('y', -45)
2484
+ .attr('text-anchor', 'middle')
2485
+ .attr('fill', '#475569')
2486
+ .attr('font-size', '10px')
2487
+ .attr('font-weight', '500')
2488
+ .text(this.getRelativeTime(run.date))
2489
+ .style('cursor', 'pointer')
2490
+ .on('click', () => this.openSuiteRun(run.runId));
2491
+ // Pass rate badge
2492
+ const passRateColor = run.passRate >= 80 ? '#22c55e' : run.passRate >= 50 ? '#f97316' : '#ef4444';
2493
+ const badgeWidth = 36;
2494
+ const badgeHeight = 18;
2495
+ chart.append('rect')
2496
+ .attr('x', x - badgeWidth / 2)
2497
+ .attr('y', -35)
2498
+ .attr('width', badgeWidth)
2499
+ .attr('height', badgeHeight)
2500
+ .attr('rx', 9)
2501
+ .attr('fill', passRateColor)
2502
+ .attr('opacity', 0.15);
2503
+ chart.append('text')
2504
+ .attr('x', x)
2505
+ .attr('y', -22)
2506
+ .attr('text-anchor', 'middle')
2507
+ .attr('fill', passRateColor)
2508
+ .attr('font-size', '11px')
2509
+ .attr('font-weight', '700')
2510
+ .text(`${run.passRate.toFixed(0)}%`);
2511
+ // Tags indicator - styled pills
2512
+ if (run.tags.length > 0) {
2513
+ const tagsToShow = run.tags.slice(0, 2);
2514
+ const tagPillWidth = 42;
2515
+ const tagPillHeight = 14;
2516
+ const tagGap = 4;
2517
+ const totalTagsWidth = tagsToShow.length * tagPillWidth + (tagsToShow.length - 1) * tagGap;
2518
+ const tagStartX = x - totalTagsWidth / 2;
2519
+ tagsToShow.forEach((tag, tagIndex) => {
2520
+ const tagX = tagStartX + tagIndex * (tagPillWidth + tagGap);
2521
+ const tagY = -68;
2522
+ // Pill background with gradient
2523
+ chart.append('rect')
2524
+ .attr('x', tagX)
2525
+ .attr('y', tagY)
2526
+ .attr('width', tagPillWidth)
2527
+ .attr('height', tagPillHeight)
2528
+ .attr('rx', 7)
2529
+ .attr('fill', '#dbeafe')
2530
+ .attr('stroke', '#93c5fd')
2531
+ .attr('stroke-width', 1)
2532
+ .style('cursor', 'pointer')
2533
+ .on('click', () => this.openSuiteRun(run.runId));
2534
+ // Tag text
2535
+ chart.append('text')
2536
+ .attr('x', tagX + tagPillWidth / 2)
2537
+ .attr('y', tagY + tagPillHeight / 2 + 3)
2538
+ .attr('text-anchor', 'middle')
2539
+ .attr('fill', '#1d4ed8')
2540
+ .attr('font-size', '8px')
2541
+ .attr('font-weight', '600')
2542
+ .text(tag.length > 8 ? tag.substring(0, 7) + '…' : tag)
2543
+ .style('cursor', 'pointer')
2544
+ .on('click', () => this.openSuiteRun(run.runId));
2545
+ });
2546
+ // Show +N indicator if more tags
2547
+ if (run.tags.length > 2) {
2548
+ const moreX = tagStartX + tagsToShow.length * (tagPillWidth + tagGap);
2549
+ chart.append('text')
2550
+ .attr('x', moreX)
2551
+ .attr('y', -68 + tagPillHeight / 2 + 3)
2552
+ .attr('text-anchor', 'start')
2553
+ .attr('fill', '#64748b')
2554
+ .attr('font-size', '8px')
2555
+ .attr('font-weight', '500')
2556
+ .text(`+${run.tags.length - 2}`);
2557
+ }
2558
+ }
2559
+ });
2560
+ // Draw row labels (test names) on left
2561
+ tests.forEach((test, i) => {
2562
+ const y = i * rowHeight + rowHeight / 2;
2563
+ chart.append('text')
2564
+ .attr('x', -12)
2565
+ .attr('y', y + 4)
2566
+ .attr('text-anchor', 'end')
2567
+ .attr('fill', '#334155')
2568
+ .attr('font-size', '11px')
2569
+ .text(test.testName.length > 28 ? test.testName.substring(0, 28) + '...' : test.testName)
2570
+ .style('cursor', 'pointer')
2571
+ .on('click', () => {
2572
+ SharedService.Instance.OpenEntityRecord('MJ: Tests', CompositeKey.FromID(test.testId));
2573
+ })
2574
+ .on('mouseover', function () {
2575
+ d3.select(this).attr('fill', '#3b82f6').attr('font-weight', '600');
2576
+ })
2577
+ .on('mouseout', function () {
2578
+ d3.select(this).attr('fill', '#334155').attr('font-weight', 'normal');
2579
+ });
2580
+ });
2581
+ // Draw grid lines
2582
+ tests.forEach((_, i) => {
2583
+ chart.append('line')
2584
+ .attr('x1', 0)
2585
+ .attr('y1', (i + 1) * rowHeight)
2586
+ .attr('x2', chartWidth)
2587
+ .attr('y2', (i + 1) * rowHeight)
2588
+ .attr('stroke', '#e2e8f0')
2589
+ .attr('stroke-width', 1);
2590
+ });
2591
+ runs.forEach((_, i) => {
2592
+ chart.append('line')
2593
+ .attr('x1', (i + 1) * colWidth)
2594
+ .attr('y1', 0)
2595
+ .attr('x2', (i + 1) * colWidth)
2596
+ .attr('y2', chartHeight)
2597
+ .attr('stroke', '#e2e8f0')
2598
+ .attr('stroke-width', 1);
2599
+ });
2600
+ // Draw cells with evaluation toggle support
2601
+ const cellPadding = 3;
2602
+ runs.forEach((run, runIndex) => {
2603
+ tests.forEach((test, testIndex) => {
2604
+ const result = run.testResults.get(test.testId);
2605
+ const x = runIndex * colWidth + cellPadding;
2606
+ const y = testIndex * rowHeight + cellPadding;
2607
+ const cellWidth = colWidth - cellPadding * 2;
2608
+ const cellHeight = rowHeight - cellPadding * 2;
2609
+ if (!result) {
2610
+ // Empty cell indicator
2611
+ chart.append('rect')
2612
+ .attr('x', x)
2613
+ .attr('y', y)
2614
+ .attr('width', cellWidth)
2615
+ .attr('height', cellHeight)
2616
+ .attr('rx', 4)
2617
+ .attr('fill', '#f8fafc')
2618
+ .attr('stroke', '#e2e8f0')
2619
+ .attr('stroke-width', 1);
2620
+ chart.append('text')
2621
+ .attr('x', x + cellWidth / 2)
2622
+ .attr('y', y + cellHeight / 2 + 4)
2623
+ .attr('text-anchor', 'middle')
2624
+ .attr('fill', '#cbd5e1')
2625
+ .attr('font-size', '12px')
2626
+ .text('—');
2627
+ return;
2628
+ }
2629
+ // Build tooltip content based on evaluation preferences
2630
+ const tooltipParts = [];
2631
+ if (this.evalPreferences.showExecution) {
2632
+ 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>`);
2633
+ }
2634
+ if (this.evalPreferences.showHuman) {
2635
+ tooltipParts.push(`<div style="margin-top:4px"><span style="color:#f59e0b">👤</span> <strong>Human:</strong> <span style="color:#94a3b8">Needs review</span></div>`);
2636
+ }
2637
+ if (this.evalPreferences.showAuto && result.score != null) {
2638
+ tooltipParts.push(`<div style="margin-top:4px"><span style="color:#3b82f6">🤖</span> <strong>Auto:</strong> ${(result.score * 100).toFixed(1)}%</div>`);
2639
+ }
2640
+ const durationText = result.duration != null ? `<div><strong>Duration:</strong> ${result.duration.toFixed(2)}s</div>` : '';
2641
+ const cellGroup = chart.append('g')
2642
+ .attr('class', 'result-cell')
2643
+ .style('cursor', 'pointer')
2644
+ .on('click', () => this.openTestRun(result.testRunId))
2645
+ .on('mouseover', (event) => {
2646
+ d3.select(event.currentTarget).select('rect.cell-bg')
2647
+ .attr('stroke', '#1e40af')
2648
+ .attr('stroke-width', 2);
2649
+ tooltip
2650
+ .style('opacity', 1)
2651
+ .html(`
2652
+ <div style="font-weight:600; margin-bottom:6px; color:#f1f5f9">${result.testName}</div>
2653
+ ${tooltipParts.join('')}
2654
+ ${durationText}
2655
+ <div style="margin-top:6px; color:#94a3b8; font-size:10px">${this.getRelativeTime(run.date)} • Click to view</div>
2656
+ `)
2657
+ .style('left', `${event.offsetX + 15}px`)
2658
+ .style('top', `${event.offsetY - 10}px`);
2659
+ })
2660
+ .on('mouseout', (event) => {
2661
+ d3.select(event.currentTarget).select('rect.cell-bg')
2662
+ .attr('stroke', 'none')
2663
+ .attr('stroke-width', 0);
2664
+ tooltip.style('opacity', 0);
2665
+ });
2666
+ // Determine cell background color based on eval preferences
2667
+ let cellBgColor = '#f1f5f9';
2668
+ let cellBgGradient = '';
2669
+ if (this.evalPreferences.showExecution) {
2670
+ // Use status-based gradient
2671
+ cellBgGradient = `url(#gradient-${result.status.toLowerCase()})`;
2672
+ }
2673
+ else if (this.evalPreferences.showAuto && result.score != null) {
2674
+ // Use score-based color
2675
+ const scoreColor = result.score >= 0.8 ? '#22c55e' : result.score >= 0.5 ? '#f97316' : '#ef4444';
2676
+ cellBgColor = scoreColor;
2677
+ }
2678
+ else {
2679
+ // Neutral background when only Human is selected
2680
+ cellBgColor = '#fef3c7';
2681
+ }
2682
+ // Cell background
2683
+ cellGroup.append('rect')
2684
+ .attr('class', 'cell-bg')
2685
+ .attr('x', x)
2686
+ .attr('y', y)
2687
+ .attr('width', cellWidth)
2688
+ .attr('height', cellHeight)
2689
+ .attr('rx', 4)
2690
+ .attr('fill', cellBgGradient || cellBgColor)
2691
+ .attr('stroke', 'none')
2692
+ .attr('stroke-width', 0)
2693
+ .style('transition', 'all 0.15s ease');
2694
+ // Calculate icon positions based on how many eval types are shown
2695
+ // With wider cells, we can use larger icons and better spacing
2696
+ const iconSize = evalCount === 1 ? 14 : evalCount === 2 ? 12 : 10;
2697
+ const iconSpacing = evalCount === 1 ? 0 : evalCount === 2 ? 16 : 14;
2698
+ const startX = x + cellWidth / 2 - ((evalCount - 1) * iconSpacing) / 2;
2699
+ let iconIndex = 0;
2700
+ // Status icon (execution)
2701
+ if (this.evalPreferences.showExecution) {
2702
+ const iconText = {
2703
+ 'Passed': '✓',
2704
+ 'Failed': '✕',
2705
+ 'Error': '!',
2706
+ 'Skipped': '»',
2707
+ 'Running': '●',
2708
+ 'Pending': '○'
2709
+ };
2710
+ const iconX = startX + iconIndex * iconSpacing;
2711
+ // If status is NOT the only thing shown, add a small circular bg
2712
+ if (evalCount > 1) {
2713
+ cellGroup.append('circle')
2714
+ .attr('cx', iconX)
2715
+ .attr('cy', y + cellHeight / 2)
2716
+ .attr('r', iconSize / 2 + 3)
2717
+ .attr('fill', statusColors[result.status])
2718
+ .attr('opacity', 0.9);
2719
+ }
2720
+ cellGroup.append('text')
2721
+ .attr('x', iconX)
2722
+ .attr('y', y + cellHeight / 2 + iconSize / 3)
2723
+ .attr('text-anchor', 'middle')
2724
+ .attr('fill', 'white')
2725
+ .attr('font-size', `${iconSize}px`)
2726
+ .attr('font-weight', 'bold')
2727
+ .attr('font-family', 'system-ui, -apple-system, sans-serif')
2728
+ .text(iconText[result.status] || '?');
2729
+ iconIndex++;
2730
+ }
2731
+ // Human icon
2732
+ if (this.evalPreferences.showHuman) {
2733
+ const iconX = startX + iconIndex * iconSpacing;
2734
+ // Human evaluation indicator (clock icon for pending)
2735
+ cellGroup.append('circle')
2736
+ .attr('cx', iconX)
2737
+ .attr('cy', y + cellHeight / 2)
2738
+ .attr('r', iconSize / 2 + 3)
2739
+ .attr('fill', '#fef3c7')
2740
+ .attr('stroke', '#f59e0b')
2741
+ .attr('stroke-width', 1);
2742
+ cellGroup.append('text')
2743
+ .attr('x', iconX)
2744
+ .attr('y', y + cellHeight / 2 + iconSize / 3)
2745
+ .attr('text-anchor', 'middle')
2746
+ .attr('fill', '#d97706')
2747
+ .attr('font-size', `${iconSize - 2}px`)
2748
+ .attr('font-weight', '500')
2749
+ .text('⏱');
2750
+ iconIndex++;
2751
+ }
2752
+ // Auto score icon/indicator
2753
+ if (this.evalPreferences.showAuto) {
2754
+ const iconX = startX + iconIndex * iconSpacing;
2755
+ if (result.score != null) {
2756
+ const scorePercent = Math.round(result.score * 100);
2757
+ const scoreColor = result.score >= 0.8 ? '#22c55e' : result.score >= 0.5 ? '#f97316' : '#ef4444';
2758
+ // Score pill background
2759
+ if (evalCount > 1) {
2760
+ cellGroup.append('rect')
2761
+ .attr('x', iconX - 10)
2762
+ .attr('y', y + cellHeight / 2 - iconSize / 2 - 1)
2763
+ .attr('width', 20)
2764
+ .attr('height', iconSize + 2)
2765
+ .attr('rx', (iconSize + 2) / 2)
2766
+ .attr('fill', scoreColor)
2767
+ .attr('opacity', 0.9);
2768
+ }
2769
+ cellGroup.append('text')
2770
+ .attr('x', iconX)
2771
+ .attr('y', y + cellHeight / 2 + iconSize / 3)
2772
+ .attr('text-anchor', 'middle')
2773
+ .attr('fill', evalCount > 1 ? 'white' : 'white')
2774
+ .attr('font-size', `${iconSize - 1}px`)
2775
+ .attr('font-weight', '700')
2776
+ .text(`${scorePercent}`);
2777
+ }
2778
+ else {
2779
+ // No auto score available
2780
+ cellGroup.append('circle')
2781
+ .attr('cx', iconX)
2782
+ .attr('cy', y + cellHeight / 2)
2783
+ .attr('r', iconSize / 2 + 2)
2784
+ .attr('fill', '#e2e8f0')
2785
+ .attr('stroke', '#94a3b8')
2786
+ .attr('stroke-width', 1);
2787
+ cellGroup.append('text')
2788
+ .attr('x', iconX)
2789
+ .attr('y', y + cellHeight / 2 + iconSize / 3)
2790
+ .attr('text-anchor', 'middle')
2791
+ .attr('fill', '#94a3b8')
2792
+ .attr('font-size', `${iconSize - 2}px`)
2793
+ .text('—');
2794
+ }
2795
+ }
2796
+ });
2797
+ });
2798
+ // Draw trend line for pass rate across runs
2799
+ if (runs.length > 1) {
2800
+ const trendLineY = chartHeight + 50;
2801
+ const trendHeight = 40;
2802
+ // Trend line label
2803
+ chart.append('text')
2804
+ .attr('x', -12)
2805
+ .attr('y', trendLineY + trendHeight / 2)
2806
+ .attr('text-anchor', 'end')
2807
+ .attr('fill', '#64748b')
2808
+ .attr('font-size', '10px')
2809
+ .attr('font-weight', '500')
2810
+ .text('Pass Rate Trend');
2811
+ // Create trend line path
2812
+ const lineGenerator = d3.line()
2813
+ .x((_, i) => i * colWidth + colWidth / 2)
2814
+ .y(d => trendLineY + trendHeight - (d.passRate / 100) * trendHeight)
2815
+ .curve(d3.curveMonotoneX);
2816
+ // Draw area under line
2817
+ const areaGenerator = d3.area()
2818
+ .x((_, i) => i * colWidth + colWidth / 2)
2819
+ .y0(trendLineY + trendHeight)
2820
+ .y1(d => trendLineY + trendHeight - (d.passRate / 100) * trendHeight)
2821
+ .curve(d3.curveMonotoneX);
2822
+ chart.append('path')
2823
+ .datum(runs)
2824
+ .attr('d', areaGenerator)
2825
+ .attr('fill', 'url(#trendGradient)')
2826
+ .attr('opacity', 0.3);
2827
+ // Create gradient for trend area
2828
+ const trendGradient = defs.append('linearGradient')
2829
+ .attr('id', 'trendGradient')
2830
+ .attr('x1', '0%')
2831
+ .attr('y1', '0%')
2832
+ .attr('x2', '0%')
2833
+ .attr('y2', '100%');
2834
+ trendGradient.append('stop')
2835
+ .attr('offset', '0%')
2836
+ .attr('stop-color', '#3b82f6')
2837
+ .attr('stop-opacity', 0.4);
2838
+ trendGradient.append('stop')
2839
+ .attr('offset', '100%')
2840
+ .attr('stop-color', '#3b82f6')
2841
+ .attr('stop-opacity', 0);
2842
+ chart.append('path')
2843
+ .datum(runs)
2844
+ .attr('d', lineGenerator)
2845
+ .attr('fill', 'none')
2846
+ .attr('stroke', '#3b82f6')
2847
+ .attr('stroke-width', 2.5)
2848
+ .attr('stroke-linecap', 'round');
2849
+ // Draw dots on trend line
2850
+ runs.forEach((run, i) => {
2851
+ const cx = i * colWidth + colWidth / 2;
2852
+ const cy = trendLineY + trendHeight - (run.passRate / 100) * trendHeight;
2853
+ chart.append('circle')
2854
+ .attr('cx', cx)
2855
+ .attr('cy', cy)
2856
+ .attr('r', 4)
2857
+ .attr('fill', 'white')
2858
+ .attr('stroke', '#3b82f6')
2859
+ .attr('stroke-width', 2);
2860
+ });
2861
+ // Update container height for trend line
2862
+ container.style.height = `${Math.max(400, height + 80)}px`;
2863
+ svg.attr('height', height + 80);
2864
+ }
2865
+ this.chartRendered = true;
2866
+ }
2867
+ /**
2868
+ * Renders a legend showing which evaluation types are currently displayed
2869
+ */
2870
+ renderChartLegend(chart, margin) {
2871
+ const legendGroup = chart.append('g')
2872
+ .attr('class', 'eval-legend')
2873
+ .attr('transform', `translate(${-margin.left + 10}, ${-margin.top + 15})`);
2874
+ // Legend title
2875
+ legendGroup.append('text')
2876
+ .attr('x', 0)
2877
+ .attr('y', 0)
2878
+ .attr('fill', '#64748b')
2879
+ .attr('font-size', '9px')
2880
+ .attr('font-weight', '600')
2881
+ .attr('text-transform', 'uppercase')
2882
+ .text('SHOWING:');
2883
+ let xOffset = 55;
2884
+ // Status indicator
2885
+ if (this.evalPreferences.showExecution) {
2886
+ const statusGroup = legendGroup.append('g')
2887
+ .attr('transform', `translate(${xOffset}, -4)`);
2888
+ statusGroup.append('circle')
2889
+ .attr('cx', 6)
2890
+ .attr('cy', 0)
2891
+ .attr('r', 6)
2892
+ .attr('fill', '#22c55e');
2893
+ statusGroup.append('text')
2894
+ .attr('x', 6)
2895
+ .attr('y', 4)
2896
+ .attr('text-anchor', 'middle')
2897
+ .attr('fill', 'white')
2898
+ .attr('font-size', '8px')
2899
+ .attr('font-weight', 'bold')
2900
+ .text('✓');
2901
+ statusGroup.append('text')
2902
+ .attr('x', 16)
2903
+ .attr('y', 3)
2904
+ .attr('fill', '#475569')
2905
+ .attr('font-size', '10px')
2906
+ .attr('font-weight', '500')
2907
+ .text('Status');
2908
+ xOffset += 60;
2909
+ }
2910
+ // Human indicator
2911
+ if (this.evalPreferences.showHuman) {
2912
+ const humanGroup = legendGroup.append('g')
2913
+ .attr('transform', `translate(${xOffset}, -4)`);
2914
+ humanGroup.append('circle')
2915
+ .attr('cx', 6)
2916
+ .attr('cy', 0)
2917
+ .attr('r', 6)
2918
+ .attr('fill', '#fef3c7')
2919
+ .attr('stroke', '#f59e0b')
2920
+ .attr('stroke-width', 1);
2921
+ humanGroup.append('text')
2922
+ .attr('x', 6)
2923
+ .attr('y', 3)
2924
+ .attr('text-anchor', 'middle')
2925
+ .attr('fill', '#d97706')
2926
+ .attr('font-size', '7px')
2927
+ .text('⏱');
2928
+ humanGroup.append('text')
2929
+ .attr('x', 16)
2930
+ .attr('y', 3)
2931
+ .attr('fill', '#475569')
2932
+ .attr('font-size', '10px')
2933
+ .attr('font-weight', '500')
2934
+ .text('Human');
2935
+ xOffset += 60;
2936
+ }
2937
+ // Auto indicator
2938
+ if (this.evalPreferences.showAuto) {
2939
+ const autoGroup = legendGroup.append('g')
2940
+ .attr('transform', `translate(${xOffset}, -4)`);
2941
+ autoGroup.append('rect')
2942
+ .attr('x', 0)
2943
+ .attr('y', -6)
2944
+ .attr('width', 18)
2945
+ .attr('height', 12)
2946
+ .attr('rx', 6)
2947
+ .attr('fill', '#3b82f6');
2948
+ autoGroup.append('text')
2949
+ .attr('x', 9)
2950
+ .attr('y', 3)
2951
+ .attr('text-anchor', 'middle')
2952
+ .attr('fill', 'white')
2953
+ .attr('font-size', '7px')
2954
+ .attr('font-weight', '700')
2955
+ .text('%');
2956
+ autoGroup.append('text')
2957
+ .attr('x', 24)
2958
+ .attr('y', 3)
2959
+ .attr('fill', '#475569')
2960
+ .attr('font-size', '10px')
2961
+ .attr('font-weight', '500')
2962
+ .text('Auto');
2963
+ }
2964
+ }
2965
+ getAveragePassRate() {
2966
+ const data = this.getFilteredAnalyticsData();
2967
+ if (data.length === 0)
2968
+ return 0;
2969
+ return data.reduce((sum, d) => sum + d.passRate, 0) / data.length;
2970
+ }
2971
+ getTotalRuns() {
2972
+ return this.getFilteredAnalyticsData().length;
2973
+ }
2974
+ getAverageDuration() {
2975
+ const data = this.getFilteredAnalyticsData();
2976
+ if (data.length === 0)
2977
+ return 0;
2978
+ return data.reduce((sum, d) => sum + d.duration, 0) / data.length;
2979
+ }
2980
+ getTotalCost() {
2981
+ return this.getFilteredAnalyticsData().reduce((sum, d) => sum + d.cost, 0);
2982
+ }
2983
+ getPassRateTrend() {
2984
+ const data = this.getFilteredAnalyticsData();
2985
+ if (data.length < 2)
2986
+ return { direction: 'stable', value: 0 };
2987
+ // Compare recent half to older half
2988
+ const midpoint = Math.floor(data.length / 2);
2989
+ const recentData = data.slice(0, midpoint);
2990
+ const olderData = data.slice(midpoint);
2991
+ const recentAvg = recentData.reduce((s, d) => s + d.passRate, 0) / recentData.length;
2992
+ const olderAvg = olderData.reduce((s, d) => s + d.passRate, 0) / olderData.length;
2993
+ const diff = recentAvg - olderAvg;
2994
+ if (Math.abs(diff) < 1)
2995
+ return { direction: 'stable', value: diff };
2996
+ return { direction: diff > 0 ? 'up' : 'down', value: Math.abs(diff) };
2997
+ }
2998
+ formatDuration(seconds) {
2999
+ if (seconds < 60)
3000
+ return `${seconds.toFixed(1)}s`;
3001
+ if (seconds < 3600) {
3002
+ const mins = Math.floor(seconds / 60);
3003
+ const secs = Math.floor(seconds % 60);
3004
+ return secs > 0 ? `${mins}m ${secs}s` : `${mins}m`;
3005
+ }
3006
+ const hours = Math.floor(seconds / 3600);
3007
+ const mins = Math.floor((seconds % 3600) / 60);
3008
+ return mins > 0 ? `${hours}h ${mins}m` : `${hours}h`;
3009
+ }
3010
+ formatCost(cost) {
3011
+ if (cost < 0.01)
3012
+ return `$${cost.toFixed(6)}`;
3013
+ if (cost < 1)
3014
+ return `$${cost.toFixed(4)}`;
3015
+ return `$${cost.toFixed(2)}`;
3016
+ }
3017
+ // ==========================================
3018
+ // Compare Tab Methods
3019
+ // ==========================================
3020
+ async selectCompareRunA(run) {
3021
+ this.compareRunA = run;
3022
+ await this.loadCompareData();
3023
+ }
3024
+ async selectCompareRunB(run) {
3025
+ this.compareRunB = run;
3026
+ await this.loadCompareData();
3027
+ }
3028
+ clearCompareSelection() {
3029
+ this.compareRunA = null;
3030
+ this.compareRunB = null;
3031
+ this.compareResults = [];
3032
+ this.compareRunATests = [];
3033
+ this.compareRunBTests = [];
3034
+ this.cdr.markForCheck();
3035
+ }
3036
+ async loadCompareData() {
3037
+ if (!this.compareRunA || !this.compareRunB) {
3038
+ this.compareResults = [];
3039
+ this.cdr.markForCheck();
3040
+ return;
3041
+ }
3042
+ this.loadingCompare = true;
3043
+ this.cdr.markForCheck();
3044
+ try {
3045
+ const rv = new RunView();
3046
+ // Load test runs for both suite runs in parallel
3047
+ const [resultA, resultB] = await Promise.all([
3048
+ rv.RunView({
3049
+ EntityName: 'MJ: Test Runs',
3050
+ ExtraFilter: `TestSuiteRunID='${this.compareRunA.ID}'`,
3051
+ OrderBy: 'Sequence ASC',
3052
+ ResultType: 'entity_object'
3053
+ }),
3054
+ rv.RunView({
3055
+ EntityName: 'MJ: Test Runs',
3056
+ ExtraFilter: `TestSuiteRunID='${this.compareRunB.ID}'`,
3057
+ OrderBy: 'Sequence ASC',
3058
+ ResultType: 'entity_object'
3059
+ })
3060
+ ]);
3061
+ this.compareRunATests = resultA.Success ? resultA.Results || [] : [];
3062
+ this.compareRunBTests = resultB.Success ? resultB.Results || [] : [];
3063
+ // Build comparison results
3064
+ this.compareResults = this.buildComparisonResults(this.compareRunATests, this.compareRunBTests);
3065
+ }
3066
+ catch (error) {
3067
+ console.error('Error loading comparison data:', error);
3068
+ SharedService.Instance.CreateSimpleNotification('Failed to load comparison data', 'error', 3000);
3069
+ }
3070
+ finally {
3071
+ this.loadingCompare = false;
3072
+ this.cdr.markForCheck();
3073
+ }
3074
+ }
3075
+ buildComparisonResults(testsA, testsB) {
3076
+ const results = [];
3077
+ const testMap = new Map();
3078
+ // Map all tests from run A
3079
+ for (const test of testsA) {
3080
+ testMap.set(test.TestID, { a: test, name: test.Test || 'Unknown Test' });
3081
+ }
3082
+ // Map all tests from run B
3083
+ for (const test of testsB) {
3084
+ const existing = testMap.get(test.TestID);
3085
+ if (existing) {
3086
+ existing.b = test;
3087
+ }
3088
+ else {
3089
+ testMap.set(test.TestID, { b: test, name: test.Test || 'Unknown Test' });
3090
+ }
3091
+ }
3092
+ // Build comparison array
3093
+ for (const [testId, { a, b, name }] of testMap) {
3094
+ const comparison = {
3095
+ testId,
3096
+ testName: name,
3097
+ runA: a ? {
3098
+ status: a.Status || 'Unknown',
3099
+ score: a.Score,
3100
+ duration: a.DurationSeconds,
3101
+ cost: a.CostUSD
3102
+ } : null,
3103
+ runB: b ? {
3104
+ status: b.Status || 'Unknown',
3105
+ score: b.Score,
3106
+ duration: b.DurationSeconds,
3107
+ cost: b.CostUSD
3108
+ } : null,
3109
+ scoreDiff: (a?.Score != null && b?.Score != null) ? b.Score - a.Score : null,
3110
+ durationDiff: (a?.DurationSeconds != null && b?.DurationSeconds != null) ? b.DurationSeconds - a.DurationSeconds : null,
3111
+ statusChanged: (a?.Status || 'none') !== (b?.Status || 'none')
3112
+ };
3113
+ results.push(comparison);
3114
+ }
3115
+ return results;
3116
+ }
3117
+ getComparePassRateDiff() {
3118
+ if (!this.compareRunA || !this.compareRunB)
3119
+ return null;
3120
+ const rateA = this.getPassRate(this.compareRunA);
3121
+ const rateB = this.getPassRate(this.compareRunB);
3122
+ return rateB - rateA;
3123
+ }
3124
+ getCompareDurationDiff() {
3125
+ if (!this.compareRunA || !this.compareRunB)
3126
+ return null;
3127
+ const durA = this.compareRunA.TotalDurationSeconds || 0;
3128
+ const durB = this.compareRunB.TotalDurationSeconds || 0;
3129
+ return durB - durA;
3130
+ }
3131
+ getAbsCompareDurationDiff() {
3132
+ const diff = this.getCompareDurationDiff();
3133
+ return diff != null ? Math.abs(diff) : 0;
3134
+ }
3135
+ getCompareCostDiff() {
3136
+ if (!this.compareRunA || !this.compareRunB)
3137
+ return null;
3138
+ const costA = this.compareRunA.TotalCostUSD || 0;
3139
+ const costB = this.compareRunB.TotalCostUSD || 0;
3140
+ return costB - costA;
3141
+ }
3142
+ getCompareImprovedCount() {
3143
+ return this.compareResults.filter(r => r.runA && r.runB &&
3144
+ r.runA.status !== 'Passed' && r.runB.status === 'Passed').length;
3145
+ }
3146
+ getCompareRegressedCount() {
3147
+ return this.compareResults.filter(r => r.runA && r.runB &&
3148
+ r.runA.status === 'Passed' && r.runB.status !== 'Passed').length;
3149
+ }
3150
+ // ==========================================
3151
+ // Excel Export Methods
3152
+ // ==========================================
3153
+ async exportToExcel() {
3154
+ try {
3155
+ // Ensure runs are loaded
3156
+ if (!this.runsLoaded)
3157
+ await this.loadRuns();
3158
+ const data = this.suiteRuns.map(run => ({
3159
+ 'Run ID': run.ID,
3160
+ 'Status': run.Status,
3161
+ 'Started At': run.StartedAt ? new Date(run.StartedAt).toLocaleString() : 'N/A',
3162
+ 'Completed At': run.CompletedAt ? new Date(run.CompletedAt).toLocaleString() : 'N/A',
3163
+ 'Duration (s)': run.TotalDurationSeconds?.toFixed(2) || 'N/A',
3164
+ 'Total Tests': run.TotalTests || 0,
3165
+ 'Passed': run.PassedTests || 0,
3166
+ 'Failed': run.FailedTests || 0,
3167
+ 'Errors': run.ErrorTests || 0,
3168
+ 'Skipped': run.SkippedTests || 0,
3169
+ 'Pass Rate (%)': run.TotalTests ? ((run.PassedTests || 0) / run.TotalTests * 100).toFixed(1) : 'N/A',
3170
+ 'Total Cost ($)': run.TotalCostUSD?.toFixed(6) || 'N/A',
3171
+ 'Tags': TagsHelper.parseTags(run.Tags).join(', ') || 'None',
3172
+ 'Environment': run.Environment || 'N/A',
3173
+ 'Trigger Type': run.TriggerType || 'N/A',
3174
+ 'Run By': run.RunByUser || 'N/A'
3175
+ }));
3176
+ this.downloadAsCSV(data, `${this.record.Name}_runs_export.csv`);
3177
+ SharedService.Instance.CreateSimpleNotification('Export successful', 'success', 2000);
3178
+ }
3179
+ catch (error) {
3180
+ console.error('Export failed:', error);
3181
+ SharedService.Instance.CreateSimpleNotification('Export failed', 'error', 3000);
3182
+ }
3183
+ }
3184
+ downloadAsCSV(data, filename) {
3185
+ if (data.length === 0)
3186
+ return;
3187
+ const headers = Object.keys(data[0]);
3188
+ const csvContent = [
3189
+ headers.join(','),
3190
+ ...data.map(row => headers.map(h => {
3191
+ const val = row[h];
3192
+ // Escape quotes and wrap in quotes if contains comma
3193
+ const strVal = String(val);
3194
+ if (strVal.includes(',') || strVal.includes('"') || strVal.includes('\n')) {
3195
+ return `"${strVal.replace(/"/g, '""')}"`;
3196
+ }
3197
+ return strVal;
3198
+ }).join(','))
3199
+ ].join('\n');
3200
+ const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
3201
+ const link = document.createElement('a');
3202
+ link.href = URL.createObjectURL(blob);
3203
+ link.download = filename;
3204
+ link.click();
3205
+ URL.revokeObjectURL(link.href);
3206
+ }
3207
+ getRunTags(run) {
3208
+ return TagsHelper.parseTags(run.Tags);
3209
+ }
3210
+ /**
3211
+ * Export matrix view data to CSV
3212
+ */
3213
+ exportMatrixToCSV() {
3214
+ if (this.matrixData.length === 0) {
3215
+ SharedService.Instance.CreateSimpleNotification('No data to export', 'warning', 2000);
3216
+ return;
3217
+ }
3218
+ try {
3219
+ const tests = this.getUniqueTestsFromMatrix();
3220
+ const runs = this.matrixData;
3221
+ // Build CSV rows
3222
+ const rows = [];
3223
+ for (const test of tests) {
3224
+ const row = {
3225
+ 'Seq': test.sequence,
3226
+ 'Test Name': test.testName
3227
+ };
3228
+ // Add column for each run
3229
+ for (const run of runs) {
3230
+ const result = run.testResults.get(test.testId);
3231
+ const runLabel = run.tags.length > 0
3232
+ ? `${run.tags.slice(0, 2).join('/')} (${this.getRelativeTime(run.date)})`
3233
+ : this.getRelativeTime(run.date);
3234
+ if (result) {
3235
+ // Build cell value based on what's shown
3236
+ const parts = [];
3237
+ if (this.evalPreferences.showExecution)
3238
+ parts.push(result.status);
3239
+ if (this.evalPreferences.showHuman)
3240
+ parts.push(result.humanRating != null ? `H:${result.humanRating}` : 'H:-');
3241
+ if (this.evalPreferences.showAuto)
3242
+ parts.push(result.score != null ? `A:${Math.round(result.score * 100)}%` : 'A:-');
3243
+ row[runLabel] = parts.join(' | ');
3244
+ }
3245
+ else {
3246
+ row[runLabel] = 'Not Run';
3247
+ }
3248
+ }
3249
+ rows.push(row);
3250
+ }
3251
+ this.downloadAsCSV(rows, `${this.record.Name}_matrix_export.csv`);
3252
+ SharedService.Instance.CreateSimpleNotification('Matrix exported successfully', 'success', 2000);
3253
+ }
3254
+ catch (error) {
3255
+ console.error('Matrix export failed:', error);
3256
+ SharedService.Instance.CreateSimpleNotification('Export failed', 'error', 3000);
3257
+ }
3258
+ }
3259
+ // ==========================================
3260
+ // Keyboard Shortcuts Settings
3261
+ // ==========================================
3262
+ /**
3263
+ * Load keyboard shortcuts visibility setting from user settings
3264
+ */
3265
+ loadShortcutsSetting() {
3266
+ try {
3267
+ const engine = UserInfoEngine.Instance;
3268
+ const setting = engine.UserSettings.find(s => s.Setting === SHORTCUTS_SETTINGS_KEY);
3269
+ if (setting) {
3270
+ this.shortcutsSettingEntity = setting;
3271
+ this.showShortcuts = setting.Value === 'true';
3272
+ }
3273
+ else {
3274
+ // Default to hidden
3275
+ this.showShortcuts = false;
3276
+ }
3277
+ this.cdr.markForCheck();
3278
+ }
3279
+ catch (error) {
3280
+ console.warn('Failed to load shortcuts setting:', error);
281
3281
  }
282
3282
  }
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);
3283
+ /**
3284
+ * Toggle analytics filters visibility
3285
+ */
3286
+ toggleFilters() {
3287
+ this.filtersCollapsed = !this.filtersCollapsed;
3288
+ this.cdr.markForCheck();
3289
+ }
3290
+ /**
3291
+ * Toggle keyboard shortcuts visibility and save preference
3292
+ */
3293
+ async toggleShortcuts() {
3294
+ this.showShortcuts = !this.showShortcuts;
3295
+ this.cdr.markForCheck();
3296
+ try {
3297
+ const userId = this.metadata.CurrentUser?.ID;
3298
+ if (!userId)
3299
+ return;
3300
+ if (!this.shortcutsSettingEntity) {
3301
+ const engine = UserInfoEngine.Instance;
3302
+ const setting = engine.UserSettings.find(s => s.Setting === SHORTCUTS_SETTINGS_KEY);
3303
+ if (setting) {
3304
+ this.shortcutsSettingEntity = setting;
3305
+ }
3306
+ else {
3307
+ this.shortcutsSettingEntity = await this.metadata.GetEntityObject('MJ: User Settings');
3308
+ this.shortcutsSettingEntity.UserID = userId;
3309
+ this.shortcutsSettingEntity.Setting = SHORTCUTS_SETTINGS_KEY;
3310
+ }
3311
+ }
3312
+ this.shortcutsSettingEntity.Value = this.showShortcuts ? 'true' : 'false';
3313
+ await this.shortcutsSettingEntity.Save();
3314
+ }
3315
+ catch (error) {
3316
+ console.warn('Failed to save shortcuts setting:', error);
3317
+ }
3318
+ }
3319
+ 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), i0.ɵɵdirectiveInject(i0.ViewContainerRef)); }; }
3320
+ static { this.ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: TestSuiteFormComponentExtended, selectors: [["mj-test-suite-form"]], viewQuery: function TestSuiteFormComponentExtended_Query(rf, ctx) { if (rf & 1) {
3321
+ i0.ɵɵviewQuery(_c0, 5);
3322
+ } if (rf & 2) {
3323
+ let _t;
3324
+ i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.chartContainer = _t.first);
3325
+ } }, hostBindings: function TestSuiteFormComponentExtended_HostBindings(rf, ctx) { if (rf & 1) {
3326
+ i0.ɵɵlistener("keydown", function TestSuiteFormComponentExtended_keydown_HostBindingHandler($event) { return ctx.handleKeyboardShortcut($event); }, false, i0.ɵɵresolveDocument);
3327
+ } }, 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) {
3328
+ i0.ɵɵelementStart(0, "div", 1)(1, "div", 2)(2, "div", 3)(3, "div", 4)(4, "div", 5);
3329
+ i0.ɵɵelement(5, "i", 6);
287
3330
  i0.ɵɵelementEnd();
288
- i0.ɵɵelementStart(6, "div", 6)(7, "h1");
3331
+ i0.ɵɵelementStart(6, "div", 7)(7, "h1");
289
3332
  i0.ɵɵtext(8);
290
3333
  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");
3334
+ i0.ɵɵelementStart(9, "div", 8)(10, "span", 9);
3335
+ i0.ɵɵelement(11, "i", 10);
3336
+ i0.ɵɵtext(12);
3337
+ i0.ɵɵelementEnd();
3338
+ i0.ɵɵtemplate(13, TestSuiteFormComponentExtended_span_13_Template, 3, 1, "span", 11);
297
3339
  i0.ɵɵelementEnd()()();
298
- i0.ɵɵtemplate(15, TestSuiteFormComponentExtended_div_15_Template, 3, 1, "div", 11);
3340
+ i0.ɵɵelementStart(14, "div", 12);
3341
+ i0.ɵɵelement(15, "app-evaluation-mode-toggle");
3342
+ i0.ɵɵelementStart(16, "button", 13);
3343
+ i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_Template_button_click_16_listener() { return ctx.exportToExcel(); });
3344
+ i0.ɵɵelement(17, "i", 14);
3345
+ i0.ɵɵtext(18, " Export ");
299
3346
  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 ");
3347
+ i0.ɵɵelementStart(19, "button", 15);
3348
+ i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_Template_button_click_19_listener() { return ctx.runSuite(); });
3349
+ i0.ɵɵelement(20, "i", 16);
3350
+ i0.ɵɵtext(21, " Run Suite ");
304
3351
  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);
3352
+ i0.ɵɵelementStart(22, "button", 17);
3353
+ i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_Template_button_click_22_listener() { return ctx.refresh(); });
3354
+ i0.ɵɵelement(23, "i", 10);
3355
+ i0.ɵɵtext(24);
3356
+ i0.ɵɵelementEnd()()();
3357
+ i0.ɵɵtemplate(25, TestSuiteFormComponentExtended_div_25_Template, 3, 1, "div", 18);
3358
+ i0.ɵɵelementEnd();
3359
+ i0.ɵɵelementStart(26, "div", 19)(27, "div", 20)(28, "button", 21);
3360
+ i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_Template_button_click_28_listener() { return ctx.changeTab("overview"); });
3361
+ i0.ɵɵelement(29, "i", 22);
3362
+ i0.ɵɵtext(30, " Overview ");
3363
+ i0.ɵɵelementEnd();
3364
+ i0.ɵɵelementStart(31, "button", 21);
3365
+ i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_Template_button_click_31_listener() { return ctx.changeTab("tests"); });
3366
+ i0.ɵɵelement(32, "i", 23);
3367
+ i0.ɵɵtext(33, " Tests ");
3368
+ i0.ɵɵtemplate(34, TestSuiteFormComponentExtended_span_34_Template, 2, 1, "span", 24);
3369
+ i0.ɵɵelementEnd();
3370
+ i0.ɵɵelementStart(35, "button", 21);
3371
+ i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_Template_button_click_35_listener() { return ctx.changeTab("runs"); });
3372
+ i0.ɵɵelement(36, "i", 25);
3373
+ i0.ɵɵtext(37, " Runs ");
3374
+ i0.ɵɵtemplate(38, TestSuiteFormComponentExtended_span_38_Template, 2, 1, "span", 24);
310
3375
  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);
3376
+ i0.ɵɵelementStart(39, "button", 21);
3377
+ i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_Template_button_click_39_listener() { return ctx.changeTab("analytics"); });
3378
+ i0.ɵɵelement(40, "i", 26);
3379
+ i0.ɵɵtext(41, " Analytics ");
3380
+ i0.ɵɵelementEnd();
3381
+ i0.ɵɵelementStart(42, "button", 21);
3382
+ i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_Template_button_click_42_listener() { return ctx.changeTab("compare"); });
3383
+ i0.ɵɵelement(43, "i", 27);
3384
+ i0.ɵɵtext(44, " Compare ");
316
3385
  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()();
3386
+ i0.ɵɵelementStart(45, "div", 28);
3387
+ 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);
3388
+ i0.ɵɵelementEnd();
3389
+ i0.ɵɵelementStart(51, "button", 34);
3390
+ i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_Template_button_click_51_listener() { return ctx.toggleShortcuts(); });
3391
+ i0.ɵɵelement(52, "i", 35);
3392
+ i0.ɵɵelementEnd();
3393
+ i0.ɵɵtemplate(53, TestSuiteFormComponentExtended_div_53_Template, 32, 0, "div", 36);
3394
+ i0.ɵɵelementEnd();
320
3395
  } if (rf & 2) {
321
3396
  i0.ɵɵadvance(4);
322
3397
  i0.ɵɵstyleProp("background-color", ctx.getStatusColor());
323
3398
  i0.ɵɵadvance(4);
324
3399
  i0.ɵɵtextInterpolate(ctx.record.Name);
325
3400
  i0.ɵɵadvance(2);
326
- i0.ɵɵstyleProp("background-color", ctx.getStatusColor());
3401
+ i0.ɵɵproperty("ngClass", ctx.getStatusClass());
3402
+ i0.ɵɵadvance();
3403
+ i0.ɵɵproperty("ngClass", ctx.record.Status === "Active" ? "fa-circle-check" : "fa-circle-pause");
327
3404
  i0.ɵɵadvance();
328
3405
  i0.ɵɵtextInterpolate1(" ", ctx.record.Status, " ");
329
- i0.ɵɵadvance(4);
3406
+ i0.ɵɵadvance();
3407
+ i0.ɵɵproperty("ngIf", ctx.testsLoaded);
3408
+ i0.ɵɵadvance(9);
3409
+ i0.ɵɵproperty("disabled", ctx.isRefreshing);
3410
+ i0.ɵɵadvance();
3411
+ i0.ɵɵproperty("ngClass", ctx.isRefreshing ? "fa-sync fa-spin" : "fa-sync");
3412
+ i0.ɵɵadvance();
3413
+ i0.ɵɵtextInterpolate1(" ", ctx.isRefreshing ? "Refreshing..." : "Refresh", " ");
3414
+ i0.ɵɵadvance();
330
3415
  i0.ɵɵproperty("ngIf", ctx.record.Description);
331
3416
  i0.ɵɵadvance(3);
332
3417
  i0.ɵɵclassProp("active", ctx.activeTab === "overview");
3418
+ i0.ɵɵattribute("aria-selected", ctx.activeTab === "overview");
333
3419
  i0.ɵɵadvance(3);
334
3420
  i0.ɵɵclassProp("active", ctx.activeTab === "tests");
3421
+ i0.ɵɵattribute("aria-selected", ctx.activeTab === "tests");
335
3422
  i0.ɵɵadvance(3);
336
3423
  i0.ɵɵproperty("ngIf", ctx.testsLoaded);
337
3424
  i0.ɵɵadvance();
338
3425
  i0.ɵɵclassProp("active", ctx.activeTab === "runs");
3426
+ i0.ɵɵattribute("aria-selected", ctx.activeTab === "runs");
339
3427
  i0.ɵɵadvance(3);
340
3428
  i0.ɵɵproperty("ngIf", ctx.runsLoaded);
341
- i0.ɵɵadvance(2);
3429
+ i0.ɵɵadvance();
3430
+ i0.ɵɵclassProp("active", ctx.activeTab === "analytics");
3431
+ i0.ɵɵattribute("aria-selected", ctx.activeTab === "analytics");
3432
+ i0.ɵɵadvance(3);
3433
+ i0.ɵɵclassProp("active", ctx.activeTab === "compare");
3434
+ i0.ɵɵattribute("aria-selected", ctx.activeTab === "compare");
3435
+ i0.ɵɵadvance(4);
342
3436
  i0.ɵɵproperty("ngIf", ctx.activeTab === "overview");
343
3437
  i0.ɵɵadvance();
344
3438
  i0.ɵɵproperty("ngIf", ctx.activeTab === "tests");
345
3439
  i0.ɵɵadvance();
346
3440
  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 }); }
3441
+ i0.ɵɵadvance();
3442
+ i0.ɵɵproperty("ngIf", ctx.activeTab === "analytics");
3443
+ i0.ɵɵadvance();
3444
+ i0.ɵɵproperty("ngIf", ctx.activeTab === "compare");
3445
+ i0.ɵɵadvance();
3446
+ i0.ɵɵproperty("title", ctx.showShortcuts ? "Hide keyboard shortcuts" : "Show keyboard shortcuts");
3447
+ i0.ɵɵadvance(2);
3448
+ i0.ɵɵproperty("ngIf", ctx.showShortcuts);
3449
+ } }, 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
3450
  };
349
3451
  TestSuiteFormComponentExtended = __decorate([
350
3452
  RegisterClass(BaseFormComponent, 'MJ: Test Suites')
@@ -352,9 +3454,15 @@ TestSuiteFormComponentExtended = __decorate([
352
3454
  export { TestSuiteFormComponentExtended };
353
3455
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(TestSuiteFormComponentExtended, [{
354
3456
  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 }); })();
3457
+ 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"] }]
3458
+ }], () => [{ type: i0.ElementRef }, { type: i1.SharedService }, { type: i2.Router }, { type: i2.ActivatedRoute }, { type: i0.ChangeDetectorRef }, { type: i3.TestingDialogService }, { type: i3.EvaluationPreferencesService }, { type: i0.ViewContainerRef }], { chartContainer: [{
3459
+ type: ViewChild,
3460
+ args: ['chartContainer']
3461
+ }], handleKeyboardShortcut: [{
3462
+ type: HostListener,
3463
+ args: ['document:keydown', ['$event']]
3464
+ }] }); })();
3465
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(TestSuiteFormComponentExtended, { className: "TestSuiteFormComponentExtended", filePath: "src/lib/custom/Tests/test-suite-form.component.ts", lineNumber: 30 }); })();
358
3466
  export function LoadTestSuiteFormComponentExtended() { }
359
3467
  LoadTestSuiteFormComponentExtended();
360
3468
  //# sourceMappingURL=test-suite-form.component.js.map