@testgorilla/tgo-coding-test 0.0.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 (163) hide show
  1. package/.eslintrc.json +45 -0
  2. package/README.md +257 -0
  3. package/jest.config.ts +21 -0
  4. package/ng-package.json +14 -0
  5. package/package.json +33 -0
  6. package/project.json +36 -0
  7. package/src/assets/i18n/en.json +124 -0
  8. package/src/index.ts +30 -0
  9. package/src/lib/components/.gitkeep +0 -0
  10. package/src/lib/components/code-editor/code-editor.component.html +10 -0
  11. package/src/lib/components/code-editor/code-editor.component.scss +21 -0
  12. package/src/lib/components/code-editor/code-editor.component.spec.ts +136 -0
  13. package/src/lib/components/code-editor/code-editor.component.ts +369 -0
  14. package/src/lib/components/code-editor/code-editor.mocks.ts +28 -0
  15. package/src/lib/components/code-editor/code-editor.service.spec.ts +160 -0
  16. package/src/lib/components/code-editor/code-editor.service.ts +94 -0
  17. package/src/lib/components/code-editor/helpers/c-helper.spec.ts +39 -0
  18. package/src/lib/components/code-editor/helpers/c-helper.ts +51 -0
  19. package/src/lib/components/code-editor/helpers/code-editor-helper.base.spec.ts +30 -0
  20. package/src/lib/components/code-editor/helpers/code-editor-helper.base.ts +16 -0
  21. package/src/lib/components/code-editor/helpers/code-editor-helper.mocks.ts +24 -0
  22. package/src/lib/components/code-editor/helpers/code-editor-helper.model.ts +67 -0
  23. package/src/lib/components/code-editor/helpers/cpp-helper.spec.ts +40 -0
  24. package/src/lib/components/code-editor/helpers/cpp-helper.ts +52 -0
  25. package/src/lib/components/code-editor/helpers/csharp-helper.spec.ts +42 -0
  26. package/src/lib/components/code-editor/helpers/csharp-helper.ts +55 -0
  27. package/src/lib/components/code-editor/helpers/go-helper.spec.ts +41 -0
  28. package/src/lib/components/code-editor/helpers/go-helper.ts +54 -0
  29. package/src/lib/components/code-editor/helpers/index.ts +15 -0
  30. package/src/lib/components/code-editor/helpers/java-helper.spec.ts +41 -0
  31. package/src/lib/components/code-editor/helpers/java-helper.ts +54 -0
  32. package/src/lib/components/code-editor/helpers/javascript-helper.spec.ts +39 -0
  33. package/src/lib/components/code-editor/helpers/javascript-helper.ts +32 -0
  34. package/src/lib/components/code-editor/helpers/kotlin-helper.spec.ts +41 -0
  35. package/src/lib/components/code-editor/helpers/kotlin-helper.ts +54 -0
  36. package/src/lib/components/code-editor/helpers/php-helper.spec.ts +39 -0
  37. package/src/lib/components/code-editor/helpers/php-helper.ts +32 -0
  38. package/src/lib/components/code-editor/helpers/python-helper.spec.ts +39 -0
  39. package/src/lib/components/code-editor/helpers/python-helper.ts +32 -0
  40. package/src/lib/components/code-editor/helpers/r-helper.spec.ts +39 -0
  41. package/src/lib/components/code-editor/helpers/r-helper.ts +32 -0
  42. package/src/lib/components/code-editor/helpers/ruby-helper.spec.ts +39 -0
  43. package/src/lib/components/code-editor/helpers/ruby-helper.ts +32 -0
  44. package/src/lib/components/code-editor/helpers/scala-helper.spec.ts +41 -0
  45. package/src/lib/components/code-editor/helpers/scala-helper.ts +53 -0
  46. package/src/lib/components/code-editor/helpers/sql-helper.spec.ts +87 -0
  47. package/src/lib/components/code-editor/helpers/sql-helper.ts +44 -0
  48. package/src/lib/components/code-editor/helpers/swift-helper.spec.ts +40 -0
  49. package/src/lib/components/code-editor/helpers/swift-helper.ts +51 -0
  50. package/src/lib/components/code-editor/helpers/typescript-helper.spec.ts +40 -0
  51. package/src/lib/components/code-editor/helpers/typescript-helper.ts +52 -0
  52. package/src/lib/components/code-editor/models/code-editor.model.ts +9 -0
  53. package/src/lib/components/code-editor/models/coding-snapshot.model.ts +4 -0
  54. package/src/lib/components/coding-question/coding-question.component.html +78 -0
  55. package/src/lib/components/coding-question/coding-question.component.scss +76 -0
  56. package/src/lib/components/coding-question/coding-question.component.spec.ts +85 -0
  57. package/src/lib/components/coding-question/coding-question.component.ts +102 -0
  58. package/src/lib/components/coding-section/coding-section.component.html +82 -0
  59. package/src/lib/components/coding-section/coding-section.component.scss +64 -0
  60. package/src/lib/components/coding-section/coding-section.component.spec.ts +257 -0
  61. package/src/lib/components/coding-section/coding-section.component.ts +187 -0
  62. package/src/lib/components/coding-test.module.ts +124 -0
  63. package/src/lib/components/common/truncated-text/truncated-text.component.html +6 -0
  64. package/src/lib/components/common/truncated-text/truncated-text.component.scss +18 -0
  65. package/src/lib/components/common/truncated-text/truncated-text.component.spec.ts +84 -0
  66. package/src/lib/components/common/truncated-text/truncated-text.component.ts +37 -0
  67. package/src/lib/components/configurations/configurations.component.html +57 -0
  68. package/src/lib/components/configurations/configurations.component.scss +42 -0
  69. package/src/lib/components/configurations/configurations.component.spec.ts +186 -0
  70. package/src/lib/components/configurations/configurations.component.ts +98 -0
  71. package/src/lib/components/instructions/instructions.component.html +41 -0
  72. package/src/lib/components/instructions/instructions.component.scss +167 -0
  73. package/src/lib/components/instructions/instructions.component.spec.ts +106 -0
  74. package/src/lib/components/instructions/instructions.component.ts +138 -0
  75. package/src/lib/components/panel/panel.component.html +19 -0
  76. package/src/lib/components/panel/panel.component.scss +41 -0
  77. package/src/lib/components/panel/panel.component.spec.ts +40 -0
  78. package/src/lib/components/panel/panel.component.ts +34 -0
  79. package/src/lib/components/runnable-editor/runnable-editor.component.html +75 -0
  80. package/src/lib/components/runnable-editor/runnable-editor.component.scss +55 -0
  81. package/src/lib/components/runnable-editor/runnable-editor.component.spec.ts +124 -0
  82. package/src/lib/components/runnable-editor/runnable-editor.component.ts +155 -0
  83. package/src/lib/components/tests/test-cases/test-cases.component.html +135 -0
  84. package/src/lib/components/tests/test-cases/test-cases.component.scss +220 -0
  85. package/src/lib/components/tests/test-cases/test-cases.component.spec.ts +401 -0
  86. package/src/lib/components/tests/test-cases/test-cases.component.ts +205 -0
  87. package/src/lib/components/tests/test-cases-content/test-cases-content.component.html +94 -0
  88. package/src/lib/components/tests/test-cases-content/test-cases-content.component.scss +103 -0
  89. package/src/lib/components/tests/test-cases-content/test-cases-content.component.spec.ts +122 -0
  90. package/src/lib/components/tests/test-cases-content/test-cases-content.component.ts +102 -0
  91. package/src/lib/components/tests/test-cases-status/test-cases-status.component.html +16 -0
  92. package/src/lib/components/tests/test-cases-status/test-cases-status.component.scss +49 -0
  93. package/src/lib/components/tests/test-cases-status/test-cases-status.component.spec.ts +22 -0
  94. package/src/lib/components/tests/test-cases-status/test-cases-status.component.ts +18 -0
  95. package/src/lib/components/tests/test-results.component.html +119 -0
  96. package/src/lib/components/tests/test-results.component.scss +189 -0
  97. package/src/lib/components/tests/test-results.component.spec.ts +140 -0
  98. package/src/lib/components/tests/test-results.component.ts +98 -0
  99. package/src/lib/components/tgo-coding-test/tgo-coding-test.component.html +96 -0
  100. package/src/lib/components/tgo-coding-test/tgo-coding-test.component.scss +6 -0
  101. package/src/lib/components/tgo-coding-test/tgo-coding-test.component.spec.ts +599 -0
  102. package/src/lib/components/tgo-coding-test/tgo-coding-test.component.ts +279 -0
  103. package/src/lib/components/tgo-coding-test-candidate-view/tgo-coding-test-candidate-view.component.html +36 -0
  104. package/src/lib/components/tgo-coding-test-candidate-view/tgo-coding-test-candidate-view.component.scss +183 -0
  105. package/src/lib/components/tgo-coding-test-candidate-view/tgo-coding-test-candidate-view.component.spec.ts +883 -0
  106. package/src/lib/components/tgo-coding-test-candidate-view/tgo-coding-test-candidate-view.component.ts +575 -0
  107. package/src/lib/config/index.ts +3 -0
  108. package/src/lib/config/tgo-coding-test.config.ts +26 -0
  109. package/src/lib/config/tgo-coding-test.provider.ts +38 -0
  110. package/src/lib/config/tgo-coding-test.token.ts +21 -0
  111. package/src/lib/models/.gitkeep +0 -0
  112. package/src/lib/models/auto-saved-data.ts +51 -0
  113. package/src/lib/models/code-event.ts +17 -0
  114. package/src/lib/models/coderunner-execution-results.ts +58 -0
  115. package/src/lib/models/coding-lib.mocks.ts +246 -0
  116. package/src/lib/models/configs.ts +18 -0
  117. package/src/lib/models/language-change-action.ts +4 -0
  118. package/src/lib/models/lat-languages.ts +12 -0
  119. package/src/lib/models/mixpanel-events.ts +3 -0
  120. package/src/lib/models/mode.ts +5 -0
  121. package/src/lib/models/paste-data.ts +4 -0
  122. package/src/lib/models/programming-language.ts +9 -0
  123. package/src/lib/models/test-cases.ts +74 -0
  124. package/src/lib/models/theme.ts +5 -0
  125. package/src/lib/models/translations.ts +1 -0
  126. package/src/lib/models/view-mode.ts +6 -0
  127. package/src/lib/pipes/memoize-func.pipe.ts +34 -0
  128. package/src/lib/services/.gitkeep +0 -0
  129. package/src/lib/services/candidate-coding-test-services/candidature-api.service.spec.ts +40 -0
  130. package/src/lib/services/candidate-coding-test-services/candidature-api.service.ts +15 -0
  131. package/src/lib/services/candidate-coding-test-services/coderunner-api.service.spec.ts +134 -0
  132. package/src/lib/services/candidate-coding-test-services/coderunner-api.service.ts +105 -0
  133. package/src/lib/services/candidate-coding-test-services/coding-test-tour.service.spec.ts +161 -0
  134. package/src/lib/services/candidate-coding-test-services/coding-test-tour.service.ts +100 -0
  135. package/src/lib/services/candidate-coding-test-services/coding-test.service.spec.ts +1524 -0
  136. package/src/lib/services/candidate-coding-test-services/coding-test.service.ts +843 -0
  137. package/src/lib/services/candidate-coding-test-services/index.ts +4 -0
  138. package/src/lib/services/coding-test-config.service.ts +48 -0
  139. package/src/lib/services/configurations.service.mocks.ts +77 -0
  140. package/src/lib/services/configurations.service.spec.ts +79 -0
  141. package/src/lib/services/configurations.service.ts +111 -0
  142. package/src/lib/services/index.ts +0 -0
  143. package/src/lib/services/lib-coding-test.service.spec.ts +265 -0
  144. package/src/lib/services/lib-coding-test.service.ts +157 -0
  145. package/src/lib/services/local-storage.service.mocks.ts +22 -0
  146. package/src/lib/services/storage.service.spec.ts +1120 -0
  147. package/src/lib/services/storage.service.ts +729 -0
  148. package/src/lib/services/test-cases.service.spec.ts +53 -0
  149. package/src/lib/services/test-cases.service.ts +29 -0
  150. package/src/lib/services/theme.service.spec.ts +76 -0
  151. package/src/lib/services/theme.service.ts +34 -0
  152. package/src/lib/styles/mixins.scss +86 -0
  153. package/src/lib/styles/styles.scss +112 -0
  154. package/src/lib/styles/variables.scss +105 -0
  155. package/src/lib/utils/.gitkeep +0 -0
  156. package/src/lib/utils/additional-languages/erlang.ts +115 -0
  157. package/src/lib/utils/resize-element.ts +15 -0
  158. package/src/lib/utils/time-to-ms.util.ts +10 -0
  159. package/src/test-setup.ts +1 -0
  160. package/tsconfig.json +16 -0
  161. package/tsconfig.lib.json +12 -0
  162. package/tsconfig.lib.prod.json +9 -0
  163. package/tsconfig.spec.json +13 -0
@@ -0,0 +1,401 @@
1
+ import { ChangeDetectorRef, NO_ERRORS_SCHEMA, SimpleChanges } from '@angular/core';
2
+ import { ComponentFixture, discardPeriodicTasks, fakeAsync, TestBed, tick } from '@angular/core/testing';
3
+ import { NoopAnimationsModule } from '@angular/platform-browser/animations';
4
+ import { MockProvider } from 'ng-mocks';
5
+ import { Subject } from 'rxjs';
6
+ import { TestCaseDeletionAction, TestCaseDeletionData, TestCaseStatus } from '../../../models/test-cases';
7
+ import { LibCodingTestService } from '../../../services/lib-coding-test.service';
8
+ import { StorageCodingService } from '../../../services/storage.service';
9
+ import { TestCasesService } from '../../../services/test-cases.service';
10
+ import { TestCasesComponent } from './test-cases.component';
11
+
12
+ describe('TestCasesComponent', () => {
13
+ let component: TestCasesComponent;
14
+ let fixture: ComponentFixture<TestCasesComponent>;
15
+ let libCodingTestService: LibCodingTestService;
16
+ let testCasesService: TestCasesService;
17
+ let updateTestCasesSpy: jest.SpyInstance;
18
+ let setActiveTestCaseSpy: jest.SpyInstance;
19
+ const testCaseDeletionSubj$ = new Subject<TestCaseDeletionData>();
20
+ let lastTestCaseLocalId = 0;
21
+
22
+ beforeEach(async () => {
23
+ await TestBed.configureTestingModule({
24
+ imports: [TestCasesComponent, NoopAnimationsModule],
25
+ schemas: [NO_ERRORS_SCHEMA],
26
+ providers: [
27
+ MockProvider(LibCodingTestService, {
28
+ handleTestCaseDeletion: jest.fn(),
29
+ updateTestCases: jest.fn(),
30
+ testCaseDeletion$: testCaseDeletionSubj$.asObservable(),
31
+ }),
32
+ MockProvider(TestCasesService, {
33
+ activeTestCaseIndex$: new Subject(),
34
+ createTestCaseLocalId() {
35
+ return (lastTestCaseLocalId += 1);
36
+ },
37
+ setActiveTestCase(index) {
38
+ this.activeTestCaseIndex$.next(index);
39
+ },
40
+ }),
41
+ MockProvider(ChangeDetectorRef),
42
+ MockProvider(StorageCodingService),
43
+ ],
44
+ })
45
+ .overrideComponent(TestCasesComponent, {
46
+ set: {
47
+ template: '',
48
+ },
49
+ })
50
+ .compileComponents();
51
+
52
+ fixture = TestBed.createComponent(TestCasesComponent);
53
+ component = fixture.componentInstance;
54
+ component.exampleTestCases = [
55
+ {
56
+ id: 1,
57
+ name: 'Example 1',
58
+ input: '1 2 3',
59
+ expectedOutput: '4',
60
+ preloaded: true,
61
+ },
62
+ {
63
+ id: 2,
64
+ name: 'Example 2',
65
+ input: '1 2 5',
66
+ expectedOutput: '5',
67
+ preloaded: true,
68
+ },
69
+ ];
70
+ component.testCases = [];
71
+ component.translations = {
72
+ TOOLTIPS: {
73
+ CANT_ADD_TEST_CASE: 'You cannot add more than 6 of your own tests.',
74
+ },
75
+ TEST_RESULTS: {
76
+ TEST_CASES: {
77
+ STATUSES: {
78
+ PASSED: 'Passed',
79
+ ERROR: 'Error',
80
+ EMPTY: 'Empty',
81
+ },
82
+ ADD_TEST_CASE: 'Add Test Case',
83
+ },
84
+ },
85
+ };
86
+ libCodingTestService = TestBed.inject(LibCodingTestService);
87
+ testCasesService = TestBed.inject(TestCasesService);
88
+ updateTestCasesSpy = jest.spyOn(libCodingTestService, 'updateTestCases');
89
+ setActiveTestCaseSpy = jest.spyOn(testCasesService, 'setActiveTestCase');
90
+ fixture.detectChanges();
91
+ // Mock tabGroup since template is overridden
92
+ component.tabGroup = { _groupId: '0' } as any;
93
+ });
94
+
95
+ describe('when component is initialized', () => {
96
+ it('should subscribe to activeTestCaseIndex$ and set activeTestCaseIndex with emitted value', fakeAsync(() => {
97
+ const activeIndex = 3;
98
+ component.ngOnInit();
99
+ testCasesService.setActiveTestCase(activeIndex);
100
+
101
+ tick();
102
+ expect(component.activeTestCaseIndex).toBe(activeIndex);
103
+ }));
104
+
105
+ describe('when testcases is empty array', () => {
106
+ it('should assign exampleTestCases to testCases and reset the index to 0', () => {
107
+ component.ngOnInit();
108
+ expect(component.testCases).toEqual(component.exampleTestCases);
109
+ expect(component.activeTestCaseIndex).toBe(0);
110
+ });
111
+ });
112
+
113
+ describe('when testcases is NOT empty array', () => {
114
+ it('should not modify testcases', () => {
115
+ const testCases = [
116
+ {
117
+ id: 1,
118
+ name: 'Example 1',
119
+ input: '1 2 3',
120
+ expectedOutput: '4',
121
+ preloaded: true,
122
+ },
123
+ {
124
+ id: 2,
125
+ name: 'Example 2',
126
+ input: '1 2 5',
127
+ expectedOutput: '5',
128
+ preloaded: true,
129
+ },
130
+ {
131
+ name: 'Test 1',
132
+ input: '1 2 5',
133
+ expectedOutput: '5',
134
+ preloaded: true,
135
+ },
136
+ ];
137
+ component.testCases = [...testCases];
138
+ component.ngOnInit();
139
+ expect(component.testCases).toEqual(testCases);
140
+ });
141
+ });
142
+
143
+ describe('and listenTestCaseDeletion is called', () => {
144
+ beforeEach(() => {
145
+ const testCases = [
146
+ {
147
+ id: 1,
148
+ name: 'Example 1',
149
+ input: '1 2 3',
150
+ expectedOutput: '4',
151
+ preloaded: true,
152
+ },
153
+ {
154
+ localId: 1,
155
+ name: 'Test 1',
156
+ input: '1 2 5',
157
+ expectedOutput: '5',
158
+ preloaded: true,
159
+ },
160
+ ];
161
+ component.testCases = [...testCases];
162
+ component.testCasesResult = [
163
+ {
164
+ actualOutput: 'something',
165
+ logs: 'something',
166
+ status: TestCaseStatus.Passed,
167
+ },
168
+ {
169
+ actualOutput: 'something 1',
170
+ logs: 'something 1',
171
+ status: TestCaseStatus.Passed,
172
+ },
173
+ ];
174
+ component.canAddCustomTestCases = true;
175
+ });
176
+
177
+ it('should NOT remove test case if action is NOT Confirm', fakeAsync(() => {
178
+ component.ngOnInit();
179
+ testCaseDeletionSubj$.next({ action: TestCaseDeletionAction.Try, index: 1 });
180
+
181
+ tick();
182
+ expect(component.testCases.length).toBe(2);
183
+ expect(component.testCasesResult.length).toBe(2);
184
+ }));
185
+
186
+ it('should remove test case if action is Confirm and call updateTestCases with testCases', fakeAsync(() => {
187
+ component.ngOnInit();
188
+ testCaseDeletionSubj$.next({ action: TestCaseDeletionAction.Confirm, index: 1 });
189
+
190
+ tick();
191
+ expect(component.testCases.length).toBe(1);
192
+ expect(component.testCasesResult.length).toBe(1);
193
+ expect(updateTestCasesSpy).toHaveBeenCalledWith(component.testCases);
194
+ discardPeriodicTasks();
195
+ }));
196
+
197
+ it('should call closeTestCaseMobileView if isMobile true', fakeAsync(() => {
198
+ const closeTestCaseMobileViewSpy = jest.spyOn(component, 'closeTestCaseMobileView');
199
+ component.isMobile = true;
200
+
201
+ component.ngOnInit();
202
+ testCaseDeletionSubj$.next({ action: TestCaseDeletionAction.Confirm, index: 1 });
203
+
204
+ tick();
205
+ expect(closeTestCaseMobileViewSpy).toHaveBeenCalled();
206
+ discardPeriodicTasks();
207
+ }));
208
+
209
+ it('should set activeTestCase to previous item', fakeAsync(() => {
210
+ component.ngOnInit();
211
+ testCaseDeletionSubj$.next({ action: TestCaseDeletionAction.Confirm, index: 1 });
212
+
213
+ tick();
214
+ expect(component.activeTestCaseIndex).toBe(0);
215
+ discardPeriodicTasks();
216
+ }));
217
+
218
+ it('should set isAddTestCasesDisabled with true if now testCases count is less then maxTests', fakeAsync(() => {
219
+ component.testCases = [];
220
+ for (let i = 0; i < component.maxTestCasesAllowed; i++) {
221
+ component.addTestCase();
222
+ }
223
+
224
+ component.ngOnInit();
225
+ testCaseDeletionSubj$.next({ action: TestCaseDeletionAction.Confirm, index: 1 });
226
+
227
+ tick();
228
+ expect(component.isAddTestCasesDisabled).toBe(true);
229
+ discardPeriodicTasks();
230
+ }));
231
+
232
+ it('should set isAddTestCasesDisabled with false if testCases count is equal or more then maxTests', fakeAsync(() => {
233
+ component.testCases = [];
234
+ for (let i = 0; i < component.maxTestCasesAllowed + 1; i++) {
235
+ component.addTestCase();
236
+ }
237
+
238
+ component.ngOnInit();
239
+ testCaseDeletionSubj$.next({ action: TestCaseDeletionAction.Confirm, index: 1 });
240
+
241
+ tick();
242
+ expect(component.isAddTestCasesDisabled).toBe(true);
243
+ discardPeriodicTasks();
244
+ }));
245
+ });
246
+ });
247
+
248
+ describe('when ngOnChanges is called', () => {
249
+ it('should NOT set testCasesResult property when apiTestCasesResult has falsy currentValue', () => {
250
+ const changes = { apiTestCasesResult: { currentValue: null } } as unknown as SimpleChanges;
251
+ component.testCasesResult = [];
252
+
253
+ component.ngOnChanges(changes);
254
+
255
+ expect(component.testCasesResult).toEqual([]);
256
+ });
257
+
258
+ it('should add TestCaseResult for empty testCase with Empty status', () => {
259
+ const changes = {
260
+ apiTestCasesResult: {
261
+ currentValue: [
262
+ {
263
+ actualOutput: 'something',
264
+ logs: 'something',
265
+ status: TestCaseStatus.Passed,
266
+ },
267
+ {
268
+ actualOutput: 'something',
269
+ logs: 'something',
270
+ status: TestCaseStatus.Passed,
271
+ },
272
+ ],
273
+ },
274
+ } as unknown as SimpleChanges;
275
+ component.apiTestCasesResult = [...changes.apiTestCasesResult.currentValue];
276
+ component.addTestCase();
277
+
278
+ component.ngOnChanges(changes);
279
+
280
+ expect(component.testCasesResult[component.testCases.length - 1]).toEqual({
281
+ actualOutput: null,
282
+ logs: null,
283
+ status: TestCaseStatus.Empty,
284
+ });
285
+ });
286
+ });
287
+
288
+ describe('when addTestCase is called', () => {
289
+ it('should add a new testcase in the array and call onTestCasesChanged', () => {
290
+ const newTestCase = {
291
+ localId: lastTestCaseLocalId + 1,
292
+ name: `Test ${lastTestCaseLocalId + 1}`,
293
+ input: '',
294
+ expectedOutput: '',
295
+ preloaded: false,
296
+ };
297
+
298
+ component.addTestCase();
299
+ expect(component.testCases[component.testCases.length - 1]).toEqual(newTestCase);
300
+ expect(updateTestCasesSpy).toHaveBeenCalledWith(component.testCases);
301
+ });
302
+
303
+ it('should set new test case as active, set isContentViewOpened', () => {
304
+ component.addTestCase();
305
+
306
+ expect(setActiveTestCaseSpy).toHaveBeenCalledWith(component.testCases.length - 1);
307
+ expect(component.isContentViewOpened).toBe(component.isMobile);
308
+ });
309
+
310
+ it('should update isAddTestCasesDisabled with true if less than maximum test cases number', () => {
311
+ component.addTestCase();
312
+
313
+ expect(component.isAddTestCasesDisabled).toBe(true);
314
+ });
315
+
316
+ it('should update isAddTestCasesDisabled with false if equal or more than maximum test cases number', () => {
317
+ for (let i = 0; i < component.maxTestCasesAllowed; i++) {
318
+ component.addTestCase();
319
+ }
320
+
321
+ expect(component.isAddTestCasesDisabled).toBe(false);
322
+ });
323
+ });
324
+
325
+ describe('when removeTestCase is called', () => {
326
+ it('should remove testcase in the array with the passed index and set the index to previous tab', () => {
327
+ const indexToRemove = 2;
328
+ const handleTestCaseDeletionSpy = jest.spyOn(libCodingTestService, 'handleTestCaseDeletion');
329
+
330
+ component.removeTestCase(indexToRemove);
331
+
332
+ expect(handleTestCaseDeletionSpy).toHaveBeenCalledWith(indexToRemove);
333
+ });
334
+ });
335
+
336
+ describe('when modifyTestCase is called', () => {
337
+ it('should modify the input and expectedOutput of a test case when not preloaded', () => {
338
+ component.testCases = [
339
+ { name: 'Test 1', input: 'Input 1', expectedOutput: 'Output 1', preloaded: false },
340
+ { name: 'Test 2', input: 'Input 2', expectedOutput: 'Output 2', preloaded: false },
341
+ ];
342
+ const indexToModify = 1;
343
+ const modifiedValue = { input: 'Modified Input', expectedOutput: 'Modified Output' };
344
+
345
+ component.modifyTestCase(indexToModify, modifiedValue);
346
+
347
+ expect(updateTestCasesSpy).toHaveBeenCalledWith(component.testCases);
348
+ expect(component.testCases[indexToModify].input).toBe('Modified Input');
349
+ expect(component.testCases[indexToModify].expectedOutput).toBe('Modified Output');
350
+ });
351
+ it('should not modify the input and expectedOutput when the test case is preloaded', () => {
352
+ component.testCases = [
353
+ { id: 1, name: 'Example 1', input: 'Input 1', expectedOutput: 'Output 1', preloaded: true },
354
+ ];
355
+ const indexToModify = 0; // Trying to modify a preloaded test case
356
+ const modifiedValue = { input: 'Modified Input', expectedOutput: 'Modified Output' };
357
+
358
+ component.modifyTestCase(indexToModify, modifiedValue);
359
+
360
+ expect(component.testCases[indexToModify].input).toBe('Input 1'); // Input should not be modified
361
+ expect(component.testCases[indexToModify].expectedOutput).toBe('Output 1'); // Output should not be modified
362
+ });
363
+ });
364
+
365
+ describe('when modifyTestCase is called', () => {
366
+ it('should set the active test case index to the provided index', () => {
367
+ const index = 1;
368
+
369
+ component.setActiveTestCaseIndex(index);
370
+
371
+ expect(setActiveTestCaseSpy).toHaveBeenCalledWith(index);
372
+ });
373
+
374
+ it('should set isContentViewOpened to true if isMobile true', () => {
375
+ component.isMobile = true;
376
+
377
+ component.setActiveTestCaseIndex(0);
378
+
379
+ expect(component.isContentViewOpened).toBe(true);
380
+ });
381
+
382
+ it('should set isContentViewOpened to false if isMobile false', () => {
383
+ component.isMobile = false;
384
+
385
+ component.setActiveTestCaseIndex(0);
386
+
387
+ expect(component.isContentViewOpened).toBe(false);
388
+ });
389
+ });
390
+
391
+ describe('when closeTestCaseMobileView is called', () => {
392
+ it('should set isContentViewOpened to false and reset activeTestCase', () => {
393
+ component.isContentViewOpened = true;
394
+
395
+ component.closeTestCaseMobileView();
396
+
397
+ expect(component.isContentViewOpened).toBe(false);
398
+ expect(setActiveTestCaseSpy).toHaveBeenCalledWith(null);
399
+ });
400
+ });
401
+ });
@@ -0,0 +1,205 @@
1
+ import {
2
+ ChangeDetectionStrategy,
3
+ ChangeDetectorRef,
4
+ Component,
5
+ Input,
6
+ OnChanges,
7
+ OnInit,
8
+ SimpleChanges,
9
+ ViewChild,
10
+ } from '@angular/core';
11
+ import { CommonModule } from '@angular/common';
12
+ import { MatTabGroup, MatTabsModule } from '@angular/material/tabs';
13
+ import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
14
+ import { filter } from 'rxjs';
15
+ import { ApplicationTheme, IconComponentModule, ButtonComponentModule } from '@testgorilla/tgo-ui';
16
+ import {
17
+ AllowedModificationFields,
18
+ CustomTestCase,
19
+ ExampleTestCase,
20
+ TestCaseDeletionAction,
21
+ TestCaseResult,
22
+ TestCaseStatus,
23
+ } from '../../../models/test-cases';
24
+ import { LibCodingTestService } from '../../../services/lib-coding-test.service';
25
+ import { StorageCodingService } from '../../../services/storage.service';
26
+ import { TestCasesService } from '../../../services/test-cases.service';
27
+ import { TestCasesStatusComponent } from '../test-cases-status/test-cases-status.component';
28
+ import { TestCasesContentComponent } from '../test-cases-content/test-cases-content.component';
29
+
30
+ @UntilDestroy()
31
+ @Component({
32
+ standalone: true,
33
+ selector: 'tgo-test-cases',
34
+ templateUrl: 'test-cases.component.html',
35
+ styleUrls: ['test-cases.component.scss'],
36
+ changeDetection: ChangeDetectionStrategy.OnPush,
37
+ imports: [
38
+ CommonModule,
39
+ MatTabsModule,
40
+ IconComponentModule,
41
+ ButtonComponentModule,
42
+ TestCasesStatusComponent,
43
+ TestCasesContentComponent,
44
+ ],
45
+ })
46
+ export class TestCasesComponent implements OnInit, OnChanges {
47
+ @Input() exampleTestCases: ExampleTestCase[];
48
+ @Input() apiTestCasesResult: TestCaseResult[];
49
+ @Input() testCasesStatus: any;
50
+ @Input() testCases: (CustomTestCase | ExampleTestCase)[];
51
+ @Input() translations: Record<string, unknown>;
52
+ @Input() areTestsRun: boolean;
53
+ @Input() applicationTheme: ApplicationTheme;
54
+ @Input() canAddCustomTestCases: boolean;
55
+ @Input() isSQL: boolean;
56
+
57
+ @ViewChild('tabGroup') tabGroup: MatTabGroup;
58
+
59
+ testCasesResult: TestCaseResult[];
60
+
61
+ activeTestCaseIndex: number | null;
62
+ maxTestCasesAllowed = 6; //Could be taken from backend
63
+
64
+ isAddTestCasesDisabled: boolean;
65
+ isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
66
+
67
+ isContentViewOpened: boolean;
68
+
69
+ constructor(
70
+ private libCodingTestService: LibCodingTestService,
71
+ private changeDetectorRef: ChangeDetectorRef,
72
+ private testCasesService: TestCasesService,
73
+ private StorageCodingService: StorageCodingService
74
+ ) {}
75
+
76
+ ngOnInit() {
77
+ this.testCasesService.activeTestCaseIndex$.pipe(untilDestroyed(this)).subscribe(index => {
78
+ this.activeTestCaseIndex = index;
79
+ });
80
+
81
+ this.setDefaultTestCases();
82
+ if (this.canAddCustomTestCases) {
83
+ this.updateAddCustomTestCaseDisabled();
84
+ this.listenTestCaseDeletion();
85
+ }
86
+ }
87
+
88
+ ngOnChanges({ apiTestCasesResult }: SimpleChanges): void {
89
+ if (!apiTestCasesResult) {
90
+ return;
91
+ }
92
+ const apiTestCases: TestCaseResult[] = apiTestCasesResult.currentValue;
93
+
94
+ if (apiTestCases) {
95
+ this.testCasesResult = apiTestCases.length ? this.handleEmptyTestCases(apiTestCases) : [];
96
+ }
97
+ }
98
+
99
+ private setDefaultTestCases() {
100
+ if (!this.testCases.length) {
101
+ this.testCases = [...(this.exampleTestCases || [])];
102
+ this.onTestCasesChanged();
103
+ this.setActiveTestCaseIndex(this.isMobile ? null : 0);
104
+ }
105
+ }
106
+
107
+ addTestCase(): void {
108
+ const testCaseLocalId = this.testCasesService.createTestCaseLocalId();
109
+
110
+ this.testCases.push({
111
+ localId: testCaseLocalId,
112
+ name: `Test ${testCaseLocalId}`,
113
+ input: '',
114
+ expectedOutput: '',
115
+ preloaded: false,
116
+ });
117
+ this.onTestCasesChanged();
118
+ this.testCasesService.setActiveTestCase(this.testCases.length - 1);
119
+ this.isContentViewOpened = this.isMobile;
120
+ this.updateAddCustomTestCaseDisabled();
121
+
122
+ if (!this.isMobile) {
123
+ this.setFocus();
124
+ }
125
+ }
126
+
127
+ removeTestCase(index: number): void {
128
+ this.libCodingTestService.handleTestCaseDeletion(index);
129
+ }
130
+
131
+ modifyTestCase(index: number, modifiedValue: AllowedModificationFields): void {
132
+ if (!this.testCases[index].preloaded) {
133
+ this.testCases[index].input = modifiedValue.input;
134
+ this.testCases[index].expectedOutput = modifiedValue.expectedOutput;
135
+ this.onTestCasesChanged();
136
+ }
137
+ }
138
+
139
+ private setFocus() {
140
+ const tabHeaderId = `mat-tab-label-${this.tabGroup['_groupId']}-${this.activeTestCaseIndex}`;
141
+ const tabHeaderElement: HTMLElement = document.getElementById(tabHeaderId);
142
+ if (tabHeaderElement) {
143
+ tabHeaderElement.focus();
144
+ }
145
+ }
146
+
147
+ private onTestCasesChanged() {
148
+ this.libCodingTestService.updateTestCases([...this.testCases]);
149
+ this.StorageCodingService.updateTestCases([...this.testCases]);
150
+ }
151
+
152
+ setActiveTestCaseIndex(index: number): void {
153
+ this.testCasesService.setActiveTestCase(index);
154
+ this.isContentViewOpened = index !== null ? this.isMobile : false;
155
+ }
156
+
157
+ closeTestCaseMobileView(): void {
158
+ this.isContentViewOpened = false;
159
+ this.testCasesService.setActiveTestCase(null);
160
+ }
161
+
162
+ private listenTestCaseDeletion(): void {
163
+ this.libCodingTestService.testCaseDeletion$
164
+ .pipe(
165
+ filter(({ action }) => action === TestCaseDeletionAction.Confirm),
166
+ untilDestroyed(this)
167
+ )
168
+ .subscribe(({ index }) => {
169
+ this.testCases.splice(index, 1);
170
+ this.testCasesResult.splice(index, 1);
171
+ this.onTestCasesChanged();
172
+
173
+ if (this.isMobile) {
174
+ this.closeTestCaseMobileView();
175
+ } else {
176
+ this.testCasesService.setActiveTestCase(index - 1);
177
+ }
178
+
179
+ this.updateAddCustomTestCaseDisabled();
180
+ this.changeDetectorRef.detectChanges();
181
+ });
182
+ }
183
+
184
+ private handleEmptyTestCases(apiTestCasesResults: TestCaseResult[]): TestCaseResult[] {
185
+ const testCasesResult = [...apiTestCasesResults];
186
+
187
+ this.testCases.forEach(({ input, expectedOutput }, index) => {
188
+ const isEmpty = this.isSQL ? !expectedOutput : !input || !expectedOutput;
189
+ if (isEmpty) {
190
+ testCasesResult.splice(index, 0, {
191
+ actualOutput: null,
192
+ logs: null,
193
+ status: TestCaseStatus.Empty,
194
+ });
195
+ }
196
+ });
197
+
198
+ return testCasesResult;
199
+ }
200
+
201
+ private updateAddCustomTestCaseDisabled(): void {
202
+ const exampleTestCasesCount = this.exampleTestCases?.length || 0;
203
+ this.isAddTestCasesDisabled = this.testCases.length - exampleTestCasesCount < this.maxTestCasesAllowed;
204
+ }
205
+ }
@@ -0,0 +1,94 @@
1
+ @if (testCase) {
2
+ <div class="test-cases-content" [class.test-cases-content-tests-run]="areTestsRun">
3
+ @if (areTestsRun && isCustomTestCase && (testCaseResult?.status | memoizeFunc: isEmptyTestCase)) {
4
+ <div class="custom-tests-hint-container">
5
+ <p class="custom-tests-info">{{ translations['BOTH_FIELDS_MUST_BE_FILLED'] }}</p>
6
+ </div>
7
+ } @if (!isCustomTestCase && !areTestsRun) {
8
+ <p class="custom-tests-info custom-tests-info-indented">
9
+ {{ translations['EXAMPLE_TEST_CASE_INFO'] }}
10
+ </p>
11
+ } @if (isCustomTestCase && (!testCaseResult || (testCaseResult?.status | memoizeFunc: isEmptyTestCase))) {
12
+ <p class="custom-tests-info custom-tests-info-indented">
13
+ {{ translations['FILL_ALL_FIELDS'] }}
14
+ </p>
15
+ } @if (testCaseResult && !(testCaseResult?.status | memoizeFunc: isEmptyTestCase)) {
16
+ <div class="custom-tests-logs">
17
+ <div class="custom-tests-log-row">
18
+ <h4 class="custom-tests-log-header">{{ translations['LOG'] }}</h4>
19
+ <div class="custom-tests-log-content">
20
+ <tgo-truncated-text [text]="testCaseResult.logs" class="custom-tests-info"></tgo-truncated-text>
21
+ </div>
22
+ </div>
23
+ @if (isSQL) {
24
+ <div class="custom-tests-log-row custom-tests-log-row-cols">
25
+ <div class="custom-tests-log-col custom-tests-log-col-expected-query">
26
+ <div class="custom-tests-log-heading">
27
+ <h4 class="custom-tests-log-header">{{ translations['EXPECTED_QUERY'] }}</h4>
28
+ </div>
29
+ <div class="custom-tests-log-content">
30
+ <tgo-truncated-text
31
+ [text]="testCase.expectedOutput"
32
+ [isWordWrap]="true"
33
+ class="custom-tests-info"
34
+ ></tgo-truncated-text>
35
+ </div>
36
+ </div>
37
+ <div class="custom-tests-log-col">
38
+ <div class="custom-tests-log-heading">
39
+ <h4 class="custom-tests-log-header">{{ translations['QUERY_RESULT'] }}</h4>
40
+ <tgo-test-cases-status
41
+ [status]="testCaseResult.status"
42
+ [translations]="translations['STATUSES']"
43
+ ></tgo-test-cases-status>
44
+ </div>
45
+ <div class="custom-tests-log-content">
46
+ <tgo-truncated-text
47
+ [text]="testCaseResult.actualOutput"
48
+ [isWordWrap]="true"
49
+ class="custom-tests-info"
50
+ ></tgo-truncated-text>
51
+ </div>
52
+ </div>
53
+ </div>
54
+ } @else {
55
+ <div class="custom-tests-log-row">
56
+ <div class="custom-tests-log-heading">
57
+ <h4 class="custom-tests-log-header">{{ translations['OUTPUT'] }}</h4>
58
+ <tgo-test-cases-status
59
+ [status]="testCaseResult.status"
60
+ [translations]="translations['STATUSES']"
61
+ ></tgo-test-cases-status>
62
+ </div>
63
+ <div class="custom-tests-log-content">
64
+ <tgo-truncated-text [text]="testCaseResult.actualOutput" class="custom-tests-info"></tgo-truncated-text>
65
+ </div>
66
+ </div>
67
+ }
68
+ </div>
69
+ } @if (!isSQL) {
70
+ <form [formGroup]="form">
71
+ <div class="custom-tests-inputs-container">
72
+ <ui-field
73
+ class="custom-tests-input"
74
+ [label]="translations['INPUT']"
75
+ [type]="'multi-line'"
76
+ [disabled]="testCase.preloaded"
77
+ [maxRows]="3"
78
+ [applicationTheme]="applicationTheme"
79
+ formControlName="input"
80
+ ></ui-field>
81
+ <ui-field
82
+ class="custom-tests-input"
83
+ [label]="translations['EXPECTED_OUTPUT']"
84
+ [type]="'multi-line'"
85
+ [maxRows]="3"
86
+ [disabled]="testCase.preloaded"
87
+ [applicationTheme]="applicationTheme"
88
+ formControlName="expectedOutput"
89
+ ></ui-field>
90
+ </div>
91
+ </form>
92
+ }
93
+ </div>
94
+ }