From 8e01c70603ac2fd2018ec464d52625cf7a163f92 Mon Sep 17 00:00:00 2001 From: Nolwenn Date: Thu, 19 Sep 2024 08:44:54 +0200 Subject: [PATCH] Handle null in TypeScript optional --- .../unit/common/domain/optional/Optional.spec.ts | 7 ++++--- .../webapp/common/domain/optional/Optional.ts | 6 ++++-- .../webapp/app/module/domain/landscape/Landscape.ts | 12 ++++++------ .../domain/landscape/LandscapeSelectionTree.ts | 2 +- .../app/module/secondary/RestModulesRepository.ts | 2 +- .../webapp/app/shared/optional/domain/Optional.ts | 7 ++++--- .../unit/shared/optional/domain/Optional.spec.ts | 6 +++--- 7 files changed, 23 insertions(+), 19 deletions(-) diff --git a/src/main/resources/generator/typescript/test/webapp/unit/common/domain/optional/Optional.spec.ts b/src/main/resources/generator/typescript/test/webapp/unit/common/domain/optional/Optional.spec.ts index 61417a80633..bbccf67fae9 100644 --- a/src/main/resources/generator/typescript/test/webapp/unit/common/domain/optional/Optional.spec.ts +++ b/src/main/resources/generator/typescript/test/webapp/unit/common/domain/optional/Optional.spec.ts @@ -1,4 +1,5 @@ import { Optional } from '@/common/domain/Optional'; +import { expect, it } from 'vitest'; describe('Optional', () => { describe('Empty check', () => { @@ -106,12 +107,12 @@ describe('Optional', () => { }); describe('Of undefinable', () => { - it('should get empty optional from undefined value', () => { - expect(Optional.ofUndefinable(undefined).isEmpty()).toBe(true); + it.each([undefined, null, NaN])('should get empty optional from %s value', value => { + expect(Optional.ofNullable(value).isEmpty()).toBe(true); }); it('should get valuated optional from actual value', () => { - expect(Optional.ofUndefinable('toto').isEmpty()).toBe(false); + expect(Optional.ofNullable('toto').isEmpty()).toBe(false); }); }); diff --git a/src/main/resources/generator/typescript/webapp/common/domain/optional/Optional.ts b/src/main/resources/generator/typescript/webapp/common/domain/optional/Optional.ts index 68b4fd3861b..3ade4ffe5bf 100644 --- a/src/main/resources/generator/typescript/webapp/common/domain/optional/Optional.ts +++ b/src/main/resources/generator/typescript/webapp/common/domain/optional/Optional.ts @@ -1,3 +1,5 @@ +const checkIsNaN = (value: Value) => typeof value === 'number' && isNaN(value); + export abstract class Optional { public static empty(): Optional { return new EmptyOptional(); @@ -7,8 +9,8 @@ export abstract class Optional { return new ValuatedOptional(value); } - static ofUndefinable(value: Value | undefined): Optional { - if (value === undefined) { + static ofNullable(value: Value | undefined): Optional { + if (value === undefined || value === null || checkIsNaN(value)) { return Optional.empty(); } diff --git a/src/main/webapp/app/module/domain/landscape/Landscape.ts b/src/main/webapp/app/module/domain/landscape/Landscape.ts index 8a5d126241c..925f09b55db 100644 --- a/src/main/webapp/app/module/domain/landscape/Landscape.ts +++ b/src/main/webapp/app/module/domain/landscape/Landscape.ts @@ -97,7 +97,7 @@ export class Landscape { private appliedModuleInFeature(module: ModuleSlug): Optional { return this.projections .getModuleFeature(module) - .flatMap(feature => Optional.ofUndefinable(feature.modules.find(featureModule => this.state.isApplied(featureModule.slugString())))); + .flatMap(feature => Optional.ofNullable(feature.modules.find(featureModule => this.state.isApplied(featureModule.slugString())))); } private incompatibleSelectedModuleInFeature(module: ModuleSlug): Optional { @@ -350,7 +350,7 @@ export class Landscape { } private getSelectedModule(feature: LandscapeFeature): Optional { - return Optional.ofUndefinable(feature.modules.find(featureModule => this.state.isSelected(featureModule.slug().get()))); + return Optional.ofNullable(feature.modules.find(featureModule => this.state.isSelected(featureModule.slug().get()))); } private moduleIs(slug: ModuleSlug, operation: (module: LandscapeModule) => boolean): boolean { @@ -370,7 +370,7 @@ export class Landscape { } private getModule(module: ModuleSlug): Optional { - return Optional.ofUndefinable(this.modules.get(module.get())); + return Optional.ofNullable(this.modules.get(module.get())); } public noNotAppliedSelectedModule(): boolean { @@ -524,14 +524,14 @@ class LevelsProjections { } public getStandaloneModule(module: ModuleId): Optional { - return Optional.ofUndefinable(this.standaloneModules.get(module)); + return Optional.ofNullable(this.standaloneModules.get(module)); } public getFeature(feature: LandscapeFeatureSlug): Optional { - return Optional.ofUndefinable(this.features.get(feature.get())); + return Optional.ofNullable(this.features.get(feature.get())); } public getModuleFeature(module: ModuleSlug) { - return Optional.ofUndefinable(this.moduleFeatures.get(module.get())); + return Optional.ofNullable(this.moduleFeatures.get(module.get())); } } diff --git a/src/main/webapp/app/module/domain/landscape/LandscapeSelectionTree.ts b/src/main/webapp/app/module/domain/landscape/LandscapeSelectionTree.ts index ea8d5abaa8c..cd7018a0ba7 100644 --- a/src/main/webapp/app/module/domain/landscape/LandscapeSelectionTree.ts +++ b/src/main/webapp/app/module/domain/landscape/LandscapeSelectionTree.ts @@ -16,6 +16,6 @@ export class LandscapeSelectionTree { } public find(element: LandscapeElementId): Optional { - return Optional.ofUndefinable(this.elements.find(selectionElement => selectionElement.slug.get() === element.get())); + return Optional.ofNullable(this.elements.find(selectionElement => selectionElement.slug.get() === element.get())); } } diff --git a/src/main/webapp/app/module/secondary/RestModulesRepository.ts b/src/main/webapp/app/module/secondary/RestModulesRepository.ts index 4c296d16b56..2525443f001 100644 --- a/src/main/webapp/app/module/secondary/RestModulesRepository.ts +++ b/src/main/webapp/app/module/secondary/RestModulesRepository.ts @@ -62,7 +62,7 @@ export class RestModulesRepository implements ModulesRepository { } const mapToProject = (response: AxiosResponse): Project => ({ - filename: Optional.ofUndefinable(response.headers['x-suggested-filename']).orElseThrow( + filename: Optional.ofNullable(response.headers['x-suggested-filename']).orElseThrow( () => new Error('Impossible to download file without filename'), ), content: response.data, diff --git a/src/main/webapp/app/shared/optional/domain/Optional.ts b/src/main/webapp/app/shared/optional/domain/Optional.ts index 119f6fb39ea..acd1add07c1 100644 --- a/src/main/webapp/app/shared/optional/domain/Optional.ts +++ b/src/main/webapp/app/shared/optional/domain/Optional.ts @@ -1,3 +1,5 @@ +const checkIsNaN = (value: Value) => typeof value === 'number' && isNaN(value); + export abstract class Optional { public static empty(): Optional { return new EmptyOptional(); @@ -7,11 +9,10 @@ export abstract class Optional { return new ValuatedOptional(value); } - static ofUndefinable(value: Value | undefined): Optional { - if (value === undefined) { + static ofNullable(value: Value | undefined | null): Optional { + if (value === undefined || value === null || checkIsNaN(value)) { return Optional.empty(); } - return Optional.of(value); } diff --git a/src/test/webapp/unit/shared/optional/domain/Optional.spec.ts b/src/test/webapp/unit/shared/optional/domain/Optional.spec.ts index f0cd6eeb517..43d20a55326 100644 --- a/src/test/webapp/unit/shared/optional/domain/Optional.spec.ts +++ b/src/test/webapp/unit/shared/optional/domain/Optional.spec.ts @@ -110,12 +110,12 @@ describe('Optional', () => { }); describe('Of undefinable', () => { - it('should get empty optional from undefined value', () => { - expect(Optional.ofUndefinable(undefined).isEmpty()).toBe(true); + it.each([undefined, null, NaN])('should get empty optional from %s value', value => { + expect(Optional.ofNullable(value).isEmpty()).toBe(true); }); it('should get valuated optional from actual value', () => { - expect(Optional.ofUndefinable('toto').isEmpty()).toBe(false); + expect(Optional.ofNullable('toto').isEmpty()).toBe(false); }); });