@dflat-core/web 1.0.48 → 1.0.50
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.
- package/README.md +40 -0
- package/fesm2022/dflat-core-web-core-data.mjs +19 -18
- package/fesm2022/dflat-core-web-core-data.mjs.map +1 -1
- package/fesm2022/dflat-core-web-core-model.mjs.map +1 -1
- package/fesm2022/dflat-core-web-core-services.mjs +275 -3
- package/fesm2022/dflat-core-web-core-services.mjs.map +1 -1
- package/fesm2022/dflat-core-web-core-ux-testing.mjs +391 -0
- package/fesm2022/dflat-core-web-core-ux-testing.mjs.map +1 -0
- package/fesm2022/dflat-core-web-core-ux.mjs +1 -1
- package/fesm2022/dflat-core-web-core-ux.mjs.map +1 -1
- package/fesm2022/dflat-core-web-system-data.mjs +51 -1
- package/fesm2022/dflat-core-web-system-data.mjs.map +1 -1
- package/fesm2022/dflat-core-web-system-model.mjs +7 -1
- package/fesm2022/dflat-core-web-system-model.mjs.map +1 -1
- package/fesm2022/dflat-core-web-system-root.mjs +29 -0
- package/fesm2022/dflat-core-web-system-root.mjs.map +1 -1
- package/fesm2022/dflat-core-web-system-ux.mjs +246 -10
- package/fesm2022/dflat-core-web-system-ux.mjs.map +1 -1
- package/fesm2022/dflat-core-web.mjs.map +1 -1
- package/package.json +5 -1
- package/types/dflat-core-web-core-data.d.ts +9 -0
- package/types/dflat-core-web-core-model.d.ts +9 -0
- package/types/dflat-core-web-core-services.d.ts +106 -2
- package/types/dflat-core-web-core-ux-testing.d.ts +232 -0
- package/types/dflat-core-web-core-ux.d.ts +8 -1
- package/types/dflat-core-web-system-data.d.ts +11 -1
- package/types/dflat-core-web-system-model.d.ts +35 -0
- package/types/dflat-core-web-system-root.d.ts +8 -0
- package/types/dflat-core-web-system-ux.d.ts +70 -24
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
import { of } from "rxjs";
|
|
2
|
+
import { ApplicationService, EntityFacadeRegistryService } from "@dflat-core/web/core-ux";
|
|
3
|
+
import { BreadcrumbService, NotificationMqService } from "@dflat-core/web/core-services";
|
|
4
|
+
import { TestBed } from "@angular/core/testing";
|
|
5
|
+
import { Title } from "@angular/platform-browser";
|
|
6
|
+
import { Utilities } from "@dflat-core/web/core";
|
|
7
|
+
import { Notification } from "@dflat-core/web/core-model";
|
|
8
|
+
var DataServiceDouble = class {
|
|
9
|
+
constructor() {
|
|
10
|
+
this.searchCalls = [];
|
|
11
|
+
this.detailsCalls = [];
|
|
12
|
+
this.searchResult = {
|
|
13
|
+
Values: [],
|
|
14
|
+
TotalRowCount: 0
|
|
15
|
+
};
|
|
16
|
+
this.details = {};
|
|
17
|
+
}
|
|
18
|
+
search(entityKey, filter) {
|
|
19
|
+
this.searchCalls.push({
|
|
20
|
+
entityKey,
|
|
21
|
+
filter
|
|
22
|
+
});
|
|
23
|
+
return of(this.searchResult);
|
|
24
|
+
}
|
|
25
|
+
getDetails(entityKey, id) {
|
|
26
|
+
this.detailsCalls.push({
|
|
27
|
+
entityKey,
|
|
28
|
+
id
|
|
29
|
+
});
|
|
30
|
+
return of(this.details);
|
|
31
|
+
}
|
|
32
|
+
getFormattedParams(params) {
|
|
33
|
+
return { ...params };
|
|
34
|
+
}
|
|
35
|
+
getViewColumns() {
|
|
36
|
+
return of([]);
|
|
37
|
+
}
|
|
38
|
+
getViewFilterDescriptors() {
|
|
39
|
+
return of([]);
|
|
40
|
+
}
|
|
41
|
+
getEmptyFilter() {
|
|
42
|
+
return of({
|
|
43
|
+
Offset: 0,
|
|
44
|
+
Take: 25
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
getEntityViewModel() {
|
|
48
|
+
return of({});
|
|
49
|
+
}
|
|
50
|
+
getExtendedActions() {
|
|
51
|
+
return of([]);
|
|
52
|
+
}
|
|
53
|
+
asDataService() {
|
|
54
|
+
return this;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
var EntityFacadeRegistrySpy = class extends EntityFacadeRegistryService {
|
|
58
|
+
constructor() {
|
|
59
|
+
super(...arguments);
|
|
60
|
+
this.requests = [];
|
|
61
|
+
}
|
|
62
|
+
getFacade(key, id) {
|
|
63
|
+
const facade = super.getFacade(key, id);
|
|
64
|
+
this.requests.push({
|
|
65
|
+
key,
|
|
66
|
+
id,
|
|
67
|
+
facade
|
|
68
|
+
});
|
|
69
|
+
return facade;
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
var RouterDouble = class RouterDouble {
|
|
73
|
+
constructor(url) {
|
|
74
|
+
this.navigations = [];
|
|
75
|
+
this.url = url;
|
|
76
|
+
}
|
|
77
|
+
createUrlTree(commands, extras) {
|
|
78
|
+
const url = RouterDouble.format(commands, extras?.queryParams);
|
|
79
|
+
return { toString: () => url };
|
|
80
|
+
}
|
|
81
|
+
navigate(commands, extras) {
|
|
82
|
+
this.navigations.push({
|
|
83
|
+
commands,
|
|
84
|
+
extras
|
|
85
|
+
});
|
|
86
|
+
this.url = RouterDouble.format(commands, extras?.queryParams);
|
|
87
|
+
return Promise.resolve(true);
|
|
88
|
+
}
|
|
89
|
+
asRouter() {
|
|
90
|
+
return this;
|
|
91
|
+
}
|
|
92
|
+
static format(commands, queryParams) {
|
|
93
|
+
const path = commands.join("/");
|
|
94
|
+
const query = Object.entries(queryParams ?? {}).map(([key, value]) => `${key}=${encodeURIComponent(String(value))}`).join("&");
|
|
95
|
+
return query ? `${path}?${query}` : path;
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
var BaseComponentTestContext = class {
|
|
99
|
+
constructor(url, title) {
|
|
100
|
+
this.dataService = new DataServiceDouble();
|
|
101
|
+
this.notifications = new NotificationMqService();
|
|
102
|
+
this.router = new RouterDouble(url);
|
|
103
|
+
this.route = {
|
|
104
|
+
snapshot: {
|
|
105
|
+
params: {},
|
|
106
|
+
queryParams: {}
|
|
107
|
+
},
|
|
108
|
+
params: of({}),
|
|
109
|
+
queryParams: of({})
|
|
110
|
+
};
|
|
111
|
+
this.breadcrumb = new BreadcrumbService(title);
|
|
112
|
+
const deps = {
|
|
113
|
+
nav: {
|
|
114
|
+
route: this.route,
|
|
115
|
+
router: this.router.asRouter(),
|
|
116
|
+
location: void 0
|
|
117
|
+
},
|
|
118
|
+
ui: {
|
|
119
|
+
layout: void 0,
|
|
120
|
+
breadcrumb: this.breadcrumb,
|
|
121
|
+
toastr: void 0,
|
|
122
|
+
alert: void 0,
|
|
123
|
+
modal: void 0
|
|
124
|
+
},
|
|
125
|
+
data: {
|
|
126
|
+
data: this.dataService.asDataService(),
|
|
127
|
+
filter: void 0,
|
|
128
|
+
selector: void 0
|
|
129
|
+
},
|
|
130
|
+
security: {
|
|
131
|
+
auth: void 0,
|
|
132
|
+
user: void 0
|
|
133
|
+
},
|
|
134
|
+
selection: void 0,
|
|
135
|
+
test: void 0
|
|
136
|
+
};
|
|
137
|
+
this.registry = new EntityFacadeRegistrySpy(deps);
|
|
138
|
+
this.app = new ApplicationService(deps, this.notifications, void 0, void 0, void 0, this.registry);
|
|
139
|
+
}
|
|
140
|
+
get currentBreadcrumb() {
|
|
141
|
+
let current;
|
|
142
|
+
this.breadcrumb.getCurrent().subscribe((data) => current = data).unsubscribe();
|
|
143
|
+
return current;
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
function countCalls(target, method) {
|
|
147
|
+
const original = target[method];
|
|
148
|
+
const counter = { count: 0 };
|
|
149
|
+
target[method] = ((...args) => {
|
|
150
|
+
counter.count++;
|
|
151
|
+
return original.apply(target, args);
|
|
152
|
+
});
|
|
153
|
+
return counter;
|
|
154
|
+
}
|
|
155
|
+
function getTestRunner() {
|
|
156
|
+
const runner = globalThis;
|
|
157
|
+
if (typeof runner.describe !== "function" || typeof runner.it !== "function" || typeof runner.beforeEach !== "function" || typeof runner.expect !== "function") throw new Error("Suites de test Dflat : describe, it, beforeEach et expect doivent être des fonctions globales (Vitest : option globals).");
|
|
158
|
+
return runner;
|
|
159
|
+
}
|
|
160
|
+
function describeBaseComponent(name, factory, expectations) {
|
|
161
|
+
const { describe, it, beforeEach, expect } = getTestRunner();
|
|
162
|
+
describe(`${name} (contrat de BaseComponent)`, () => {
|
|
163
|
+
let context;
|
|
164
|
+
let component;
|
|
165
|
+
const notify = (entityId) => context.notifications.publish(new Notification({
|
|
166
|
+
entityType: Utilities.GetChannel(expectations.entityKey),
|
|
167
|
+
entityId
|
|
168
|
+
}));
|
|
169
|
+
beforeEach(() => {
|
|
170
|
+
TestBed.configureTestingModule({ providers: expectations.providers ?? [] });
|
|
171
|
+
context = new BaseComponentTestContext(expectations.path, TestBed.inject(Title));
|
|
172
|
+
component = TestBed.runInInjectionContext(() => factory(context.app, context.route));
|
|
173
|
+
});
|
|
174
|
+
it("est créé dans un contexte d'injection", () => {
|
|
175
|
+
expect(component).toBeTruthy();
|
|
176
|
+
expect(component.destroy$.closed).toBe(false);
|
|
177
|
+
});
|
|
178
|
+
it("expose la clé d'entité, le lien, le titre et la permission attendus", () => {
|
|
179
|
+
expect(component.getEntityKey()).toBe(expectations.entityKey);
|
|
180
|
+
expect(component.getPath()).toBe(expectations.path);
|
|
181
|
+
expect(component.getTitle()).toBe(expectations.title);
|
|
182
|
+
expect(component.PermissionKey).toBe(expectations.permissionKey);
|
|
183
|
+
expect(component.layoutKey).toBe(expectations.entityKey);
|
|
184
|
+
});
|
|
185
|
+
it("demande sa façade au registre avec la clé de l'entité", () => {
|
|
186
|
+
expect(context.registry.requests.length).toBe(1);
|
|
187
|
+
expect(context.registry.requests[0].key).toBe(expectations.entityKey);
|
|
188
|
+
expect(context.registry.requests[0].facade).toBe(component.facade);
|
|
189
|
+
expect(component.facade.actions.handler.path).toBe(expectations.path);
|
|
190
|
+
});
|
|
191
|
+
it("ngOnInit publie le fil d'Ariane du composant principal", () => {
|
|
192
|
+
component.ngOnInit();
|
|
193
|
+
expect(component.displayComponent).toBe(true);
|
|
194
|
+
expect(component.actionTitle).toBe(expectations.title);
|
|
195
|
+
expect(context.currentBreadcrumb).toEqual({
|
|
196
|
+
description: expectations.title,
|
|
197
|
+
display: true,
|
|
198
|
+
header: expectations.title,
|
|
199
|
+
levels: [{
|
|
200
|
+
icon: component.getIcon(),
|
|
201
|
+
link: [expectations.path],
|
|
202
|
+
title: expectations.title
|
|
203
|
+
}]
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
it("ngOnInit ne publie pas de fil d'Ariane pour un composant d'onglet", () => {
|
|
207
|
+
component.IsTheMainComponent = false;
|
|
208
|
+
component.ngOnInit();
|
|
209
|
+
expect(context.currentBreadcrumb.display).toBe(false);
|
|
210
|
+
});
|
|
211
|
+
it("ngOnInit branche le chargement sur dataService.search avec la clé de l'entité", () => {
|
|
212
|
+
context.dataService.searchResult = {
|
|
213
|
+
Values: [{
|
|
214
|
+
Id: 7,
|
|
215
|
+
Name: "avant"
|
|
216
|
+
}],
|
|
217
|
+
TotalRowCount: 1
|
|
218
|
+
};
|
|
219
|
+
component.ngOnInit();
|
|
220
|
+
const filter = {
|
|
221
|
+
Offset: 0,
|
|
222
|
+
Take: 25
|
|
223
|
+
};
|
|
224
|
+
let total;
|
|
225
|
+
component.facade.data.loadData(filter).subscribe((result) => total = result.TotalRowCount);
|
|
226
|
+
expect(context.dataService.searchCalls).toEqual([{
|
|
227
|
+
entityKey: expectations.entityKey,
|
|
228
|
+
filter
|
|
229
|
+
}]);
|
|
230
|
+
expect(total).toBe(1);
|
|
231
|
+
expect(component.facade.data.getOriginal(7).Name).toBe("avant");
|
|
232
|
+
});
|
|
233
|
+
it("ngOnInit s'abonne une seule fois aux notifications de l'entité", () => {
|
|
234
|
+
component.ngOnInit();
|
|
235
|
+
const first = component.notif;
|
|
236
|
+
component.ngOnInit();
|
|
237
|
+
expect(first.closed).toBe(true);
|
|
238
|
+
expect(component.notif.closed).toBe(false);
|
|
239
|
+
});
|
|
240
|
+
if (expectations.sections) it("ngOnInit enregistre les onglets attendus", async () => {
|
|
241
|
+
component.ngOnInit();
|
|
242
|
+
await Promise.resolve();
|
|
243
|
+
await Promise.resolve();
|
|
244
|
+
let sections = [];
|
|
245
|
+
component.facade.sections.sections$.subscribe((list) => sections = list.map((s) => s.Name)).unsubscribe();
|
|
246
|
+
expect(sections).toEqual(expectations.sections);
|
|
247
|
+
});
|
|
248
|
+
it("Search copie le filtre, remet la page à zéro et navigue vers l'URL du filtre", () => {
|
|
249
|
+
component.ngOnInit();
|
|
250
|
+
const loads = countCalls(component.facade.filter, "triggerSearch");
|
|
251
|
+
component.Search({
|
|
252
|
+
Keywords: "abc",
|
|
253
|
+
Offset: 50
|
|
254
|
+
});
|
|
255
|
+
expect(component.Filter.Keywords).toBe("abc");
|
|
256
|
+
expect(component.Filter.Offset).toBe(0);
|
|
257
|
+
expect(context.router.navigations.length).toBe(1);
|
|
258
|
+
expect(context.router.navigations[0].commands).toEqual([expectations.path]);
|
|
259
|
+
expect(context.router.navigations[0].extras?.queryParams).toEqual({
|
|
260
|
+
Keywords: "abc",
|
|
261
|
+
Offset: 0
|
|
262
|
+
});
|
|
263
|
+
expect(loads.count).toBe(0);
|
|
264
|
+
});
|
|
265
|
+
it("Search charge les données quand l'URL porte déjà le filtre", () => {
|
|
266
|
+
component.ngOnInit();
|
|
267
|
+
const loads = countCalls(component.facade.filter, "triggerSearch");
|
|
268
|
+
component.Search({ Keywords: "abc" });
|
|
269
|
+
component.Search({ Keywords: "abc" });
|
|
270
|
+
expect(context.router.navigations.length).toBe(1);
|
|
271
|
+
expect(loads.count).toBe(1);
|
|
272
|
+
});
|
|
273
|
+
it("Search garde la page demandée sans remise à zéro", () => {
|
|
274
|
+
component.ngOnInit();
|
|
275
|
+
component.Search({ Offset: 50 }, false);
|
|
276
|
+
expect(component.Filter.Offset).toBe(50);
|
|
277
|
+
});
|
|
278
|
+
it("Search et refresh chargent les données sans naviguer pour un composant d'onglet", () => {
|
|
279
|
+
component.IsTheMainComponent = false;
|
|
280
|
+
component.ngOnInit();
|
|
281
|
+
const loads = countCalls(component.facade.filter, "triggerSearch");
|
|
282
|
+
component.Search({
|
|
283
|
+
Keywords: "abc",
|
|
284
|
+
Offset: 50
|
|
285
|
+
});
|
|
286
|
+
expect(loads.count).toBe(1);
|
|
287
|
+
expect(component.Filter.Keywords).toBe("abc");
|
|
288
|
+
component.Filter.Offset = 50;
|
|
289
|
+
component.refresh();
|
|
290
|
+
expect(loads.count).toBe(2);
|
|
291
|
+
expect(component.Filter.Offset).toBe(0);
|
|
292
|
+
expect(context.router.navigations.length).toBe(0);
|
|
293
|
+
});
|
|
294
|
+
it("met à jour la ligne d'une entité chargée à sa notification", () => {
|
|
295
|
+
context.dataService.searchResult = {
|
|
296
|
+
Values: [{
|
|
297
|
+
Id: 7,
|
|
298
|
+
Name: "avant"
|
|
299
|
+
}],
|
|
300
|
+
TotalRowCount: 1
|
|
301
|
+
};
|
|
302
|
+
component.ngOnInit();
|
|
303
|
+
component.facade.data.loadData({
|
|
304
|
+
Offset: 0,
|
|
305
|
+
Take: 25
|
|
306
|
+
}).subscribe();
|
|
307
|
+
context.dataService.details = {
|
|
308
|
+
Id: 7,
|
|
309
|
+
Name: "après"
|
|
310
|
+
};
|
|
311
|
+
notify(7);
|
|
312
|
+
expect(context.dataService.detailsCalls).toEqual([{
|
|
313
|
+
entityKey: expectations.entityKey,
|
|
314
|
+
id: 7
|
|
315
|
+
}]);
|
|
316
|
+
expect(component.facade.data.getOriginal(7).Name).toBe("après");
|
|
317
|
+
});
|
|
318
|
+
it("ignore la notification d'une entité non chargée, ou recharge si loadDataOnNotification", () => {
|
|
319
|
+
component.ngOnInit();
|
|
320
|
+
const loads = countCalls(component.facade.filter, "triggerSearch");
|
|
321
|
+
notify(8);
|
|
322
|
+
expect(context.dataService.detailsCalls.length).toBe(0);
|
|
323
|
+
expect(loads.count).toBe(0);
|
|
324
|
+
component.loadDataOnNotification = true;
|
|
325
|
+
notify(8);
|
|
326
|
+
expect(context.dataService.detailsCalls.length).toBe(0);
|
|
327
|
+
expect(loads.count).toBe(1);
|
|
328
|
+
});
|
|
329
|
+
it("ngOnDestroy termine destroy$, se désabonne des notifications et efface le fil d'Ariane", () => {
|
|
330
|
+
component.ngOnInit();
|
|
331
|
+
let emitted = 0;
|
|
332
|
+
let completed = false;
|
|
333
|
+
component.destroy$.subscribe({
|
|
334
|
+
next: () => emitted++,
|
|
335
|
+
complete: () => completed = true
|
|
336
|
+
});
|
|
337
|
+
component.ngOnDestroy();
|
|
338
|
+
expect(emitted).toBe(1);
|
|
339
|
+
expect(completed).toBe(true);
|
|
340
|
+
expect(component.notif.closed).toBe(true);
|
|
341
|
+
expect(context.currentBreadcrumb.display).toBe(false);
|
|
342
|
+
notify(7);
|
|
343
|
+
expect(context.dataService.detailsCalls.length).toBe(0);
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
function getConstants(constants) {
|
|
348
|
+
return Object.values(constants).filter((value) => typeof value === "string");
|
|
349
|
+
}
|
|
350
|
+
function getDuplicates(values) {
|
|
351
|
+
return values.filter((value, index) => values.indexOf(value) !== index);
|
|
352
|
+
}
|
|
353
|
+
function describeGeneratedModule(name, module) {
|
|
354
|
+
const { describe, it, expect } = getTestRunner();
|
|
355
|
+
describe(`Module ${name} (éléments générés)`, () => {
|
|
356
|
+
const paths = module.routes.map((route) => route.path ?? "");
|
|
357
|
+
it("donne une route à chaque composant généré", () => {
|
|
358
|
+
const missing = module.components.filter((component) => !module.routes.some((route) => route.component === component)).map((component) => component.name);
|
|
359
|
+
expect(missing).toEqual([]);
|
|
360
|
+
});
|
|
361
|
+
it("n'a que des routes de composants générés, de chemins distincts", () => {
|
|
362
|
+
const unknown = module.routes.filter((route) => !module.components.some((component) => component === route.component)).map((route) => route.path);
|
|
363
|
+
expect(unknown).toEqual([]);
|
|
364
|
+
expect(getDuplicates(paths)).toEqual([]);
|
|
365
|
+
});
|
|
366
|
+
it("a des clés d'entité uniques, dont celles des routes", () => {
|
|
367
|
+
const keys = getConstants(module.entityKeys);
|
|
368
|
+
expect(getDuplicates(keys)).toEqual([]);
|
|
369
|
+
expect(paths.filter((path) => !keys.includes(path))).toEqual([]);
|
|
370
|
+
});
|
|
371
|
+
it("donne à chaque sélecteur d'entité le composant de la route de sa clé", () => {
|
|
372
|
+
const mismatched = module.selectors.filter((selector) => !module.routes.some((route) => route.path === selector.key && route.component === selector.component)).map((selector) => selector.key);
|
|
373
|
+
expect(mismatched).toEqual([]);
|
|
374
|
+
});
|
|
375
|
+
it("fait pointer chaque élément de menu généré vers une route existante", () => {
|
|
376
|
+
const links = paths.map((path) => module.getLink(path));
|
|
377
|
+
const orphans = module.menuItems.filter((item) => !links.includes(item.link ?? "")).map((item) => item.link);
|
|
378
|
+
expect(orphans).toEqual([]);
|
|
379
|
+
});
|
|
380
|
+
if (module.permissions) {
|
|
381
|
+
const permissions = getConstants(module.permissions);
|
|
382
|
+
it("donne à chaque élément de menu généré une permission du module", () => {
|
|
383
|
+
const ungranted = module.menuItems.filter((item) => !permissions.includes(item.permissionKey ?? "")).map((item) => item.link);
|
|
384
|
+
expect(ungranted).toEqual([]);
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
export { BaseComponentTestContext, DataServiceDouble, EntityFacadeRegistrySpy, RouterDouble, countCalls, describeBaseComponent, describeGeneratedModule, getTestRunner };
|
|
390
|
+
|
|
391
|
+
//# sourceMappingURL=dflat-core-web-core-ux-testing.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dflat-core-web-core-ux-testing.mjs","names":[],"sources":["../../core-ux/testing/src/lib/doubles/data-service-double.ts","../../core-ux/testing/src/lib/doubles/entity-facade-registry-spy.ts","../../core-ux/testing/src/lib/doubles/router-double.ts","../../core-ux/testing/src/lib/base-component-test-context.ts","../../core-ux/testing/src/lib/count-calls.ts","../../core-ux/testing/src/lib/get-test-runner.ts","../../core-ux/testing/src/lib/describe-base-component.ts","../../core-ux/testing/src/lib/describe-generated-module.ts"],"sourcesContent":["import { DataService } from '@dflat-core/web/core-data';\r\nimport {\r\n ActionDescriptor,\r\n ColumnDescriptor,\r\n EntityViewModel,\r\n EntityViewModelWithFields,\r\n FilterDescriptor,\r\n FilterViewModel,\r\n PagedList\r\n} from '@dflat-core/web/core-model';\r\nimport { Observable, of } from 'rxjs';\r\n\r\n/**\r\n * Doublure de DataService : aucune requête HTTP. Fournit les données d'initialisation d'une\r\n * façade (colonnes, filtre vide, formulaire, actions) et enregistre les recherches et lectures\r\n * de détail demandées par le composant testé.\r\n */\r\nexport class DataServiceDouble {\r\n /** Recherches demandées (search), dans l'ordre. */\r\n readonly searchCalls: { entityKey: string; filter: FilterViewModel }[] = [];\r\n\r\n /** Lectures de détail demandées (getDetails), dans l'ordre. */\r\n readonly detailsCalls: { entityKey: string; id: number }[] = [];\r\n\r\n /** Résultat renvoyé par search. */\r\n searchResult: PagedList<EntityViewModel> = { Values: [], TotalRowCount: 0 };\r\n\r\n /** Entité renvoyée par getDetails. */\r\n details: EntityViewModel = {};\r\n\r\n /** Enregistre la recherche et renvoie searchResult. */\r\n search<TData>(entityKey: string, filter: FilterViewModel): Observable<PagedList<TData>> {\r\n this.searchCalls.push({ entityKey, filter });\r\n return of(this.searchResult as PagedList<TData>);\r\n }\r\n\r\n /** Enregistre la lecture et renvoie details. */\r\n getDetails<TData>(entityKey: string, id: number): Observable<TData> {\r\n this.detailsCalls.push({ entityKey, id });\r\n return of(this.details as TData);\r\n }\r\n\r\n /** Paramètres d'URL d'un filtre : copie du filtre, sans mise en forme. */\r\n getFormattedParams(params: object): object {\r\n return { ...params };\r\n }\r\n\r\n /** Colonnes de la grille : aucune. */\r\n getViewColumns(): Observable<ColumnDescriptor[]> {\r\n return of([]);\r\n }\r\n\r\n /** Critères du filtre : aucun. */\r\n getViewFilterDescriptors(): Observable<FilterDescriptor[]> {\r\n return of([]);\r\n }\r\n\r\n /** Filtre vide : première page de 25 lignes. */\r\n getEmptyFilter<TFilter>(): Observable<TFilter> {\r\n return of({ Offset: 0, Take: 25 } as TFilter);\r\n }\r\n\r\n /** Formulaire vide, sans champ. */\r\n getEntityViewModel<TData>(): Observable<EntityViewModelWithFields<TData>> {\r\n return of({} as EntityViewModelWithFields<TData>);\r\n }\r\n\r\n /** Actions étendues : aucune. */\r\n getExtendedActions(): Observable<ActionDescriptor[]> {\r\n return of([]);\r\n }\r\n\r\n /** La doublure, vue comme le DataService qu'elle remplace. */\r\n asDataService(): DataService {\r\n return this as unknown as DataService;\r\n }\r\n}\r\n","import { EntityViewModel } from '@dflat-core/web/core-model';\r\nimport { EntityFacade, EntityFacadeRegistryService } from '@dflat-core/web/core-ux';\r\nimport { Guid } from 'guid-typescript';\r\n\r\n/** Registre de façades du socle (vraies façades), qui enregistre les façades demandées. */\r\nexport class EntityFacadeRegistrySpy extends EntityFacadeRegistryService {\r\n /** Façades demandées (getFacade), dans l'ordre : clé d'entité, identifiant et façade renvoyée. */\r\n readonly requests: { key: string; id: Guid; facade: EntityFacade<EntityViewModel> }[] = [];\r\n\r\n /** Enregistre la demande et renvoie la façade du registre. */\r\n override getFacade<TData extends EntityViewModel>(key: string, id: Guid): EntityFacade<TData> {\r\n const facade = super.getFacade<TData>(key, id);\r\n this.requests.push({ key, id, facade });\r\n return facade;\r\n }\r\n}\r\n","import { NavigationExtras, Router, UrlCreationOptions, UrlTree } from '@angular/router';\r\n\r\n/**\r\n * Doublure de Router : URL courante, construction d'URL (chemin et paramètres) et navigations\r\n * enregistrées. Une navigation réussit toujours et devient l'URL courante.\r\n */\r\nexport class RouterDouble {\r\n /** URL courante. */\r\n url: string;\r\n\r\n /** Navigations demandées (navigate), dans l'ordre. */\r\n readonly navigations: { commands: unknown[]; extras?: NavigationExtras }[] = [];\r\n\r\n constructor(url: string) {\r\n this.url = url;\r\n }\r\n\r\n /** URL formée des segments `commands` et des paramètres `extras.queryParams`. */\r\n createUrlTree(commands: unknown[], extras?: UrlCreationOptions): UrlTree {\r\n const url = RouterDouble.format(commands, extras?.queryParams);\r\n return { toString: () => url } as UrlTree;\r\n }\r\n\r\n /** Enregistre la navigation, qui devient l'URL courante. */\r\n navigate(commands: unknown[], extras?: NavigationExtras): Promise<boolean> {\r\n this.navigations.push({ commands, extras });\r\n this.url = RouterDouble.format(commands, extras?.queryParams);\r\n return Promise.resolve(true);\r\n }\r\n\r\n /** La doublure, vue comme le Router qu'elle remplace. */\r\n asRouter(): Router {\r\n return this as unknown as Router;\r\n }\r\n\r\n /** URL d'un chemin et de ses paramètres. */\r\n private static format(commands: unknown[], queryParams?: object | null): string {\r\n const path = commands.join('/');\r\n const query = Object.entries(queryParams ?? {})\r\n .map(([key, value]) => `${key}=${encodeURIComponent(String(value))}`)\r\n .join('&');\r\n return query ? `${path}?${query}` : path;\r\n }\r\n}\r\n","import { Title } from '@angular/platform-browser';\r\nimport { ActivatedRoute } from '@angular/router';\r\nimport { BreadcrumbService, NotificationMqService } from '@dflat-core/web/core-services';\r\nimport { ApplicationService, RootDeps } from '@dflat-core/web/core-ux';\r\nimport { of } from 'rxjs';\r\nimport { DataServiceDouble } from './doubles/data-service-double';\r\nimport { EntityFacadeRegistrySpy } from './doubles/entity-facade-registry-spy';\r\nimport { RouterDouble } from './doubles/router-double';\r\nimport { BreadcrumbData } from './interfaces/breadcrumb-data';\r\n\r\n/**\r\n * Services donnés à un composant dérivé de BaseComponent pendant un test : vraies classes du\r\n * socle quand elles s'instancient sans dépendance (ApplicationService, registre de façades et\r\n * façades, fil d'Ariane, file de messages), doublures pour le service de données, le routeur et\r\n * la route. Les services que BaseComponent n'utilise pas (authentification, fenêtres modales,\r\n * alertes, mise en page...) ne sont pas fournis.\r\n */\r\nexport class BaseComponentTestContext {\r\n /** Doublure du service de données. */\r\n readonly dataService = new DataServiceDouble();\r\n\r\n /** Doublure du routeur. */\r\n readonly router: RouterDouble;\r\n\r\n /** Route active, sans paramètre. */\r\n readonly route: ActivatedRoute;\r\n\r\n /** Fil d'Ariane du socle. */\r\n readonly breadcrumb: BreadcrumbService;\r\n\r\n /** File de messages (notifications d'entité) du socle. */\r\n readonly notifications = new NotificationMqService();\r\n\r\n /** Registre de façades du socle, qui enregistre les façades demandées. */\r\n readonly registry: EntityFacadeRegistrySpy;\r\n\r\n /** ApplicationService du socle, construit sur les services ci-dessus. */\r\n readonly app: ApplicationService;\r\n\r\n /**\r\n * @param url URL courante du routeur.\r\n * @param title Service du titre de la page (TestBed.inject(Title)), utilisé par le fil d'Ariane.\r\n */\r\n constructor(url: string, title: Title) {\r\n this.router = new RouterDouble(url);\r\n this.route = { snapshot: { params: {}, queryParams: {} }, params: of({}), queryParams: of({}) } as unknown as ActivatedRoute;\r\n this.breadcrumb = new BreadcrumbService(title);\r\n\r\n const deps = {\r\n nav: { route: this.route, router: this.router.asRouter(), location: undefined },\r\n ui: { layout: undefined, breadcrumb: this.breadcrumb, toastr: undefined, alert: undefined, modal: undefined },\r\n data: { data: this.dataService.asDataService(), filter: undefined, selector: undefined },\r\n security: { auth: undefined, user: undefined },\r\n selection: undefined,\r\n test: undefined\r\n } as unknown as RootDeps;\r\n\r\n this.registry = new EntityFacadeRegistrySpy(deps);\r\n this.app = new ApplicationService(deps, this.notifications, undefined, undefined, undefined, this.registry);\r\n }\r\n\r\n /** Fil d'Ariane courant (dernière valeur publiée). */\r\n get currentBreadcrumb(): BreadcrumbData {\r\n let current: BreadcrumbData;\r\n this.breadcrumb.getCurrent().subscribe((data: BreadcrumbData) => current = data).unsubscribe();\r\n return current;\r\n }\r\n}\r\n","import { CallCounter } from './interfaces/call-counter';\r\n\r\n/**\r\n * Compte les appels de la méthode `method` de `target`, qui reste exécutée : équivalent, sans\r\n * outil de test, d'un espion transparent.\r\n */\r\nexport function countCalls<T extends object, K extends keyof T>(target: T, method: K): CallCounter {\r\n const original = target[method] as unknown as (...args: unknown[]) => unknown;\r\n const counter: CallCounter = { count: 0 };\r\n target[method] = ((...args: unknown[]) => {\r\n counter.count++;\r\n return original.apply(target, args);\r\n }) as unknown as T[K];\r\n return counter;\r\n}\r\n","import { TestRunner } from './interfaces/test-runner';\r\n\r\n/**\r\n * Fonctions globales de l'outil de test (describe, it, beforeEach, expect), lues sur globalThis\r\n * pour ne dépendre d'aucun outil ni de ses types.\r\n */\r\nexport function getTestRunner(): TestRunner {\r\n const runner = globalThis as unknown as Partial<TestRunner>;\r\n if (typeof runner.describe !== 'function' || typeof runner.it !== 'function'\r\n || typeof runner.beforeEach !== 'function' || typeof runner.expect !== 'function')\r\n throw new Error('Suites de test Dflat : describe, it, beforeEach et expect doivent être des fonctions globales (Vitest : option globals).');\r\n return runner as TestRunner;\r\n}\r\n","import { TestBed } from '@angular/core/testing';\r\nimport { Title } from '@angular/platform-browser';\r\nimport { Utilities } from '@dflat-core/web/core';\r\nimport { EntityViewModel, FilterViewModel, Notification } from '@dflat-core/web/core-model';\r\nimport { BaseComponent } from '@dflat-core/web/core-ux';\r\nimport { BaseComponentTestContext } from './base-component-test-context';\r\nimport { countCalls } from './count-calls';\r\nimport { getTestRunner } from './get-test-runner';\r\nimport { BaseComponentExpectations } from './interfaces/base-component-expectations';\r\nimport { BaseComponentFactory } from './interfaces/base-component-factory';\r\n\r\n/**\r\n * Suite de test standard d'un composant dérivé de BaseComponent : vérifie, sans rendre son\r\n * gabarit, le contrat de BaseComponent (création, clé d'entité, lien, titre, permission, façade,\r\n * ngOnInit, Search / refresh, notifications, ngOnDestroy). À appeler au premier niveau d'un\r\n * fichier de test :\r\n * `describeBaseComponent('CountryComponent', (app, route) => new TestCountryComponent(app, route), { ... })`.\r\n * La suite n'utilise que les fonctions globales de l'outil de test (describe, it, beforeEach,\r\n * expect) et TestBed.\r\n *\r\n * @param name Nom du composant, repris dans le titre de la suite.\r\n * @param factory Fabrique du composant, appelée dans un contexte d'injection avant chaque test.\r\n * @param expectations Valeurs attendues du composant.\r\n */\r\nexport function describeBaseComponent<TFilter extends FilterViewModel, TData extends EntityViewModel>(\r\n name: string, factory: BaseComponentFactory<TFilter, TData>, expectations: BaseComponentExpectations): void {\r\n const { describe, it, beforeEach, expect } = getTestRunner();\r\n\r\n describe(`${name} (contrat de BaseComponent)`, () => {\r\n let context: BaseComponentTestContext;\r\n let component: BaseComponent<TFilter, TData>;\r\n\r\n /** Publie la notification de modification de l'entité `entityId` sur le canal de l'entité du composant. */\r\n const notify = (entityId: number): void =>\r\n context.notifications.publish(new Notification({ entityType: Utilities.GetChannel(expectations.entityKey), entityId }));\r\n\r\n beforeEach(() => {\r\n TestBed.configureTestingModule({ providers: expectations.providers ?? [] });\r\n context = new BaseComponentTestContext(expectations.path, TestBed.inject(Title));\r\n component = TestBed.runInInjectionContext(() => factory(context.app, context.route));\r\n });\r\n\r\n it('est créé dans un contexte d\\'injection', () => {\r\n expect(component).toBeTruthy();\r\n expect(component.destroy$.closed).toBe(false);\r\n });\r\n\r\n it('expose la clé d\\'entité, le lien, le titre et la permission attendus', () => {\r\n expect(component.getEntityKey()).toBe(expectations.entityKey);\r\n expect(component.getPath()).toBe(expectations.path);\r\n expect(component.getTitle()).toBe(expectations.title);\r\n expect(component.PermissionKey).toBe(expectations.permissionKey);\r\n expect(component.layoutKey).toBe(expectations.entityKey);\r\n });\r\n\r\n it('demande sa façade au registre avec la clé de l\\'entité', () => {\r\n expect(context.registry.requests.length).toBe(1);\r\n expect(context.registry.requests[0].key).toBe(expectations.entityKey);\r\n expect(context.registry.requests[0].facade).toBe(component.facade);\r\n expect(component.facade.actions.handler.path).toBe(expectations.path);\r\n });\r\n\r\n it('ngOnInit publie le fil d\\'Ariane du composant principal', () => {\r\n component.ngOnInit();\r\n\r\n expect(component.displayComponent).toBe(true);\r\n expect(component.actionTitle).toBe(expectations.title);\r\n expect(context.currentBreadcrumb).toEqual({\r\n description: expectations.title,\r\n display: true,\r\n header: expectations.title,\r\n levels: [{ icon: component.getIcon(), link: [expectations.path], title: expectations.title }]\r\n });\r\n });\r\n\r\n it('ngOnInit ne publie pas de fil d\\'Ariane pour un composant d\\'onglet', () => {\r\n component.IsTheMainComponent = false;\r\n component.ngOnInit();\r\n\r\n expect(context.currentBreadcrumb.display).toBe(false);\r\n });\r\n\r\n it('ngOnInit branche le chargement sur dataService.search avec la clé de l\\'entité', () => {\r\n context.dataService.searchResult = { Values: [{ Id: 7, Name: 'avant' }], TotalRowCount: 1 };\r\n component.ngOnInit();\r\n\r\n const filter: FilterViewModel = { Offset: 0, Take: 25 };\r\n let total: number | undefined;\r\n component.facade.data.loadData(filter).subscribe(result => total = result.TotalRowCount);\r\n\r\n expect(context.dataService.searchCalls).toEqual([{ entityKey: expectations.entityKey, filter }]);\r\n expect(total).toBe(1);\r\n expect(component.facade.data.getOriginal(7).Name).toBe('avant');\r\n });\r\n\r\n it('ngOnInit s\\'abonne une seule fois aux notifications de l\\'entité', () => {\r\n component.ngOnInit();\r\n const first = component.notif;\r\n component.ngOnInit();\r\n\r\n expect(first.closed).toBe(true);\r\n expect(component.notif.closed).toBe(false);\r\n });\r\n\r\n if (expectations.sections) {\r\n it('ngOnInit enregistre les onglets attendus', async () => {\r\n component.ngOnInit();\r\n // Les onglets générés sont enregistrés dans une microtâche, après ceux du composant spécifique.\r\n await Promise.resolve();\r\n await Promise.resolve();\r\n\r\n let sections: (string | undefined)[] = [];\r\n component.facade.sections.sections$.subscribe(list => sections = list.map(s => s.Name)).unsubscribe();\r\n expect(sections).toEqual(expectations.sections);\r\n });\r\n }\r\n\r\n it('Search copie le filtre, remet la page à zéro et navigue vers l\\'URL du filtre', () => {\r\n component.ngOnInit();\r\n const loads = countCalls(component.facade.filter, 'triggerSearch');\r\n\r\n component.Search({ Keywords: 'abc', Offset: 50 });\r\n\r\n expect(component.Filter.Keywords).toBe('abc');\r\n expect(component.Filter.Offset).toBe(0);\r\n expect(context.router.navigations.length).toBe(1);\r\n expect(context.router.navigations[0].commands).toEqual([expectations.path]);\r\n expect(context.router.navigations[0].extras?.queryParams).toEqual({ Keywords: 'abc', Offset: 0 });\r\n expect(loads.count).toBe(0);\r\n });\r\n\r\n it('Search charge les données quand l\\'URL porte déjà le filtre', () => {\r\n component.ngOnInit();\r\n const loads = countCalls(component.facade.filter, 'triggerSearch');\r\n\r\n component.Search({ Keywords: 'abc' });\r\n component.Search({ Keywords: 'abc' });\r\n\r\n expect(context.router.navigations.length).toBe(1);\r\n expect(loads.count).toBe(1);\r\n });\r\n\r\n it('Search garde la page demandée sans remise à zéro', () => {\r\n component.ngOnInit();\r\n\r\n component.Search({ Offset: 50 }, false);\r\n\r\n expect(component.Filter.Offset).toBe(50);\r\n });\r\n\r\n it('Search et refresh chargent les données sans naviguer pour un composant d\\'onglet', () => {\r\n component.IsTheMainComponent = false;\r\n component.ngOnInit();\r\n const loads = countCalls(component.facade.filter, 'triggerSearch');\r\n\r\n component.Search({ Keywords: 'abc', Offset: 50 });\r\n expect(loads.count).toBe(1);\r\n expect(component.Filter.Keywords).toBe('abc');\r\n\r\n component.Filter.Offset = 50;\r\n component.refresh();\r\n expect(loads.count).toBe(2);\r\n expect(component.Filter.Offset).toBe(0);\r\n expect(context.router.navigations.length).toBe(0);\r\n });\r\n\r\n it('met à jour la ligne d\\'une entité chargée à sa notification', () => {\r\n context.dataService.searchResult = { Values: [{ Id: 7, Name: 'avant' }], TotalRowCount: 1 };\r\n component.ngOnInit();\r\n component.facade.data.loadData({ Offset: 0, Take: 25 }).subscribe();\r\n context.dataService.details = { Id: 7, Name: 'après' };\r\n\r\n notify(7);\r\n\r\n expect(context.dataService.detailsCalls).toEqual([{ entityKey: expectations.entityKey, id: 7 }]);\r\n expect(component.facade.data.getOriginal(7).Name).toBe('après');\r\n });\r\n\r\n it('ignore la notification d\\'une entité non chargée, ou recharge si loadDataOnNotification', () => {\r\n component.ngOnInit();\r\n const loads = countCalls(component.facade.filter, 'triggerSearch');\r\n\r\n notify(8);\r\n expect(context.dataService.detailsCalls.length).toBe(0);\r\n expect(loads.count).toBe(0);\r\n\r\n component.loadDataOnNotification = true;\r\n notify(8);\r\n expect(context.dataService.detailsCalls.length).toBe(0);\r\n expect(loads.count).toBe(1);\r\n });\r\n\r\n it('ngOnDestroy termine destroy$, se désabonne des notifications et efface le fil d\\'Ariane', () => {\r\n component.ngOnInit();\r\n let emitted = 0;\r\n let completed = false;\r\n component.destroy$.subscribe({ next: () => emitted++, complete: () => completed = true });\r\n\r\n component.ngOnDestroy();\r\n\r\n expect(emitted).toBe(1);\r\n expect(completed).toBe(true);\r\n expect(component.notif.closed).toBe(true);\r\n expect(context.currentBreadcrumb.display).toBe(false);\r\n\r\n notify(7);\r\n expect(context.dataService.detailsCalls.length).toBe(0);\r\n });\r\n });\r\n}\r\n","import { getTestRunner } from './get-test-runner';\r\nimport { GeneratedModuleExpectations } from './interfaces/generated-module-expectations';\r\n\r\n/** Valeurs des propriétés statiques de type chaîne d'une classe de constantes (clés d'entité, permissions). */\r\nfunction getConstants(constants: object): string[] {\r\n return Object.values(constants).filter((value): value is string => typeof value === 'string');\r\n}\r\n\r\n/** Valeurs présentes plusieurs fois dans `values`. */\r\nfunction getDuplicates(values: string[]): string[] {\r\n return values.filter((value, index) => values.indexOf(value) !== index);\r\n}\r\n\r\n/**\r\n * Suite de test standard d'un module généré (dflat -g ts) : cohérence de ses composants, routes,\r\n * sélecteurs, clés d'entité, éléments de menu et permissions. Chaque test compare à une liste\r\n * vide la liste des éléments en défaut, que l'outil de test affiche en cas d'échec.\r\n *\r\n * @param name Nom du module (ex. System), repris dans le titre de la suite.\r\n * @param module Éléments générés du module.\r\n */\r\nexport function describeGeneratedModule(name: string, module: GeneratedModuleExpectations): void {\r\n const { describe, it, expect } = getTestRunner();\r\n\r\n describe(`Module ${name} (éléments générés)`, () => {\r\n const paths = module.routes.map(route => route.path ?? '');\r\n\r\n it('donne une route à chaque composant généré', () => {\r\n const missing = module.components\r\n .filter(component => !module.routes.some(route => route.component === component))\r\n .map(component => component.name);\r\n expect(missing).toEqual([]);\r\n });\r\n\r\n it('n\\'a que des routes de composants générés, de chemins distincts', () => {\r\n const unknown = module.routes\r\n .filter(route => !module.components.some(component => component === route.component))\r\n .map(route => route.path);\r\n expect(unknown).toEqual([]);\r\n expect(getDuplicates(paths)).toEqual([]);\r\n });\r\n\r\n it('a des clés d\\'entité uniques, dont celles des routes', () => {\r\n const keys = getConstants(module.entityKeys);\r\n expect(getDuplicates(keys)).toEqual([]);\r\n expect(paths.filter(path => !keys.includes(path))).toEqual([]);\r\n });\r\n\r\n it('donne à chaque sélecteur d\\'entité le composant de la route de sa clé', () => {\r\n const mismatched = module.selectors\r\n .filter(selector => !module.routes.some(route => route.path === selector.key && route.component === selector.component))\r\n .map(selector => selector.key);\r\n expect(mismatched).toEqual([]);\r\n });\r\n\r\n it('fait pointer chaque élément de menu généré vers une route existante', () => {\r\n const links = paths.map(path => module.getLink(path));\r\n const orphans = module.menuItems.filter(item => !links.includes(item.link ?? '')).map(item => item.link);\r\n expect(orphans).toEqual([]);\r\n });\r\n\r\n if (module.permissions) {\r\n const permissions = getConstants(module.permissions);\r\n\r\n it('donne à chaque élément de menu généré une permission du module', () => {\r\n const ungranted = module.menuItems.filter(item => !permissions.includes(item.permissionKey ?? '')).map(item => item.link);\r\n expect(ungranted).toEqual([]);\r\n });\r\n }\r\n });\r\n}\r\n"],"mappings":"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"}
|
|
@@ -137,6 +137,7 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
137
137
|
args: [ViewModeDirective]
|
|
138
138
|
}] }
|
|
139
139
|
});
|
|
140
|
+
const ROOT_DEPS = new InjectionToken("ROOT_DEPS");
|
|
140
141
|
var ActionDefinition = class {
|
|
141
142
|
constructor(init) {
|
|
142
143
|
this.action = () => {};
|
|
@@ -2789,7 +2790,6 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
2789
2790
|
}]
|
|
2790
2791
|
}]
|
|
2791
2792
|
});
|
|
2792
|
-
const ROOT_DEPS = new InjectionToken("ROOT_DEPS");
|
|
2793
2793
|
var ApplicationService = class ApplicationService {
|
|
2794
2794
|
get DataService() {
|
|
2795
2795
|
return this.deps.data.data;
|