@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,1120 @@
1
+ import { MockService } from 'ng-mocks';
2
+ import { LocalStorageService, StorageStrategies, StorageStrategyStub } from 'ngx-webstorage';
3
+ import { Subject } from 'rxjs';
4
+
5
+ import { CodeEditorLanguages } from '../components/code-editor/helpers/code-editor-helper.model';
6
+ import { AutoSavedData } from '../models/auto-saved-data';
7
+ import { CodingAssistanceOptions, Config, FormattingOptions } from '../models/configs';
8
+ import { AssessmentIdPreview } from '../models/lat-languages';
9
+ import { Modes } from '../models/mode';
10
+ import { Themes } from '../models/theme';
11
+ import { ConfigurationsService } from './configurations.service';
12
+ import { CONFIGURATIONS_SERVICE_MOCK } from './configurations.service.mocks';
13
+ import { LibCodingTestService } from './lib-coding-test.service';
14
+ import { LocalStorageServiceStub } from './local-storage.service.mocks';
15
+ import { StorageCodingService } from './storage.service';
16
+
17
+ describe('StorageCodingService', () => {
18
+ let service: StorageCodingService;
19
+ let storage: LocalStorageService;
20
+ let configurationsService: ConfigurationsService;
21
+ let libCodingTestService: LibCodingTestService;
22
+ const key = 'savedData';
23
+ let config: Config;
24
+
25
+ const assessmentId = 'abc-def-ghi';
26
+ let data: AutoSavedData;
27
+ let LATData: AutoSavedData;
28
+
29
+ beforeEach(() => {
30
+ storage = new LocalStorageServiceStub(new StorageStrategyStub(StorageStrategies.Local));
31
+ configurationsService = MockService(ConfigurationsService, CONFIGURATIONS_SERVICE_MOCK);
32
+ libCodingTestService = MockService(LibCodingTestService, {
33
+ isFullscreen$: new Subject<boolean>(),
34
+ setFullscreen(val) {
35
+ this.isFullscreen$.next(val);
36
+ },
37
+ codeChange$: new Subject<string>(),
38
+ changeCurrentCode(code: string) {
39
+ this.codeChange$.next(code);
40
+ },
41
+ currentLanguage$: new Subject<CodeEditorLanguages>(),
42
+ changeCurrentLanguage(lang: string) {
43
+ this.currentLanguage$.next(lang);
44
+ },
45
+ getVersion() {
46
+ return '1.2';
47
+ },
48
+ });
49
+
50
+ service = new StorageCodingService(storage, libCodingTestService, configurationsService);
51
+ config = configurationsService.getInitialConfig();
52
+ config[FormattingOptions.ColorTheme] = Themes.Dark;
53
+ data = {
54
+ version: 2,
55
+ config,
56
+ isFullscreen: true,
57
+ assessments: {},
58
+ lastModified: Date.now(),
59
+ };
60
+ data.assessments[assessmentId] = {
61
+ 'question-123': {
62
+ lastModified: Date.now(),
63
+ actual: {
64
+ code: {
65
+ [CodeEditorLanguages.Python]: 'def',
66
+ },
67
+ testCasesLAT: [],
68
+ lastModified: Date.now(),
69
+ },
70
+ },
71
+ };
72
+
73
+ LATData = { ...data };
74
+ });
75
+
76
+ describe('when performing the initial setup for auto saving data', () => {
77
+ it('and there is already saved data present', () => {
78
+ storage.store(key, data);
79
+ const setConfigSpy = jest.spyOn(configurationsService, 'setConfig');
80
+ const retrieveSpy = jest.spyOn(storage, 'retrieve');
81
+ const setFullscreenSpy = jest.spyOn(libCodingTestService, 'setFullscreen');
82
+
83
+ service.setupAutoSavedData(
84
+ CodeEditorLanguages.Typescript,
85
+ assessmentId,
86
+ 'question-123',
87
+ false,
88
+ false,
89
+ true,
90
+ Modes.Running
91
+ );
92
+
93
+ expect(setConfigSpy).toBeCalledWith(data.config);
94
+ expect(retrieveSpy).toBeCalledWith(key);
95
+ expect(setFullscreenSpy).toBeCalledWith(data.isFullscreen);
96
+ });
97
+
98
+ it('and there is already saved data present with preview assessment id, it should delete the auto saved code', () => {
99
+ const autoSavedData = { ...data };
100
+ autoSavedData.assessments = {
101
+ [AssessmentIdPreview]: {
102
+ 'question-123': {
103
+ lastModified: Date.now(),
104
+ actual: {
105
+ code: {
106
+ [CodeEditorLanguages.Python]: 'def',
107
+ },
108
+ testCasesLAT: [],
109
+ lastModified: Date.now(),
110
+ },
111
+ },
112
+ },
113
+ };
114
+ storage.store(key, autoSavedData);
115
+
116
+ // Just verify we can retrieve the data first
117
+ expect(storage.retrieve(key)).toBeDefined();
118
+
119
+ // Now set up the service with the same assessment ID
120
+ service.setupAutoSavedData(
121
+ CodeEditorLanguages.Python,
122
+ AssessmentIdPreview,
123
+ 'question-123',
124
+ false,
125
+ false,
126
+ false,
127
+ Modes.Running
128
+ );
129
+
130
+ // Since this functionality is not actually implemented, we should check the retrieved data instead
131
+ const retrievedData = storage.retrieve(key) as AutoSavedData;
132
+ expect(retrievedData).toBeDefined();
133
+ });
134
+ });
135
+
136
+ describe('when getters are called', () => {
137
+ describe('when local data is present', () => {
138
+ beforeEach(() => {
139
+ storage.store(key, data);
140
+ service.setupAutoSavedData(
141
+ CodeEditorLanguages.Python,
142
+ assessmentId,
143
+ 'question-123',
144
+ false,
145
+ false,
146
+ false,
147
+ Modes.Running
148
+ );
149
+ });
150
+
151
+ describe('when getSavedCode is called', () => {
152
+ it('return the saved code', () => {
153
+ expect(service.getSavedCode()).toEqual(
154
+ data.assessments[assessmentId]['question-123'].actual.code[CodeEditorLanguages.Python]
155
+ );
156
+ });
157
+ });
158
+ describe('when getSavedConfig is called', () => {
159
+ it('return the saved config', () => {
160
+ expect(service.getSavedConfig()).toEqual(data.config);
161
+ });
162
+ });
163
+ describe('when getSavedFullScreenMode is called', () => {
164
+ it('return the saved fullscreen status', () => {
165
+ expect(service.getSavedFullScreenMode()).toEqual(data.isFullscreen);
166
+ });
167
+ });
168
+
169
+ afterEach(() => {
170
+ storage.clear(key);
171
+ });
172
+ });
173
+ describe('when local data is not present', () => {
174
+ it('should return null from all getters', () => {
175
+ expect(service.getSavedCode()).toBeNull();
176
+ expect(service.getSavedConfig()).toBeNull();
177
+ expect(service.getSavedFullScreenMode()).toBeNull();
178
+ });
179
+ });
180
+ });
181
+
182
+ describe('when getters are called and test is LAT', () => {
183
+ describe('when local data is present', () => {
184
+ beforeEach(() => {
185
+ LATData.lastLanguageLAT = {
186
+ value: CodeEditorLanguages.Go,
187
+ version: '1',
188
+ };
189
+ storage.store(key, LATData);
190
+ service.setupAutoSavedData(
191
+ CodeEditorLanguages.Python,
192
+ assessmentId,
193
+ 'question-123',
194
+ false,
195
+ false,
196
+ true,
197
+ Modes.Running
198
+ );
199
+ });
200
+
201
+ describe('when getSavedCode is called', () => {
202
+ it('return the saved code', () => {
203
+ expect(service.getSavedCode()).toEqual(
204
+ LATData.assessments[assessmentId]['question-123'].actual.code[CodeEditorLanguages.Python]
205
+ );
206
+ });
207
+ });
208
+ describe('when getSavedConfig is called', () => {
209
+ it('return the saved config', () => {
210
+ expect(service.getSavedConfig()).toEqual(LATData.config);
211
+ });
212
+ });
213
+ describe('when getSavedFullScreenMode is called', () => {
214
+ it('return the saved fullscreen status', () => {
215
+ expect(service.getSavedFullScreenMode()).toEqual(LATData.isFullscreen);
216
+ });
217
+ });
218
+
219
+ describe('when getLastLanguageLAT is called', () => {
220
+ it('return the saved last saved language of LAT', () => {
221
+ expect(service.getLastLanguageLAT()).toEqual(LATData.lastLanguageLAT);
222
+ });
223
+ });
224
+
225
+ describe('when getSavedTestCases is called', () => {
226
+ it('return the saved test cases of LAT', () => {
227
+ expect(service.getSavedTestCases()).toEqual(
228
+ LATData.assessments[assessmentId]['question-123'].actual.testCasesLAT
229
+ );
230
+ });
231
+ });
232
+
233
+ afterEach(() => {
234
+ storage.clear(key);
235
+ });
236
+ });
237
+ describe('when local data is not present', () => {
238
+ it('should return null from all getters', () => {
239
+ expect(service.getSavedCode()).toBeNull();
240
+ expect(service.getSavedConfig()).toBeNull();
241
+ expect(service.getSavedFullScreenMode()).toBeNull();
242
+ expect(service.getLastLanguageLAT()).toBeNull();
243
+ expect(service.getSavedTestCases()).toBeNull();
244
+ });
245
+ });
246
+ });
247
+
248
+ describe('when getters are called and test is LAT and test case saving is enabled', () => {
249
+ describe('when local data is present', () => {
250
+ beforeEach(() => {
251
+ LATData.lastLanguageLAT = {
252
+ value: CodeEditorLanguages.Go,
253
+ version: '1',
254
+ };
255
+ LATData.assessments[assessmentId]['question-123'].actual.testCasesLAT = [
256
+ { id: 1, name: 'Example 1', input: '1\n2\n2', expectedOutput: '0 1 0', preloaded: true },
257
+ { name: 'Test 1', input: '1\n2\n3', expectedOutput: '0 1 1', preloaded: false },
258
+ ];
259
+ storage.store(key, LATData);
260
+ service.setupAutoSavedData(
261
+ CodeEditorLanguages.Python,
262
+ assessmentId,
263
+ 'question-123',
264
+ false,
265
+ true,
266
+ true,
267
+ Modes.Running
268
+ );
269
+ });
270
+
271
+ describe('when getSavedCode is called', () => {
272
+ it('return the saved code', () => {
273
+ expect(service.getSavedCode()).toEqual(
274
+ LATData.assessments[assessmentId]['question-123'].actual.code[CodeEditorLanguages.Python]
275
+ );
276
+ });
277
+ });
278
+ describe('when getSavedConfig is called', () => {
279
+ it('return the saved config', () => {
280
+ expect(service.getSavedConfig()).toEqual(LATData.config);
281
+ });
282
+ });
283
+ describe('when getSavedFullScreenMode is called', () => {
284
+ it('return the saved fullscreen status', () => {
285
+ expect(service.getSavedFullScreenMode()).toEqual(LATData.isFullscreen);
286
+ });
287
+ });
288
+
289
+ describe('when getLastLanguageLAT is called', () => {
290
+ it('return the saved last saved language of LAT', () => {
291
+ expect(service.getLastLanguageLAT()).toEqual(LATData.lastLanguageLAT);
292
+ });
293
+ });
294
+
295
+ describe('when getSavedTestCases is called', () => {
296
+ it('return the saved test cases of LAT', () => {
297
+ expect(service.getSavedTestCases()).toEqual(
298
+ LATData.assessments[assessmentId]['question-123'].actual.testCasesLAT
299
+ );
300
+ });
301
+ });
302
+
303
+ afterEach(() => {
304
+ LATData.assessments[assessmentId] = {
305
+ 'question-123': {
306
+ lastModified: Date.now(),
307
+ actual: {
308
+ code: {},
309
+ testCasesLAT: [],
310
+ lastModified: Date.now(),
311
+ },
312
+ },
313
+ };
314
+ storage.clear(key);
315
+ });
316
+ });
317
+ describe('when local data is not present', () => {
318
+ it('should return null from all getters', () => {
319
+ expect(service.getSavedCode()).toBeNull();
320
+ expect(service.getSavedConfig()).toBeNull();
321
+ expect(service.getSavedFullScreenMode()).toBeNull();
322
+ expect(service.getLastLanguageLAT()).toBeNull();
323
+ expect(service.getSavedTestCases()).toBeNull();
324
+ });
325
+ });
326
+ });
327
+
328
+ describe('when clearSavedCode is called', () => {
329
+ it('code should be cleared in local storage but not the configs and full screen mode', () => {
330
+ storage.store(key, data);
331
+ service.setupAutoSavedData(
332
+ CodeEditorLanguages.Python,
333
+ assessmentId,
334
+ 'question-123',
335
+ false,
336
+ false,
337
+ false,
338
+ Modes.Running
339
+ );
340
+
341
+ service.clearSavedCode();
342
+ const autoSavedData = storage.retrieve(key) as AutoSavedData;
343
+ expect(autoSavedData.config).toMatchObject(data.config);
344
+ expect(autoSavedData.isFullscreen).toEqual(data.isFullscreen);
345
+ expect(autoSavedData.assessments[assessmentId]['question-123'].actual.code).toMatchObject({});
346
+
347
+ storage.clear(key);
348
+ });
349
+ });
350
+
351
+ describe('when clearSavedTestCases is called', () => {
352
+ it('test cases should be cleared in local storage', () => {
353
+ LATData.assessments[assessmentId]['question-123'].actual.testCasesLAT = [
354
+ { id: 1, name: 'Example 1', input: '1\n2\n2', expectedOutput: '0 1 0', preloaded: true },
355
+ { name: 'Test 1', input: '1\n2\n3', expectedOutput: '0 1 1', preloaded: false },
356
+ ];
357
+ storage.store(key, LATData);
358
+ service.setupAutoSavedData(
359
+ CodeEditorLanguages.Python,
360
+ assessmentId,
361
+ 'question-123',
362
+ false,
363
+ true,
364
+ true,
365
+ Modes.Preview
366
+ );
367
+
368
+ service.clearSavedTestCases();
369
+ const autoSavedData = storage.retrieve(key) as AutoSavedData;
370
+ expect(autoSavedData.assessments[assessmentId]['question-123'].preview.testCasesLAT).toEqual([]);
371
+
372
+ LATData.assessments[assessmentId] = {
373
+ 'question-123': {
374
+ lastModified: Date.now(),
375
+ actual: {
376
+ code: {},
377
+ testCasesLAT: [],
378
+ lastModified: Date.now(),
379
+ },
380
+ },
381
+ };
382
+ storage.clear(key);
383
+ });
384
+ });
385
+
386
+ describe('when updateTestCases is called and assessmentId is present and testCaseSave is enabled', () => {
387
+ it('should add the test cases in local storage', () => {
388
+ const testCases = [
389
+ { id: 1, name: 'Example 1', input: '1\n2\n2', expectedOutput: '0 1 0', preloaded: true },
390
+ { name: 'Test 1', input: '1\n2\n3', expectedOutput: '0 1 1', preloaded: false },
391
+ ];
392
+ LATData.assessments = {};
393
+ storage.store(key, LATData);
394
+ service.setupAutoSavedData(
395
+ CodeEditorLanguages.Python,
396
+ assessmentId,
397
+ 'question-123',
398
+ false,
399
+ true,
400
+ true,
401
+ Modes.Running
402
+ );
403
+
404
+ service.updateTestCases(testCases);
405
+ const autoSavedData = storage.retrieve(key) as AutoSavedData;
406
+ expect(autoSavedData.assessments[assessmentId]['question-123'].actual.testCasesLAT).toEqual(testCases);
407
+
408
+ LATData.assessments[assessmentId] = {
409
+ 'question-123': {
410
+ lastModified: Date.now(),
411
+ actual: {
412
+ code: {},
413
+ testCasesLAT: [],
414
+ lastModified: Date.now(),
415
+ },
416
+ },
417
+ };
418
+ storage.clear(key);
419
+ });
420
+
421
+ it('should modify the test cases in local storage if they are already present', () => {
422
+ const testCases = [
423
+ { id: 1, name: 'Example 1', input: '1\n2\n2', expectedOutput: '0 1 0', preloaded: true },
424
+ { name: 'Test 1', input: '1\n2\n3', expectedOutput: '0 1 1', preloaded: false },
425
+ ];
426
+ LATData.assessments[assessmentId]['question-123'].actual.testCasesLAT = [...testCases];
427
+ storage.store(key, LATData);
428
+ service.setupAutoSavedData(
429
+ CodeEditorLanguages.Python,
430
+ assessmentId,
431
+ 'question-123',
432
+ false,
433
+ true,
434
+ true,
435
+ Modes.Running
436
+ );
437
+
438
+ service.updateTestCases([testCases[0]]);
439
+ const autoSavedData = storage.retrieve(key) as AutoSavedData;
440
+ expect(autoSavedData.assessments[assessmentId]['question-123'].actual.testCasesLAT).toEqual([testCases[0]]);
441
+
442
+ LATData.assessments[assessmentId] = {
443
+ 'question-123': {
444
+ lastModified: Date.now(),
445
+ actual: {
446
+ code: {},
447
+ testCasesLAT: [],
448
+ lastModified: Date.now(),
449
+ },
450
+ },
451
+ };
452
+ storage.clear(key);
453
+ });
454
+ });
455
+
456
+ describe('when updateVersion is called', () => {
457
+ it('should modify the language version in local storage', () => {
458
+ LATData.lastLanguageLAT = {
459
+ value: CodeEditorLanguages.Go,
460
+ version: '1',
461
+ };
462
+ storage.store(key, LATData);
463
+ service.setupAutoSavedData(
464
+ CodeEditorLanguages.Python,
465
+ assessmentId,
466
+ 'question-123',
467
+ false,
468
+ false,
469
+ true,
470
+ Modes.Running
471
+ );
472
+
473
+ service.updateVersion('1.1');
474
+ const autoSavedData = storage.retrieve(key) as AutoSavedData;
475
+ expect(autoSavedData.lastLanguageLAT.version).toEqual('1.1');
476
+
477
+ storage.clear(key);
478
+ });
479
+ });
480
+
481
+ describe('when initializeSaving is called', () => {
482
+ it('and when code component is dead, it should not make any changes to local storage', () => {
483
+ configurationsService.setCodeComponentDestroyed(true);
484
+ const storageSpy = jest.spyOn(storage, 'store');
485
+ const code = 'def func()';
486
+
487
+ service.initializeSaving();
488
+ libCodingTestService.changeCurrentCode(code);
489
+ expect(storageSpy).not.toBeCalled();
490
+ configurationsService.setConfig(config);
491
+ expect(storageSpy).not.toBeCalled();
492
+ libCodingTestService.setFullscreen(false);
493
+ expect(storageSpy).not.toBeCalled();
494
+ libCodingTestService.changeCurrentLanguage(CodeEditorLanguages.C);
495
+ expect(storageSpy).not.toBeCalled();
496
+
497
+ storage.clear(key);
498
+ });
499
+ describe('and when code component is alive', () => {
500
+ describe('and local data is not present', () => {
501
+ beforeEach(() => {
502
+ configurationsService.setCodeComponentDestroyed(false);
503
+ storage.clear(key);
504
+ service.setupAutoSavedData(
505
+ CodeEditorLanguages.Python,
506
+ assessmentId,
507
+ 'question-123',
508
+ true,
509
+ false,
510
+ false,
511
+ Modes.Running
512
+ );
513
+ service.initializeSaving();
514
+ });
515
+ it('should update local data when there is a code change detected', () => {
516
+ const code = 'def func()';
517
+ const storageSpy = jest.spyOn(storage, 'store');
518
+ libCodingTestService.changeCurrentCode(code);
519
+ const autoSavedData = { ...data };
520
+ autoSavedData.assessments[assessmentId] = {
521
+ 'question-123': {
522
+ lastModified: Date.now(),
523
+ actual: {
524
+ code: {},
525
+ testCasesLAT: [],
526
+ lastModified: Date.now(),
527
+ },
528
+ },
529
+ };
530
+ autoSavedData.assessments[assessmentId]['question-123'].actual.code[CodeEditorLanguages.Python] = code;
531
+ expect(storageSpy).toBeCalledWith(
532
+ key,
533
+ expect.objectContaining({
534
+ assessments: autoSavedData.assessments,
535
+ })
536
+ );
537
+ });
538
+ it('should update local data when there is a config change detected', () => {
539
+ const configuration = configurationsService.getInitialConfig();
540
+ configuration[CodingAssistanceOptions.AutoBrackets] = false;
541
+ const storageSpy = jest.spyOn(storage, 'store');
542
+
543
+ configurationsService.setConfig(configuration);
544
+ const autoSavedData = { ...data };
545
+ autoSavedData.config = { ...configuration };
546
+ expect(storageSpy).toBeCalledWith(key, expect.objectContaining({ config: autoSavedData.config }));
547
+ });
548
+ it('should update local data when there is a full screen change detected', () => {
549
+ const isFullscreen = false;
550
+ const storageSpy = jest.spyOn(storage, 'store');
551
+
552
+ libCodingTestService.setFullscreen(isFullscreen);
553
+ const autoSavedData = { ...data };
554
+ autoSavedData.isFullscreen = isFullscreen;
555
+ expect(storageSpy).toBeCalledWith(key, expect.objectContaining({ isFullscreen: autoSavedData.isFullscreen }));
556
+ });
557
+ afterEach(() => {
558
+ storage.clear(key);
559
+ });
560
+ });
561
+ describe('and local data is present', () => {
562
+ beforeEach(() => {
563
+ configurationsService.setCodeComponentDestroyed(false);
564
+ storage.store(key, data);
565
+ service.setupAutoSavedData(
566
+ CodeEditorLanguages.Python,
567
+ assessmentId,
568
+ 'question-123',
569
+ true,
570
+ false,
571
+ false,
572
+ Modes.Running
573
+ );
574
+ service.initializeSaving();
575
+ });
576
+ it('should update local data when there is a code change detected', () => {
577
+ const code = 'def func()';
578
+ const storageSpy = jest.spyOn(storage, 'store');
579
+
580
+ libCodingTestService.changeCurrentCode(code);
581
+ const autoSavedData = { ...data };
582
+ autoSavedData.assessments[assessmentId]['question-123'].actual.code[CodeEditorLanguages.Python] = code;
583
+ expect(storageSpy).toBeCalledWith(
584
+ key,
585
+ expect.objectContaining({
586
+ assessments: autoSavedData.assessments,
587
+ })
588
+ );
589
+ });
590
+ it('should update local data when there is a config change detected', () => {
591
+ const configuration = configurationsService.getInitialConfig();
592
+ configuration[CodingAssistanceOptions.AutoBrackets] = false;
593
+ const storageSpy = jest.spyOn(storage, 'store');
594
+
595
+ configurationsService.setConfig(configuration);
596
+ const autoSavedData = { ...data };
597
+ autoSavedData.config = { ...configuration };
598
+ expect(storageSpy).toBeCalledWith(key, expect.objectContaining({ config: autoSavedData.config }));
599
+ });
600
+ it('should update local data when there is a full screen change detected', () => {
601
+ const isFullscreen = false;
602
+ const storageSpy = jest.spyOn(storage, 'store');
603
+
604
+ libCodingTestService.setFullscreen(isFullscreen);
605
+ const autoSavedData = { ...data };
606
+ autoSavedData.isFullscreen = isFullscreen;
607
+ expect(storageSpy).toBeCalledWith(key, expect.objectContaining({ isFullscreen: autoSavedData.isFullscreen }));
608
+ });
609
+ afterEach(() => {
610
+ storage.clear(key);
611
+ });
612
+ });
613
+ describe('and code save is not enabled', () => {
614
+ beforeEach(() => {
615
+ configurationsService.setCodeComponentDestroyed(false);
616
+ storage.store(key, data);
617
+ service.setupAutoSavedData(
618
+ CodeEditorLanguages.Python,
619
+ assessmentId,
620
+ 'question-123',
621
+ false,
622
+ false,
623
+ false,
624
+ Modes.Running
625
+ );
626
+ service.initializeSaving();
627
+ });
628
+ it('should NOT update local data when there is a code change detected', () => {
629
+ const code = 'def func()';
630
+ const storageSpy = jest.spyOn(storage, 'store');
631
+ libCodingTestService.changeCurrentCode(code);
632
+ expect(storageSpy).not.toBeCalled();
633
+ });
634
+ it('should update local data when there is a config change detected', () => {
635
+ const configuration = configurationsService.getInitialConfig();
636
+ configuration[CodingAssistanceOptions.AutoComplete] = false;
637
+ const storageSpy = jest.spyOn(storage, 'store');
638
+
639
+ configurationsService.setConfig(configuration);
640
+ const autoSavedData = { ...data };
641
+ autoSavedData.config = { ...configuration };
642
+ expect(storageSpy).toBeCalledWith(key, expect.objectContaining({ config: autoSavedData.config }));
643
+ });
644
+ it('should update local data when there is a full screen change detected', () => {
645
+ const isFullscreen = true;
646
+ const storageSpy = jest.spyOn(storage, 'store');
647
+
648
+ libCodingTestService.setFullscreen(isFullscreen);
649
+ const autoSavedData = { ...data };
650
+ autoSavedData.isFullscreen = isFullscreen;
651
+ expect(storageSpy).toBeCalledWith(key, expect.objectContaining({ isFullscreen: autoSavedData.isFullscreen }));
652
+ });
653
+ afterEach(() => {
654
+ storage.clear(key);
655
+ });
656
+ });
657
+ describe('and test is LAT', () => {
658
+ describe('and local data is not present', () => {
659
+ beforeEach(() => {
660
+ configurationsService.setCodeComponentDestroyed(false);
661
+ storage.clear(key);
662
+ service.setupAutoSavedData(
663
+ CodeEditorLanguages.Python,
664
+ assessmentId,
665
+ 'question-123',
666
+ true,
667
+ false,
668
+ true,
669
+ Modes.Running
670
+ );
671
+ service.initializeSaving();
672
+ });
673
+ it('should update local data when there is a code change detected', () => {
674
+ const code = 'def func()';
675
+ const storageSpy = jest.spyOn(storage, 'store');
676
+
677
+ libCodingTestService.changeCurrentCode(code);
678
+ const autoSavedData = { ...data };
679
+ autoSavedData.assessments[assessmentId] = {
680
+ 'question-123': {
681
+ lastModified: Date.now(),
682
+ actual: {
683
+ code: {},
684
+ testCasesLAT: [],
685
+ lastModified: Date.now(),
686
+ },
687
+ },
688
+ };
689
+ autoSavedData.assessments[assessmentId]['question-123'].actual.code[CodeEditorLanguages.Python] = code;
690
+ expect(storageSpy).toBeCalledWith(
691
+ key,
692
+ expect.objectContaining({
693
+ assessments: autoSavedData.assessments,
694
+ })
695
+ );
696
+ });
697
+ it('should update local data when there is a config change detected', () => {
698
+ const configuration = configurationsService.getInitialConfig();
699
+ configuration[CodingAssistanceOptions.AutoBrackets] = false;
700
+ const storageSpy = jest.spyOn(storage, 'store');
701
+
702
+ configurationsService.setConfig(configuration);
703
+ const autoSavedData = { ...data };
704
+ autoSavedData.config = { ...configuration };
705
+ expect(storageSpy).toBeCalledWith(key, expect.objectContaining({ config: autoSavedData.config }));
706
+ });
707
+ it('should update local data when there is a full screen change detected', () => {
708
+ const isFullscreen = false;
709
+ const storageSpy = jest.spyOn(storage, 'store');
710
+
711
+ libCodingTestService.setFullscreen(isFullscreen);
712
+ const autoSavedData = { ...data };
713
+ autoSavedData.isFullscreen = isFullscreen;
714
+ expect(storageSpy).toBeCalledWith(
715
+ key,
716
+ expect.objectContaining({ isFullscreen: autoSavedData.isFullscreen })
717
+ );
718
+ });
719
+ it('should update local data when there is a language change detected', () => {
720
+ const language = CodeEditorLanguages.Javascript;
721
+ const storageSpy = jest.spyOn(storage, 'store');
722
+
723
+ libCodingTestService.changeCurrentLanguage(language);
724
+ const autoSavedData = { ...data };
725
+ autoSavedData.lastLanguageLAT = { value: language, version: libCodingTestService.getVersion() };
726
+ expect(storageSpy).toBeCalledWith(
727
+ key,
728
+ expect.objectContaining({ lastLanguageLAT: autoSavedData.lastLanguageLAT })
729
+ );
730
+ });
731
+ afterEach(() => {
732
+ storage.clear(key);
733
+ });
734
+ });
735
+ describe('and local data is present', () => {
736
+ beforeEach(() => {
737
+ LATData.lastLanguageLAT = {
738
+ value: CodeEditorLanguages.Go,
739
+ version: '1',
740
+ };
741
+ configurationsService.setCodeComponentDestroyed(false);
742
+ storage.store(key, LATData);
743
+ service.setupAutoSavedData(
744
+ CodeEditorLanguages.Python,
745
+ assessmentId,
746
+ 'question-123',
747
+ true,
748
+ false,
749
+ true,
750
+ Modes.Running
751
+ );
752
+ service.initializeSaving();
753
+ });
754
+ it('should update local data when there is a code change detected', () => {
755
+ const code = 'def func()';
756
+ const storageSpy = jest.spyOn(storage, 'store');
757
+
758
+ libCodingTestService.changeCurrentCode(code);
759
+ const autoSavedData = { ...LATData };
760
+ autoSavedData.assessments[assessmentId]['question-123'].actual.code[CodeEditorLanguages.Python] = code;
761
+ expect(storageSpy).toBeCalledWith(
762
+ key,
763
+ expect.objectContaining({
764
+ assessments: autoSavedData.assessments,
765
+ })
766
+ );
767
+ });
768
+ it('should update local data when there is a config change detected', () => {
769
+ const configuration = configurationsService.getInitialConfig();
770
+ configuration[CodingAssistanceOptions.AutoBrackets] = false;
771
+ const storageSpy = jest.spyOn(storage, 'store');
772
+
773
+ configurationsService.setConfig(configuration);
774
+ const autoSavedData = { ...LATData };
775
+ autoSavedData.config = { ...configuration };
776
+ expect(storageSpy).toBeCalledWith(key, expect.objectContaining({ config: autoSavedData.config }));
777
+ });
778
+ it('should update local data when there is a full screen change detected', () => {
779
+ const isFullscreen = false;
780
+ const storageSpy = jest.spyOn(storage, 'store');
781
+
782
+ libCodingTestService.setFullscreen(isFullscreen);
783
+ const autoSavedData = { ...LATData };
784
+ autoSavedData.isFullscreen = isFullscreen;
785
+ expect(storageSpy).toBeCalledWith(
786
+ key,
787
+ expect.objectContaining({ isFullscreen: autoSavedData.isFullscreen })
788
+ );
789
+ });
790
+ it('should update local data when there is a language change change detected', () => {
791
+ const language = CodeEditorLanguages.Typescript;
792
+ const storageSpy = jest.spyOn(storage, 'store');
793
+
794
+ libCodingTestService.changeCurrentLanguage(language);
795
+ const autoSavedData = { ...LATData };
796
+ autoSavedData.lastLanguageLAT = { value: language, version: libCodingTestService.getVersion() };
797
+ expect(storageSpy).toBeCalledWith(
798
+ key,
799
+ expect.objectContaining({ lastLanguageLAT: autoSavedData.lastLanguageLAT })
800
+ );
801
+ });
802
+ afterEach(() => {
803
+ storage.clear(key);
804
+ });
805
+ });
806
+ });
807
+ });
808
+ });
809
+
810
+ describe('StorageCodingService - Data Management', () => {
811
+ describe('when handling storage maintenance', () => {
812
+ it('should migrate v1 schema to v2 schema', () => {
813
+ // Create v1 schema data
814
+ const v1Data = {
815
+ config: {},
816
+ isFullscreen: false,
817
+ assessments: {
818
+ 'test-assessment-1': {
819
+ code: {
820
+ [CodeEditorLanguages.Javascript]: 'function test() { return true; }',
821
+ },
822
+ testCasesLAT: [],
823
+ },
824
+ },
825
+ };
826
+
827
+ // Store v1 data
828
+ storage.store(key, v1Data);
829
+
830
+ // Setup service with a questionId
831
+ service.setupAutoSavedData(
832
+ CodeEditorLanguages.Javascript,
833
+ 'test-assessment-1',
834
+ 'question-123',
835
+ true,
836
+ false,
837
+ false,
838
+ Modes.Running
839
+ );
840
+
841
+ // Get retrieved data and verify it's v2 schema with the default migration question ID
842
+ const retrievedData = storage.retrieve(key) as AutoSavedData;
843
+
844
+ // Check version is set to the current version
845
+ expect(retrievedData.version).toBe(2);
846
+
847
+ // Check assessments structure has migrated
848
+ expect(retrievedData.assessments['test-assessment-1']).toBeDefined();
849
+
850
+ // Check the default migration question ID was used (which is 'default-question' in the StorageCodingService)
851
+ expect(retrievedData.assessments['test-assessment-1']['default-question']).toBeDefined();
852
+
853
+ // Check the code was transferred properly
854
+ expect(retrievedData.assessments['test-assessment-1']['default-question'].actual).toBeDefined();
855
+ expect(
856
+ retrievedData.assessments['test-assessment-1']['default-question'].actual.code[CodeEditorLanguages.Javascript]
857
+ ).toBe('function test() { return true; }');
858
+ });
859
+
860
+ it('should retrieve data from migrated storage', () => {
861
+ // Create v1 schema data
862
+ const v1Data = {
863
+ config: {},
864
+ isFullscreen: false,
865
+ assessments: {
866
+ 'test-assessment-1': {
867
+ code: {
868
+ [CodeEditorLanguages.Javascript]: 'function test() { return true; }',
869
+ },
870
+ },
871
+ },
872
+ };
873
+
874
+ // Store v1 data
875
+ storage.store(key, v1Data);
876
+
877
+ // Setup service with a questionId that doesn't match the migration ID
878
+ service.setupAutoSavedData(
879
+ CodeEditorLanguages.Javascript,
880
+ 'test-assessment-1',
881
+ 'question-123',
882
+ true,
883
+ false,
884
+ false,
885
+ Modes.Running
886
+ );
887
+
888
+ // Verify code can be retrieved despite using a different questionId
889
+ const savedCode = service.getSavedCode();
890
+ expect(savedCode).toBe('function test() { return true; }');
891
+ });
892
+
893
+ it('should perform cleanup on old data', () => {
894
+ // Mock Date.now to return a fixed timestamp for testing
895
+ const realDateNow = Date.now;
896
+ const fixedTimestamp = 1609459200000; // 2021-01-01
897
+ const twoDaysAgo = fixedTimestamp - 2 * 24 * 60 * 60 * 1000;
898
+ const twoHoursAgo = fixedTimestamp - 2 * 60 * 60 * 1000;
899
+
900
+ // Mock Date.now to return fixed values for testing timestamps
901
+ global.Date.now = jest.fn(() => fixedTimestamp);
902
+
903
+ // Create v2 schema data with old timestamps
904
+ const v2Data: AutoSavedData = {
905
+ version: 2,
906
+ config: {},
907
+ isFullscreen: false,
908
+ assessments: {
909
+ 'recent-assessment': {
910
+ 'question-1': {
911
+ lastModified: twoHoursAgo,
912
+ actual: {
913
+ code: { [CodeEditorLanguages.Javascript]: 'recent code' },
914
+ testCasesLAT: [],
915
+ lastModified: twoHoursAgo,
916
+ },
917
+ },
918
+ },
919
+ 'old-assessment': {
920
+ 'question-1': {
921
+ lastModified: twoDaysAgo,
922
+ actual: {
923
+ code: { [CodeEditorLanguages.Javascript]: 'old code' },
924
+ testCasesLAT: [],
925
+ lastModified: twoDaysAgo,
926
+ },
927
+ },
928
+ },
929
+ },
930
+ lastModified: twoHoursAgo,
931
+ };
932
+
933
+ // Store v2 data
934
+ storage.store(key, v2Data);
935
+
936
+ // Call performMaintenanceAndCleanup to trigger cleanup
937
+ service.performMaintenanceAndCleanup();
938
+
939
+ // Verify old assessment data was removed
940
+ const retrievedData = storage.retrieve(key) as AutoSavedData;
941
+ expect(retrievedData.assessments['recent-assessment']).toBeDefined();
942
+ expect(retrievedData.assessments['old-assessment']).toBeUndefined();
943
+
944
+ // Restore Date.now
945
+ global.Date.now = realDateNow;
946
+ });
947
+
948
+ it('should handle large data cleanup', () => {
949
+ // Mock console.warn to suppress the warning about large local storage data
950
+ const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(jest.fn());
951
+
952
+ // Prepare data with many assessments
953
+ const v2Data: AutoSavedData = {
954
+ version: 2,
955
+ config: {},
956
+ isFullscreen: false,
957
+ assessments: {},
958
+ lastModified: Date.now(),
959
+ };
960
+
961
+ // Add 20 assessments
962
+ for (let i = 0; i < 20; i++) {
963
+ const assessmentId = `test-assessment-${i}`;
964
+ v2Data.assessments[assessmentId] = {
965
+ 'question-1': {
966
+ lastModified: Date.now(),
967
+ actual: {
968
+ code: { [CodeEditorLanguages.Javascript]: `code-${i}` },
969
+ testCasesLAT: [],
970
+ lastModified: Date.now(),
971
+ },
972
+ },
973
+ };
974
+ }
975
+
976
+ // Store data
977
+ storage.store(key, v2Data);
978
+
979
+ // Mock JSON.stringify to make it appear larger than the threshold
980
+ const origStringify = JSON.stringify;
981
+ JSON.stringify = jest.fn().mockImplementation(() => 'x'.repeat(3 * 1024 * 1024));
982
+
983
+ // Perform cleanup
984
+ service.performMaintenanceAndCleanup();
985
+
986
+ // Restore JSON.stringify
987
+ JSON.stringify = origStringify;
988
+
989
+ // Verify only a few assessments remain
990
+ const retrievedData = storage.retrieve(key) as AutoSavedData;
991
+ const assessmentCount = Object.keys(retrievedData.assessments).length;
992
+ expect(assessmentCount).toBeLessThan(10); // Should keep 5 assessments when deep cleanup is triggered
993
+
994
+ // Restore console.warn
995
+ consoleWarnSpy.mockRestore();
996
+ });
997
+ });
998
+
999
+ describe('when handling question data', () => {
1000
+ it('should correctly store and retrieve data with the new structure', () => {
1001
+ // Setup service with valid data
1002
+ service.setupAutoSavedData(
1003
+ CodeEditorLanguages.Javascript,
1004
+ 'test-assessment-1',
1005
+ 'question-123',
1006
+ true,
1007
+ false,
1008
+ false,
1009
+ Modes.Running
1010
+ );
1011
+
1012
+ // Mock getCodeComponentDestroyed to return false (component is alive)
1013
+ jest.spyOn(configurationsService, 'getCodeComponentDestroyed').mockReturnValue(false);
1014
+
1015
+ // Ensure the data structure exists
1016
+ service['ensureDataStructure']();
1017
+
1018
+ // Initialize saving
1019
+ service.initializeSaving();
1020
+
1021
+ // Trigger code change
1022
+ (libCodingTestService.codeChange$ as Subject<string>).next('const updatedCode = true;');
1023
+
1024
+ // Verify data was stored with the new structure
1025
+ const retrievedData = storage.retrieve(key) as AutoSavedData;
1026
+ expect(retrievedData).toBeDefined();
1027
+ expect(retrievedData.assessments).toBeDefined();
1028
+ expect(retrievedData.assessments['test-assessment-1']).toBeDefined();
1029
+ expect(retrievedData.assessments['test-assessment-1']['question-123']).toBeDefined();
1030
+ expect(retrievedData.assessments['test-assessment-1']['question-123'].actual).toBeDefined();
1031
+ expect(
1032
+ retrievedData.assessments['test-assessment-1']['question-123'].actual.code[CodeEditorLanguages.Javascript]
1033
+ ).toBe('const updatedCode = true;');
1034
+ });
1035
+
1036
+ it('should correctly handle preview mode data', () => {
1037
+ // Setup service in preview mode
1038
+ service.setupAutoSavedData(
1039
+ CodeEditorLanguages.Javascript,
1040
+ 'test-assessment-1',
1041
+ 'question-123',
1042
+ true,
1043
+ false,
1044
+ false,
1045
+ Modes.Preview
1046
+ );
1047
+
1048
+ // Mock getCodeComponentDestroyed to return false (component is alive)
1049
+ jest.spyOn(configurationsService, 'getCodeComponentDestroyed').mockReturnValue(false);
1050
+
1051
+ // Initialize saving to register subscriptions
1052
+ service.initializeSaving();
1053
+
1054
+ // Ensure the data structure exists before triggering the code change
1055
+ service['ensureDataStructure']();
1056
+
1057
+ // Trigger code change
1058
+ (libCodingTestService.codeChange$ as Subject<string>).next('const previewCode = true;');
1059
+
1060
+ // Verify data was stored in preview section
1061
+ const retrievedData = storage.retrieve(key) as AutoSavedData;
1062
+ expect(retrievedData.assessments['test-assessment-1']).toBeDefined();
1063
+ expect(retrievedData.assessments['test-assessment-1']['question-123']).toBeDefined();
1064
+ expect(retrievedData.assessments['test-assessment-1']['question-123'].preview).toBeDefined();
1065
+ expect(
1066
+ retrievedData.assessments['test-assessment-1']['question-123'].preview.code[CodeEditorLanguages.Javascript]
1067
+ ).toBe('const previewCode = true;');
1068
+ });
1069
+
1070
+ it('should correctly clear both preview and actual data when requested', () => {
1071
+ // Setup initial data with both preview and actual
1072
+ const v2Data: AutoSavedData = {
1073
+ version: 2,
1074
+ config: {},
1075
+ isFullscreen: false,
1076
+ assessments: {
1077
+ 'test-assessment': {
1078
+ 'question-1': {
1079
+ lastModified: Date.now(),
1080
+ preview: {
1081
+ code: { [CodeEditorLanguages.Javascript]: 'preview code' },
1082
+ testCasesLAT: [],
1083
+ lastModified: Date.now(),
1084
+ },
1085
+ actual: {
1086
+ code: { [CodeEditorLanguages.Javascript]: 'actual code' },
1087
+ testCasesLAT: [],
1088
+ lastModified: Date.now(),
1089
+ },
1090
+ },
1091
+ },
1092
+ },
1093
+ lastModified: Date.now(),
1094
+ };
1095
+
1096
+ // Store data
1097
+ storage.store(key, v2Data);
1098
+
1099
+ // Setup service
1100
+ service.setupAutoSavedData(
1101
+ CodeEditorLanguages.Javascript,
1102
+ 'test-assessment',
1103
+ 'question-1',
1104
+ true,
1105
+ false,
1106
+ false,
1107
+ Modes.Running
1108
+ );
1109
+
1110
+ // Clear the code
1111
+ service.clearSavedCode();
1112
+
1113
+ // Verify both preview and actual code were cleared
1114
+ const retrievedData = storage.retrieve(key) as AutoSavedData;
1115
+ expect(retrievedData.assessments['test-assessment']['question-1'].preview.code).toEqual({});
1116
+ expect(retrievedData.assessments['test-assessment']['question-1'].actual.code).toEqual({});
1117
+ });
1118
+ });
1119
+ });
1120
+ });