|
1 | 1 | import type {
|
2 | 2 | __experimental_CheckoutCacheState,
|
| 3 | + __experimental_CheckoutInstance, |
3 | 4 | ClerkAPIResponseError,
|
4 | 5 | CommerceCheckoutResource,
|
5 | 6 | CommerceSubscriptionPlanPeriod,
|
@@ -158,5 +159,96 @@ describe('useCheckout type tests', () => {
|
158 | 159 | >();
|
159 | 160 | });
|
160 | 161 | });
|
| 162 | + |
| 163 | + describe('discriminated unions', () => { |
| 164 | + describe('error state discrimination', () => { |
| 165 | + it('has correct fetchStatus type union', () => { |
| 166 | + type FetchStatus = CheckoutObject['fetchStatus']; |
| 167 | + expectTypeOf<FetchStatus>().toEqualTypeOf<'idle' | 'fetching' | 'error'>(); |
| 168 | + }); |
| 169 | + |
| 170 | + it('has correct error type union', () => { |
| 171 | + type ErrorType = CheckoutObject['error']; |
| 172 | + expectTypeOf<ErrorType>().toMatchTypeOf<ClerkAPIResponseError | null>(); |
| 173 | + }); |
| 174 | + |
| 175 | + it('enforces error state correlation', () => { |
| 176 | + // When fetchStatus is 'error', error should not be null |
| 177 | + type ErrorFetchState = CheckoutObject & { fetchStatus: 'error' }; |
| 178 | + expectTypeOf<ErrorFetchState['error']>().not.toEqualTypeOf<null>(); |
| 179 | + |
| 180 | + // When fetchStatus is not 'error', error must be null |
| 181 | + type NonErrorFetchState = CheckoutObject & { fetchStatus: 'idle' | 'fetching' }; |
| 182 | + expectTypeOf<NonErrorFetchState['error']>().toEqualTypeOf<null>(); |
| 183 | + }); |
| 184 | + }); |
| 185 | + |
| 186 | + describe('status-based property discrimination', () => { |
| 187 | + it('has correct status type union', () => { |
| 188 | + type Status = CheckoutObject['status']; |
| 189 | + expectTypeOf<Status>().toEqualTypeOf<'needs_initialization' | 'needs_confirmation' | 'completed'>(); |
| 190 | + }); |
| 191 | + |
| 192 | + it('enforces null properties when status is needs_initialization', () => { |
| 193 | + type InitializationState = CheckoutObject & { status: 'needs_initialization' }; |
| 194 | + |
| 195 | + // Test that properties are nullable (null or undefined) in initialization state |
| 196 | + expectTypeOf<InitializationState['id']>().toEqualTypeOf<null>(); |
| 197 | + expectTypeOf<InitializationState['externalClientSecret']>().toEqualTypeOf<null>(); |
| 198 | + expectTypeOf<InitializationState['externalGatewayId']>().toEqualTypeOf<null>(); |
| 199 | + expectTypeOf<InitializationState['totals']>().toEqualTypeOf<null>(); |
| 200 | + expectTypeOf<InitializationState['isImmediatePlanChange']>().toEqualTypeOf<null>(); |
| 201 | + expectTypeOf<InitializationState['planPeriod']>().toEqualTypeOf<null>(); |
| 202 | + expectTypeOf<InitializationState['plan']>().toEqualTypeOf<null>(); |
| 203 | + expectTypeOf<InitializationState['paymentSource']>().toEqualTypeOf<null | undefined>(); |
| 204 | + |
| 205 | + // Test that the status property is correctly typed |
| 206 | + expectTypeOf<InitializationState['status']>().toEqualTypeOf<'needs_initialization'>(); |
| 207 | + }); |
| 208 | + |
| 209 | + it('enforces proper types when status is needs_confirmation or completed', () => { |
| 210 | + type ConfirmationState = CheckoutObject & { status: 'needs_confirmation' }; |
| 211 | + type CompletedState = CheckoutObject & { status: 'completed' }; |
| 212 | + |
| 213 | + // These should not be null for confirmation and completed states |
| 214 | + expectTypeOf<ConfirmationState['id']>().not.toEqualTypeOf<null>(); |
| 215 | + expectTypeOf<ConfirmationState['totals']>().not.toEqualTypeOf<null>(); |
| 216 | + expectTypeOf<ConfirmationState['plan']>().not.toEqualTypeOf<null>(); |
| 217 | + |
| 218 | + expectTypeOf<CompletedState['id']>().not.toEqualTypeOf<null>(); |
| 219 | + expectTypeOf<CompletedState['totals']>().not.toEqualTypeOf<null>(); |
| 220 | + expectTypeOf<CompletedState['plan']>().not.toEqualTypeOf<null>(); |
| 221 | + }); |
| 222 | + }); |
| 223 | + |
| 224 | + describe('type structure validation', () => { |
| 225 | + it('validates the overall discriminated union structure', () => { |
| 226 | + // Test that CheckoutObject is a proper discriminated union |
| 227 | + type CheckoutUnion = CheckoutObject; |
| 228 | + |
| 229 | + // Should include all required properties |
| 230 | + expectTypeOf<CheckoutUnion>().toHaveProperty('status'); |
| 231 | + expectTypeOf<CheckoutUnion>().toHaveProperty('fetchStatus'); |
| 232 | + expectTypeOf<CheckoutUnion>().toHaveProperty('error'); |
| 233 | + expectTypeOf<CheckoutUnion>().toHaveProperty('id'); |
| 234 | + expectTypeOf<CheckoutUnion>().toHaveProperty('confirm'); |
| 235 | + expectTypeOf<CheckoutUnion>().toHaveProperty('start'); |
| 236 | + expectTypeOf<CheckoutUnion>().toHaveProperty('clear'); |
| 237 | + expectTypeOf<CheckoutUnion>().toHaveProperty('finalize'); |
| 238 | + expectTypeOf<CheckoutUnion>().toHaveProperty('getState'); |
| 239 | + expectTypeOf<CheckoutUnion>().toHaveProperty('isStarting'); |
| 240 | + expectTypeOf<CheckoutUnion>().toHaveProperty('isConfirming'); |
| 241 | + }); |
| 242 | + |
| 243 | + it('validates method types remain unchanged', () => { |
| 244 | + // Ensure the discriminated union doesn't affect method types |
| 245 | + expectTypeOf<CheckoutObject['confirm']>().toEqualTypeOf<__experimental_CheckoutInstance['confirm']>(); |
| 246 | + expectTypeOf<CheckoutObject['start']>().toEqualTypeOf<__experimental_CheckoutInstance['start']>(); |
| 247 | + expectTypeOf<CheckoutObject['clear']>().toEqualTypeOf<() => void>(); |
| 248 | + expectTypeOf<CheckoutObject['finalize']>().toEqualTypeOf<(params?: { redirectUrl: string }) => void>(); |
| 249 | + expectTypeOf<CheckoutObject['getState']>().toEqualTypeOf<() => __experimental_CheckoutCacheState>(); |
| 250 | + }); |
| 251 | + }); |
| 252 | + }); |
161 | 253 | });
|
162 | 254 | });
|
0 commit comments