@plyaz/types 1.3.7 → 1.4.0
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.
- package/dist/features/community/index.d.ts +1 -0
- package/dist/features/community/types.d.ts +23 -0
- package/dist/features/feature-flag/types.d.ts +209 -0
- package/dist/features/index.d.ts +1 -0
- package/dist/testing/common/patterns/types.d.ts +161 -0
- package/dist/testing/common/wrappers/types.d.ts +593 -0
- package/dist/testing/features/feature-flags/types.d.ts +63 -15
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type * from './types';
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Community Domain Types
|
|
3
|
+
*
|
|
4
|
+
* Core type definitions for the community domain.
|
|
5
|
+
*
|
|
6
|
+
* @fileoverview Community domain type definitions
|
|
7
|
+
* @version 1.0.0
|
|
8
|
+
*/
|
|
9
|
+
export interface BlogPost {
|
|
10
|
+
id: number;
|
|
11
|
+
title: string;
|
|
12
|
+
excerpt: string;
|
|
13
|
+
content: string;
|
|
14
|
+
date: string;
|
|
15
|
+
slug: string;
|
|
16
|
+
featuredImage?: string;
|
|
17
|
+
author: {
|
|
18
|
+
name: string;
|
|
19
|
+
avatar?: string;
|
|
20
|
+
};
|
|
21
|
+
categories: string[];
|
|
22
|
+
readingTime: number;
|
|
23
|
+
}
|
|
@@ -331,6 +331,24 @@ export interface FeatureFlagHelpers<FeatureFlagKey extends string = string> {
|
|
|
331
331
|
isAllEnabled: (keys: FeatureFlagKey[], context?: FeatureFlagContext) => Promise<boolean>;
|
|
332
332
|
whenEnabled: <T>(key: FeatureFlagKey, callback: () => T | Promise<T>, fallback?: () => T | Promise<T>, context?: FeatureFlagContext) => Promise<T | undefined>;
|
|
333
333
|
}
|
|
334
|
+
/**
|
|
335
|
+
* Response structure for fetching feature flag data.
|
|
336
|
+
* Contains both flags and their associated rules.
|
|
337
|
+
*
|
|
338
|
+
* @template FeatureFlagKey - Type of feature flag keys
|
|
339
|
+
*
|
|
340
|
+
* @example
|
|
341
|
+
* ```typescript
|
|
342
|
+
* const response: FetchFeatureFlagDataResponse<AppFeatures> = {
|
|
343
|
+
* flags: [
|
|
344
|
+
* { key: 'newUI', name: 'New UI', value: true, isEnabled: true }
|
|
345
|
+
* ],
|
|
346
|
+
* rules: [
|
|
347
|
+
* { flagKey: 'newUI', conditions: { userRole: 'beta' }, value: true }
|
|
348
|
+
* ]
|
|
349
|
+
* };
|
|
350
|
+
* ```
|
|
351
|
+
*/
|
|
334
352
|
export interface FetchFeatureFlagDataResponse<FeatureFlagKey extends string> {
|
|
335
353
|
flags: FeatureFlag<FeatureFlagKey>[];
|
|
336
354
|
rules: FeatureFlagRule<FeatureFlagKey>[];
|
|
@@ -460,66 +478,257 @@ export interface ModuleConfigurationTestInput {
|
|
|
460
478
|
config: Record<string, unknown>;
|
|
461
479
|
expectedConfig: Record<string, unknown>;
|
|
462
480
|
}
|
|
481
|
+
/**
|
|
482
|
+
* Input for provider type test cases.
|
|
483
|
+
* Tests different provider implementations.
|
|
484
|
+
*
|
|
485
|
+
* @example
|
|
486
|
+
* ```typescript
|
|
487
|
+
* const input: ProviderTypeTestInput = {
|
|
488
|
+
* provider: 'LaunchDarklyProvider'
|
|
489
|
+
* };
|
|
490
|
+
* ```
|
|
491
|
+
*/
|
|
463
492
|
export interface ProviderTypeTestInput {
|
|
493
|
+
/** Name or type of the provider to test */
|
|
464
494
|
provider: string;
|
|
465
495
|
}
|
|
496
|
+
/**
|
|
497
|
+
* Input for feature flag operation test cases.
|
|
498
|
+
* Tests CRUD and evaluation operations on flags.
|
|
499
|
+
*
|
|
500
|
+
* @template FeatureFlagKey - Type of feature flag keys
|
|
501
|
+
*
|
|
502
|
+
* @example
|
|
503
|
+
* ```typescript
|
|
504
|
+
* const input: FlagOperationTestInput<'darkMode'> = {
|
|
505
|
+
* operation: 'evaluate',
|
|
506
|
+
* flagKey: 'darkMode',
|
|
507
|
+
* context: { userId: '123', userRole: 'admin' },
|
|
508
|
+
* expectedResult: true
|
|
509
|
+
* };
|
|
510
|
+
* ```
|
|
511
|
+
*/
|
|
466
512
|
export interface FlagOperationTestInput<FeatureFlagKey extends string> {
|
|
513
|
+
/** Type of operation to perform */
|
|
467
514
|
operation: 'create' | 'update' | 'delete' | 'evaluate';
|
|
515
|
+
/** Key of the flag to operate on */
|
|
468
516
|
flagKey: FeatureFlagKey;
|
|
517
|
+
/** Data for create/update operations */
|
|
469
518
|
data?: unknown;
|
|
519
|
+
/** Context for evaluation operations */
|
|
470
520
|
context?: FeatureFlagContext;
|
|
521
|
+
/** Expected result of the operation */
|
|
471
522
|
expectedResult?: unknown;
|
|
472
523
|
}
|
|
524
|
+
/**
|
|
525
|
+
* Input for permission test cases.
|
|
526
|
+
* Tests role-based access control for operations.
|
|
527
|
+
*
|
|
528
|
+
* @example
|
|
529
|
+
* ```typescript
|
|
530
|
+
* const input: PermissionTestInput = {
|
|
531
|
+
* role: 'editor',
|
|
532
|
+
* operation: 'update',
|
|
533
|
+
* resource: 'feature-flag',
|
|
534
|
+
* expected: true
|
|
535
|
+
* };
|
|
536
|
+
* ```
|
|
537
|
+
*/
|
|
473
538
|
export interface PermissionTestInput {
|
|
539
|
+
/** User role to test */
|
|
474
540
|
role: string;
|
|
541
|
+
/** Operation being performed */
|
|
475
542
|
operation: string;
|
|
543
|
+
/** Resource being accessed */
|
|
476
544
|
resource: string;
|
|
545
|
+
/** Expected permission result */
|
|
477
546
|
expected: boolean;
|
|
478
547
|
}
|
|
548
|
+
/**
|
|
549
|
+
* Input for cache operation test cases.
|
|
550
|
+
* Tests caching behavior for feature flags.
|
|
551
|
+
*
|
|
552
|
+
* @template FeatureFlagKey - Type of feature flag keys
|
|
553
|
+
*
|
|
554
|
+
* @example
|
|
555
|
+
* ```typescript
|
|
556
|
+
* const input: CacheOperationTestInput<'apiLimit'> = {
|
|
557
|
+
* operation: 'refresh',
|
|
558
|
+
* flagKey: 'apiLimit',
|
|
559
|
+
* expectedBehavior: 'Cache should be updated with new value'
|
|
560
|
+
* };
|
|
561
|
+
* ```
|
|
562
|
+
*/
|
|
479
563
|
export interface CacheOperationTestInput<FeatureFlagKey extends string> {
|
|
564
|
+
/** Cache operation to perform */
|
|
480
565
|
operation: 'set' | 'get' | 'refresh' | 'clear';
|
|
566
|
+
/** Flag key for operations (optional for 'clear') */
|
|
481
567
|
flagKey?: FeatureFlagKey;
|
|
568
|
+
/** Description of expected cache behavior */
|
|
482
569
|
expectedBehavior: string;
|
|
483
570
|
}
|
|
571
|
+
/**
|
|
572
|
+
* Input for rule evaluation test cases.
|
|
573
|
+
* Tests conditional flag value evaluation based on rules.
|
|
574
|
+
*
|
|
575
|
+
* @template FeatureFlagKey - Type of feature flag keys
|
|
576
|
+
*
|
|
577
|
+
* @example
|
|
578
|
+
* ```typescript
|
|
579
|
+
* const input: RuleEvaluationTestInput<'discount'> = {
|
|
580
|
+
* flagKey: 'discount',
|
|
581
|
+
* rules: [
|
|
582
|
+
* { conditions: { userType: 'premium' }, value: 0.2 },
|
|
583
|
+
* { conditions: { userType: 'regular' }, value: 0.1 }
|
|
584
|
+
* ],
|
|
585
|
+
* context: { userType: 'premium' },
|
|
586
|
+
* expectedValue: 0.2
|
|
587
|
+
* };
|
|
588
|
+
* ```
|
|
589
|
+
*/
|
|
484
590
|
export interface RuleEvaluationTestInput<FeatureFlagKey extends string> {
|
|
591
|
+
/** Flag key to evaluate */
|
|
485
592
|
flagKey: FeatureFlagKey;
|
|
593
|
+
/** Array of rules with conditions and values */
|
|
486
594
|
rules: Array<{
|
|
595
|
+
/** Conditions that must be met */
|
|
487
596
|
conditions: Record<string, unknown>;
|
|
597
|
+
/** Value to return if conditions match */
|
|
488
598
|
value: FeatureFlagValue;
|
|
489
599
|
}>;
|
|
600
|
+
/** Context for rule evaluation */
|
|
490
601
|
context: FeatureFlagContext;
|
|
602
|
+
/** Expected evaluated value */
|
|
491
603
|
expectedValue: FeatureFlagValue;
|
|
492
604
|
}
|
|
605
|
+
/**
|
|
606
|
+
* Input for batch operation test cases.
|
|
607
|
+
* Tests multiple flag operations executed together.
|
|
608
|
+
*
|
|
609
|
+
* @template FeatureFlagKey - Type of feature flag keys
|
|
610
|
+
*
|
|
611
|
+
* @example
|
|
612
|
+
* ```typescript
|
|
613
|
+
* const input: BatchOperationTestInput<AppFeatures> = {
|
|
614
|
+
* operations: [
|
|
615
|
+
* { type: 'create', flagKey: 'feature1', data: { value: true } },
|
|
616
|
+
* { type: 'update', flagKey: 'feature2', data: { isEnabled: false } },
|
|
617
|
+
* { type: 'delete', flagKey: 'feature3' }
|
|
618
|
+
* ],
|
|
619
|
+
* expectedResults: [true, true, true]
|
|
620
|
+
* };
|
|
621
|
+
* ```
|
|
622
|
+
*/
|
|
493
623
|
export interface BatchOperationTestInput<FeatureFlagKey extends string> {
|
|
624
|
+
/** Array of operations to execute in batch */
|
|
494
625
|
operations: Array<{
|
|
626
|
+
/** Type of operation */
|
|
495
627
|
type: 'create' | 'update' | 'delete';
|
|
628
|
+
/** Flag key to operate on */
|
|
496
629
|
flagKey: FeatureFlagKey;
|
|
630
|
+
/** Operation data (for create/update) */
|
|
497
631
|
data?: unknown;
|
|
498
632
|
}>;
|
|
633
|
+
/** Expected results for each operation */
|
|
499
634
|
expectedResults: unknown[];
|
|
500
635
|
}
|
|
636
|
+
/**
|
|
637
|
+
* Input for validation test cases.
|
|
638
|
+
* Tests input validation for various fields.
|
|
639
|
+
*
|
|
640
|
+
* @example
|
|
641
|
+
* ```typescript
|
|
642
|
+
* const input: ValidationTestInput = {
|
|
643
|
+
* input: 'invalid-email',
|
|
644
|
+
* field: 'email',
|
|
645
|
+
* expectedError: 'Invalid email format',
|
|
646
|
+
* isValid: false
|
|
647
|
+
* };
|
|
648
|
+
* ```
|
|
649
|
+
*/
|
|
501
650
|
export interface ValidationTestInput {
|
|
651
|
+
/** Input value to validate */
|
|
502
652
|
input: unknown;
|
|
653
|
+
/** Field name being validated */
|
|
503
654
|
field: string;
|
|
655
|
+
/** Expected error message if validation fails */
|
|
504
656
|
expectedError?: string;
|
|
657
|
+
/** Whether the input should be valid */
|
|
505
658
|
isValid: boolean;
|
|
506
659
|
}
|
|
660
|
+
/**
|
|
661
|
+
* Input for dynamic module configuration test cases.
|
|
662
|
+
* Tests module setup with different configurations.
|
|
663
|
+
*
|
|
664
|
+
* @example
|
|
665
|
+
* ```typescript
|
|
666
|
+
* const input: DynamicModuleTestInput = {
|
|
667
|
+
* config: { apiKey: 'test-key', environment: 'staging' },
|
|
668
|
+
* expectedProviders: ['ConfigService', 'FeatureFlagService'],
|
|
669
|
+
* expectedImports: ['HttpModule'],
|
|
670
|
+
* expectedExports: ['FeatureFlagService']
|
|
671
|
+
* };
|
|
672
|
+
* ```
|
|
673
|
+
*/
|
|
507
674
|
export interface DynamicModuleTestInput {
|
|
675
|
+
/** Module configuration object */
|
|
508
676
|
config: Record<string, unknown>;
|
|
677
|
+
/** Expected providers to be registered */
|
|
509
678
|
expectedProviders?: string[];
|
|
679
|
+
/** Expected modules to be imported */
|
|
510
680
|
expectedImports?: string[];
|
|
681
|
+
/** Expected services to be exported */
|
|
511
682
|
expectedExports?: string[];
|
|
512
683
|
}
|
|
684
|
+
/**
|
|
685
|
+
* Input for async module configuration test cases.
|
|
686
|
+
* Tests asynchronous module setup with factory functions.
|
|
687
|
+
*
|
|
688
|
+
* @example
|
|
689
|
+
* ```typescript
|
|
690
|
+
* const input: AsyncModuleConfigTestInput = {
|
|
691
|
+
* imports: ['ConfigModule'],
|
|
692
|
+
* inject: ['ConfigService'],
|
|
693
|
+
* useFactory: (config) => ({ apiKey: config.get('API_KEY') }),
|
|
694
|
+
* expectedImports: ['ConfigModule', 'HttpModule'],
|
|
695
|
+
* expectedProviders: ['FeatureFlagService']
|
|
696
|
+
* };
|
|
697
|
+
* ```
|
|
698
|
+
*/
|
|
513
699
|
export interface AsyncModuleConfigTestInput {
|
|
700
|
+
/** Modules to import */
|
|
514
701
|
imports?: string[];
|
|
702
|
+
/** Services to inject into factory */
|
|
515
703
|
inject?: string[];
|
|
704
|
+
/** Factory function for configuration */
|
|
516
705
|
useFactory?: (...args: unknown[]) => Record<string, unknown>;
|
|
706
|
+
/** Expected modules in result */
|
|
517
707
|
expectedImports?: string[];
|
|
708
|
+
/** Expected providers in result */
|
|
518
709
|
expectedProviders?: string[];
|
|
519
710
|
}
|
|
711
|
+
/**
|
|
712
|
+
* Input for repository operation test cases.
|
|
713
|
+
* Tests data persistence operations for feature flags.
|
|
714
|
+
*
|
|
715
|
+
* @example
|
|
716
|
+
* ```typescript
|
|
717
|
+
* const input: RepositoryOperationTestInput = {
|
|
718
|
+
* operation: 'create',
|
|
719
|
+
* data: { key: 'newFeature', value: true },
|
|
720
|
+
* expectedResult: { id: 1, key: 'newFeature', value: true },
|
|
721
|
+
* shouldThrow: false
|
|
722
|
+
* };
|
|
723
|
+
* ```
|
|
724
|
+
*/
|
|
520
725
|
export interface RepositoryOperationTestInput {
|
|
726
|
+
/** Repository operation to test */
|
|
521
727
|
operation: 'create' | 'update' | 'delete' | 'find';
|
|
728
|
+
/** Data for the operation */
|
|
522
729
|
data?: unknown;
|
|
730
|
+
/** Expected operation result */
|
|
523
731
|
expectedResult?: unknown;
|
|
732
|
+
/** Whether operation should throw an error */
|
|
524
733
|
shouldThrow?: boolean;
|
|
525
734
|
}
|
package/dist/features/index.d.ts
CHANGED
|
@@ -411,3 +411,164 @@ export interface CreateTableDrivenTestOptions<TInput, TExpected> {
|
|
|
411
411
|
expected?: TExpected;
|
|
412
412
|
}) => void | Promise<void>;
|
|
413
413
|
}
|
|
414
|
+
/**
|
|
415
|
+
* Statistics collected for operation performance tracking.
|
|
416
|
+
* Provides detailed metrics about operation execution.
|
|
417
|
+
*
|
|
418
|
+
* @example
|
|
419
|
+
* ```typescript
|
|
420
|
+
* const stats: OperationStats = {
|
|
421
|
+
* count: 1000,
|
|
422
|
+
* totalDuration: 45000,
|
|
423
|
+
* avgDuration: 45,
|
|
424
|
+
* minDuration: 10,
|
|
425
|
+
* maxDuration: 250,
|
|
426
|
+
* errorCount: 3,
|
|
427
|
+
* errors: [new Error('Timeout'), new Error('Connection failed')]
|
|
428
|
+
* };
|
|
429
|
+
*
|
|
430
|
+
* console.log(`Average operation time: ${stats.avgDuration}ms`);
|
|
431
|
+
* console.log(`Error rate: ${(stats.errorCount / stats.count * 100).toFixed(2)}%`);
|
|
432
|
+
* ```
|
|
433
|
+
*
|
|
434
|
+
* @public
|
|
435
|
+
*/
|
|
436
|
+
export interface OperationStats {
|
|
437
|
+
/** Total number of operations executed */
|
|
438
|
+
count: number;
|
|
439
|
+
/** Total duration of all operations in milliseconds */
|
|
440
|
+
totalDuration: number;
|
|
441
|
+
/** Average duration per operation in milliseconds */
|
|
442
|
+
avgDuration: number;
|
|
443
|
+
/** Minimum operation duration in milliseconds */
|
|
444
|
+
minDuration: number;
|
|
445
|
+
/** Maximum operation duration in milliseconds */
|
|
446
|
+
maxDuration: number;
|
|
447
|
+
/** Number of operations that resulted in errors */
|
|
448
|
+
errorCount: number;
|
|
449
|
+
/** Array of errors encountered during operations */
|
|
450
|
+
errors: Error[];
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Interface for tracking and measuring operation performance.
|
|
454
|
+
* Collects timing and error statistics for named operations.
|
|
455
|
+
*
|
|
456
|
+
* @example
|
|
457
|
+
* ```typescript
|
|
458
|
+
* const tracker: OperationTracker = createOperationTracker();
|
|
459
|
+
*
|
|
460
|
+
* // Track an operation
|
|
461
|
+
* const result = await tracker.track('createUser', async () => {
|
|
462
|
+
* return await userService.create({ name: 'John' });
|
|
463
|
+
* });
|
|
464
|
+
*
|
|
465
|
+
* // Get statistics
|
|
466
|
+
* const stats = tracker.getStats();
|
|
467
|
+
* console.log(stats.createUser.avgDuration);
|
|
468
|
+
*
|
|
469
|
+
* // Reset tracking data
|
|
470
|
+
* tracker.reset();
|
|
471
|
+
* ```
|
|
472
|
+
*
|
|
473
|
+
* @public
|
|
474
|
+
*/
|
|
475
|
+
export interface OperationTracker {
|
|
476
|
+
/** Track execution of a named operation */
|
|
477
|
+
track: <T>(operationName: string, operation: () => Promise<T>) => Promise<T>;
|
|
478
|
+
/** Get statistics for all tracked operations */
|
|
479
|
+
getStats: () => Record<string, OperationStats>;
|
|
480
|
+
/** Reset all tracking data */
|
|
481
|
+
reset: () => void;
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* Test operation for stress testing
|
|
485
|
+
*
|
|
486
|
+
* @public
|
|
487
|
+
*/
|
|
488
|
+
export interface TestOperation {
|
|
489
|
+
name: string;
|
|
490
|
+
operation: () => unknown | Promise<unknown>;
|
|
491
|
+
weight?: number;
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Context information for worker threads in stress testing.
|
|
495
|
+
* Tracks the state and configuration for individual worker processes.
|
|
496
|
+
*
|
|
497
|
+
* @template TestOperation - Type of test operations being executed
|
|
498
|
+
*
|
|
499
|
+
* @example
|
|
500
|
+
* ```typescript
|
|
501
|
+
* const context: WorkerContext<TestOperation> = {
|
|
502
|
+
* workerId: 1,
|
|
503
|
+
* operationsPerWorker: 1000,
|
|
504
|
+
* operations: [createOp, updateOp, deleteOp],
|
|
505
|
+
* totalWeight: 10,
|
|
506
|
+
* operationCounts: { create: 400, update: 400, delete: 200 },
|
|
507
|
+
* errors: [],
|
|
508
|
+
* delayBetweenOps: 50
|
|
509
|
+
* };
|
|
510
|
+
* ```
|
|
511
|
+
*
|
|
512
|
+
* @public
|
|
513
|
+
*/
|
|
514
|
+
export interface WorkerContext<TestOperation> {
|
|
515
|
+
/** Unique identifier for this worker thread */
|
|
516
|
+
workerId: number;
|
|
517
|
+
/** Number of operations this worker should execute */
|
|
518
|
+
operationsPerWorker: number;
|
|
519
|
+
/** Available operations to execute */
|
|
520
|
+
operations: TestOperation[];
|
|
521
|
+
/** Total weight sum for weighted random selection */
|
|
522
|
+
totalWeight: number;
|
|
523
|
+
/** Tracking counts for each operation type */
|
|
524
|
+
operationCounts: Record<string, number>;
|
|
525
|
+
/** Errors encountered during execution */
|
|
526
|
+
errors: Array<{
|
|
527
|
+
operation: string;
|
|
528
|
+
error: Error;
|
|
529
|
+
}>;
|
|
530
|
+
/** Delay in milliseconds between operations */
|
|
531
|
+
delayBetweenOps: number;
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* Resolved configuration for worker-based stress testing.
|
|
535
|
+
* Contains the final computed values after applying defaults.
|
|
536
|
+
*
|
|
537
|
+
* @example
|
|
538
|
+
* ```typescript
|
|
539
|
+
* const config: ResolvedWorkerConfig = {
|
|
540
|
+
* workerCount: 4,
|
|
541
|
+
* operationsPerWorker: 1000,
|
|
542
|
+
* operations: [
|
|
543
|
+
* { name: 'create', operation: createUser, weight: 2 },
|
|
544
|
+
* { name: 'read', operation: readUser, weight: 5 },
|
|
545
|
+
* { name: 'update', operation: updateUser, weight: 2 },
|
|
546
|
+
* { name: 'delete', operation: deleteUser, weight: 1 }
|
|
547
|
+
* ]
|
|
548
|
+
* };
|
|
549
|
+
* ```
|
|
550
|
+
*
|
|
551
|
+
* @public
|
|
552
|
+
*/
|
|
553
|
+
export interface ResolvedWorkerConfig {
|
|
554
|
+
/** Number of worker threads to spawn */
|
|
555
|
+
workerCount: number;
|
|
556
|
+
/** Operations each worker should perform */
|
|
557
|
+
operationsPerWorker: number;
|
|
558
|
+
/** Test operations to be distributed among workers */
|
|
559
|
+
operations: TestOperation[];
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* Stress test configuration
|
|
563
|
+
*
|
|
564
|
+
* @public
|
|
565
|
+
*/
|
|
566
|
+
export interface StressTestConfig {
|
|
567
|
+
workerCount?: number;
|
|
568
|
+
operationsPerWorker?: number;
|
|
569
|
+
delayBetweenOps?: number;
|
|
570
|
+
operations?: TestOperation[];
|
|
571
|
+
operationCount?: number;
|
|
572
|
+
concurrency?: number;
|
|
573
|
+
operationMix?: Record<string, number>;
|
|
574
|
+
}
|
|
@@ -101,14 +101,62 @@ export interface WrapperComponent {
|
|
|
101
101
|
export interface ChildrenProps {
|
|
102
102
|
children: React.ReactNode;
|
|
103
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* Options for configuring test wrapper components.
|
|
106
|
+
* Allows specifying providers and their props for test setup.
|
|
107
|
+
*
|
|
108
|
+
* @example
|
|
109
|
+
* ```typescript
|
|
110
|
+
* const options: WrapperOptions = {
|
|
111
|
+
* providers: [ThemeProvider, AuthProvider],
|
|
112
|
+
* providerProps: {
|
|
113
|
+
* theme: darkTheme,
|
|
114
|
+
* user: mockUser
|
|
115
|
+
* }
|
|
116
|
+
* };
|
|
117
|
+
* ```
|
|
118
|
+
*/
|
|
104
119
|
export interface WrapperOptions {
|
|
120
|
+
/** Array of provider components to wrap tests with */
|
|
105
121
|
providers?: Array<React.ComponentType<{
|
|
106
122
|
children: React.ReactNode;
|
|
107
123
|
}>>;
|
|
124
|
+
/** Props to pass to the providers */
|
|
108
125
|
providerProps?: Record<string, unknown>;
|
|
109
126
|
}
|
|
127
|
+
/**
|
|
128
|
+
* Extended render options combining RTL options with wrapper configuration.
|
|
129
|
+
* Provides a unified interface for rendering components with providers.
|
|
130
|
+
*
|
|
131
|
+
* @example
|
|
132
|
+
* ```typescript
|
|
133
|
+
* const options: GenericRenderOptions = {
|
|
134
|
+
* providers: [QueryClientProvider],
|
|
135
|
+
* providerProps: { client: queryClient },
|
|
136
|
+
* container: document.body
|
|
137
|
+
* };
|
|
138
|
+
*
|
|
139
|
+
* const { getByText } = render(<MyComponent />, options);
|
|
140
|
+
* ```
|
|
141
|
+
*/
|
|
110
142
|
export interface GenericRenderOptions extends Omit<RenderOptions, 'wrapper'>, WrapperOptions {
|
|
111
143
|
}
|
|
144
|
+
/**
|
|
145
|
+
* Extended render hook options combining RTL hook options with wrapper configuration.
|
|
146
|
+
* Provides a unified interface for rendering hooks with providers.
|
|
147
|
+
*
|
|
148
|
+
* @template TProps - Type of props passed to the hook
|
|
149
|
+
*
|
|
150
|
+
* @example
|
|
151
|
+
* ```typescript
|
|
152
|
+
* const options: GenericRenderHookOptions<UseCounterProps> = {
|
|
153
|
+
* providers: [CounterProvider],
|
|
154
|
+
* initialProps: { initialValue: 10 }
|
|
155
|
+
* };
|
|
156
|
+
*
|
|
157
|
+
* const { result } = renderHook(() => useCounter(props), options);
|
|
158
|
+
* ```
|
|
159
|
+
*/
|
|
112
160
|
export interface GenericRenderHookOptions<TProps> extends Omit<RenderHookOptions<TProps>, 'wrapper'>, WrapperOptions {
|
|
113
161
|
}
|
|
114
162
|
/**
|
|
@@ -139,8 +187,25 @@ export interface ErrorBoundaryState {
|
|
|
139
187
|
export type RenderWithRouterResult = TestingLibraryReact.RenderResult & {
|
|
140
188
|
router: MockNextRouter;
|
|
141
189
|
};
|
|
190
|
+
/**
|
|
191
|
+
* Props for error boundary components in testing.
|
|
192
|
+
* Catches errors in child components during rendering.
|
|
193
|
+
*
|
|
194
|
+
* @example
|
|
195
|
+
* ```typescript
|
|
196
|
+
* const props: ErrorBoundaryProps = {
|
|
197
|
+
* children: <ComponentThatMightThrow />,
|
|
198
|
+
* onError: (error, errorInfo) => {
|
|
199
|
+
* console.error('Component error:', error);
|
|
200
|
+
* console.log('Error stack:', errorInfo.componentStack);
|
|
201
|
+
* }
|
|
202
|
+
* };
|
|
203
|
+
* ```
|
|
204
|
+
*/
|
|
142
205
|
export interface ErrorBoundaryProps {
|
|
206
|
+
/** Child components to wrap */
|
|
143
207
|
children: React.ReactNode;
|
|
208
|
+
/** Callback when error is caught */
|
|
144
209
|
onError?: (error: Error, errorInfo: React.ErrorInfo) => void;
|
|
145
210
|
}
|
|
146
211
|
/**
|
|
@@ -173,72 +238,284 @@ export interface MockProviderContextValue<T> {
|
|
|
173
238
|
setValue: (value: T) => void;
|
|
174
239
|
reset: () => void;
|
|
175
240
|
}
|
|
241
|
+
/**
|
|
242
|
+
* Props for mock provider components.
|
|
243
|
+
* Allows setting initial values for testing contexts.
|
|
244
|
+
*
|
|
245
|
+
* @template T - Type of the provided value
|
|
246
|
+
*
|
|
247
|
+
* @example
|
|
248
|
+
* ```typescript
|
|
249
|
+
* const props: MockProviderProps<User> = {
|
|
250
|
+
* children: <UserProfile />,
|
|
251
|
+
* initialValue: { id: 1, name: 'Test User' }
|
|
252
|
+
* };
|
|
253
|
+
* ```
|
|
254
|
+
*/
|
|
176
255
|
export interface MockProviderProps<T> {
|
|
256
|
+
/** Child components to provide context to */
|
|
177
257
|
children: React.ReactNode;
|
|
258
|
+
/** Initial value for the mock context */
|
|
178
259
|
initialValue?: T;
|
|
179
260
|
}
|
|
261
|
+
/**
|
|
262
|
+
* Return type for createMockProvider function.
|
|
263
|
+
* Provides components and hooks for testing with mock contexts.
|
|
264
|
+
*
|
|
265
|
+
* @template T - Type of the provided value
|
|
266
|
+
*
|
|
267
|
+
* @example
|
|
268
|
+
* ```typescript
|
|
269
|
+
* const mockAuth: MockProviderReturn<AuthState> = createMockProvider();
|
|
270
|
+
*
|
|
271
|
+
* // Use in tests
|
|
272
|
+
* render(
|
|
273
|
+
* <mockAuth.Provider initialValue={testAuth}>
|
|
274
|
+
* <AuthenticatedComponent />
|
|
275
|
+
* </mockAuth.Provider>
|
|
276
|
+
* );
|
|
277
|
+
*
|
|
278
|
+
* // Access context in tests
|
|
279
|
+
* const { value, setValue } = mockAuth.useValue();
|
|
280
|
+
* ```
|
|
281
|
+
*/
|
|
180
282
|
export interface MockProviderReturn<T> {
|
|
283
|
+
/** Mock provider component */
|
|
181
284
|
Provider: React.FC<MockProviderProps<T>>;
|
|
285
|
+
/** Hook to access mock context value */
|
|
182
286
|
useValue: () => MockProviderContextValue<T>;
|
|
287
|
+
/** The React context instance */
|
|
183
288
|
Context: React.Context<MockProviderContextValue<T> | null>;
|
|
184
289
|
}
|
|
290
|
+
/**
|
|
291
|
+
* Props for tracked provider components.
|
|
292
|
+
* Monitors render counts and value changes for performance testing.
|
|
293
|
+
*
|
|
294
|
+
* @template T - Type of the provided value
|
|
295
|
+
*
|
|
296
|
+
* @example
|
|
297
|
+
* ```typescript
|
|
298
|
+
* const props: TrackedProviderProps<Config> = {
|
|
299
|
+
* children: <ConfigConsumer />,
|
|
300
|
+
* value: { theme: 'dark', locale: 'en' },
|
|
301
|
+
* onRender: (count) => console.log(`Rendered ${count} times`)
|
|
302
|
+
* };
|
|
303
|
+
* ```
|
|
304
|
+
*/
|
|
185
305
|
export interface TrackedProviderProps<T> {
|
|
306
|
+
/** Child components to track */
|
|
186
307
|
children: React.ReactNode;
|
|
308
|
+
/** Value to provide and track */
|
|
187
309
|
value: T;
|
|
310
|
+
/** Callback on each render with count */
|
|
188
311
|
onRender?: (count: number) => void;
|
|
189
312
|
}
|
|
313
|
+
/**
|
|
314
|
+
* Return type for createTrackedProvider function.
|
|
315
|
+
* Provides tracking capabilities for renders and updates.
|
|
316
|
+
*
|
|
317
|
+
* @template T - Type of the tracked value
|
|
318
|
+
*
|
|
319
|
+
* @example
|
|
320
|
+
* ```typescript
|
|
321
|
+
* const tracker: TrackedProviderReturn<State> = createTrackedProvider();
|
|
322
|
+
*
|
|
323
|
+
* // Check render performance
|
|
324
|
+
* expect(tracker.getRenderCount()).toBeLessThan(3);
|
|
325
|
+
*
|
|
326
|
+
* // Verify update history
|
|
327
|
+
* const history = tracker.getUpdateHistory();
|
|
328
|
+
* expect(history[0].value).toEqual(initialState);
|
|
329
|
+
* ```
|
|
330
|
+
*/
|
|
190
331
|
export interface TrackedProviderReturn<T> {
|
|
332
|
+
/** Tracked provider component */
|
|
191
333
|
Provider: React.FC<TrackedProviderProps<T>>;
|
|
334
|
+
/** Get total render count */
|
|
192
335
|
getRenderCount: () => number;
|
|
336
|
+
/** Get history of value updates */
|
|
193
337
|
getUpdateHistory: () => Array<UpdateHistoryEntry<T>>;
|
|
338
|
+
/** Reset tracking data */
|
|
194
339
|
reset: () => void;
|
|
195
340
|
}
|
|
341
|
+
/**
|
|
342
|
+
* Context value for async data providers.
|
|
343
|
+
* Manages loading states and error handling for async operations.
|
|
344
|
+
*
|
|
345
|
+
* @template T - Type of the async data
|
|
346
|
+
*
|
|
347
|
+
* @example
|
|
348
|
+
* ```typescript
|
|
349
|
+
* const context: AsyncProviderContextValue<User[]> = {
|
|
350
|
+
* data: undefined,
|
|
351
|
+
* loading: true,
|
|
352
|
+
* error: null,
|
|
353
|
+
* refetch: async () => {
|
|
354
|
+
* // Refetch user data
|
|
355
|
+
* }
|
|
356
|
+
* };
|
|
357
|
+
* ```
|
|
358
|
+
*/
|
|
196
359
|
export interface AsyncProviderContextValue<T> {
|
|
360
|
+
/** The loaded data (undefined while loading) */
|
|
197
361
|
data: T | undefined;
|
|
362
|
+
/** Loading state indicator */
|
|
198
363
|
loading: boolean;
|
|
364
|
+
/** Error from failed async operation */
|
|
199
365
|
error: Error | null;
|
|
366
|
+
/** Function to refetch the data */
|
|
200
367
|
refetch: () => Promise<void>;
|
|
201
368
|
}
|
|
369
|
+
/**
|
|
370
|
+
* Props for async provider components.
|
|
371
|
+
* Simple wrapper for components that need async data.
|
|
372
|
+
*
|
|
373
|
+
* @example
|
|
374
|
+
* ```typescript
|
|
375
|
+
* const props: AsyncProviderProps = {
|
|
376
|
+
* children: <DataConsumer />
|
|
377
|
+
* };
|
|
378
|
+
* ```
|
|
379
|
+
*/
|
|
202
380
|
export interface AsyncProviderProps {
|
|
381
|
+
/** Child components that will consume async data */
|
|
203
382
|
children: React.ReactNode;
|
|
204
383
|
}
|
|
384
|
+
/**
|
|
385
|
+
* Return type for createAsyncProvider function.
|
|
386
|
+
* Provides components and hooks for async data management.
|
|
387
|
+
*
|
|
388
|
+
* @template T - Type of the async data
|
|
389
|
+
*
|
|
390
|
+
* @example
|
|
391
|
+
* ```typescript
|
|
392
|
+
* const asyncData: AsyncProviderReturn<Products> = createAsyncProvider();
|
|
393
|
+
*
|
|
394
|
+
* // Use in component
|
|
395
|
+
* const { data, loading, error } = asyncData.useAsyncValue();
|
|
396
|
+
* if (loading) return <Spinner />;
|
|
397
|
+
* if (error) return <ErrorMessage error={error} />;
|
|
398
|
+
* return <ProductList products={data} />;
|
|
399
|
+
* ```
|
|
400
|
+
*/
|
|
205
401
|
export interface AsyncProviderReturn<T> {
|
|
402
|
+
/** Async provider component */
|
|
206
403
|
Provider: React.FC<AsyncProviderProps>;
|
|
404
|
+
/** Hook to access async data state */
|
|
207
405
|
useAsyncValue: () => AsyncProviderContextValue<T>;
|
|
406
|
+
/** The React context instance */
|
|
208
407
|
Context: React.Context<AsyncProviderContextValue<T> | null>;
|
|
209
408
|
}
|
|
409
|
+
/**
|
|
410
|
+
* Props for Suspense wrapper components.
|
|
411
|
+
* Wraps components with React Suspense for async rendering.
|
|
412
|
+
*
|
|
413
|
+
* @example
|
|
414
|
+
* ```typescript
|
|
415
|
+
* const props: SuspenseWrapperProps = {
|
|
416
|
+
* children: <LazyComponent />,
|
|
417
|
+
* fallback: <LoadingSpinner />
|
|
418
|
+
* };
|
|
419
|
+
* ```
|
|
420
|
+
*/
|
|
210
421
|
export interface SuspenseWrapperProps {
|
|
422
|
+
/** Components that might suspend */
|
|
211
423
|
children: React.ReactNode;
|
|
424
|
+
/** Fallback UI while suspended */
|
|
212
425
|
fallback?: React.ReactNode;
|
|
213
426
|
}
|
|
427
|
+
/**
|
|
428
|
+
* Options for creating a comprehensive test wrapper.
|
|
429
|
+
* Combines multiple common testing wrappers in one.
|
|
430
|
+
*
|
|
431
|
+
* @example
|
|
432
|
+
* ```typescript
|
|
433
|
+
* const options: AllProvidersOptions = {
|
|
434
|
+
* includeErrorBoundary: true,
|
|
435
|
+
* includeSuspense: true,
|
|
436
|
+
* includeStrictMode: process.env.NODE_ENV === 'test',
|
|
437
|
+
* errorBoundaryProps: {
|
|
438
|
+
* onError: (error) => console.error('Test error:', error)
|
|
439
|
+
* },
|
|
440
|
+
* suspenseProps: {
|
|
441
|
+
* fallback: <div>Loading...</div>
|
|
442
|
+
* },
|
|
443
|
+
* additionalProviders: [ThemeProvider, I18nProvider]
|
|
444
|
+
* };
|
|
445
|
+
* ```
|
|
446
|
+
*/
|
|
214
447
|
export interface AllProvidersOptions {
|
|
448
|
+
/** Include error boundary wrapper */
|
|
215
449
|
includeErrorBoundary?: boolean;
|
|
450
|
+
/** Include Suspense wrapper */
|
|
216
451
|
includeSuspense?: boolean;
|
|
452
|
+
/** Include React StrictMode */
|
|
217
453
|
includeStrictMode?: boolean;
|
|
454
|
+
/** Props for error boundary */
|
|
218
455
|
errorBoundaryProps?: {
|
|
219
456
|
onError?: (error: Error, errorInfo: React.ErrorInfo) => void;
|
|
220
457
|
};
|
|
458
|
+
/** Props for Suspense wrapper */
|
|
221
459
|
suspenseProps?: {
|
|
222
460
|
fallback?: React.ReactNode;
|
|
223
461
|
};
|
|
462
|
+
/** Additional provider components */
|
|
224
463
|
additionalProviders?: Array<React.ComponentType<{
|
|
225
464
|
children: React.ReactNode;
|
|
226
465
|
}>>;
|
|
227
466
|
}
|
|
467
|
+
/**
|
|
468
|
+
* Generic context provider interface.
|
|
469
|
+
* Represents the structure of React context providers.
|
|
470
|
+
*
|
|
471
|
+
* @template T - Type of the context value
|
|
472
|
+
*
|
|
473
|
+
* @example
|
|
474
|
+
* ```typescript
|
|
475
|
+
* const AuthContext: ContextProvider<AuthState> = {
|
|
476
|
+
* Provider: AuthProvider,
|
|
477
|
+
* Consumer: AuthConsumer,
|
|
478
|
+
* displayName: 'AuthContext'
|
|
479
|
+
* };
|
|
480
|
+
* ```
|
|
481
|
+
*/
|
|
228
482
|
export interface ContextProvider<T = unknown> {
|
|
483
|
+
/** Provider component that supplies the value */
|
|
229
484
|
Provider: React.ComponentType<{
|
|
230
485
|
value: T;
|
|
231
486
|
children: React.ReactNode;
|
|
232
487
|
}>;
|
|
488
|
+
/** Consumer component for render prop pattern */
|
|
233
489
|
Consumer: React.ComponentType<{
|
|
234
490
|
children: (value: T) => React.ReactNode;
|
|
235
491
|
}>;
|
|
492
|
+
/** Optional display name for debugging */
|
|
236
493
|
displayName?: string;
|
|
237
494
|
}
|
|
495
|
+
/**
|
|
496
|
+
* Configuration options for providers in a stack.
|
|
497
|
+
* Defines how providers are organized and their dependencies.
|
|
498
|
+
*
|
|
499
|
+
* @template T - Type of the provider's value
|
|
500
|
+
*
|
|
501
|
+
* @example
|
|
502
|
+
* ```typescript
|
|
503
|
+
* const config: ProviderConfigOptions<ThemeConfig> = {
|
|
504
|
+
* provider: ThemeContext,
|
|
505
|
+
* value: { mode: 'dark', colors: darkColors },
|
|
506
|
+
* name: 'theme',
|
|
507
|
+
* dependencies: ['i18n'] // Depends on i18n provider
|
|
508
|
+
* };
|
|
509
|
+
* ```
|
|
510
|
+
*/
|
|
238
511
|
export interface ProviderConfigOptions<T = unknown> {
|
|
512
|
+
/** The context provider to configure */
|
|
239
513
|
provider: ContextProvider<T>;
|
|
514
|
+
/** Value to provide through the context */
|
|
240
515
|
value: T;
|
|
516
|
+
/** Optional name for identification */
|
|
241
517
|
name?: string;
|
|
518
|
+
/** Names of providers this depends on */
|
|
242
519
|
dependencies?: string[];
|
|
243
520
|
}
|
|
244
521
|
/**
|
|
@@ -296,78 +573,394 @@ export interface WrapperComposition {
|
|
|
296
573
|
clear: () => WrapperComposition;
|
|
297
574
|
clone: () => WrapperComposition;
|
|
298
575
|
}
|
|
576
|
+
/**
|
|
577
|
+
* Options for creating mock providers.
|
|
578
|
+
* Configures initial state and change handlers.
|
|
579
|
+
*
|
|
580
|
+
* @template T - Type of the mock value
|
|
581
|
+
*
|
|
582
|
+
* @example
|
|
583
|
+
* ```typescript
|
|
584
|
+
* const options: MockProviderOptions<User> = {
|
|
585
|
+
* defaultValue: { id: 1, name: 'John' },
|
|
586
|
+
* onValueChange: (newUser) => {
|
|
587
|
+
* console.log('User changed:', newUser);
|
|
588
|
+
* }
|
|
589
|
+
* };
|
|
590
|
+
* ```
|
|
591
|
+
*/
|
|
299
592
|
export interface MockProviderOptions<T> {
|
|
593
|
+
/** Default value for the mock provider */
|
|
300
594
|
defaultValue: T;
|
|
595
|
+
/** Callback when value changes */
|
|
301
596
|
onValueChange?: (newValue: T) => void;
|
|
302
597
|
}
|
|
598
|
+
/**
|
|
599
|
+
* Options for tracked providers.
|
|
600
|
+
* Configures render tracking and logging.
|
|
601
|
+
*
|
|
602
|
+
* @example
|
|
603
|
+
* ```typescript
|
|
604
|
+
* const options: TrackedProviderOptions = {
|
|
605
|
+
* name: 'UserProvider',
|
|
606
|
+
* logRenders: true // Log each render to console
|
|
607
|
+
* };
|
|
608
|
+
* ```
|
|
609
|
+
*/
|
|
303
610
|
export interface TrackedProviderOptions {
|
|
611
|
+
/** Name for identification in logs */
|
|
304
612
|
name: string;
|
|
613
|
+
/** Whether to log renders to console */
|
|
305
614
|
logRenders?: boolean;
|
|
306
615
|
}
|
|
616
|
+
/**
|
|
617
|
+
* Options for async data providers.
|
|
618
|
+
* Configures data loading and error handling.
|
|
619
|
+
*
|
|
620
|
+
* @template T - Type of the async data
|
|
621
|
+
*
|
|
622
|
+
* @example
|
|
623
|
+
* ```typescript
|
|
624
|
+
* const options: AsyncProviderOptions<Products[]> = {
|
|
625
|
+
* loadData: () => fetchProducts(),
|
|
626
|
+
* fallback: [],
|
|
627
|
+
* onError: (error) => console.error('Failed to load:', error)
|
|
628
|
+
* };
|
|
629
|
+
* ```
|
|
630
|
+
*/
|
|
307
631
|
export interface AsyncProviderOptions<T> {
|
|
632
|
+
/** Function to load async data */
|
|
308
633
|
loadData: () => Promise<T>;
|
|
634
|
+
/** Fallback value while loading */
|
|
309
635
|
fallback?: T;
|
|
636
|
+
/** Error handler callback */
|
|
310
637
|
onError?: (error: Error) => void;
|
|
311
638
|
}
|
|
639
|
+
/**
|
|
640
|
+
* Options for subscribable providers.
|
|
641
|
+
* Configures providers that support subscription patterns.
|
|
642
|
+
*
|
|
643
|
+
* @template T - Type of the subscribable value
|
|
644
|
+
*
|
|
645
|
+
* @example
|
|
646
|
+
* ```typescript
|
|
647
|
+
* const options: SubscribableProviderOptions<Settings> = {
|
|
648
|
+
* initialValue: { theme: 'light', lang: 'en' },
|
|
649
|
+
* name: 'SettingsProvider'
|
|
650
|
+
* };
|
|
651
|
+
* ```
|
|
652
|
+
*/
|
|
312
653
|
export interface SubscribableProviderOptions<T> {
|
|
654
|
+
/** Initial value for subscribers */
|
|
313
655
|
initialValue: T;
|
|
656
|
+
/** Provider name for debugging */
|
|
314
657
|
name: string;
|
|
315
658
|
}
|
|
659
|
+
/**
|
|
660
|
+
* Options for time-travel providers.
|
|
661
|
+
* Configures state history and undo/redo functionality.
|
|
662
|
+
*
|
|
663
|
+
* @template T - Type of the state to track
|
|
664
|
+
*
|
|
665
|
+
* @example
|
|
666
|
+
* ```typescript
|
|
667
|
+
* const options: TimeTravelProviderOptions<EditorState> = {
|
|
668
|
+
* initialState: { content: '', cursor: 0 },
|
|
669
|
+
* maxHistory: 50, // Keep last 50 states
|
|
670
|
+
* name: 'EditorHistory'
|
|
671
|
+
* };
|
|
672
|
+
* ```
|
|
673
|
+
*/
|
|
316
674
|
export interface TimeTravelProviderOptions<T> {
|
|
675
|
+
/** Initial state value */
|
|
317
676
|
initialState: T;
|
|
677
|
+
/** Maximum history entries to keep */
|
|
318
678
|
maxHistory?: number;
|
|
679
|
+
/** Provider name for debugging */
|
|
319
680
|
name: string;
|
|
320
681
|
}
|
|
682
|
+
/**
|
|
683
|
+
* Options for mock API providers.
|
|
684
|
+
* Configures endpoints and response behaviors.
|
|
685
|
+
*
|
|
686
|
+
* @example
|
|
687
|
+
* ```typescript
|
|
688
|
+
* const options: MockApiProviderOptions = {
|
|
689
|
+
* endpoints: {
|
|
690
|
+
* getUser: () => ({ id: 1, name: 'John' }),
|
|
691
|
+
* getPosts: async () => [
|
|
692
|
+
* { id: 1, title: 'First Post' }
|
|
693
|
+
* ]
|
|
694
|
+
* },
|
|
695
|
+
* delay: 100, // Simulate network delay
|
|
696
|
+
* name: 'MockAPI'
|
|
697
|
+
* };
|
|
698
|
+
* ```
|
|
699
|
+
*/
|
|
321
700
|
export interface MockApiProviderOptions {
|
|
701
|
+
/** Mock endpoint implementations */
|
|
322
702
|
endpoints: Record<string, () => Promise<unknown> | unknown>;
|
|
703
|
+
/** Artificial delay in ms */
|
|
323
704
|
delay?: number;
|
|
705
|
+
/** Provider name for debugging */
|
|
324
706
|
name: string;
|
|
325
707
|
}
|
|
708
|
+
/**
|
|
709
|
+
* Entry in the update history for tracked providers.
|
|
710
|
+
* Records state changes over time.
|
|
711
|
+
*
|
|
712
|
+
* @template T - Type of the tracked state
|
|
713
|
+
*
|
|
714
|
+
* @example
|
|
715
|
+
* ```typescript
|
|
716
|
+
* const entry: UpdateHistoryEntry<UserProfile> = {
|
|
717
|
+
* timestamp: Date.now(),
|
|
718
|
+
* updates: { name: 'Jane', email: 'jane@example.com' }
|
|
719
|
+
* };
|
|
720
|
+
* ```
|
|
721
|
+
*/
|
|
326
722
|
export interface UpdateHistoryEntry<T> {
|
|
723
|
+
/** When the update occurred */
|
|
327
724
|
timestamp: number;
|
|
725
|
+
/** Partial state updates applied */
|
|
328
726
|
updates: Partial<T>;
|
|
329
727
|
}
|
|
728
|
+
/**
|
|
729
|
+
* Item in a provider chain.
|
|
730
|
+
* Represents a single provider with its props.
|
|
731
|
+
*
|
|
732
|
+
* @example
|
|
733
|
+
* ```typescript
|
|
734
|
+
* const chainItem: ProviderChainItem = {
|
|
735
|
+
* provider: ThemeProvider,
|
|
736
|
+
* props: { theme: darkTheme }
|
|
737
|
+
* };
|
|
738
|
+
* ```
|
|
739
|
+
*/
|
|
330
740
|
export interface ProviderChainItem {
|
|
741
|
+
/** Provider component */
|
|
331
742
|
provider: React.ComponentType<{
|
|
332
743
|
children: React.ReactNode;
|
|
333
744
|
}>;
|
|
745
|
+
/** Props to pass to provider */
|
|
334
746
|
props?: Record<string, unknown>;
|
|
335
747
|
}
|
|
748
|
+
/**
|
|
749
|
+
* Context value for subscribable providers.
|
|
750
|
+
* Implements pub/sub pattern for state updates.
|
|
751
|
+
*
|
|
752
|
+
* @template T - Type of the subscribable value
|
|
753
|
+
*
|
|
754
|
+
* @example
|
|
755
|
+
* ```typescript
|
|
756
|
+
* const context: SubscribableContextValue<Config> = {
|
|
757
|
+
* value: currentConfig,
|
|
758
|
+
* subscribe: (callback) => {
|
|
759
|
+
* subscribers.add(callback);
|
|
760
|
+
* return () => subscribers.delete(callback);
|
|
761
|
+
* },
|
|
762
|
+
* update: (newConfig) => {
|
|
763
|
+
* currentConfig = newConfig;
|
|
764
|
+
* subscribers.forEach(cb => cb(newConfig));
|
|
765
|
+
* }
|
|
766
|
+
* };
|
|
767
|
+
* ```
|
|
768
|
+
*/
|
|
336
769
|
export interface SubscribableContextValue<T> {
|
|
770
|
+
/** Current value */
|
|
337
771
|
value: T;
|
|
772
|
+
/** Subscribe to value changes */
|
|
338
773
|
subscribe: (callback: (value: T) => void) => () => void;
|
|
774
|
+
/** Update the value and notify subscribers */
|
|
339
775
|
update: (value: T) => void;
|
|
340
776
|
}
|
|
777
|
+
/**
|
|
778
|
+
* Return type for createSubscribableProvider.
|
|
779
|
+
* Provides subscription-based state management.
|
|
780
|
+
*
|
|
781
|
+
* @template T - Type of the subscribable value
|
|
782
|
+
*
|
|
783
|
+
* @example
|
|
784
|
+
* ```typescript
|
|
785
|
+
* const store: SubscribableProviderReturn<AppState> = createSubscribableProvider();
|
|
786
|
+
*
|
|
787
|
+
* // Check subscriber count
|
|
788
|
+
* expect(store.getSubscriberCount()).toBe(3);
|
|
789
|
+
*
|
|
790
|
+
* // Clear all subscriptions
|
|
791
|
+
* store.clearSubscribers();
|
|
792
|
+
* ```
|
|
793
|
+
*/
|
|
341
794
|
export interface SubscribableProviderReturn<T> {
|
|
795
|
+
/** Subscribable provider component */
|
|
342
796
|
Provider: React.FC<{
|
|
343
797
|
children: React.ReactNode;
|
|
344
798
|
}>;
|
|
799
|
+
/** Hook to access subscribable context */
|
|
345
800
|
useSubscribable: () => SubscribableContextValue<T>;
|
|
801
|
+
/** Get current subscriber count */
|
|
346
802
|
getSubscriberCount: () => number;
|
|
803
|
+
/** Clear all active subscriptions */
|
|
347
804
|
clearSubscribers: () => void;
|
|
348
805
|
}
|
|
806
|
+
/**
|
|
807
|
+
* Callback function type for subscribing to value changes.
|
|
808
|
+
* Invoked whenever the subscribed value is updated.
|
|
809
|
+
*
|
|
810
|
+
* @template T - Type of the value being subscribed to
|
|
811
|
+
*
|
|
812
|
+
* @param value - The new value after an update
|
|
813
|
+
*
|
|
814
|
+
* @example
|
|
815
|
+
* ```typescript
|
|
816
|
+
* // Subscribe to user state changes
|
|
817
|
+
* const userSubscriber: Subscriber<User> = (user) => {
|
|
818
|
+
* console.log('User updated:', user);
|
|
819
|
+
* updateUI(user);
|
|
820
|
+
* };
|
|
821
|
+
*
|
|
822
|
+
* // Subscribe to theme changes
|
|
823
|
+
* const themeSubscriber: Subscriber<Theme> = (theme) => {
|
|
824
|
+
* document.body.className = theme.mode;
|
|
825
|
+
* };
|
|
826
|
+
* ```
|
|
827
|
+
*/
|
|
349
828
|
export type Subscriber<T> = (value: T) => void;
|
|
829
|
+
/**
|
|
830
|
+
* Context value for time-travel providers.
|
|
831
|
+
* Provides undo/redo functionality with state history management.
|
|
832
|
+
*
|
|
833
|
+
* @template T - Type of the state being tracked
|
|
834
|
+
*
|
|
835
|
+
* @example
|
|
836
|
+
* ```typescript
|
|
837
|
+
* const context: TimeTravelContextValue<EditorState> = {
|
|
838
|
+
* state: { content: 'Hello', cursor: 5 },
|
|
839
|
+
* canUndo: true,
|
|
840
|
+
* canRedo: false,
|
|
841
|
+
* undo: () => {
|
|
842
|
+
* // Restore previous state
|
|
843
|
+
* },
|
|
844
|
+
* redo: () => {
|
|
845
|
+
* // Restore next state
|
|
846
|
+
* },
|
|
847
|
+
* update: (newState) => {
|
|
848
|
+
* // Add to history and update current
|
|
849
|
+
* },
|
|
850
|
+
* reset: () => {
|
|
851
|
+
* // Clear all history
|
|
852
|
+
* },
|
|
853
|
+
* getHistory: () => [
|
|
854
|
+
* { content: '', cursor: 0 },
|
|
855
|
+
* { content: 'Hello', cursor: 5 }
|
|
856
|
+
* ]
|
|
857
|
+
* };
|
|
858
|
+
* ```
|
|
859
|
+
*/
|
|
350
860
|
export interface TimeTravelContextValue<T> {
|
|
861
|
+
/** Current state value */
|
|
351
862
|
state: T;
|
|
863
|
+
/** Whether undo operation is available */
|
|
352
864
|
canUndo: boolean;
|
|
865
|
+
/** Whether redo operation is available */
|
|
353
866
|
canRedo: boolean;
|
|
867
|
+
/** Restore the previous state from history */
|
|
354
868
|
undo: () => void;
|
|
869
|
+
/** Restore the next state from history */
|
|
355
870
|
redo: () => void;
|
|
871
|
+
/** Update state and add to history */
|
|
356
872
|
update: (newState: T) => void;
|
|
873
|
+
/** Clear all history and reset to initial state */
|
|
357
874
|
reset: () => void;
|
|
875
|
+
/** Get the complete state history */
|
|
358
876
|
getHistory: () => T[];
|
|
359
877
|
}
|
|
878
|
+
/**
|
|
879
|
+
* Return type for createTimeTravelProvider.
|
|
880
|
+
* Provides components and hooks for time-travel state management.
|
|
881
|
+
*
|
|
882
|
+
* @template T - Type of the state with history tracking
|
|
883
|
+
*
|
|
884
|
+
* @example
|
|
885
|
+
* ```typescript
|
|
886
|
+
* const editor: TimeTravelProviderReturn<EditorState> = createTimeTravelProvider();
|
|
887
|
+
*
|
|
888
|
+
* // Use in component
|
|
889
|
+
* function Editor() {
|
|
890
|
+
* const { state, canUndo, undo, update } = editor.useTimeTravel();
|
|
891
|
+
*
|
|
892
|
+
* return (
|
|
893
|
+
* <div>
|
|
894
|
+
* <button disabled={!canUndo} onClick={undo}>Undo</button>
|
|
895
|
+
* <textarea
|
|
896
|
+
* value={state.content}
|
|
897
|
+
* onChange={(e) => update({ ...state, content: e.target.value })}
|
|
898
|
+
* />
|
|
899
|
+
* </div>
|
|
900
|
+
* );
|
|
901
|
+
* }
|
|
902
|
+
*
|
|
903
|
+
* // Wrap with provider
|
|
904
|
+
* <editor.Provider>
|
|
905
|
+
* <Editor />
|
|
906
|
+
* </editor.Provider>
|
|
907
|
+
* ```
|
|
908
|
+
*/
|
|
360
909
|
export interface TimeTravelProviderReturn<T> {
|
|
910
|
+
/** Time-travel provider component */
|
|
361
911
|
Provider: React.FC<{
|
|
362
912
|
children: React.ReactNode;
|
|
363
913
|
}>;
|
|
914
|
+
/** Hook to access time-travel functionality */
|
|
364
915
|
useTimeTravel: () => TimeTravelContextValue<T>;
|
|
365
916
|
}
|
|
917
|
+
/**
|
|
918
|
+
* Return type for createMockApiProvider.
|
|
919
|
+
* Provides mock API endpoints for testing API interactions.
|
|
920
|
+
*
|
|
921
|
+
* @template T - Type of the API interface as a record of endpoint methods
|
|
922
|
+
*
|
|
923
|
+
* @example
|
|
924
|
+
* ```typescript
|
|
925
|
+
* interface UserAPI {
|
|
926
|
+
* getUser: (id: number) => Promise<User>;
|
|
927
|
+
* createUser: (data: CreateUserData) => Promise<User>;
|
|
928
|
+
* updateUser: (id: number, data: UpdateUserData) => Promise<User>;
|
|
929
|
+
* deleteUser: (id: number) => Promise<void>;
|
|
930
|
+
* }
|
|
931
|
+
*
|
|
932
|
+
* const api: MockApiProviderReturn<UserAPI> = createMockApiProvider();
|
|
933
|
+
*
|
|
934
|
+
* // Configure mock responses
|
|
935
|
+
* api.mockApi.getUser = vi.fn().mockResolvedValue({ id: 1, name: 'John' });
|
|
936
|
+
*
|
|
937
|
+
* // Use in component
|
|
938
|
+
* function UserProfile() {
|
|
939
|
+
* const { getUser } = api.useApi();
|
|
940
|
+
* const [user, setUser] = useState<User>();
|
|
941
|
+
*
|
|
942
|
+
* useEffect(() => {
|
|
943
|
+
* getUser(1).then(setUser);
|
|
944
|
+
* }, []);
|
|
945
|
+
*
|
|
946
|
+
* return <div>{user?.name}</div>;
|
|
947
|
+
* }
|
|
948
|
+
*
|
|
949
|
+
* // Reset mocks between tests
|
|
950
|
+
* afterEach(() => {
|
|
951
|
+
* api.resetMocks();
|
|
952
|
+
* });
|
|
953
|
+
* ```
|
|
954
|
+
*/
|
|
366
955
|
export interface MockApiProviderReturn<T extends Record<string, unknown>> {
|
|
956
|
+
/** Mock API provider component */
|
|
367
957
|
Provider: React.FC<{
|
|
368
958
|
children: React.ReactNode;
|
|
369
959
|
}>;
|
|
960
|
+
/** Hook to access mock API methods */
|
|
370
961
|
useApi: () => T;
|
|
962
|
+
/** Direct access to mock API implementations for configuration */
|
|
371
963
|
mockApi: T;
|
|
964
|
+
/** Reset all mock implementations to their default state */
|
|
372
965
|
resetMocks: () => void;
|
|
373
966
|
}
|
|
@@ -1291,33 +1291,81 @@ export interface FeatureFlagFileData<FeatureFlagKey extends string> {
|
|
|
1291
1291
|
*
|
|
1292
1292
|
* @public
|
|
1293
1293
|
*/
|
|
1294
|
-
export interface
|
|
1294
|
+
export interface FileTestContext {
|
|
1295
1295
|
testDir: string;
|
|
1296
1296
|
jsonFile: string;
|
|
1297
1297
|
yamlFile: string;
|
|
1298
1298
|
cleanup: () => Promise<void>;
|
|
1299
1299
|
}
|
|
1300
1300
|
/**
|
|
1301
|
-
* Provider
|
|
1301
|
+
* Provider interface that supports dynamic rule addition.
|
|
1302
|
+
* Used for testing providers that allow runtime rule configuration.
|
|
1303
|
+
*
|
|
1304
|
+
* @template FeatureFlagKey - Type of feature flag keys
|
|
1305
|
+
*
|
|
1306
|
+
* @example
|
|
1307
|
+
* ```typescript
|
|
1308
|
+
* const provider: ProviderWithRules<MyFlags> = createTestProvider();
|
|
1309
|
+
*
|
|
1310
|
+
* // Add a targeting rule
|
|
1311
|
+
* provider.addRule({
|
|
1312
|
+
* id: 'premium-users',
|
|
1313
|
+
* flagKey: 'premium-feature',
|
|
1314
|
+
* name: 'Target premium users',
|
|
1315
|
+
* conditions: [{ field: 'userRole', operator: 'equals', value: 'premium' }],
|
|
1316
|
+
* value: true,
|
|
1317
|
+
* priority: 1,
|
|
1318
|
+
* isEnabled: true
|
|
1319
|
+
* });
|
|
1320
|
+
*
|
|
1321
|
+
* // Evaluate flag with rules
|
|
1322
|
+
* const evaluation = await provider.getFlag('premium-feature');
|
|
1323
|
+
* ```
|
|
1302
1324
|
*
|
|
1303
1325
|
* @public
|
|
1304
1326
|
*/
|
|
1305
|
-
export interface
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1327
|
+
export interface ProviderWithRules<FeatureFlagKey extends string> {
|
|
1328
|
+
/** Add a new rule to the provider */
|
|
1329
|
+
addRule: (rule: FeatureFlagRule<FeatureFlagKey>) => void;
|
|
1330
|
+
/** Evaluate a flag considering all rules */
|
|
1331
|
+
getFlag: (key: FeatureFlagKey) => Promise<FeatureFlagEvaluation<FeatureFlagKey>>;
|
|
1309
1332
|
}
|
|
1310
1333
|
/**
|
|
1311
|
-
*
|
|
1334
|
+
* Test scenario interface for file-based feature flag providers.
|
|
1335
|
+
* Extends FileTestContext to provide file-specific testing utilities.
|
|
1336
|
+
*
|
|
1337
|
+
* @template FeatureFlagKey - Type of feature flag keys
|
|
1338
|
+
*
|
|
1339
|
+
* @example
|
|
1340
|
+
* ```typescript
|
|
1341
|
+
* const scenario: FileProviderTestScenario<'feature1' | 'feature2'> = {
|
|
1342
|
+
* testDir: '/tmp/test-flags',
|
|
1343
|
+
* jsonFile: '/tmp/test-flags/flags.json',
|
|
1344
|
+
* yamlFile: '/tmp/test-flags/flags.yaml',
|
|
1345
|
+
* filePath: '/tmp/test-flags/flags.json',
|
|
1346
|
+
* format: 'json',
|
|
1347
|
+
* cleanup: async () => {
|
|
1348
|
+
* await fs.rmdir(testDir, { recursive: true });
|
|
1349
|
+
* },
|
|
1350
|
+
* updateFile: async (updates) => {
|
|
1351
|
+
* const current = await readFile(filePath);
|
|
1352
|
+
* await writeFile(filePath, { ...current, ...updates });
|
|
1353
|
+
* },
|
|
1354
|
+
* waitForFileChange: async (ms = 100) => {
|
|
1355
|
+
* await new Promise(resolve => setTimeout(resolve, ms));
|
|
1356
|
+
* }
|
|
1357
|
+
* };
|
|
1358
|
+
* ```
|
|
1312
1359
|
*
|
|
1313
1360
|
* @public
|
|
1314
1361
|
*/
|
|
1315
|
-
export interface
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1362
|
+
export interface FileProviderTestScenario<FeatureFlagKey extends string> extends FileTestContext {
|
|
1363
|
+
/** Path to the active test file */
|
|
1364
|
+
filePath: string;
|
|
1365
|
+
/** Format of the feature flag file */
|
|
1366
|
+
format: 'json' | 'yaml';
|
|
1367
|
+
/** Update flag values in the test file */
|
|
1368
|
+
updateFile: (updates: Record<FeatureFlagKey, FeatureFlagValue>) => Promise<void>;
|
|
1369
|
+
/** Wait for file system changes to propagate */
|
|
1370
|
+
waitForFileChange: (ms?: number) => Promise<void>;
|
|
1323
1371
|
}
|
package/package.json
CHANGED