newportsite 1.1.3

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 (131) hide show
  1. package/newportsite-1.1.3.tgz +0 -0
  2. package/ng-package.json +7 -0
  3. package/obfuscate.js +70 -0
  4. package/package.json +15 -0
  5. package/src/lib/app.component.ts +47 -0
  6. package/src/lib/app.routing.ts +38 -0
  7. package/src/lib/auth/alert.component.html +5 -0
  8. package/src/lib/auth/alert.component.ts +24 -0
  9. package/src/lib/auth/auth.component.html +1 -0
  10. package/src/lib/auth/auth.component.ts +10 -0
  11. package/src/lib/auth/auth.routes.ts +16 -0
  12. package/src/lib/auth/index.ts +4 -0
  13. package/src/lib/auth/login.component.html +87 -0
  14. package/src/lib/auth/login.component.ts +158 -0
  15. package/src/lib/auth/models/index.ts +1 -0
  16. package/src/lib/auth/models/user.ts +25 -0
  17. package/src/lib/auth/register.component.html +157 -0
  18. package/src/lib/auth/register.component.ts +219 -0
  19. package/src/lib/auth/services/alert.service.ts +47 -0
  20. package/src/lib/auth/services/auth.service.ts +28 -0
  21. package/src/lib/auth/services/index.ts +3 -0
  22. package/src/lib/auth/services/user.service.spec.ts +112 -0
  23. package/src/lib/auth/services/user.service.ts +47 -0
  24. package/src/lib/common/card.component.html +72 -0
  25. package/src/lib/common/card.component.ts +102 -0
  26. package/src/lib/common/commands.component.html +8 -0
  27. package/src/lib/common/commands.component.ts +42 -0
  28. package/src/lib/common/context.component.html +9 -0
  29. package/src/lib/common/context.component.ts +38 -0
  30. package/src/lib/common/grid.component.html +20 -0
  31. package/src/lib/common/grid.component.ts +747 -0
  32. package/src/lib/common/index.ts +9 -0
  33. package/src/lib/common/loader.component.html +5 -0
  34. package/src/lib/common/loader.component.ts +27 -0
  35. package/src/lib/common/lookup.component.html +29 -0
  36. package/src/lib/common/lookup.component.ts +115 -0
  37. package/src/lib/common/messagebox.component.html +39 -0
  38. package/src/lib/common/messagebox.component.ts +74 -0
  39. package/src/lib/common/theme-toggle.component.ts +139 -0
  40. package/src/lib/config.ts +62 -0
  41. package/src/lib/containers/default-layout/default-layout.component.html +191 -0
  42. package/src/lib/containers/default-layout/default-layout.component.ts +158 -0
  43. package/src/lib/containers/default-layout/index.ts +1 -0
  44. package/src/lib/containers/index.ts +1 -0
  45. package/src/lib/directives/component.draggable.ts +80 -0
  46. package/src/lib/directives/index.ts +2 -0
  47. package/src/lib/directives/input.directive.spec.ts +158 -0
  48. package/src/lib/directives/input.directive.ts +210 -0
  49. package/src/lib/home/dashboard/dashboard.component.html +38 -0
  50. package/src/lib/home/dashboard/dashboard.component.ts +50 -0
  51. package/src/lib/home/dashboard/index.ts +1 -0
  52. package/src/lib/home/index.component.html +1 -0
  53. package/src/lib/home/index.component.ts +10 -0
  54. package/src/lib/home/index.routes.ts +29 -0
  55. package/src/lib/home/index.ts +1 -0
  56. package/src/lib/home/info/index.ts +1 -0
  57. package/src/lib/home/info/info.component.css +476 -0
  58. package/src/lib/home/info/info.component.html +174 -0
  59. package/src/lib/home/info/info.component.ts +287 -0
  60. package/src/lib/home/model/article.component.html +10 -0
  61. package/src/lib/home/model/article.component.ts +50 -0
  62. package/src/lib/home/model/barchart.component.html +8 -0
  63. package/src/lib/home/model/barchart.component.ts +59 -0
  64. package/src/lib/home/model/index.ts +7 -0
  65. package/src/lib/home/model/itemdetail.component.html +25 -0
  66. package/src/lib/home/model/itemdetail.component.ts +93 -0
  67. package/src/lib/home/model/itemtab.component.html +25 -0
  68. package/src/lib/home/model/itemtab.component.ts +105 -0
  69. package/src/lib/home/model/model.component.html +121 -0
  70. package/src/lib/home/model/model.component.ts +510 -0
  71. package/src/lib/home/model/modeltoolbar.component.html +111 -0
  72. package/src/lib/home/model/modeltoolbar.component.ts +157 -0
  73. package/src/lib/home/model/navigation.component.html +86 -0
  74. package/src/lib/home/model/navigation.component.ts +247 -0
  75. package/src/lib/home/model/services/index.ts +1 -0
  76. package/src/lib/home/model/services/model.service.spec.ts +423 -0
  77. package/src/lib/home/model/services/model.service.ts +319 -0
  78. package/src/lib/home/modelsearch/index.ts +1 -0
  79. package/src/lib/home/modelsearch/modelsearch.component.html +124 -0
  80. package/src/lib/home/modelsearch/modelsearch.component.ts +453 -0
  81. package/src/lib/interfaces/data.interface.ts +131 -0
  82. package/src/lib/interfaces/index.ts +2 -0
  83. package/src/lib/interfaces/item.interface.ts +438 -0
  84. package/src/lib/players/lookup/lookup.directive.ts +6 -0
  85. package/src/lib/players/lookup/lookup.item.component.ts +37 -0
  86. package/src/lib/players/lookup/lookup.item.ts +9 -0
  87. package/src/lib/players/lookup/lookup.player.component.ts +59 -0
  88. package/src/lib/players/lookup/lookup.selector.component.ts +41 -0
  89. package/src/lib/players/model/model.directive.ts +6 -0
  90. package/src/lib/players/model/model.item.component.spec.ts +311 -0
  91. package/src/lib/players/model/model.item.component.ts +3457 -0
  92. package/src/lib/players/model/model.item.ts +9 -0
  93. package/src/lib/players/model/model.player.component.ts +109 -0
  94. package/src/lib/players/model/model.selector.component.ts +59 -0
  95. package/src/lib/scheduler/scheduler.component.html +13 -0
  96. package/src/lib/scheduler/scheduler.component.scss +6 -0
  97. package/src/lib/scheduler/scheduler.component.ts +296 -0
  98. package/src/lib/scheduler/scheduler.routes.ts +15 -0
  99. package/src/lib/scheduler/schedulerdialog.component.html +72 -0
  100. package/src/lib/scheduler/schedulerdialog.component.ts +208 -0
  101. package/src/lib/scheduler/services/scheduler.service.ts +133 -0
  102. package/src/lib/services/auth-state.service.ts +129 -0
  103. package/src/lib/services/auth.interceptor.spec.ts +144 -0
  104. package/src/lib/services/auth.interceptor.ts +44 -0
  105. package/src/lib/services/cache.service.spec.ts +143 -0
  106. package/src/lib/services/cache.service.ts +71 -0
  107. package/src/lib/services/global-error-handler.spec.ts +39 -0
  108. package/src/lib/services/global-error-handler.ts +28 -0
  109. package/src/lib/services/global.service.spec.ts +801 -0
  110. package/src/lib/services/global.service.ts +724 -0
  111. package/src/lib/services/message.service.ts +556 -0
  112. package/src/lib/services/theme.service.ts +96 -0
  113. package/src/lib/template/authtemplate.component.html +6 -0
  114. package/src/lib/template/authtemplate.component.ts +13 -0
  115. package/src/lib/template/basetemplate.component.html +7 -0
  116. package/src/lib/template/basetemplate.component.ts +13 -0
  117. package/src/lib/template/index.ts +3 -0
  118. package/src/lib/template/modeltemplate.component.html +7 -0
  119. package/src/lib/template/modeltemplate.component.ts +21 -0
  120. package/src/lib/utils/piva.spec.ts +56 -0
  121. package/src/lib/utils/piva.ts +29 -0
  122. package/src/lib/validators/email.validator.spec.ts +57 -0
  123. package/src/lib/validators/email.validator.ts +17 -0
  124. package/src/lib/validators/equalPasswords.validator.spec.ts +54 -0
  125. package/src/lib/validators/equalPasswords.validator.ts +17 -0
  126. package/src/lib/validators/index.ts +2 -0
  127. package/src/lib/version.ts +1 -0
  128. package/src/public-api.ts +64 -0
  129. package/src/typings.d.ts +2 -0
  130. package/tsconfig.lib.json +18 -0
  131. package/tsconfig.lib.prod.json +9 -0
@@ -0,0 +1,801 @@
1
+ import { TestBed } from '@angular/core/testing';
2
+ import { Router } from '@angular/router';
3
+ import { Location } from '@angular/common';
4
+ import { HttpHeaders } from '@angular/common/http';
5
+ import { GlobalService, Languages } from './global.service';
6
+ import { User } from '../auth/models/index';
7
+ import { NEWPORT_CONFIG } from '../config';
8
+
9
+ const mockProjectInfo = { getData: () => ({ entities: [] } as any), getSchema: () => ({ entities: [] } as any) };
10
+ const TEST_CONFIG = { apiUrl: 'http://test/', logicFactory: () => {}, projectsInfoLoader: () => Promise.resolve(mockProjectInfo) };
11
+
12
+ // ── Minimal stubs ────────────────────────────────────────────────────────────
13
+
14
+ const routerStub = { navigateByUrl: jest.fn().mockResolvedValue(true) };
15
+ const locationStub = { normalize: (url: string) => url };
16
+
17
+ // ── Helpers ─────────────────────────────────────────────────────────────────
18
+
19
+ function createService(): GlobalService {
20
+ TestBed.configureTestingModule({
21
+ providers: [
22
+ GlobalService,
23
+ { provide: Router, useValue: routerStub },
24
+ { provide: Location, useValue: locationStub },
25
+ { provide: NEWPORT_CONFIG, useValue: TEST_CONFIG },
26
+ ],
27
+ });
28
+ return TestBed.inject(GlobalService);
29
+ }
30
+
31
+ // ── Test suite ───────────────────────────────────────────────────────────────
32
+
33
+ describe('GlobalService', () => {
34
+ let svc: GlobalService;
35
+
36
+ beforeEach(() => {
37
+ localStorage.clear();
38
+ jest.clearAllMocks();
39
+ svc = createService();
40
+ });
41
+
42
+ // ── getInt / getFloat ────────────────────────────────────────────────────
43
+
44
+ describe('getInt()', () => {
45
+ it('parses a plain pixel string', () => {
46
+ expect(svc.getInt('120px')).toBe(120);
47
+ });
48
+
49
+ it('parses a bare integer string', () => {
50
+ expect(svc.getInt('42')).toBe(42);
51
+ });
52
+
53
+ it('returns NaN for non-numeric input', () => {
54
+ expect(svc.getInt('abc')).toBeNaN();
55
+ });
56
+ });
57
+
58
+ describe('getFloat()', () => {
59
+ it('parses a decimal string', () => {
60
+ expect(svc.getFloat('3.14')).toBeCloseTo(3.14);
61
+ });
62
+
63
+ it('parses integer string as float', () => {
64
+ expect(svc.getFloat('7')).toBe(7.0);
65
+ });
66
+ });
67
+
68
+ // ── makeId ───────────────────────────────────────────────────────────────
69
+
70
+ describe('makeId()', () => {
71
+ it('returns a string of the requested length', () => {
72
+ expect(svc.makeId(8)).toHaveLength(8);
73
+ expect(svc.makeId(16)).toHaveLength(16);
74
+ });
75
+
76
+ it('only uses uppercase alphanumeric characters', () => {
77
+ const id = svc.makeId(100);
78
+ expect(id).toMatch(/^[A-Z0-9]+$/);
79
+ });
80
+ });
81
+
82
+ // ── scale ────────────────────────────────────────────────────────────────
83
+
84
+ describe('scale getter / setter', () => {
85
+ it('defaults to 1', () => {
86
+ expect(svc.getScale()).toBe(1);
87
+ });
88
+
89
+ it('round-trips a custom scale value', () => {
90
+ svc.setScale(1.5);
91
+ expect(svc.getScale()).toBe(1.5);
92
+ });
93
+ });
94
+
95
+ // ── keyId ────────────────────────────────────────────────────────────────
96
+
97
+ describe('keyId getter / setter', () => {
98
+ it('defaults to empty string', () => {
99
+ expect(svc.getKeyId()).toBe('');
100
+ });
101
+
102
+ it('round-trips a keyId', () => {
103
+ svc.setKeyId('42');
104
+ expect(svc.getKeyId()).toBe('42');
105
+ });
106
+ });
107
+
108
+ // ── activeLink ───────────────────────────────────────────────────────────
109
+
110
+ describe('activeLink getter / setter', () => {
111
+ it('returns the set active link', () => {
112
+ svc.setActiveLink('/home/model');
113
+ expect(svc.getActiveLink()).toBe('/home/model');
114
+ });
115
+ });
116
+
117
+ // ── language / decimal separators ───────────────────────────────────────
118
+
119
+ describe('setLng()', () => {
120
+ it('sets Italian separators when lng=IT', () => {
121
+ svc.setLng(Languages.IT);
122
+ expect(svc.getLng()).toBe(Languages.IT);
123
+ expect(svc.getDecimalChar()).toBe(',');
124
+ expect(svc.getThousandChar()).toBe('.');
125
+ });
126
+
127
+ it('sets English separators when lng=EN', () => {
128
+ svc.setLng(Languages.EN);
129
+ expect(svc.getLng()).toBe(Languages.EN);
130
+ expect(svc.getDecimalChar()).toBe('.');
131
+ expect(svc.getThousandChar()).toBe(',');
132
+ });
133
+
134
+ it('leaves separators unchanged for an unknown language code', () => {
135
+ svc.setLng(Languages.IT);
136
+ svc.setLng('FR');
137
+ expect(svc.getLng()).toBe('FR');
138
+ expect(svc.getDecimalChar()).toBe(',');
139
+ expect(svc.getThousandChar()).toBe('.');
140
+ });
141
+ });
142
+
143
+ // ── year ─────────────────────────────────────────────────────────────────
144
+
145
+ describe('year getter / setter', () => {
146
+ it('round-trips a year', () => {
147
+ svc.setYear(2024);
148
+ expect(svc.getYear()).toBe(2024);
149
+ });
150
+ });
151
+
152
+ // ── years ────────────────────────────────────────────────────────────────
153
+
154
+ describe('years getter / setter', () => {
155
+ it('round-trips the years array', () => {
156
+ svc.setYears([2023, 2024, 2025]);
157
+ expect(svc.getYears()).toEqual([2023, 2024, 2025]);
158
+ });
159
+ });
160
+
161
+ // ── data cache ───────────────────────────────────────────────────────────
162
+
163
+ describe('data cache (setData / getData)', () => {
164
+ it('stores and retrieves a data object by id', () => {
165
+ const fakeData = { entities: [] } as any;
166
+ svc.setData('APP.ENT.2024', fakeData);
167
+ expect(svc.getData('APP.ENT.2024')).toBe(fakeData);
168
+ });
169
+
170
+ it('returns undefined for an unknown id', () => {
171
+ expect(svc.getData('UNKNOWN')).toBeUndefined();
172
+ });
173
+ });
174
+
175
+ // ── schema / data JSON cache ─────────────────────────────────────────────
176
+
177
+ describe('schema/data JSON cache', () => {
178
+ it('stores and retrieves schema JSON', () => {
179
+ const schema = { entities: [] } as any;
180
+ svc.setSchemaJson('PRJ.2024', schema);
181
+ expect(svc.getSchemaJson('PRJ.2024')).toBe(schema);
182
+ });
183
+
184
+ it('stores and retrieves data JSON', () => {
185
+ const data = { entities: [] } as any;
186
+ svc.setDataJson('PRJ.2024', data);
187
+ expect(svc.getDataJson('PRJ.2024')).toBe(data);
188
+ });
189
+ });
190
+
191
+ // ── currentUser (localStorage) ───────────────────────────────────────────
192
+
193
+ describe('getCurrentUser() / setCurrentUser()', () => {
194
+ const fakeUser: User = { name: 'Mario', token: 'tok123' } as unknown as User;
195
+
196
+ it('returns null when localStorage is empty', () => {
197
+ expect(svc.getCurrentUser()).toBeNull();
198
+ });
199
+
200
+ it('round-trips a user object through localStorage', () => {
201
+ svc.setCurrentUser(fakeUser);
202
+ expect(svc.getCurrentUser()).toEqual(fakeUser);
203
+ });
204
+
205
+ it('removes the user when setCurrentUser(null) is called', () => {
206
+ svc.setCurrentUser(fakeUser);
207
+ svc.setCurrentUser(null);
208
+ expect(svc.getCurrentUser()).toBeNull();
209
+ });
210
+ });
211
+
212
+ // ── isLogged ─────────────────────────────────────────────────────────────
213
+
214
+ describe('isLogged()', () => {
215
+ function makeJwt(exp: number): string {
216
+ const payload = btoa(JSON.stringify({ exp }));
217
+ return `header.${payload}.sig`;
218
+ }
219
+
220
+ it('returns false when no user is in localStorage', () => {
221
+ expect(svc.isLogged()).toBe(false);
222
+ });
223
+
224
+ it('returns true when a user with a valid (non-expired) JWT is stored', () => {
225
+ const futureExp = Math.floor(Date.now() / 1000) + 3600;
226
+ const fakeUser: User = { name: 'Mario', token: makeJwt(futureExp) } as unknown as User;
227
+ svc.setCurrentUser(fakeUser);
228
+ expect(svc.isLogged()).toBe(true);
229
+ });
230
+
231
+ it('returns false when the stored JWT is expired', () => {
232
+ const pastExp = Math.floor(Date.now() / 1000) - 3600;
233
+ const fakeUser: User = { name: 'Mario', token: makeJwt(pastExp) } as unknown as User;
234
+ svc.setCurrentUser(fakeUser);
235
+ expect(svc.isLogged()).toBe(false);
236
+ });
237
+
238
+ it('returns false when the token is not a valid JWT', () => {
239
+ const fakeUser: User = { name: 'Mario', token: 'not-a-jwt' } as unknown as User;
240
+ svc.setCurrentUser(fakeUser);
241
+ expect(svc.isLogged()).toBe(false);
242
+ });
243
+ });
244
+
245
+ // ── getHeaders ───────────────────────────────────────────────────────────
246
+
247
+ describe('getHeaders()', () => {
248
+ it('includes Content-Type header always', () => {
249
+ const headers = svc.getHeaders(null);
250
+ expect(headers.get('Content-Type')).toBe('application/json');
251
+ });
252
+
253
+ it('includes Authorization header when user is provided', () => {
254
+ const user = { name: 'u', token: 'mytoken' } as unknown as User;
255
+ const headers: HttpHeaders = svc.getHeaders(user);
256
+ expect(headers.get('Authorization')).toBe('Bearer mytoken');
257
+ });
258
+
259
+ it('omits Authorization header when user is null', () => {
260
+ const headers = svc.getHeaders(null);
261
+ expect(headers.get('Authorization')).toBeNull();
262
+ });
263
+ });
264
+
265
+ // ── jwt ──────────────────────────────────────────────────────────────────
266
+
267
+ describe('jwt()', () => {
268
+ it('returns plain headers object when no fileType given', () => {
269
+ const result = svc.jwt();
270
+ expect(result).toHaveProperty('headers');
271
+ expect(result).not.toHaveProperty('responseType');
272
+ });
273
+
274
+ it('returns blob options when fileType is provided', () => {
275
+ const result = svc.jwt('pdf');
276
+ expect(result).toHaveProperty('responseType', 'blob');
277
+ expect(result).toHaveProperty('reportProgress', true);
278
+ });
279
+ });
280
+
281
+ // ── getManagmentInterface ────────────────────────────────────────────────
282
+
283
+ describe('getManagmentInterface()', () => {
284
+ it('returns a zeroed ManagmentInterface', () => {
285
+ const m = svc.getManagmentInterface();
286
+ expect(m.functionId).toBe('');
287
+ expect(m.appId).toBe('');
288
+ expect(m.keys).toBe('');
289
+ expect(m.page).toBe(1);
290
+ expect(m.keyId).toBe('1');
291
+ expect(m.fields).toEqual([]);
292
+ });
293
+ });
294
+
295
+ // ── currentFocus ─────────────────────────────────────────────────────────
296
+
297
+ describe('currentFocus getter / setter', () => {
298
+ it('round-trips the focus object', () => {
299
+ svc.setCurrentFocus({ id: 'field1', value: 'abc' });
300
+ expect(svc.getCurrentFocus()).toEqual({ id: 'field1', value: 'abc' });
301
+ });
302
+ });
303
+
304
+ // ── currentKeys ──────────────────────────────────────────────────────────
305
+
306
+ describe('currentKeys getter / setter', () => {
307
+ it('defaults to empty string', () => {
308
+ expect(svc.getCurrentKeys()).toBe('');
309
+ });
310
+
311
+ it('round-trips a key string', () => {
312
+ svc.setCurrentKeys('prg=5;prg=3');
313
+ expect(svc.getCurrentKeys()).toBe('prg=5;prg=3');
314
+ });
315
+ });
316
+
317
+ // ── extendedDescription ──────────────────────────────────────────────────
318
+
319
+ describe('extendedDescription getter / setter', () => {
320
+ it('round-trips a description string', () => {
321
+ svc.setExtendedDescription('Test description');
322
+ expect(svc.getExtendedDescription()).toBe('Test description');
323
+ });
324
+ });
325
+
326
+ // ── navigate ─────────────────────────────────────────────────────────────
327
+
328
+ describe('navigate()', () => {
329
+ it('calls router.navigateByUrl with the normalized URL', () => {
330
+ svc.navigate('/home');
331
+ expect(routerStub.navigateByUrl).toHaveBeenCalledWith('/home');
332
+ });
333
+
334
+ it('updates the active link before navigating', () => {
335
+ svc.navigate('/home/model');
336
+ expect(svc.getActiveLink()).toBe('/home/model');
337
+ });
338
+ });
339
+
340
+ // ── sanitizeurl ──────────────────────────────────────────────────────────
341
+
342
+ describe('sanitizeurl()', () => {
343
+ it('delegates to location.normalize', () => {
344
+ expect(svc.sanitizeurl('/foo/bar')).toBe('/foo/bar');
345
+ });
346
+ });
347
+
348
+ // ── navigateToDefault ────────────────────────────────────────────────────
349
+
350
+ describe('navigateToDefault()', () => {
351
+ it('navigates to /auth/login', () => {
352
+ svc.navigateToDefault();
353
+ expect(routerStub.navigateByUrl).toHaveBeenCalledWith('/auth/login');
354
+ });
355
+ });
356
+
357
+ // ── loggedUser ───────────────────────────────────────────────────────────
358
+
359
+ describe('loggedUser()', () => {
360
+ it('returns the name of the current user', () => {
361
+ svc.setCurrentUser({ name: 'Mario', token: 'tok' } as any);
362
+ expect(svc.loggedUser()).toBe('Mario');
363
+ });
364
+
365
+ it('returns empty string when no user is logged in', () => {
366
+ expect(svc.loggedUser()).toBe('');
367
+ });
368
+ });
369
+
370
+ // ── setView ──────────────────────────────────────────────────────────────
371
+
372
+ describe('setView()', () => {
373
+ it('navigates to /scheduler for "Calendario"', () => {
374
+ svc.setView('Calendario');
375
+ expect(routerStub.navigateByUrl).toHaveBeenCalledWith('/scheduler');
376
+ });
377
+
378
+ it('navigates to /scheduler for "Calendar"', () => {
379
+ svc.setView('Calendar');
380
+ expect(routerStub.navigateByUrl).toHaveBeenCalledWith('/scheduler');
381
+ });
382
+
383
+ it('navigates to /home for "Dashboard"', () => {
384
+ svc.setView('Dashboard');
385
+ expect(routerStub.navigateByUrl).toHaveBeenCalledWith('/home');
386
+ });
387
+
388
+ it('navigates to /home/info for "Informazioni"', () => {
389
+ svc.setView('Informazioni');
390
+ expect(routerStub.navigateByUrl).toHaveBeenCalledWith('/home/info');
391
+ });
392
+
393
+ it('navigates to /home/info for "Information"', () => {
394
+ svc.setView('Information');
395
+ expect(routerStub.navigateByUrl).toHaveBeenCalledWith('/home/info');
396
+ });
397
+
398
+ it('stores the view name regardless of navigation', () => {
399
+ svc.setView('Custom');
400
+ expect(svc.getView()).toBe('Custom');
401
+ });
402
+ });
403
+
404
+ // ── setProject / getProject / setProjects / getProjects ──────────────────
405
+
406
+ describe('setProject() / getProject()', () => {
407
+ const prj = { project: 'APP', year: 2024 } as any;
408
+
409
+ it('stores and retrieves the current project', () => {
410
+ svc.setProject(prj);
411
+ expect(svc.getProject()).toBe(prj);
412
+ });
413
+
414
+ it('emits projectChanged when the project name changes', (done) => {
415
+ svc.getProjectChanged().subscribe(key => {
416
+ expect(key).toContain('APP');
417
+ done();
418
+ });
419
+ svc.setProject(prj);
420
+ });
421
+
422
+ it('does not emit projectChanged when the project name is unchanged', () => {
423
+ svc.setProject(prj);
424
+ const spy = jest.fn();
425
+ svc.getProjectChanged().subscribe(spy);
426
+ svc.setProject({ ...prj });
427
+ expect(spy).not.toHaveBeenCalled();
428
+ });
429
+ });
430
+
431
+ describe('setProjects() / getProjects()', () => {
432
+ it('stores and retrieves the projects matrix', () => {
433
+ const projects = [[{ project: 'APP' }]] as any;
434
+ svc.setProjects(projects);
435
+ expect(svc.getProjects()).toBe(projects);
436
+ });
437
+ });
438
+
439
+ // ── setYear with project ─────────────────────────────────────────────────
440
+
441
+ describe('setYear() — projectChanged side effect', () => {
442
+ it('emits projectChanged containing the new year when project is set', (done) => {
443
+ svc.setProject({ project: 'APP', year: 2023 } as any);
444
+ svc.getProjectChanged().subscribe(key => {
445
+ expect(key).toContain('2024');
446
+ done();
447
+ });
448
+ svc.setYear(2024);
449
+ });
450
+ });
451
+
452
+ // ── setLng with project ──────────────────────────────────────────────────
453
+
454
+ describe('setLng() — projectChanged side effect', () => {
455
+ it('fires projectChanged when a project is already set', (done) => {
456
+ svc.setProject({ project: 'APP', year: 2024 } as any);
457
+ svc.getProjectChanged().subscribe(key => {
458
+ expect(key).toContain('APP');
459
+ done();
460
+ });
461
+ svc.setLng(Languages.EN);
462
+ });
463
+ });
464
+
465
+ // ── Subject-based observables ────────────────────────────────────────────
466
+
467
+ describe('Subject-based observables', () => {
468
+ it('setCurrentField emits via getCurrentField()', (done) => {
469
+ const field = { name: 'testField' } as any;
470
+ svc.getCurrentField().subscribe(f => { expect(f).toBe(field); done(); });
471
+ svc.setCurrentField(field);
472
+ });
473
+
474
+ it('setFieldChanged emits via getFieldChanged()', (done) => {
475
+ const change = { id: 'f1', value: 'A', function: 'sendChanged' };
476
+ svc.getFieldChanged().subscribe(c => { expect(c).toBe(change); done(); });
477
+ svc.setFieldChanged(change);
478
+ });
479
+
480
+ it('setCurrentItem emits when model is defined', (done) => {
481
+ const item = { id: 'item1' } as any;
482
+ svc.getCurrentItem().subscribe(i => { expect(i).toBe(item); done(); });
483
+ svc.setCurrentItem(item);
484
+ });
485
+
486
+ it('setCurrentItem does not emit when model is undefined', () => {
487
+ const spy = jest.fn();
488
+ svc.getCurrentItem().subscribe(spy);
489
+ svc.setCurrentItem(undefined);
490
+ expect(spy).not.toHaveBeenCalled();
491
+ });
492
+
493
+ it('emitGridRefresh emits via getGridRefresh()', (done) => {
494
+ svc.getGridRefresh().subscribe(() => done());
495
+ svc.emitGridRefresh();
496
+ });
497
+
498
+ it('emitItemState emits via getItemState()', (done) => {
499
+ svc.getItemState().subscribe(() => done());
500
+ svc.emitItemState();
501
+ });
502
+
503
+ it('getStateChanged emits after setLng()', (done) => {
504
+ svc.getStateChanged().subscribe(() => done());
505
+ svc.setLng(Languages.IT);
506
+ });
507
+
508
+ it('setLoaderState emits via getLoaderState() and updates signal', (done) => {
509
+ svc.getLoaderState().subscribe(v => { expect(v).toBe(true); done(); });
510
+ svc.setLoaderState(true);
511
+ expect(svc.getLoading()).toBe(true);
512
+ });
513
+
514
+ it('setLoading updates the loading signal directly', () => {
515
+ svc.setLoading(true);
516
+ expect(svc.getLoading()).toBe(true);
517
+ svc.setLoading(false);
518
+ expect(svc.getLoading()).toBe(false);
519
+ });
520
+
521
+ it('setModalActive updates signal and emits via getModalActiveChanges()', (done) => {
522
+ svc.getModalActiveChanges().subscribe(v => { expect(v).toBe(true); done(); });
523
+ svc.setModalActive(true);
524
+ expect(svc.getModalActive()).toBe(true);
525
+ });
526
+
527
+ it('setLoadedState emits via getLoadedState()', (done) => {
528
+ svc.getLoadedState().subscribe(v => { expect(v).toBe(true); done(); });
529
+ svc.setLoadedState(true);
530
+ });
531
+
532
+ it('setItemCommands emits via getItemCommands()', (done) => {
533
+ const cmds = [{ name: 'save', enabled: true }] as any;
534
+ svc.getItemCommands().subscribe(c => { expect(c).toBe(cmds); done(); });
535
+ svc.setItemCommands(cmds);
536
+ });
537
+
538
+ it('setLookup emits via getLookup()', (done) => {
539
+ const lookup = { selected: 'row1' } as any;
540
+ svc.getLookup().subscribe(l => { expect(l).toBe(lookup); done(); });
541
+ svc.setLookup(lookup);
542
+ });
543
+
544
+ it('setLookupField emits via getLookupField()', (done) => {
545
+ const field = { visible: true } as any;
546
+ svc.getLookupField().subscribe(f => { expect(f).toBe(field); done(); });
547
+ svc.setLookupField(field);
548
+ });
549
+
550
+ it('setDialogField emits via getDialogField()', (done) => {
551
+ const dialog = { message: 'hello' } as any;
552
+ svc.getDialogField().subscribe(d => { expect(d).toBe(dialog); done(); });
553
+ svc.setDialogField(dialog);
554
+ });
555
+
556
+ it('complete methods do not throw', () => {
557
+ expect(() => {
558
+ svc.completeCurrentField();
559
+ svc.completeFieldChanged();
560
+ svc.completeCurrentItem();
561
+ svc.completeLoaderState();
562
+ svc.completeItemCommands();
563
+ svc.completeLookup();
564
+ svc.completeLookupField();
565
+ }).not.toThrow();
566
+ });
567
+ });
568
+
569
+ // ── setDialog / getDialog ─────────────────────────────────────────────────
570
+
571
+ describe('setDialog() / getDialog()', () => {
572
+ it('stores and retrieves the dialog state', () => {
573
+ const dialog = { target: 'field1', state: true };
574
+ svc.setDialog(dialog);
575
+ expect(svc.getDialog()).toBe(dialog);
576
+ });
577
+
578
+ it('stores null dialog', () => {
579
+ svc.setDialog(null);
580
+ expect(svc.getDialog()).toBeNull();
581
+ });
582
+ });
583
+
584
+ // ── removeData ───────────────────────────────────────────────────────────
585
+
586
+ describe('removeData()', () => {
587
+ it('removes the cached data for the given id', () => {
588
+ svc.setData('APP.ENT.2024', { entities: [] } as any);
589
+ svc.removeData('APP.ENT.2024');
590
+ expect(svc.getData('APP.ENT.2024')).toBeUndefined();
591
+ });
592
+ });
593
+
594
+ // ── getFields / setFields ─────────────────────────────────────────────────
595
+
596
+ describe('getFields() / setFields()', () => {
597
+ it('stores and retrieves field definitions', () => {
598
+ const fields = [{ name: 'field1' }] as any[];
599
+ svc.setFields('APP.ENT.2024', fields);
600
+ expect(svc.getFields('APP.ENT.2024')).toBe(fields);
601
+ });
602
+ });
603
+
604
+ // ── getProjectItems / setProjectItems ────────────────────────────────────
605
+
606
+ describe('getProjectItems() / setProjectItems()', () => {
607
+ it('stores and retrieves project items', () => {
608
+ const items = [{ id: 'item1' }] as any[];
609
+ svc.setProjectItems('APP.2024', items);
610
+ expect(svc.getProjectItems('APP.2024')).toBe(items);
611
+ });
612
+ });
613
+
614
+ // ── setCurrentLinkItem / getCurrentLinkItem ──────────────────────────────
615
+
616
+ describe('setCurrentLinkItem() / getCurrentLinkItem()', () => {
617
+ it('stores and retrieves the current link item', () => {
618
+ const link = { entity: 'ENT', key: '5' } as any;
619
+ svc.setCurrentLinkItem(link);
620
+ expect(svc.getCurrentLinkItem()).toBe(link);
621
+ });
622
+ });
623
+
624
+ // ── sortArray ────────────────────────────────────────────────────────────
625
+
626
+ describe('sortArray()', () => {
627
+ it('sorts an array by a simple string property', () => {
628
+ const arr = [{ name: 'C' }, { name: 'A' }, { name: 'B' }];
629
+ const sorted = svc.sortArray('name', arr);
630
+ expect(sorted.map((x: any) => x.name)).toEqual(['A', 'B', 'C']);
631
+ });
632
+
633
+ it('sorts an array by a nested property path', () => {
634
+ const arr = [{ a: { b: 3 } }, { a: { b: 1 } }, { a: { b: 2 } }];
635
+ const sorted = svc.sortArray('a.b', arr);
636
+ expect(sorted.map((x: any) => x.a.b)).toEqual([1, 2, 3]);
637
+ });
638
+ });
639
+
640
+ // ── padNumber ────────────────────────────────────────────────────────────
641
+
642
+ describe('padNumber()', () => {
643
+ it('pads a single digit to 2 places', () => {
644
+ expect(svc.padNumber(5, 2)).toBe('05');
645
+ });
646
+
647
+ it('does not pad when already at required length', () => {
648
+ expect(svc.padNumber(42, 2)).toBe('42');
649
+ });
650
+
651
+ it('pads to 7 places for key generation', () => {
652
+ expect(svc.padNumber(123, 7)).toBe('0000123');
653
+ });
654
+ });
655
+
656
+ // ── normalizeDate ─────────────────────────────────────────────────────────
657
+
658
+ describe('normalizeDate()', () => {
659
+ it('pads single-digit day and month to two digits', () => {
660
+ expect(svc.normalizeDate(['1', '3', '2024'])).toBe('01/03/2024');
661
+ });
662
+
663
+ it('keeps double-digit values unchanged', () => {
664
+ expect(svc.normalizeDate(['15', '12', '2023'])).toBe('15/12/2023');
665
+ });
666
+ });
667
+
668
+ // ── getKey ────────────────────────────────────────────────────────────────
669
+
670
+ describe('getKey()', () => {
671
+ it('pads each key segment to 7 digits and concatenates', () => {
672
+ expect(svc.getKey(['5', '123'])).toBe('00000050000123');
673
+ });
674
+
675
+ it('handles a single key segment', () => {
676
+ expect(svc.getKey(['1'])).toBe('0000001');
677
+ });
678
+ });
679
+
680
+ // ── getSchedulerInterface ────────────────────────────────────────────────
681
+
682
+ describe('getSchedulerInterface()', () => {
683
+ it('returns a zeroed SchedulerInterface', () => {
684
+ const s = svc.getSchedulerInterface();
685
+ expect(s.year).toBe(0);
686
+ expect(s.id).toBe('');
687
+ expect(s.start).toBeNull();
688
+ expect(s.end).toBeNull();
689
+ expect(s.allday).toBe(false);
690
+ expect(s.assigned).toBe(0);
691
+ });
692
+ });
693
+
694
+ // ── externalnavigate ─────────────────────────────────────────────────────
695
+
696
+ describe('externalnavigate()', () => {
697
+ it('pushes a history state entry after setting the external URL', () => {
698
+ const pushStateSpy = jest.spyOn(window.history, 'pushState');
699
+ const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
700
+
701
+ svc.externalnavigate('https://example.com');
702
+
703
+ expect(pushStateSpy).toHaveBeenCalledWith('', '', '');
704
+
705
+ consoleSpy.mockRestore();
706
+ pushStateSpy.mockRestore();
707
+ });
708
+ });
709
+ });
710
+
711
+ // ── activateProject (separate setup needed for projectsInfoLoader) ─────────
712
+
713
+ describe('GlobalService — activateProject()', () => {
714
+ let svc: GlobalService;
715
+
716
+ const mockInfo = {
717
+ getData: () => ({ entities: [] } as any),
718
+ getSchema: () => ({ entities: [] } as any),
719
+ };
720
+
721
+ beforeEach(() => {
722
+ localStorage.clear();
723
+ jest.clearAllMocks();
724
+ TestBed.configureTestingModule({
725
+ providers: [
726
+ GlobalService,
727
+ { provide: Router, useValue: routerStub },
728
+ { provide: Location, useValue: locationStub },
729
+ {
730
+ provide: NEWPORT_CONFIG,
731
+ useValue: {
732
+ apiUrl: 'http://test/',
733
+ logicFactory: () => {},
734
+ projectsInfoLoader: () => Promise.resolve(mockInfo),
735
+ },
736
+ },
737
+ ],
738
+ });
739
+ svc = TestBed.inject(GlobalService);
740
+ });
741
+
742
+ it('navigates to /scheduler for type "C" with IT language', () => {
743
+ svc.setLng(Languages.IT);
744
+ svc.activateProject({ project: 'APP', year: 2024, type: 'C' } as any);
745
+ expect(routerStub.navigateByUrl).toHaveBeenCalledWith('/scheduler');
746
+ });
747
+
748
+ it('navigates to /scheduler for type "C" with EN language', () => {
749
+ svc.setLng(Languages.EN);
750
+ svc.activateProject({ project: 'APP', year: 2024, type: 'C' } as any);
751
+ expect(routerStub.navigateByUrl).toHaveBeenCalledWith('/scheduler');
752
+ });
753
+
754
+ it('navigates to /home/modelsearch for type "D"', async () => {
755
+ svc.activateProject({ project: 'APP', year: 2024, type: 'D' } as any);
756
+ await Promise.resolve();
757
+ expect(routerStub.navigateByUrl).toHaveBeenCalledWith('/home/modelsearch');
758
+ });
759
+
760
+ it('navigates to /home/modelsearch for type "G"', async () => {
761
+ svc.activateProject({ project: 'APP', year: 2024, type: 'G' } as any);
762
+ await Promise.resolve();
763
+ expect(routerStub.navigateByUrl).toHaveBeenCalledWith('/home/modelsearch');
764
+ });
765
+
766
+ it('navigates to /home/model for other project types', async () => {
767
+ svc.activateProject({ project: 'APP', year: 2024, type: 'A' } as any);
768
+ await Promise.resolve();
769
+ expect(routerStub.navigateByUrl).toHaveBeenCalledWith('/home/model');
770
+ });
771
+
772
+ it('caches dataJson and schemaJson on first load', async () => {
773
+ svc.activateProject({ project: 'APP', year: 2024, type: 'D' } as any);
774
+ await Promise.resolve();
775
+ expect(svc.getDataJson('APP.2024')).toBeDefined();
776
+ expect(svc.getSchemaJson('APP.2024')).toBeDefined();
777
+ });
778
+
779
+ it('does not overwrite an existing dataJson cache entry', async () => {
780
+ const existing = { entities: [{ name: 'cached' }] } as any;
781
+ svc.setDataJson('APP.2024', existing);
782
+ svc.activateProject({ project: 'APP', year: 2024, type: 'D' } as any);
783
+ await Promise.resolve();
784
+ expect(svc.getDataJson('APP.2024')).toBe(existing);
785
+ });
786
+
787
+ it('does not overwrite an existing schemaJson cache entry', async () => {
788
+ const existing = { entities: [{ name: 'cached-schema' }] } as any;
789
+ svc.setSchemaJson('APP.2024', existing);
790
+ svc.activateProject({ project: 'APP', year: 2024, type: 'D' } as any);
791
+ await Promise.resolve();
792
+ expect(svc.getSchemaJson('APP.2024')).toBe(existing);
793
+ });
794
+
795
+ it('does not navigate to /scheduler for type "C" with an unknown language', () => {
796
+ svc.setLng('FR');
797
+ jest.clearAllMocks();
798
+ svc.activateProject({ project: 'APP', year: 2024, type: 'C' } as any);
799
+ expect(routerStub.navigateByUrl).not.toHaveBeenCalledWith('/scheduler');
800
+ });
801
+ });