@dflat-core/web 1.0.59 → 1.0.61

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.
@@ -9,6 +9,9 @@ var DataServiceDouble = class {
9
9
  constructor() {
10
10
  this.searchCalls = [];
11
11
  this.detailsCalls = [];
12
+ this.columnCalls = 0;
13
+ this.clearedCaches = [];
14
+ this.viewModelCalls = 0;
12
15
  this.searchResult = {
13
16
  Values: [],
14
17
  TotalRowCount: 0
@@ -33,6 +36,7 @@ var DataServiceDouble = class {
33
36
  return { ...params };
34
37
  }
35
38
  getViewColumns() {
39
+ this.columnCalls++;
36
40
  return of([]);
37
41
  }
38
42
  getViewFilterDescriptors() {
@@ -45,8 +49,12 @@ var DataServiceDouble = class {
45
49
  });
46
50
  }
47
51
  getEntityViewModel() {
52
+ this.viewModelCalls++;
48
53
  return of({});
49
54
  }
55
+ clearCached(entityKey) {
56
+ this.clearedCaches.push(entityKey);
57
+ }
50
58
  getExtendedActions() {
51
59
  return of([]);
52
60
  }
@@ -1 +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 /** Valeur d'initialisation calculée par le front : calculée à chaque appel, sans cache. */\r\n getCachedValue<T>(entityKey: string, name: string, factory: () => T): Observable<T> {\r\n return of(factory());\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, EntitySections } 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, demande des onglets, 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 if (expectations.requestsSections !== undefined) {\r\n const title = expectations.requestsSections\r\n ? 'ngOnInit demande les onglets de l\\'entité au registre du socle (EntitySections)'\r\n : 'ngOnInit ne demande pas d\\'onglet au registre du socle (EntitySections)';\r\n\r\n it(title, async () => {\r\n const original = EntitySections.registerSections;\r\n const requests: { entityKey: string; facade: unknown }[] = [];\r\n EntitySections.registerSections = ((entityKey: string, facade: unknown) => {\r\n requests.push({ entityKey, facade });\r\n }) as typeof EntitySections.registerSections;\r\n try {\r\n component.ngOnInit();\r\n // Les onglets sont demandé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 finally {\r\n EntitySections.registerSections = original;\r\n }\r\n\r\n expect(requests.length).toBe(expectations.requestsSections ? 1 : 0);\r\n if (expectations.requestsSections) {\r\n expect(requests[0].entityKey).toBe(expectations.entityKey);\r\n expect(requests[0].facade).toBe(component.facade);\r\n }\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é, onglets, é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 if (module.sectionProvider) {\r\n const provider = module.sectionProvider;\r\n\r\n if (module.sections) {\r\n const expected = module.sections;\r\n\r\n it('déclare les onglets attendus de chaque entité, dans l\\'ordre', () => {\r\n const declared: { [entityKey: string]: string[] } = {};\r\n provider.getEntityKeys().forEach(key => declared[key] = provider.getSections(key).map(section => section.title));\r\n expect(declared).toEqual(expected);\r\n });\r\n }\r\n\r\n it('ne déclare d\\'onglets que pour des entités du module, vers des composants générés du module', () => {\r\n const keys = getConstants(module.entityKeys);\r\n expect(provider.getEntityKeys().filter(key => !keys.includes(key))).toEqual([]);\r\n\r\n const unknown = provider.getEntityKeys()\r\n .flatMap(key => provider.getSections(key).map(section => ({ key, section })))\r\n .filter(({ section }) => !module.components.some(component => component === section.component))\r\n .map(({ key, section }) => `${key} : ${section.title}`);\r\n expect(unknown).toEqual([]);\r\n });\r\n\r\n it('ne déclare aucun onglet pour une entité sans onglet', () => {\r\n expect(provider.getSections('')).toEqual([]);\r\n });\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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"}
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 /** Nombre de lectures des colonnes de la grille (getViewColumns). */\r\n columnCalls = 0;\r\n\r\n /** Entités dont le cache d'initialisation a été vidé (clearCached), dans l'ordre. */\r\n readonly clearedCaches: string[] = [];\r\n\r\n /** Nombre de lectures du formulaire (getEntityViewModel). */\r\n viewModelCalls = 0;\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 this.columnCalls++;\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 this.viewModelCalls++;\r\n return of({} as EntityViewModelWithFields<TData>);\r\n }\r\n\r\n /** Enregistre l'entité dont le cache d'initialisation est vidé. */\r\n clearCached(entityKey?: string): void {\r\n this.clearedCaches.push(entityKey);\r\n }\r\n\r\n /** Actions étendues : aucune. */\r\n getExtendedActions(): Observable<ActionDescriptor[]> {\r\n return of([]);\r\n }\r\n\r\n /** Valeur d'initialisation calculée par le front : calculée à chaque appel, sans cache. */\r\n getCachedValue<T>(entityKey: string, name: string, factory: () => T): Observable<T> {\r\n return of(factory());\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, EntitySections } 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, demande des onglets, 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 if (expectations.requestsSections !== undefined) {\r\n const title = expectations.requestsSections\r\n ? 'ngOnInit demande les onglets de l\\'entité au registre du socle (EntitySections)'\r\n : 'ngOnInit ne demande pas d\\'onglet au registre du socle (EntitySections)';\r\n\r\n it(title, async () => {\r\n const original = EntitySections.registerSections;\r\n const requests: { entityKey: string; facade: unknown }[] = [];\r\n EntitySections.registerSections = ((entityKey: string, facade: unknown) => {\r\n requests.push({ entityKey, facade });\r\n }) as typeof EntitySections.registerSections;\r\n try {\r\n component.ngOnInit();\r\n // Les onglets sont demandé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 finally {\r\n EntitySections.registerSections = original;\r\n }\r\n\r\n expect(requests.length).toBe(expectations.requestsSections ? 1 : 0);\r\n if (expectations.requestsSections) {\r\n expect(requests[0].entityKey).toBe(expectations.entityKey);\r\n expect(requests[0].facade).toBe(component.facade);\r\n }\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é, onglets, é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 if (module.sectionProvider) {\r\n const provider = module.sectionProvider;\r\n\r\n if (module.sections) {\r\n const expected = module.sections;\r\n\r\n it('déclare les onglets attendus de chaque entité, dans l\\'ordre', () => {\r\n const declared: { [entityKey: string]: string[] } = {};\r\n provider.getEntityKeys().forEach(key => declared[key] = provider.getSections(key).map(section => section.title));\r\n expect(declared).toEqual(expected);\r\n });\r\n }\r\n\r\n it('ne déclare d\\'onglets que pour des entités du module, vers des composants générés du module', () => {\r\n const keys = getConstants(module.entityKeys);\r\n expect(provider.getEntityKeys().filter(key => !keys.includes(key))).toEqual([]);\r\n\r\n const unknown = provider.getEntityKeys()\r\n .flatMap(key => provider.getSections(key).map(section => ({ key, section })))\r\n .filter(({ section }) => !module.components.some(component => component === section.component))\r\n .map(({ key, section }) => `${key} : ${section.title}`);\r\n expect(unknown).toEqual([]);\r\n });\r\n\r\n it('ne déclare aucun onglet pour une entité sans onglet', () => {\r\n expect(provider.getSections('')).toEqual([]);\r\n });\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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"}
@@ -1,4 +1,4 @@
1
- import { ActionDisplay, AggregateFunction, ApiErrorTypes, ConditionalResults, CoreModelModule, CustomFieldTypes, DictionnaryWrapper, FilterOperator, FilterType, User, ValidatorTypes } from "@dflat-core/web/core-model";
1
+ import { ActionDisplay, AggregateFunction, ApiErrorTypes, ConditionalResults, CoreModelModule, CustomFieldTypes, DictionnaryWrapper, FilterOperator, FilterType, FormChangedMessage, PresentationChangedMessage, User, ValidatorTypes } from "@dflat-core/web/core-model";
2
2
  import * as i2$4 from "@angular/common";
3
3
  import { CommonModule, Location } from "@angular/common";
4
4
  import * as i0 from "@angular/core";
@@ -16,9 +16,9 @@ import { BsModalService, ModalModule, ModalOptions } from "ngx-bootstrap/modal";
16
16
  import * as i1$3 from "@dflat-core/web/core";
17
17
  import { Utilities, isNumeric, nameof } from "@dflat-core/web/core";
18
18
  import { BehaviorSubject, EMPTY, Observable, ReplaySubject, Subject, catchError, combineLatest, debounceTime, distinctUntilChanged, filter, firstValueFrom, forkJoin, from, fromEvent, groupBy, map, merge, mergeMap, of, share, shareReplay, startWith, switchMap, take, takeUntil, tap, throwError, toArray, withLatestFrom } from "rxjs";
19
- import { debounceTime as debounceTime$1, distinctUntilChanged as distinctUntilChanged$1, filter as filter$1, map as map$1, shareReplay as shareReplay$1, switchMap as switchMap$1, take as take$1, takeUntil as takeUntil$1 } from "rxjs/operators";
19
+ import { debounceTime as debounceTime$1, distinctUntilChanged as distinctUntilChanged$1, exhaustMap, filter as filter$1, map as map$1, shareReplay as shareReplay$1, switchMap as switchMap$1, take as take$1, takeUntil as takeUntil$1 } from "rxjs/operators";
20
20
  import * as i1$5 from "@dflat-core/web/core-services";
21
- import { AuthService, BreadcrumbService, FilterService, UserService } from "@dflat-core/web/core-services";
21
+ import { AuthService, BreadcrumbService, BusMessageService, FilterService, UserService } from "@dflat-core/web/core-services";
22
22
  import { SelectionType } from "@swimlane/ngx-datatable";
23
23
  import * as i2$2 from "@dflat-core/web/system-data";
24
24
  import { HttpResponseBase } from "@angular/common/http";
@@ -1750,9 +1750,15 @@ var EntityConfigContext = class {
1750
1750
  setColumnSource(factory) {
1751
1751
  this.columnSource$.next(factory);
1752
1752
  }
1753
+ reloadColumns() {
1754
+ this.columnSource$.next(this.columnSource$.value);
1755
+ }
1753
1756
  setviewModelSource(factory) {
1754
1757
  this.viewModelSource$.next(factory);
1755
1758
  }
1759
+ reloadViewModel() {
1760
+ this.viewModelSource$.next(this.viewModelSource$.value);
1761
+ }
1756
1762
  updateFieldSections(newSections) {
1757
1763
  const alter = (vm) => {
1758
1764
  return {
@@ -2163,6 +2169,7 @@ var SectionContext = class SectionContext {
2163
2169
  facade.layout.setShowFilterButton(false);
2164
2170
  };
2165
2171
  this.relatedSections = /* @__PURE__ */ new Set();
2172
+ this.hiddenSections = /* @__PURE__ */ new Set();
2166
2173
  this.renderedSections = /* @__PURE__ */ new Set();
2167
2174
  this.lastSelected = [];
2168
2175
  this.extendedPropertiesSubject = new Subject();
@@ -2196,7 +2203,11 @@ var SectionContext = class SectionContext {
2196
2203
  const key = SectionContext.getEntityKey(template);
2197
2204
  if (this.sections.some((s) => s.Name === name || !this.relatedSections.has(s.Name) && !Utilities.IsNil(key) && SectionContext.getEntityKey(s.Template) === key)) return;
2198
2205
  this.relatedSections.add(name);
2199
- this.registerSection(name, template, { filterOverride: filterFromId(filterProperty) });
2206
+ const section = this.registerSection(name, template, { filterOverride: filterFromId(filterProperty) });
2207
+ section.IsVisible = !this.isHidden(name);
2208
+ }
2209
+ isHidden(name) {
2210
+ return this.relatedSections.has(name) && this.hiddenSections.has(name);
2200
2211
  }
2201
2212
  static getEntityKey(template) {
2202
2213
  try {
@@ -2244,6 +2255,8 @@ var SectionContext = class SectionContext {
2244
2255
  }
2245
2256
  setViewModel(vm) {
2246
2257
  this.viewModel = vm;
2258
+ this.hiddenSections = new Set(vm?.HiddenSections ?? []);
2259
+ this.sections.filter((s) => this.isHidden(s.Name)).forEach((s) => s.IsVisible = false);
2247
2260
  }
2248
2261
  handleSelectionChanged(selected) {
2249
2262
  if (this.lastSelected.length > 0 && this.lastSelected[0].Id != selected[0].Id) this.sections.forEach((s) => {
@@ -2271,7 +2284,7 @@ var SectionContext = class SectionContext {
2271
2284
  const activeSection = this.sections[activeTab - 1];
2272
2285
  this.sections = this.sections.map((section) => {
2273
2286
  let isVisible = section.Config.show ? section.Config.show(selected) : true;
2274
- isVisible = isVisible && !this.selection.isNewEntity();
2287
+ isVisible = isVisible && !this.selection.isNewEntity() && !this.isHidden(section.Name);
2275
2288
  section.IsVisible = isVisible;
2276
2289
  if (!isVisible && activeSection == section) hasHiddenSection = true;
2277
2290
  return section;
@@ -20982,7 +20995,7 @@ var BaseComponent = class BaseComponent extends DflatBaseComponent {
20982
20995
  return this.facade.filter.defaultFilter;
20983
20996
  }
20984
20997
  get propertyGridComponent() {
20985
- return this.newEditor.propertyEditorComponent;
20998
+ return this.newEditor?.propertyEditorComponent;
20986
20999
  }
20987
21000
  get selectedEntities() {
20988
21001
  return this.facade.selection.Selected;
@@ -20999,6 +21012,7 @@ var BaseComponent = class BaseComponent extends DflatBaseComponent {
20999
21012
  this.displayComponent = false;
21000
21013
  this.IsTheMainComponent = true;
21001
21014
  this.actionBarSearchText = "Rechercher...";
21015
+ this.editorComponent = void 0;
21002
21016
  this.actionTitle = "";
21003
21017
  this.loadDataOnInit = true;
21004
21018
  this.baseComponentClass = "basecomponent";
@@ -21007,6 +21021,7 @@ var BaseComponent = class BaseComponent extends DflatBaseComponent {
21007
21021
  this.filterInUrl = true;
21008
21022
  this.destroy$ = new Subject();
21009
21023
  this.FILE_SECTION = "Fichiers";
21024
+ this.busMessages = inject(BusMessageService, { optional: true });
21010
21025
  this.ppService = inject(PP_SERVICE, { optional: true });
21011
21026
  this.guid = Guid.create();
21012
21027
  this.facade = this.app.EntityFacadeRegistryService.getFacade(this.getEntityKey(), this.guid);
@@ -21031,6 +21046,38 @@ var BaseComponent = class BaseComponent extends DflatBaseComponent {
21031
21046
  this.facade.data.setloadDataFn((criteria) => this.dataService.search(this.getEntityKey(), criteria));
21032
21047
  this.facade.editor.setStandaloneMode(false);
21033
21048
  this.registerSelectColumnsAction();
21049
+ this.subscribeFormChanged();
21050
+ this.subscribePresentationChanged();
21051
+ }
21052
+ subscribeFormChanged() {
21053
+ this.busMessages?.of(FormChangedMessage).pipe(filter$1((message) => message.concerns(this.getEntityKey())), exhaustMap(() => this.confirmFormReload$()), takeUntil$1(this.destroy$)).subscribe(() => this.reloadForm());
21054
+ }
21055
+ confirmFormReload$() {
21056
+ if (!this.facade.change.isDirty) return of(true);
21057
+ return this.modalService.confirmModal("Le formulaire de cet écran vient d'être modifié par un administrateur. Vous avez des modifications en cours : souhaitez-vous Continuer à modifier, les Enregistrer ou les Annuler avant de recharger le formulaire ?").pipe(take$1(1), switchMap$1((result) => {
21058
+ if (result === ConfirmOptions.SAVE_LEAVE) return this.facade.actions.executeAction("save", true).pipe(take$1(1), map$1(() => true));
21059
+ if (result === ConfirmOptions.LEAVE) {
21060
+ this.facade.change.reset();
21061
+ return of(true);
21062
+ }
21063
+ return EMPTY;
21064
+ }));
21065
+ }
21066
+ reloadForm() {
21067
+ this.dataService.clearCached(this.getEntityKey());
21068
+ this.facade.config.reloadViewModel();
21069
+ }
21070
+ subscribePresentationChanged() {
21071
+ this.busMessages?.of(PresentationChangedMessage).pipe(takeUntil$1(this.destroy$)).subscribe((message) => {
21072
+ if (message.concerns(this.getEntityKey())) this.reloadColumns();
21073
+ });
21074
+ }
21075
+ reloadColumns() {
21076
+ this.dataService.clearCached(this.getEntityKey());
21077
+ this.facade.config.reloadColumns();
21078
+ if (this.ppService && this.getPpMetaEntityKey()) this.ppService.resolveColumns(this.getPpConfig()).pipe(take$1(1)).subscribe((cols) => {
21079
+ if (cols.length) this.applyProfileColumns(cols);
21080
+ });
21034
21081
  }
21035
21082
  ngAfterViewInit() {
21036
21083
  this.facade.interceptor.markComponentInitReady();
@@ -21192,6 +21239,115 @@ i0.ɵɵngDeclareClassMetadata({
21192
21239
  }]
21193
21240
  }
21194
21241
  });
21242
+ var BaseEntityEditor = class BaseEntityEditor {
21243
+ constructor(app, validationService) {
21244
+ this.app = app;
21245
+ this.validationService = validationService;
21246
+ this.sections = [];
21247
+ this.form = new FormGroup({});
21248
+ this.readonly = false;
21249
+ this.destroy$ = new Subject();
21250
+ this.facadeReadOnly = false;
21251
+ }
21252
+ ngOnInit() {
21253
+ this.facade = this.app.EntityFacadeRegistryService.getFacade(this.entityKey, this.guid);
21254
+ this.facade.config.viewModel$.pipe(takeUntil$1(this.destroy$)).subscribe((viewModel) => {
21255
+ this.sections = this.alterSections(viewModel?.FieldSections ?? []);
21256
+ this.initForm();
21257
+ this.patchForm();
21258
+ });
21259
+ this.facade.selection.selected$.pipe(takeUntil$1(this.destroy$)).subscribe((selected) => this.setEntity(selected?.length === 1 ? selected[0] : void 0));
21260
+ this.facade.selection.cleared$.pipe(takeUntil$1(this.destroy$)).subscribe(() => this.setEntity(void 0));
21261
+ this.facade.editor.readOnlyMode$.pipe(takeUntil$1(this.destroy$)).subscribe((value) => {
21262
+ this.facadeReadOnly = value;
21263
+ this.updateReadonly();
21264
+ });
21265
+ }
21266
+ ngOnDestroy() {
21267
+ this.destroy$.next();
21268
+ this.destroy$.complete();
21269
+ }
21270
+ get fields() {
21271
+ return this.sections.flatMap((section) => section.FieldViewModels ?? []);
21272
+ }
21273
+ alterSections(sections) {
21274
+ return sections;
21275
+ }
21276
+ onEntityChanged(entity) {}
21277
+ onPropertyChanged(event) {
21278
+ if (Utilities.IsNil(this.entity) || this.readonly) return;
21279
+ event.field["isValid"] = this.form.valid;
21280
+ this.facade.change.updateSelected(event);
21281
+ }
21282
+ publishChanges(values) {
21283
+ if (Utilities.IsNil(this.entity) || this.readonly) return;
21284
+ const updated = {
21285
+ ...this.facade.change.getModified(this.entity.Id) ?? this.entity,
21286
+ ...values
21287
+ };
21288
+ updated["isValid"] = this.form.valid;
21289
+ this.facade.change.emitUpdated([updated]);
21290
+ }
21291
+ setEntity(entity) {
21292
+ this.entity = entity;
21293
+ this.updateReadonly();
21294
+ this.patchForm();
21295
+ this.onEntityChanged(entity);
21296
+ }
21297
+ updateReadonly() {
21298
+ this.readonly = this.facadeReadOnly || !Utilities.IsNil(this.entity?.["IsEditable"]) && !this.entity.IsEditable;
21299
+ }
21300
+ initForm() {
21301
+ this.fields.forEach((field) => {
21302
+ const validators = this.validationService.getFieldValidators(field);
21303
+ const control = this.form.get(field.PropertyName);
21304
+ if (!control) this.form.addControl(field.PropertyName, new FormControl(null, validators));
21305
+ else {
21306
+ control.setValidators(validators);
21307
+ control.updateValueAndValidity({ emitEvent: false });
21308
+ }
21309
+ });
21310
+ }
21311
+ patchForm() {
21312
+ this.form.reset(void 0, { emitEvent: false });
21313
+ if (!Utilities.IsNil(this.entity)) this.form.patchValue(this.entity, { emitEvent: false });
21314
+ }
21315
+ static {
21316
+ this.ɵfac = i0.ɵɵngDeclareFactory({
21317
+ minVersion: "12.0.0",
21318
+ version: "22.2.1",
21319
+ ngImport: i0,
21320
+ type: BaseEntityEditor,
21321
+ deps: [{ token: ApplicationService }, { token: ValidationService }],
21322
+ target: i0.ɵɵFactoryTarget.Directive
21323
+ });
21324
+ }
21325
+ static {
21326
+ this.ɵdir = i0.ɵɵngDeclareDirective({
21327
+ minVersion: "14.0.0",
21328
+ version: "22.2.1",
21329
+ type: BaseEntityEditor,
21330
+ isStandalone: true,
21331
+ inputs: {
21332
+ entityKey: "entityKey",
21333
+ guid: "guid"
21334
+ },
21335
+ ngImport: i0
21336
+ });
21337
+ }
21338
+ };
21339
+ i0.ɵɵngDeclareClassMetadata({
21340
+ minVersion: "12.0.0",
21341
+ version: "22.2.1",
21342
+ ngImport: i0,
21343
+ type: BaseEntityEditor,
21344
+ decorators: [{ type: Directive }],
21345
+ ctorParameters: () => [{ type: ApplicationService }, { type: ValidationService }],
21346
+ propDecorators: {
21347
+ entityKey: [{ type: Input }],
21348
+ guid: [{ type: Input }]
21349
+ }
21350
+ });
21195
21351
  var SelectorRegistry = class SelectorRegistry {
21196
21352
  constructor() {
21197
21353
  this.registry = /* @__PURE__ */ new Map();
@@ -21490,6 +21646,6 @@ var SortingState = class {
21490
21646
  this.direction$.next("asc");
21491
21647
  }
21492
21648
  };
21493
- export { Action, ActionContext, ActionDefinition, ActionHandlerContext, ActionPanelComponent, ActionService, ActionsComponent, ActionsContentComponent, AddclassDirective, AlertDialog, AlertMessage, AlertService, AppConfigService, ApplicationService, BaseArrayParameterTypeComponent, BaseComponent, BaseEditorComponent, BaseFormContext, BaseItemListComponent, BaseParameterTypeComponent, BaseShortcutsDirective, BreadcrumbComponent, CanDeactivateGuard, CellRendererComponent, ChangeDetectionContext, ChangeUtilities, CollapseDirective, ConfirmModalComponent, ConfirmOptions, ContextChoice, CoreUxModule, DataContext, DataGridComponent, DataGridStyleContext, DataListComponent, DatagridCellComponent, DatagridCustomComponent, DefaultDataGridStyleContext, DefaultFilterComponent, DefaultLayoutComponent, DflatBaseComponent, DflatBaseFilterComponent, DialogComponent, DialogComponentParameter, DialogType, DraggableDirective, DroppableDirective, DynamicFieldComponent, DynamicFilterComponent, DynamicFilterExtraComponent, DynamicFilterPanel, DynamicFormComponent, EXTENDED_ACTION_COMPONENT, EditModeDirective, EditorContext, EditorTabComponent, EditorTabSetComponent, EntityConfigContext, EntityFacade, EntityFacadeRegistryService, EntitySections, EntitySelectors, EntityUpdater, ExportDataModalComponent, ExtendedAction, ExtendedActionComponent, FacadeLoadGuard, FacadeModalService, FieldComponent, FilterContext, FilterFormModalComponent, FilterLifecycleGuard, FilterSummaryComponent, FilterSummaryPanelComponent, FireAuthService, FocusableDirective, FormPasswordComponent, FormWrapper, GridFieldComponent, GridRowComponent, GridStylePipe, HideOnThresholdDirective, IIsCancelWithWarning, ImageComponent, ImageService, InputSelectorComponent, InputSelectorOptions, InputTreeSelectorComponent, InsertionDirective, LayoutContext, LayoutHandlerDirective, LayoutService, LoginLayoutComponent, LogsComponent, MenuComponent, MenuDashboardComponent, MessageSeverity, ModalService, ModalStateService, NewEditorComponent, NewPaginatorComponent, OldDataPageComponent, OldLayoutHandlerDirective, OnEnterDirective, OrderableModalComponent, OrderableModalService, PP_SERVICE, PageLayoutComponent, PaginatorComponent, ParameterFieldComponent, ParameterTypeBoolArrayComponent, ParameterTypeBoolComponent, ParameterTypeDateArrayComponent, ParameterTypeDateComponent, ParameterTypeNumberArrayComponent, ParameterTypeNumberComponent, ParameterTypePasswordComponent, ParameterTypeTextArrayComponent, ParameterTypeTextComponent, PropertyChangeBus, PropertyEditorComponent, PropertyGridComponent, ROOT_DEPS, RowRefDirective, RowRefHost, RowRegistry, SearchResultsComponent, SectionContext, SelectionContext, SelectionService, SelectionStyler, SelectorComponent, SelectorDataSource, SelectorMapperServiceBase, SelectorRegistry, SortableColumnComponent, SortingState, StandardPageLayoutComponent, TRACK_RECORD_MODAL_COMPONENT, Tab, TabHeadingDirective, TabTranscludeDirective, Tabset, TestService, TextViewerComponent, TimePickerComponent, ToDefinePipe, ToFixedSizeIntegerPipe, TooltipDirective, TrackRecordModalComponent, TreeComponent, ValidateOnEnterDirective, ValidationErrorComponent, ValidationModalComponent, ValidationService, ViewModeDirective, ViewModelLoaderService, WizardComponent, WizardStepComponent, downloadFile, filterFromId, layoutKeyToString, normalisedOrder };
21649
+ export { Action, ActionContext, ActionDefinition, ActionHandlerContext, ActionPanelComponent, ActionService, ActionsComponent, ActionsContentComponent, AddclassDirective, AlertDialog, AlertMessage, AlertService, AppConfigService, ApplicationService, BaseArrayParameterTypeComponent, BaseComponent, BaseEditorComponent, BaseEntityEditor, BaseFormContext, BaseItemListComponent, BaseParameterTypeComponent, BaseShortcutsDirective, BreadcrumbComponent, CanDeactivateGuard, CellRendererComponent, ChangeDetectionContext, ChangeUtilities, CollapseDirective, ConfirmModalComponent, ConfirmOptions, ContextChoice, CoreUxModule, DataContext, DataGridComponent, DataGridStyleContext, DataListComponent, DatagridCellComponent, DatagridCustomComponent, DefaultDataGridStyleContext, DefaultFilterComponent, DefaultLayoutComponent, DflatBaseComponent, DflatBaseFilterComponent, DialogComponent, DialogComponentParameter, DialogType, DraggableDirective, DroppableDirective, DynamicFieldComponent, DynamicFilterComponent, DynamicFilterExtraComponent, DynamicFilterPanel, DynamicFormComponent, EXTENDED_ACTION_COMPONENT, EditModeDirective, EditorContext, EditorTabComponent, EditorTabSetComponent, EntityConfigContext, EntityFacade, EntityFacadeRegistryService, EntitySections, EntitySelectors, EntityUpdater, ExportDataModalComponent, ExtendedAction, ExtendedActionComponent, FacadeLoadGuard, FacadeModalService, FieldComponent, FilterContext, FilterFormModalComponent, FilterLifecycleGuard, FilterSummaryComponent, FilterSummaryPanelComponent, FireAuthService, FocusableDirective, FormPasswordComponent, FormWrapper, GridFieldComponent, GridRowComponent, GridStylePipe, HideOnThresholdDirective, IIsCancelWithWarning, ImageComponent, ImageService, InputSelectorComponent, InputSelectorOptions, InputTreeSelectorComponent, InsertionDirective, LayoutContext, LayoutHandlerDirective, LayoutService, LoginLayoutComponent, LogsComponent, MenuComponent, MenuDashboardComponent, MessageSeverity, ModalService, ModalStateService, NewEditorComponent, NewPaginatorComponent, OldDataPageComponent, OldLayoutHandlerDirective, OnEnterDirective, OrderableModalComponent, OrderableModalService, PP_SERVICE, PageLayoutComponent, PaginatorComponent, ParameterFieldComponent, ParameterTypeBoolArrayComponent, ParameterTypeBoolComponent, ParameterTypeDateArrayComponent, ParameterTypeDateComponent, ParameterTypeNumberArrayComponent, ParameterTypeNumberComponent, ParameterTypePasswordComponent, ParameterTypeTextArrayComponent, ParameterTypeTextComponent, PropertyChangeBus, PropertyEditorComponent, PropertyGridComponent, ROOT_DEPS, RowRefDirective, RowRefHost, RowRegistry, SearchResultsComponent, SectionContext, SelectionContext, SelectionService, SelectionStyler, SelectorComponent, SelectorDataSource, SelectorMapperServiceBase, SelectorRegistry, SortableColumnComponent, SortingState, StandardPageLayoutComponent, TRACK_RECORD_MODAL_COMPONENT, Tab, TabHeadingDirective, TabTranscludeDirective, Tabset, TestService, TextViewerComponent, TimePickerComponent, ToDefinePipe, ToFixedSizeIntegerPipe, TooltipDirective, TrackRecordModalComponent, TreeComponent, ValidateOnEnterDirective, ValidationErrorComponent, ValidationModalComponent, ValidationService, ViewModeDirective, ViewModelLoaderService, WizardComponent, WizardStepComponent, downloadFile, filterFromId, layoutKeyToString, normalisedOrder };
21494
21650
 
21495
21651
  //# sourceMappingURL=dflat-core-web-core-ux.mjs.map