From 95e0809039423af94e4c4927a0982471e0a80aaf Mon Sep 17 00:00:00 2001 From: Valery Zinchenko Date: Tue, 23 May 2023 14:06:24 +0300 Subject: [PATCH 1/2] Add "Named Components" --- src/ModalController.ts | 42 +++++++++++++++++++++++++-- src/__tests__/ModalController.spec.ts | 42 ++++++++++++++++++++++++++- src/types.ts | 15 +++++----- 3 files changed, 87 insertions(+), 12 deletions(-) diff --git a/src/ModalController.ts b/src/ModalController.ts index f3679ea..bfab826 100644 --- a/src/ModalController.ts +++ b/src/ModalController.ts @@ -19,7 +19,7 @@ copies or substantial portions of the Software. import EventEmitter from "eventemitter3" import { ModalWindow, ModalWindowAny } from "./ModalWindow" -import { ExternalStore, MODAL_WINDOW_PARAMS_EXPLANATION, ModalComponent, ModalParams, ModalSnapshot, ModalWindowParams } from "./types" +import { ExternalStore, MODAL_WINDOW_PARAMS_EXPLANATION, ModalComponent, ModalComponentProps, ModalNamedComponents, ModalParams, ModalSnapshot, ModalWindowParams } from "./types" interface ModalControllerEvents { @@ -28,11 +28,13 @@ interface ModalControllerEvents { update: [] } -interface ModalControllerConfig { +interface ModalControllerConfig { + components: Components + defaultParams: Partial } -class ModalController implements ExternalStore { +class ModalController = ModalControllerConfig> implements ExternalStore { protected windows: Set = new Set protected events: EventEmitter = new EventEmitter @@ -118,6 +120,14 @@ class ModalController + >(componentName: Name, ...[modalParams]: ModalWindowParams

): ModalWindow

{ + const component = this.getNamedComponent

(componentName) + + return this.open(component, modalParams as MODAL_WINDOW_PARAMS_EXPLANATION

) + } /** * Replaces the last modal window in the queue with a new one. @@ -133,6 +143,15 @@ class ModalController) } + public replaceNamed< + Name extends keyof Config["components"], + P extends ModalComponentProps + >(componentName: Name, ...[modalParams]: ModalWindowParams

): ModalWindow

{ + const component = this.getNamedComponent

(componentName) + + return this.replace(component, modalParams as MODAL_WINDOW_PARAMS_EXPLANATION

) + } + /** * Closes modal by its instance. */ @@ -185,6 +204,23 @@ class ModalController this.close(modalWindow)) } + private getNamedComponent

(componentName: keyof Config["components"]): ModalComponent

{ + // Error cause. + const cause = { config: this.config, componentName } + + if (this.config == null) { + throw new Error("ModalController `config` is not defined.", { cause }) + } + + if (this.config.components == null) { + throw new Error("ModalController `config.components` is not defined.", { cause }) + } + + return this.config.components[componentName] + } + + + /** * Subscribes to `event` with `listener`. * diff --git a/src/__tests__/ModalController.spec.ts b/src/__tests__/ModalController.spec.ts index 5f552e3..8c7f309 100644 --- a/src/__tests__/ModalController.spec.ts +++ b/src/__tests__/ModalController.spec.ts @@ -16,7 +16,7 @@ copies or substantial portions of the Software. */ -import { createElement, Fragment } from "react" +import { createElement, Fragment, lazy } from "react" import { act } from "react-dom/test-utils" import { ModalController } from "../ModalController" @@ -126,5 +126,45 @@ describe("ModalController (with container)", () => { expect(modal.params).toStrictEqual(defaultParams) }) + + it("openNamed", () => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + function Test1(props: { a: 1 }) { return null } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + function Test2(props: { b?: 2 }) { return createElement("a") } + + const controller = new ModalController({ + components: { + test1: Test1, + test2: Test2, + lazied: lazy(async () => ({ default: (await import("../ModalContainer")).ModalContainer })) + } + }) + + controller.openNamed("test1", { a: 1 }) + controller.openNamed("test2") + controller.openNamed("test2", { b: 2 }) + controller.openNamed("lazied", { controller: new ModalController }) + }) + + it("replaceNamed", () => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + function Test1(props: { a: 1 }) { return null } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + function Test2(props: { b?: 2 }) { return createElement("a") } + + const controller = new ModalController({ + components: { + test1: Test1, + test2: Test2, + lazied: lazy(async () => ({ default: (await import("../ModalContainer")).ModalContainer })) + } + }) + + controller.replaceNamed("test1", { a: 1 }) + controller.replaceNamed("test2") + controller.replaceNamed("test2", { b: 2 }) + controller.replaceNamed("lazied", { controller: new ModalController }) + }) }) }) diff --git a/src/types.ts b/src/types.ts index 798a2a1..a5628e6 100644 --- a/src/types.ts +++ b/src/types.ts @@ -16,8 +16,9 @@ copies or substantial portions of the Software. */ -import { Component, ReactElement } from "react" +import { ComponentProps, JSXElementConstructor } from "react" import { HasRequiredKeys } from "type-fest" +import { IsAny } from "type-fest/source/internal" import { ModalWindow } from "./ModalWindow" @@ -34,13 +35,11 @@ export interface ModalSnapshot { /** * A modal component can be either a function component or a class component. */ -export type ModalComponent

= - // Function Component - | ((props: P) => ReactElement | null) - | (() => ReactElement | null) - // Class Component - | (new (props: P) => Component

) - | (new () => Component) +export type ModalComponent

= JSXElementConstructor

+export type ModalComponentProps = IsAny> extends true ? never : ComponentProps + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type ModalNamedComponents = Record> export interface ModalParams { /** From 1a5085029619ecd71afb362ea18f088dcfae063e Mon Sep 17 00:00:00 2001 From: Valery Zinchenko Date: Tue, 23 May 2023 14:06:26 +0300 Subject: [PATCH 2/2] Update coverage --- coverage/clover.xml | 222 ++++++++++++----------- coverage/lcov.info | 420 +++++++++++++++++++++++--------------------- 2 files changed, 336 insertions(+), 306 deletions(-) diff --git a/coverage/clover.xml b/coverage/clover.xml index b0a1701..f132aab 100644 --- a/coverage/clover.xml +++ b/coverage/clover.xml @@ -1,12 +1,12 @@ - - - + + + - - - + + + @@ -23,86 +23,96 @@ - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - + + + + + + + + + - - + + @@ -131,26 +141,26 @@ - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + @@ -159,17 +169,17 @@ - - - - - - - - - - - + + + + + + + + + + + diff --git a/coverage/lcov.info b/coverage/lcov.info index 7c1ce05..b2a70b6 100644 --- a/coverage/lcov.info +++ b/coverage/lcov.info @@ -4,11 +4,11 @@ FN:24,(anonymous_0) FN:25,(anonymous_1) FNF:2 FNH:2 -FNDA:65,(anonymous_0) -FNDA:65,(anonymous_1) -DA:25,65 -DA:26,65 -DA:27,65 +FNDA:73,(anonymous_0) +FNDA:73,(anonymous_1) +DA:25,73 +DA:26,73 +DA:27,73 LF:3 LH:3 BRF:0 @@ -52,145 +52,165 @@ BRH:8 end_of_record TN: SF:src/ModalController.ts -FN:39,(anonymous_0) -FN:40,(anonymous_1) -FN:44,(anonymous_2) -FN:47,(anonymous_3) -FN:55,(anonymous_4) -FN:64,(anonymous_5) -FN:96,(anonymous_6) +FN:41,(anonymous_0) +FN:42,(anonymous_1) +FN:46,(anonymous_2) +FN:49,(anonymous_3) +FN:57,(anonymous_4) +FN:66,(anonymous_5) +FN:98,(anonymous_6) FN:102,(anonymous_7) -FN:129,(anonymous_8) -FN:143,(anonymous_9) -FN:160,(anonymous_10) -FN:161,(anonymous_11) -FN:162,(anonymous_12) -FN:168,(anonymous_13) -FN:169,(anonymous_14) -FN:182,(anonymous_15) -FN:188,(anonymous_16) -FN:189,(anonymous_17) -FN:200,(anonymous_18) -FN:203,(anonymous_19) -FN:215,(anonymous_20) -FN:220,(anonymous_21) -FN:226,(anonymous_22) -FN:231,(anonymous_23) -FNF:24 -FNH:22 -FNDA:44,(anonymous_0) -FNDA:124,(anonymous_1) -FNDA:49,(anonymous_2) -FNDA:341,(anonymous_3) +FN:123,(anonymous_8) +FN:137,(anonymous_9) +FN:146,(anonymous_10) +FN:158,(anonymous_11) +FN:175,(anonymous_12) +FN:176,(anonymous_13) +FN:177,(anonymous_14) +FN:183,(anonymous_15) +FN:184,(anonymous_16) +FN:197,(anonymous_17) +FN:203,(anonymous_18) +FN:204,(anonymous_19) +FN:207,(anonymous_20) +FN:232,(anonymous_21) +FN:235,(anonymous_22) +FN:247,(anonymous_23) +FN:252,(anonymous_24) +FN:258,(anonymous_25) +FN:263,(anonymous_26) +FNF:27 +FNH:25 +FNDA:50,(anonymous_0) +FNDA:134,(anonymous_1) +FNDA:51,(anonymous_2) +FNDA:376,(anonymous_3) FNDA:14,(anonymous_4) -FNDA:61,(anonymous_5) -FNDA:65,(anonymous_6) +FNDA:69,(anonymous_5) +FNDA:73,(anonymous_6) FNDA:14,(anonymous_7) -FNDA:2,(anonymous_8) -FNDA:28,(anonymous_9) -FNDA:1,(anonymous_10) -FNDA:5,(anonymous_11) -FNDA:2,(anonymous_12) -FNDA:2,(anonymous_13) -FNDA:9,(anonymous_14) -FNDA:8,(anonymous_15) -FNDA:1,(anonymous_16) -FNDA:4,(anonymous_17) -FNDA:0,(anonymous_18) -FNDA:0,(anonymous_19) -FNDA:107,(anonymous_20) -FNDA:63,(anonymous_21) -FNDA:291,(anonymous_22) -FNDA:124,(anonymous_23) -DA:36,44 -DA:37,44 -DA:40,124 -DA:43,44 -DA:45,49 -DA:48,341 -DA:56,14 +FNDA:4,(anonymous_8) +FNDA:6,(anonymous_9) +FNDA:4,(anonymous_10) +FNDA:28,(anonymous_11) +FNDA:1,(anonymous_12) +FNDA:5,(anonymous_13) +FNDA:2,(anonymous_14) +FNDA:2,(anonymous_15) +FNDA:9,(anonymous_16) +FNDA:8,(anonymous_17) +FNDA:1,(anonymous_18) +FNDA:4,(anonymous_19) +FNDA:8,(anonymous_20) +FNDA:0,(anonymous_21) +FNDA:0,(anonymous_22) +FNDA:113,(anonymous_23) +FNDA:63,(anonymous_24) +FNDA:291,(anonymous_25) +FNDA:134,(anonymous_26) +DA:38,50 +DA:39,50 +DA:42,134 +DA:45,50 +DA:47,51 +DA:50,376 DA:58,14 -DA:59,14 -DA:65,61 -DA:67,35 -DA:68,35 -DA:99,65 -DA:102,65 -DA:106,65 -DA:107,30 -DA:108,30 +DA:60,14 +DA:61,14 +DA:67,69 +DA:69,37 +DA:70,37 +DA:99,73 +DA:102,73 +DA:106,73 +DA:107,36 +DA:108,36 DA:109,4 -DA:113,61 +DA:113,69 DA:114,3 -DA:116,61 -DA:117,61 -DA:119,61 -DA:121,61 -DA:130,2 -DA:131,2 -DA:132,2 -DA:137,2 -DA:144,28 -DA:145,28 -DA:147,28 -DA:148,14 -DA:150,14 -DA:153,14 -DA:154,14 -DA:161,5 -DA:162,2 -DA:169,2 -DA:170,9 -DA:171,1 -DA:174,8 -DA:175,0 -DA:176,0 -DA:180,8 -DA:182,8 -DA:189,4 -DA:201,0 -DA:203,0 -DA:204,0 -DA:216,107 -DA:217,107 -DA:218,107 -DA:220,107 -DA:221,63 -DA:222,63 -DA:223,63 -DA:227,291 -DA:230,44 -DA:232,124 -LF:59 -LH:54 -BRDA:56,0,0,0 -BRDA:56,0,1,14 -BRDA:65,1,0,26 -BRDA:65,1,1,35 -BRDA:106,2,0,30 -BRDA:106,2,1,35 +DA:116,69 +DA:117,69 +DA:119,69 +DA:121,69 +DA:127,4 +DA:129,4 +DA:138,6 +DA:139,6 +DA:140,5 +DA:143,6 +DA:150,4 +DA:152,4 +DA:159,28 +DA:160,28 +DA:162,28 +DA:163,14 +DA:165,14 +DA:168,14 +DA:169,14 +DA:176,5 +DA:177,2 +DA:184,2 +DA:185,9 +DA:186,1 +DA:189,8 +DA:190,0 +DA:191,0 +DA:195,8 +DA:197,8 +DA:204,4 +DA:209,8 +DA:211,8 +DA:212,0 +DA:215,8 +DA:216,0 +DA:219,8 +DA:233,0 +DA:235,0 +DA:236,0 +DA:248,113 +DA:249,113 +DA:250,113 +DA:252,113 +DA:253,63 +DA:254,63 +DA:255,63 +DA:259,291 +DA:262,50 +DA:264,134 +LF:69 +LH:62 +BRDA:58,0,0,0 +BRDA:58,0,1,14 +BRDA:67,1,0,32 +BRDA:67,1,1,37 +BRDA:106,2,0,36 +BRDA:106,2,1,37 BRDA:108,3,0,4 -BRDA:108,3,1,26 +BRDA:108,3,1,32 BRDA:113,4,0,3 -BRDA:113,4,1,58 -BRDA:113,5,0,61 -BRDA:113,5,1,27 -BRDA:131,6,0,2 -BRDA:131,6,1,0 -BRDA:144,7,0,0 -BRDA:144,7,1,28 -BRDA:145,8,0,0 -BRDA:145,8,1,28 -BRDA:147,9,0,14 -BRDA:147,9,1,14 -BRDA:170,10,0,1 -BRDA:170,10,1,8 -BRDA:174,11,0,0 -BRDA:174,11,1,8 -BRDA:175,12,0,0 -BRDA:175,12,1,0 -BRF:26 -BRH:19 +BRDA:113,4,1,66 +BRDA:113,5,0,69 +BRDA:113,5,1,30 +BRDA:139,6,0,5 +BRDA:139,6,1,1 +BRDA:159,7,0,0 +BRDA:159,7,1,28 +BRDA:160,8,0,0 +BRDA:160,8,1,28 +BRDA:162,9,0,14 +BRDA:162,9,1,14 +BRDA:185,10,0,1 +BRDA:185,10,1,8 +BRDA:189,11,0,0 +BRDA:189,11,1,8 +BRDA:190,12,0,0 +BRDA:190,12,1,0 +BRDA:211,13,0,0 +BRDA:211,13,1,8 +BRDA:215,14,0,0 +BRDA:215,14,1,8 +BRF:30 +BRH:22 end_of_record TN: SF:src/ModalWindow.ts @@ -201,28 +221,28 @@ FN:127,(anonymous_3) FN:130,(anonymous_4) FNF:5 FNH:4 -FNDA:65,(anonymous_0) +FNDA:73,(anonymous_0) FNDA:14,(anonymous_1) FNDA:2,(anonymous_2) -FNDA:65,(anonymous_3) +FNDA:73,(anonymous_3) FNDA:0,(anonymous_4) DA:25,3 DA:32,3 -DA:75,65 -DA:76,65 -DA:78,65 -DA:79,65 -DA:81,65 -DA:83,65 -DA:84,65 -DA:86,65 -DA:99,65 +DA:75,73 +DA:76,73 +DA:78,73 +DA:79,73 +DA:81,73 +DA:83,73 +DA:84,73 +DA:86,73 +DA:99,73 DA:100,14 DA:101,14 DA:103,14 DA:115,2 -DA:128,65 -DA:130,65 +DA:128,73 +DA:130,73 DA:131,0 LF:18 LH:17 @@ -297,17 +317,17 @@ FNF:14 FNH:14 FNDA:101,classWithModifiers FNDA:25,(anonymous_1) -FNDA:66,serialize -FNDA:66,getCircularReplacer -FNDA:246,(anonymous_4) -FNDA:246,transform -FNDA:66,replacer -FNDA:246,(anonymous_7) +FNDA:74,serialize +FNDA:74,getCircularReplacer +FNDA:320,(anonymous_4) +FNDA:320,transform +FNDA:74,replacer +FNDA:320,(anonymous_7) FNDA:36,stopPropagation FNDA:8,(anonymous_9) FNDA:1,expectToThrow FNDA:3,(anonymous_11) -FNDA:65,cyrb53 +FNDA:73,cyrb53 FNDA:2,elementClick DA:27,101 DA:28,101 @@ -315,26 +335,26 @@ DA:30,25 DA:31,25 DA:33,25 DA:34,25 -DA:39,66 -DA:42,66 -DA:43,66 -DA:44,246 -DA:45,132 -DA:46,1 -DA:48,131 -DA:51,245 -DA:56,246 -DA:58,65 -DA:59,12 -DA:62,53 -DA:65,181 -DA:69,66 -DA:71,66 -DA:72,246 -DA:74,246 -DA:75,246 -DA:79,66 -DA:80,66 +DA:39,74 +DA:42,74 +DA:43,74 +DA:44,320 +DA:45,172 +DA:46,7 +DA:48,165 +DA:51,313 +DA:56,320 +DA:58,81 +DA:59,20 +DA:62,61 +DA:65,239 +DA:69,74 +DA:71,74 +DA:72,320 +DA:74,320 +DA:75,320 +DA:79,74 +DA:80,74 DA:89,36 DA:90,8 DA:91,8 @@ -343,17 +363,17 @@ DA:107,1 DA:108,3 DA:110,1 DA:112,1 -DA:121,65 -DA:122,65 -DA:124,65 -DA:125,3356 -DA:127,3356 -DA:128,3356 -DA:131,65 -DA:132,65 -DA:133,65 -DA:134,65 -DA:136,65 +DA:121,73 +DA:122,73 +DA:124,73 +DA:125,5324 +DA:127,5324 +DA:128,5324 +DA:131,73 +DA:132,73 +DA:133,73 +DA:134,73 +DA:136,73 DA:140,2 DA:142,2 DA:144,2 @@ -362,19 +382,19 @@ LH:48 BRDA:28,0,0,76 BRDA:28,0,1,25 BRDA:39,1,0,0 -BRDA:39,1,1,66 -BRDA:44,2,0,132 -BRDA:44,2,1,114 -BRDA:44,3,0,246 -BRDA:44,3,1,132 -BRDA:45,4,0,1 -BRDA:45,4,1,131 -BRDA:56,5,0,65 -BRDA:56,5,1,181 -BRDA:58,6,0,12 -BRDA:58,6,1,53 -BRDA:58,7,0,65 -BRDA:58,7,1,65 +BRDA:39,1,1,74 +BRDA:44,2,0,172 +BRDA:44,2,1,148 +BRDA:44,3,0,320 +BRDA:44,3,1,172 +BRDA:45,4,0,7 +BRDA:45,4,1,165 +BRDA:56,5,0,81 +BRDA:56,5,1,239 +BRDA:58,6,0,20 +BRDA:58,6,1,61 +BRDA:58,7,0,81 +BRDA:58,7,1,81 BRDA:90,8,0,8 BRDA:90,8,1,0 BRDA:90,9,0,8