From eaae82bb4c16f544abd8c3be4d32e32fce5bf5ee Mon Sep 17 00:00:00 2001 From: AJ Ancheta <7781450+ancheetah@users.noreply.github.com> Date: Tue, 19 May 2026 17:20:39 -0400 Subject: [PATCH] fix(davinci-client): fix collector value types and simplify node reducer imports --- .changeset/little-lamps-float.md | 14 + .gitignore | 2 + .../api-report/davinci-client.api.md | 36 +- .../api-report/davinci-client.types.api.md | 36 +- .../davinci-client/src/lib/client.store.ts | 17 +- .../src/lib/client.types.test-d.ts | 6 +- .../davinci-client/src/lib/client.types.ts | 72 ++-- .../davinci-client/src/lib/node.reducer.ts | 73 +--- .../src/lib/updater-narrowing.types.test-d.ts | 315 ------------------ 9 files changed, 116 insertions(+), 455 deletions(-) create mode 100644 .changeset/little-lamps-float.md delete mode 100644 packages/davinci-client/src/lib/updater-narrowing.types.test-d.ts diff --git a/.changeset/little-lamps-float.md b/.changeset/little-lamps-float.md new file mode 100644 index 0000000000..413604e7ff --- /dev/null +++ b/.changeset/little-lamps-float.md @@ -0,0 +1,14 @@ +--- +'@forgerock/davinci-client': patch +--- + +Fixed collector value types and simplified node reducer imports + +### `client.types.ts` + +- Renamed `CollectorInputTypes` → `CollectorValueTypes` and added `DeviceValue` to the union. +- Rewrote `CollectorValueType` conditional type: grouped branches by return type (string, boolean, string[], specialized, category catch-alls), fixed `DeviceAuthenticationCollector` to return `DeviceValue` instead of `string`, added `never` for `ActionCollector` and `NoValueCollector`, and added a `SingleValueAutoCollector` catch-all. + +### `node.reducer.ts` / `client.store.ts` + +- Replaced ~30 individual collector type imports with `Collectors` (from `node.types`) and `CollectorValueTypes` (from `client.types`), collapsing verbose inline union annotations to single-type references. diff --git a/.gitignore b/.gitignore index 97ddd27147..8fd316de3d 100644 --- a/.gitignore +++ b/.gitignore @@ -100,3 +100,5 @@ GEMINI.md # Polaris .polaris-setup-progress.json + +.polaris/ diff --git a/packages/davinci-client/api-report/davinci-client.api.md b/packages/davinci-client/api-report/davinci-client.api.md index ce4a9d9b7b..1bb3e6e1ac 100644 --- a/packages/davinci-client/api-report/davinci-client.api.md +++ b/packages/davinci-client/api-report/davinci-client.api.md @@ -177,9 +177,6 @@ export interface CollectorErrors { target: string; } -// @public (undocumented) -export type CollectorInputTypes = string | string[] | boolean | PhoneNumberInputValue | PhoneNumberExtensionInputValue | FidoRegistrationInputValue | FidoAuthenticationInputValue; - // @public export interface CollectorRichContent { // (undocumented) @@ -194,8 +191,16 @@ export type Collectors = FlowCollector | PasswordCollector | ValidatedPasswordCo // @public export type CollectorValueType = T extends { type: 'PasswordCollector'; -} ? string : T extends { +} | { type: 'ValidatedPasswordCollector'; +} | { + type: 'SingleSelectCollector'; +} | { + type: 'DeviceRegistrationCollector'; +} | { + type: 'ProtectCollector'; +} | { + type: 'PollingCollector'; } ? string : T extends { type: 'TextCollector'; category: 'SingleValueCollector'; @@ -205,14 +210,10 @@ export type CollectorValueType = T extends { } ? string : T extends { type: 'ValidatedBooleanCollector'; } ? boolean : T extends { - type: 'SingleSelectCollector'; -} ? string : T extends { type: 'MultiSelectCollector'; } ? string[] : T extends { - type: 'DeviceRegistrationCollector'; -} ? string : T extends { type: 'DeviceAuthenticationCollector'; -} ? string : T extends { +} ? DeviceValue : T extends { type: 'PhoneNumberCollector'; } ? PhoneNumberInputValue : T extends { type: 'PhoneNumberExtensionCollector'; @@ -224,9 +225,18 @@ export type CollectorValueType = T extends { category: 'SingleValueCollector'; } ? string : T extends { category: 'ValidatedSingleValueCollector'; +} ? string : T extends { + category: 'SingleValueAutoCollector'; } ? string : T extends { category: 'MultiValueCollector'; -} ? string[] : CollectorInputTypes; +} ? string[] : T extends { + category: 'ActionCollector'; +} ? never : T extends { + category: 'NoValueCollector'; +} ? never : CollectorValueTypes; + +// @public (undocumented) +export type CollectorValueTypes = string | string[] | boolean | DeviceValue | PhoneNumberInputValue | PhoneNumberExtensionInputValue | FidoRegistrationInputValue | FidoAuthenticationInputValue; // @public (undocumented) export type ComplexValueFields = DeviceAuthenticationField | DeviceRegistrationField | PhoneNumberField | PhoneNumberExtensionField | FidoRegistrationField | FidoAuthenticationField | PollingField; @@ -1187,8 +1197,8 @@ value: Record; }, string>; // @public -export const nodeCollectorReducer: Reducer<(ValidatedTextCollector | ValidatedBooleanCollector | ValidatedPasswordCollector | DeviceAuthenticationCollector | DeviceRegistrationCollector | PhoneNumberCollector | PhoneNumberExtensionCollector | ProtectCollector | FidoRegistrationCollector | FidoAuthenticationCollector | PollingCollector | TextCollector | PasswordCollector | SingleSelectCollector | UnknownCollector | IdpCollector | FlowCollector | SubmitCollector | ReadOnlyCollector | RichTextCollector | QrCodeCollector | AgreementCollector | ActionCollector<"ActionCollector"> | SingleValueCollector<"SingleValueCollector"> | MultiSelectCollector)[]> & { - getInitialState: () => (ValidatedTextCollector | ValidatedBooleanCollector | ValidatedPasswordCollector | DeviceAuthenticationCollector | DeviceRegistrationCollector | PhoneNumberCollector | PhoneNumberExtensionCollector | ProtectCollector | FidoRegistrationCollector | FidoAuthenticationCollector | PollingCollector | TextCollector | PasswordCollector | SingleSelectCollector | UnknownCollector | IdpCollector | FlowCollector | SubmitCollector | ReadOnlyCollector | RichTextCollector | QrCodeCollector | AgreementCollector | ActionCollector<"ActionCollector"> | SingleValueCollector<"SingleValueCollector"> | MultiSelectCollector)[]; +export const nodeCollectorReducer: Reducer & { + getInitialState: () => Collectors[]; }; // @public (undocumented) @@ -1918,7 +1928,7 @@ export type UnknownField = Record; // @public (undocumented) export const updateCollectorValues: ActionCreatorWithPayload< { id: string; -value: string | string[] | boolean | PhoneNumberInputValue | PhoneNumberExtensionInputValue | FidoRegistrationInputValue | FidoAuthenticationInputValue; +value: CollectorValueTypes; index?: number; }, string>; diff --git a/packages/davinci-client/api-report/davinci-client.types.api.md b/packages/davinci-client/api-report/davinci-client.types.api.md index 87ac710a3b..ce731cb354 100644 --- a/packages/davinci-client/api-report/davinci-client.types.api.md +++ b/packages/davinci-client/api-report/davinci-client.types.api.md @@ -177,9 +177,6 @@ export interface CollectorErrors { target: string; } -// @public (undocumented) -export type CollectorInputTypes = string | string[] | boolean | PhoneNumberInputValue | PhoneNumberExtensionInputValue | FidoRegistrationInputValue | FidoAuthenticationInputValue; - // @public export interface CollectorRichContent { // (undocumented) @@ -194,8 +191,16 @@ export type Collectors = FlowCollector | PasswordCollector | ValidatedPasswordCo // @public export type CollectorValueType = T extends { type: 'PasswordCollector'; -} ? string : T extends { +} | { type: 'ValidatedPasswordCollector'; +} | { + type: 'SingleSelectCollector'; +} | { + type: 'DeviceRegistrationCollector'; +} | { + type: 'ProtectCollector'; +} | { + type: 'PollingCollector'; } ? string : T extends { type: 'TextCollector'; category: 'SingleValueCollector'; @@ -205,14 +210,10 @@ export type CollectorValueType = T extends { } ? string : T extends { type: 'ValidatedBooleanCollector'; } ? boolean : T extends { - type: 'SingleSelectCollector'; -} ? string : T extends { type: 'MultiSelectCollector'; } ? string[] : T extends { - type: 'DeviceRegistrationCollector'; -} ? string : T extends { type: 'DeviceAuthenticationCollector'; -} ? string : T extends { +} ? DeviceValue : T extends { type: 'PhoneNumberCollector'; } ? PhoneNumberInputValue : T extends { type: 'PhoneNumberExtensionCollector'; @@ -224,9 +225,18 @@ export type CollectorValueType = T extends { category: 'SingleValueCollector'; } ? string : T extends { category: 'ValidatedSingleValueCollector'; +} ? string : T extends { + category: 'SingleValueAutoCollector'; } ? string : T extends { category: 'MultiValueCollector'; -} ? string[] : CollectorInputTypes; +} ? string[] : T extends { + category: 'ActionCollector'; +} ? never : T extends { + category: 'NoValueCollector'; +} ? never : CollectorValueTypes; + +// @public (undocumented) +export type CollectorValueTypes = string | string[] | boolean | DeviceValue | PhoneNumberInputValue | PhoneNumberExtensionInputValue | FidoRegistrationInputValue | FidoAuthenticationInputValue; // @public (undocumented) export type ComplexValueFields = DeviceAuthenticationField | DeviceRegistrationField | PhoneNumberField | PhoneNumberExtensionField | FidoRegistrationField | FidoAuthenticationField | PollingField; @@ -1184,8 +1194,8 @@ value: Record; }, string>; // @public -export const nodeCollectorReducer: Reducer<(ValidatedTextCollector | ValidatedBooleanCollector | ValidatedPasswordCollector | DeviceAuthenticationCollector | DeviceRegistrationCollector | PhoneNumberCollector | PhoneNumberExtensionCollector | ProtectCollector | FidoRegistrationCollector | FidoAuthenticationCollector | PollingCollector | TextCollector | PasswordCollector | SingleSelectCollector | UnknownCollector | IdpCollector | FlowCollector | SubmitCollector | ReadOnlyCollector | RichTextCollector | QrCodeCollector | AgreementCollector | ActionCollector<"ActionCollector"> | SingleValueCollector<"SingleValueCollector"> | MultiSelectCollector)[]> & { - getInitialState: () => (ValidatedTextCollector | ValidatedBooleanCollector | ValidatedPasswordCollector | DeviceAuthenticationCollector | DeviceRegistrationCollector | PhoneNumberCollector | PhoneNumberExtensionCollector | ProtectCollector | FidoRegistrationCollector | FidoAuthenticationCollector | PollingCollector | TextCollector | PasswordCollector | SingleSelectCollector | UnknownCollector | IdpCollector | FlowCollector | SubmitCollector | ReadOnlyCollector | RichTextCollector | QrCodeCollector | AgreementCollector | ActionCollector<"ActionCollector"> | SingleValueCollector<"SingleValueCollector"> | MultiSelectCollector)[]; +export const nodeCollectorReducer: Reducer & { + getInitialState: () => Collectors[]; }; // @public (undocumented) @@ -1915,7 +1925,7 @@ export type UnknownField = Record; // @public (undocumented) export const updateCollectorValues: ActionCreatorWithPayload< { id: string; -value: string | string[] | boolean | PhoneNumberInputValue | PhoneNumberExtensionInputValue | FidoRegistrationInputValue | FidoAuthenticationInputValue; +value: CollectorValueTypes; index?: number; }, string>; diff --git a/packages/davinci-client/src/lib/client.store.ts b/packages/davinci-client/src/lib/client.store.ts index 966875bd77..880bdc8e49 100644 --- a/packages/davinci-client/src/lib/client.store.ts +++ b/packages/davinci-client/src/lib/client.store.ts @@ -40,13 +40,9 @@ import type { SingleValueCollectors, MultiSelectCollector, ObjectValueCollectors, - PhoneNumberInputValue, AutoCollectors, PollingCollector, MultiValueCollectors, - FidoRegistrationInputValue, - FidoAuthenticationInputValue, - PhoneNumberExtensionInputValue, } from './collector.types.js'; import type { InitFlow, @@ -55,6 +51,7 @@ import type { Updater, Validator, Poller, + CollectorValueTypes, } from './client.types.js'; import { returnValidator } from './collector.utils.js'; import { returnPasswordPolicyValidator } from './password-policy.rules.js'; @@ -335,17 +332,7 @@ export async function davinci({ ); } - return function ( - value: - | string - | string[] - | boolean - | PhoneNumberInputValue - | PhoneNumberExtensionInputValue - | FidoRegistrationInputValue - | FidoAuthenticationInputValue, - index?: number, - ) { + return function (value: CollectorValueTypes, index?: number) { try { store.dispatch(nodeSlice.actions.update({ id, value, index })); return null; diff --git a/packages/davinci-client/src/lib/client.types.test-d.ts b/packages/davinci-client/src/lib/client.types.test-d.ts index 2de997e359..aaafff1381 100644 --- a/packages/davinci-client/src/lib/client.types.test-d.ts +++ b/packages/davinci-client/src/lib/client.types.test-d.ts @@ -10,7 +10,7 @@ import { describe, expectTypeOf, it } from 'vitest'; import type { GenericError } from '@forgerock/sdk-types'; import type { - CollectorInputTypes, + CollectorValueTypes, InitFlow, InternalErrorResponse, Updater, @@ -173,13 +173,13 @@ describe('Client Types', () => { describe('Updater', () => { it('should accept string, boolean, or object value and optional index', () => { - const updater: Updater = (value: CollectorInputTypes, index?: number) => { + const updater: Updater = (value: CollectorValueTypes, index?: number) => { return { error: { message: 'Invalid value', code: 'INVALID', type: 'state_error' }, type: 'internal_error', }; }; - expectTypeOf(updater).parameter(0).toEqualTypeOf(); + expectTypeOf(updater).parameter(0).toEqualTypeOf(); expectTypeOf(updater).parameter(1).toBeNullable(); }); diff --git a/packages/davinci-client/src/lib/client.types.ts b/packages/davinci-client/src/lib/client.types.ts index 3bad50a584..ce0cd9c9de 100644 --- a/packages/davinci-client/src/lib/client.types.ts +++ b/packages/davinci-client/src/lib/client.types.ts @@ -11,6 +11,7 @@ import type { FidoAuthenticationInputValue, PhoneNumberInputValue, PhoneNumberExtensionInputValue, + DeviceValue, AutoCollectors, MultiValueCollectors, ObjectValueCollectors, @@ -29,14 +30,16 @@ export interface InternalErrorResponse { export type InitFlow = () => Promise; -export type CollectorInputTypes = +export type CollectorValueTypes = | string | string[] | boolean + | DeviceValue | PhoneNumberInputValue | PhoneNumberExtensionInputValue | FidoRegistrationInputValue | FidoAuthenticationInputValue; + /** * Maps collector types to the specific value type they accept. * This enables type narrowing when using the update method with specific collector types. @@ -49,39 +52,52 @@ export type CollectorInputTypes = * } * ``` */ -export type CollectorValueType = T extends { type: 'PasswordCollector' } - ? string - : T extends { type: 'ValidatedPasswordCollector' } +export type CollectorValueType = + // string input types + T extends + | { type: 'PasswordCollector' } + | { type: 'ValidatedPasswordCollector' } + | { type: 'SingleSelectCollector' } + | { type: 'DeviceRegistrationCollector' } + | { type: 'ProtectCollector' } + | { type: 'PollingCollector' } ? string - : T extends { type: 'TextCollector'; category: 'SingleValueCollector' } + : // TextCollector branches must remain compound — category is the only discriminant + T extends { type: 'TextCollector'; category: 'SingleValueCollector' } ? string : T extends { type: 'TextCollector'; category: 'ValidatedSingleValueCollector' } ? string - : T extends { type: 'ValidatedBooleanCollector' } + : // boolean input types + T extends { type: 'ValidatedBooleanCollector' } ? boolean - : T extends { type: 'SingleSelectCollector' } - ? string - : T extends { type: 'MultiSelectCollector' } - ? string[] - : T extends { type: 'DeviceRegistrationCollector' } - ? string - : T extends { type: 'DeviceAuthenticationCollector' } - ? string - : T extends { type: 'PhoneNumberCollector' } - ? PhoneNumberInputValue - : T extends { type: 'PhoneNumberExtensionCollector' } - ? PhoneNumberExtensionInputValue - : T extends { type: 'FidoRegistrationCollector' } - ? FidoRegistrationInputValue - : T extends { type: 'FidoAuthenticationCollector' } - ? FidoAuthenticationInputValue - : T extends { category: 'SingleValueCollector' } + : // string[] input types + T extends { type: 'MultiSelectCollector' } + ? string[] + : // specialized input types + T extends { type: 'DeviceAuthenticationCollector' } + ? DeviceValue + : T extends { type: 'PhoneNumberCollector' } + ? PhoneNumberInputValue + : T extends { type: 'PhoneNumberExtensionCollector' } + ? PhoneNumberExtensionInputValue + : T extends { type: 'FidoRegistrationCollector' } + ? FidoRegistrationInputValue + : T extends { type: 'FidoAuthenticationCollector' } + ? FidoAuthenticationInputValue + : // category catch-alls + T extends { category: 'SingleValueCollector' } + ? string + : T extends { category: 'ValidatedSingleValueCollector' } + ? string + : T extends { category: 'SingleValueAutoCollector' } ? string - : T extends { category: 'ValidatedSingleValueCollector' } - ? string - : T extends { category: 'MultiValueCollector' } - ? string[] - : CollectorInputTypes; + : T extends { category: 'MultiValueCollector' } + ? string[] + : T extends { category: 'ActionCollector' } + ? never + : T extends { category: 'NoValueCollector' } + ? never + : CollectorValueTypes; /** * A function type that updates a collector's value. Accepts values appropriate for the collector type. diff --git a/packages/davinci-client/src/lib/node.reducer.ts b/packages/davinci-client/src/lib/node.reducer.ts index d49fe3e0be..68067667d4 100644 --- a/packages/davinci-client/src/lib/node.reducer.ts +++ b/packages/davinci-client/src/lib/node.reducer.ts @@ -35,39 +35,9 @@ import { returnAgreementCollector, } from './collector.utils.js'; import type { DaVinciField, UnknownField } from './davinci.types.js'; -import type { - ActionCollector, - MultiSelectCollector, - ValidatedBooleanCollector, - SingleSelectCollector, - FlowCollector, - PasswordCollector, - ValidatedPasswordCollector, - SingleValueCollector, - IdpCollector, - SubmitCollector, - TextCollector, - ReadOnlyCollector, - RichTextCollector, - ValidatedTextCollector, - DeviceAuthenticationCollector, - DeviceRegistrationCollector, - PhoneNumberCollector, - PhoneNumberInputValue, - PhoneNumberOutputValue, - UnknownCollector, - ProtectCollector, - PollingCollector, - FidoRegistrationCollector, - FidoAuthenticationCollector, - FidoAuthenticationInputValue, - FidoRegistrationInputValue, - QrCodeCollector, - AgreementCollector, - PhoneNumberExtensionOutputValue, - PhoneNumberExtensionCollector, - PhoneNumberExtensionInputValue, -} from './collector.types.js'; +import type { PhoneNumberOutputValue, PhoneNumberExtensionOutputValue } from './collector.types.js'; +import { CollectorValueTypes } from './client.types.js'; +import { Collectors } from './node.types.js'; /** * @const nextCollectorValues - Action for setting the next collector values @@ -81,14 +51,7 @@ export const nextCollectorValues = createAction<{ }>('node/next'); export const updateCollectorValues = createAction<{ id: string; - value: - | string - | string[] - | boolean - | PhoneNumberInputValue - | PhoneNumberExtensionInputValue - | FidoRegistrationInputValue - | FidoAuthenticationInputValue; + value: CollectorValueTypes; index?: number; }>('node/update'); export const pollCollectorValues = createAction('node/poll'); @@ -96,33 +59,7 @@ export const pollCollectorValues = createAction('node/poll'); /** * @const initialCollectorValues - Initial state for the collector values */ -const initialCollectorValues: ( - | FlowCollector - | PasswordCollector - | ValidatedPasswordCollector - | TextCollector - | IdpCollector - | SubmitCollector - | ActionCollector<'ActionCollector'> - | SingleValueCollector<'SingleValueCollector'> - | ValidatedBooleanCollector - | SingleSelectCollector - | MultiSelectCollector - | DeviceAuthenticationCollector - | DeviceRegistrationCollector - | PhoneNumberCollector - | PhoneNumberExtensionCollector - | ReadOnlyCollector - | RichTextCollector - | ValidatedTextCollector - | UnknownCollector - | ProtectCollector - | PollingCollector - | FidoRegistrationCollector - | FidoAuthenticationCollector - | QrCodeCollector - | AgreementCollector -)[] = []; +const initialCollectorValues: Collectors[] = []; /** * @const nodeCollectorReducer - Reducer for handling the collector values diff --git a/packages/davinci-client/src/lib/updater-narrowing.types.test-d.ts b/packages/davinci-client/src/lib/updater-narrowing.types.test-d.ts deleted file mode 100644 index ca7d78b180..0000000000 --- a/packages/davinci-client/src/lib/updater-narrowing.types.test-d.ts +++ /dev/null @@ -1,315 +0,0 @@ -/* - * Copyright (c) 2025 Ping Identity Corporation. All rights reserved. - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ -/* eslint-disable @typescript-eslint/no-unused-vars */ -import { describe, expectTypeOf, it } from 'vitest'; - -import type { Updater } from './client.types.js'; -import type { - PasswordCollector, - ValidatedPasswordCollector, - TextCollector, - ValidatedTextCollector, - SingleSelectCollector, - MultiSelectCollector, - DeviceRegistrationCollector, - DeviceAuthenticationCollector, - PhoneNumberCollector, - FidoRegistrationCollector, - FidoAuthenticationCollector, - PhoneNumberInputValue, - FidoRegistrationInputValue, - FidoAuthenticationInputValue, -} from './collector.types.js'; -import type { Collectors } from './node.types.js'; - -// Mock update function that mimics davinciClient.update signature -type MockUpdate = < - T extends - | PasswordCollector - | ValidatedPasswordCollector - | TextCollector - | ValidatedTextCollector - | SingleSelectCollector - | MultiSelectCollector - | DeviceRegistrationCollector - | DeviceAuthenticationCollector - | PhoneNumberCollector - | FidoRegistrationCollector - | FidoAuthenticationCollector, ->( - collector: T, -) => Updater; - -const mockUpdate: MockUpdate = (collector) => { - return ((value: unknown) => null) as any; -}; - -describe('Updater Type Narrowing with Real Usage Pattern', () => { - describe('Single Value Collectors - should narrow collector type and updater parameter', () => { - it('PasswordCollector should narrow collector to PasswordCollector type', () => { - const collector = {} as Collectors; - - if (collector.type === 'PasswordCollector') { - // 1. Collector itself should be narrowed to PasswordCollector - expectTypeOf(collector).toEqualTypeOf(); - - // 2. update() should return Updater - const updater = mockUpdate(collector); - expectTypeOf(updater).toEqualTypeOf>(); - - // 3. The updater parameter should accept string - expectTypeOf(updater).parameter(0).toEqualTypeOf(); - } - }); - - it('ValidatedPasswordCollector should narrow collector to ValidatedPasswordCollector type', () => { - const collector = {} as Collectors; - - if (collector.type === 'ValidatedPasswordCollector') { - // 1. Collector itself should be narrowed to ValidatedPasswordCollector - expectTypeOf(collector).toEqualTypeOf(); - - // 2. update() should return Updater - const updater = mockUpdate(collector); - expectTypeOf(updater).toEqualTypeOf>(); - - // 3. The updater parameter should accept string - expectTypeOf(updater).parameter(0).toEqualTypeOf(); - } - }); - - it('TextCollector should narrow collector to TextCollector | ValidatedTextCollector', () => { - const collector = {} as Collectors; - - if (collector.type === 'TextCollector') { - // 1. Collector narrows to union of both text collector types - expectTypeOf(collector).toEqualTypeOf(); - - // 2. update() should return Updater - const updater = mockUpdate(collector); - expectTypeOf(updater).toEqualTypeOf>(); - - // 3. The updater parameter should accept string (both types accept string) - expectTypeOf(updater).parameter(0).toEqualTypeOf(); - } - }); - - it('SingleSelectCollector should narrow collector to SingleSelectCollector type', () => { - const collector = {} as Collectors; - - if (collector.type === 'SingleSelectCollector') { - // 1. Collector should be narrowed to SingleSelectCollector - expectTypeOf(collector).toEqualTypeOf(); - - // 2. update() should return Updater - const updater = mockUpdate(collector); - expectTypeOf(updater).toEqualTypeOf>(); - - // 3. The updater parameter should accept string - expectTypeOf(updater).parameter(0).toEqualTypeOf(); - } - }); - - it('DeviceRegistrationCollector should narrow collector type', () => { - const collector = {} as Collectors; - - if (collector.type === 'DeviceRegistrationCollector') { - // 1. Collector should be narrowed to DeviceRegistrationCollector - expectTypeOf(collector).toEqualTypeOf(); - - // 2. update() should return Updater - const updater = mockUpdate(collector); - expectTypeOf(updater).toEqualTypeOf>(); - - // 3. The updater parameter should accept string - expectTypeOf(updater).parameter(0).toEqualTypeOf(); - } - }); - - it('DeviceAuthenticationCollector should narrow collector type', () => { - const collector = {} as Collectors; - - if (collector.type === 'DeviceAuthenticationCollector') { - // 1. Collector should be narrowed to DeviceAuthenticationCollector - expectTypeOf(collector).toEqualTypeOf(); - - // 2. update() should return Updater - const updater = mockUpdate(collector); - expectTypeOf(updater).toEqualTypeOf>(); - - // 3. The updater parameter should accept string - expectTypeOf(updater).parameter(0).toEqualTypeOf(); - } - }); - }); - - describe('Multi Value Collectors - should narrow collector type and updater parameter', () => { - it('MultiSelectCollector should narrow collector type', () => { - const collector = {} as Collectors; - - if (collector.type === 'MultiSelectCollector') { - // 1. Collector should be narrowed to MultiSelectCollector - expectTypeOf(collector).toEqualTypeOf(); - - // 2. update() should return Updater - const updater = mockUpdate(collector); - expectTypeOf(updater).toEqualTypeOf>(); - - // 3. The updater parameter should accept string[] - expectTypeOf(updater).parameter(0).toEqualTypeOf(); - } - }); - }); - - describe('Object Value Collectors - should narrow collector type and updater parameter', () => { - it('PhoneNumberCollector should narrow collector type', () => { - const collector = {} as Collectors; - - if (collector.type === 'PhoneNumberCollector') { - // 1. Collector should be narrowed to PhoneNumberCollector - expectTypeOf(collector).toEqualTypeOf(); - - // 2. update() should return Updater - const updater = mockUpdate(collector); - expectTypeOf(updater).toEqualTypeOf>(); - - // 3. The updater parameter should accept PhoneNumberInputValue - expectTypeOf(updater).parameter(0).toEqualTypeOf(); - } - }); - - it('FidoRegistrationCollector should narrow collector type', () => { - const collector = {} as Collectors; - - if (collector.type === 'FidoRegistrationCollector') { - // 1. Collector should be narrowed to FidoRegistrationCollector - expectTypeOf(collector).toEqualTypeOf(); - - // 2. update() should return Updater - const updater = mockUpdate(collector); - expectTypeOf(updater).toEqualTypeOf>(); - - // 3. The updater parameter should accept FidoRegistrationInputValue - expectTypeOf(updater).parameter(0).toEqualTypeOf(); - } - }); - - it('FidoAuthenticationCollector should narrow collector type', () => { - const collector = {} as Collectors; - - if (collector.type === 'FidoAuthenticationCollector') { - // 1. Collector should be narrowed to FidoAuthenticationCollector - expectTypeOf(collector).toEqualTypeOf(); - - // 2. update() should return Updater - const updater = mockUpdate(collector); - expectTypeOf(updater).toEqualTypeOf>(); - - // 3. The updater parameter should accept FidoAuthenticationInputValue - expectTypeOf(updater).parameter(0).toEqualTypeOf(); - } - }); - }); - - describe('Real-world usage patterns', () => { - it('should narrow correctly with forEach loop', () => { - const collectors = [] as Collectors[]; - - collectors.forEach((collector) => { - if (collector.type === 'PasswordCollector') { - expectTypeOf(collector).toEqualTypeOf(); - const updater = mockUpdate(collector); - expectTypeOf(updater).toEqualTypeOf>(); - expectTypeOf(updater).parameter(0).toEqualTypeOf(); - } else if (collector.type === 'PhoneNumberCollector') { - expectTypeOf(collector).toEqualTypeOf(); - const updater = mockUpdate(collector); - expectTypeOf(updater).toEqualTypeOf>(); - expectTypeOf(updater).parameter(0).toEqualTypeOf(); - } else if (collector.type === 'MultiSelectCollector') { - expectTypeOf(collector).toEqualTypeOf(); - const updater = mockUpdate(collector); - expectTypeOf(updater).toEqualTypeOf>(); - expectTypeOf(updater).parameter(0).toEqualTypeOf(); - } - }); - }); - - it('should narrow correctly with for...of loop', () => { - const collectors = [] as Collectors[]; - - for (const collector of collectors) { - if (collector.type === 'TextCollector') { - expectTypeOf(collector).toEqualTypeOf(); - const updater = mockUpdate(collector); - expectTypeOf(updater).toEqualTypeOf>(); - expectTypeOf(updater).parameter(0).toEqualTypeOf(); - } - } - }); - - it('should work with early return pattern', () => { - const collector = {} as Collectors; - - if (collector.type !== 'PasswordCollector') { - return; - } - - // After early return, collector is narrowed to PasswordCollector - expectTypeOf(collector).toEqualTypeOf(); - const updater = mockUpdate(collector); - expectTypeOf(updater).toEqualTypeOf>(); - expectTypeOf(updater).parameter(0).toEqualTypeOf(); - }); - }); - - describe('Edge cases', () => { - it('should maintain index parameter optionality after narrowing', () => { - const collector = {} as Collectors; - - if (collector.type === 'PasswordCollector') { - expectTypeOf(collector).toEqualTypeOf(); - const updater = mockUpdate(collector); - expectTypeOf(updater).toEqualTypeOf>(); - - // Index parameter should be optional (number | undefined) - expectTypeOf(updater).parameter(1).toMatchTypeOf(); - } - }); - - it('should work with complex conditional chains', () => { - const collector = {} as Collectors; - - if (collector.type === 'PasswordCollector') { - expectTypeOf(collector).toEqualTypeOf(); - const updater = mockUpdate(collector); - expectTypeOf(updater).toEqualTypeOf>(); - expectTypeOf(updater).parameter(0).toEqualTypeOf(); - } else if (collector.type === 'TextCollector') { - expectTypeOf(collector).toEqualTypeOf(); - const updater = mockUpdate(collector); - expectTypeOf(updater).toEqualTypeOf>(); - expectTypeOf(updater).parameter(0).toEqualTypeOf(); - } else if (collector.type === 'PhoneNumberCollector') { - expectTypeOf(collector).toEqualTypeOf(); - const updater = mockUpdate(collector); - expectTypeOf(updater).toEqualTypeOf>(); - expectTypeOf(updater).parameter(0).toEqualTypeOf(); - } else if (collector.type === 'MultiSelectCollector') { - expectTypeOf(collector).toEqualTypeOf(); - const updater = mockUpdate(collector); - expectTypeOf(updater).toEqualTypeOf>(); - expectTypeOf(updater).parameter(0).toEqualTypeOf(); - } else if (collector.type === 'FidoRegistrationCollector') { - expectTypeOf(collector).toEqualTypeOf(); - const updater = mockUpdate(collector); - expectTypeOf(updater).toEqualTypeOf>(); - expectTypeOf(updater).parameter(0).toEqualTypeOf(); - } - }); - }); -});