aigroup-workflow 2.2.1 → 2.2.2
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/.claude/commands/fix-build.md +10 -5
- package/.claude/commands/init-project.md +13 -8
- package/.claude/commands/plan.md +15 -8
- package/.claude/commands/review.md +12 -6
- package/.claude/commands/tdd.md +11 -5
- package/.claude/commands/workflow-start.md +20 -11
- package/.claude/settings.json +28 -0
- package/.codex/agents/architect.toml +207 -0
- package/.codex/agents/build-error-resolver.toml +110 -0
- package/.codex/agents/code-reviewer.toml +233 -0
- package/.codex/agents/doc-updater.toml +103 -0
- package/.codex/agents/e2e-runner.toml +103 -0
- package/.codex/agents/get-current-datetime.toml +23 -0
- package/.codex/agents/init-architect.toml +181 -0
- package/.codex/agents/planner.toml +208 -0
- package/.codex/agents/refactor-cleaner.toml +81 -0
- package/.codex/agents/rust-reviewer.toml +90 -0
- package/.codex/agents/security-reviewer.toml +104 -0
- package/.codex/agents/tdd-guide.toml +87 -0
- package/AGENTS.md +2 -2
- package/CLAUDE.md +23 -1
- package/LICENSE +20 -20
- package/README.md +333 -333
- package/agents/a11y-architect.md +141 -141
- package/agents/architect.md +211 -211
- package/agents/build-error-resolver.md +114 -114
- package/agents/chief-of-staff.md +151 -151
- package/agents/code-architect.md +71 -71
- package/agents/code-explorer.md +69 -69
- package/agents/code-reviewer.md +237 -237
- package/agents/code-simplifier.md +47 -47
- package/agents/comment-analyzer.md +45 -45
- package/agents/conversation-analyzer.md +52 -52
- package/agents/cpp-build-resolver.md +90 -90
- package/agents/cpp-reviewer.md +72 -72
- package/agents/csharp-reviewer.md +101 -101
- package/agents/dart-build-resolver.md +201 -201
- package/agents/database-reviewer.md +91 -91
- package/agents/doc-updater.md +107 -107
- package/agents/docs-lookup.md +68 -68
- package/agents/e2e-runner.md +107 -107
- package/agents/flutter-reviewer.md +243 -243
- package/agents/gan-evaluator.md +209 -209
- package/agents/gan-generator.md +131 -131
- package/agents/gan-planner.md +99 -99
- package/agents/get-current-datetime.md +26 -26
- package/agents/go-build-resolver.md +94 -94
- package/agents/go-reviewer.md +76 -76
- package/agents/harness-optimizer.md +35 -35
- package/agents/healthcare-reviewer.md +83 -83
- package/agents/java-build-resolver.md +153 -153
- package/agents/java-reviewer.md +92 -92
- package/agents/kotlin-build-resolver.md +118 -118
- package/agents/kotlin-reviewer.md +159 -159
- package/agents/loop-operator.md +36 -36
- package/agents/opensource-forker.md +198 -198
- package/agents/opensource-packager.md +249 -249
- package/agents/opensource-sanitizer.md +188 -188
- package/agents/performance-optimizer.md +446 -446
- package/agents/planner.md +212 -212
- package/agents/pr-test-analyzer.md +45 -45
- package/agents/python-reviewer.md +98 -98
- package/agents/pytorch-build-resolver.md +120 -120
- package/agents/refactor-cleaner.md +85 -85
- package/agents/rust-build-resolver.md +148 -148
- package/agents/rust-reviewer.md +94 -94
- package/agents/security-reviewer.md +108 -108
- package/agents/seo-specialist.md +59 -59
- package/agents/silent-failure-hunter.md +50 -50
- package/agents/tdd-guide.md +91 -91
- package/agents/type-design-analyzer.md +41 -41
- package/agents/typescript-reviewer.md +112 -112
- package/cli/commands/update.mjs +1 -1
- package/cli/utils/scaffold.mjs +53 -0
- package/docs/rules/agents.md +166 -50
- package/docs/rules/cpp/coding-style.md +44 -44
- package/docs/rules/cpp/hooks.md +39 -39
- package/docs/rules/cpp/patterns.md +51 -51
- package/docs/rules/cpp/security.md +51 -51
- package/docs/rules/cpp/testing.md +44 -44
- package/docs/rules/csharp/coding-style.md +72 -72
- package/docs/rules/csharp/hooks.md +25 -25
- package/docs/rules/csharp/patterns.md +50 -50
- package/docs/rules/csharp/security.md +58 -58
- package/docs/rules/csharp/testing.md +46 -46
- package/docs/rules/dart/coding-style.md +159 -159
- package/docs/rules/dart/hooks.md +66 -66
- package/docs/rules/dart/patterns.md +261 -261
- package/docs/rules/dart/security.md +135 -135
- package/docs/rules/dart/testing.md +215 -215
- package/docs/rules/golang/coding-style.md +32 -32
- package/docs/rules/golang/hooks.md +17 -17
- package/docs/rules/golang/patterns.md +45 -45
- package/docs/rules/golang/security.md +34 -34
- package/docs/rules/golang/testing.md +31 -31
- package/docs/rules/java/coding-style.md +114 -114
- package/docs/rules/java/hooks.md +18 -18
- package/docs/rules/java/patterns.md +146 -146
- package/docs/rules/java/security.md +100 -100
- package/docs/rules/java/testing.md +131 -131
- package/docs/rules/kotlin/coding-style.md +86 -86
- package/docs/rules/kotlin/hooks.md +17 -17
- package/docs/rules/kotlin/patterns.md +146 -146
- package/docs/rules/kotlin/security.md +82 -82
- package/docs/rules/kotlin/testing.md +128 -128
- package/docs/rules/perl/coding-style.md +46 -46
- package/docs/rules/perl/hooks.md +22 -22
- package/docs/rules/perl/patterns.md +76 -76
- package/docs/rules/perl/security.md +69 -69
- package/docs/rules/perl/testing.md +54 -54
- package/docs/rules/php/coding-style.md +40 -40
- package/docs/rules/php/hooks.md +24 -24
- package/docs/rules/php/patterns.md +33 -33
- package/docs/rules/php/security.md +37 -37
- package/docs/rules/php/testing.md +39 -39
- package/docs/rules/python/coding-style.md +42 -42
- package/docs/rules/python/hooks.md +19 -19
- package/docs/rules/python/patterns.md +39 -39
- package/docs/rules/python/security.md +30 -30
- package/docs/rules/python/testing.md +38 -38
- package/docs/rules/rust/coding-style.md +151 -151
- package/docs/rules/rust/hooks.md +16 -16
- package/docs/rules/rust/patterns.md +168 -168
- package/docs/rules/rust/security.md +141 -141
- package/docs/rules/rust/testing.md +154 -154
- package/docs/rules/swift/coding-style.md +47 -47
- package/docs/rules/swift/hooks.md +20 -20
- package/docs/rules/swift/patterns.md +66 -66
- package/docs/rules/swift/security.md +33 -33
- package/docs/rules/swift/testing.md +45 -45
- package/docs/rules/typescript/coding-style.md +199 -199
- package/docs/rules/typescript/hooks.md +22 -22
- package/docs/rules/typescript/patterns.md +52 -52
- package/docs/rules/typescript/security.md +28 -28
- package/docs/rules/typescript/testing.md +18 -18
- package/docs/rules/web/coding-style.md +96 -96
- package/docs/rules/web/design-quality.md +62 -62
- package/docs/rules/web/hooks.md +120 -120
- package/docs/rules/web/patterns.md +79 -79
- package/docs/rules/web/performance.md +64 -64
- package/docs/rules/web/security.md +57 -57
- package/docs/rules/web/testing.md +55 -55
- package/docs/templates/README.md +36 -36
- package/docs/templates/ai-project-final.md +124 -124
- package/docs/templates/ai-project.md +105 -105
- package/docs/templates/api.md +157 -157
- package/docs/templates/bug.md +62 -62
- package/docs/templates/code-review.md +87 -87
- package/docs/templates/generic.md +116 -116
- package/docs/templates/implementation-plan.md +1 -1
- package/docs/templates/meeting.md +68 -68
- package/docs/templates/prd.md +98 -98
- package/docs/templates/ui.md +134 -134
- package/docs/workflow-pipeline.md +5 -5
- package/package.json +40 -39
- package/skills/SUPERPOWERS-LICENSE +21 -21
- package/skills/ai-ml/fine-tuning-expert/SKILL.md +162 -162
- package/skills/ai-ml/fine-tuning-expert/references/dataset-preparation.md +540 -540
- package/skills/ai-ml/fine-tuning-expert/references/deployment-optimization.md +673 -673
- package/skills/ai-ml/fine-tuning-expert/references/evaluation-metrics.md +597 -597
- package/skills/ai-ml/fine-tuning-expert/references/hyperparameter-tuning.md +565 -565
- package/skills/ai-ml/fine-tuning-expert/references/lora-peft.md +347 -347
- package/skills/ai-ml/ml-pipeline/SKILL.md +159 -159
- package/skills/ai-ml/ml-pipeline/references/experiment-tracking.md +833 -833
- package/skills/ai-ml/ml-pipeline/references/feature-engineering.md +631 -631
- package/skills/ai-ml/ml-pipeline/references/model-validation.md +978 -978
- package/skills/ai-ml/ml-pipeline/references/pipeline-orchestration.md +907 -907
- package/skills/ai-ml/ml-pipeline/references/training-pipelines.md +782 -782
- package/skills/ai-ml/rag-architect/SKILL.md +194 -194
- package/skills/ai-ml/rag-architect/references/chunking-strategies.md +878 -878
- package/skills/ai-ml/rag-architect/references/embedding-models.md +561 -561
- package/skills/ai-ml/rag-architect/references/rag-evaluation.md +833 -833
- package/skills/ai-ml/rag-architect/references/retrieval-optimization.md +795 -795
- package/skills/ai-ml/rag-architect/references/vector-databases.md +589 -589
- package/skills/ai-ml/spark-engineer/SKILL.md +148 -148
- package/skills/ai-ml/spark-engineer/references/partitioning-caching.md +543 -543
- package/skills/ai-ml/spark-engineer/references/performance-tuning.md +544 -544
- package/skills/ai-ml/spark-engineer/references/rdd-operations.md +599 -599
- package/skills/ai-ml/spark-engineer/references/spark-sql-dataframes.md +474 -474
- package/skills/ai-ml/spark-engineer/references/streaming-patterns.md +786 -786
- package/skills/backend/api-designer/SKILL.md +217 -217
- package/skills/backend/api-designer/references/error-handling.md +541 -541
- package/skills/backend/api-designer/references/openapi.md +824 -824
- package/skills/backend/api-designer/references/pagination.md +494 -494
- package/skills/backend/api-designer/references/rest-patterns.md +335 -335
- package/skills/backend/api-designer/references/versioning.md +391 -391
- package/skills/backend/architecture-designer/SKILL.md +117 -117
- package/skills/backend/architecture-designer/references/adr-template.md +116 -116
- package/skills/backend/architecture-designer/references/architecture-patterns.md +111 -111
- package/skills/backend/architecture-designer/references/database-selection.md +102 -102
- package/skills/backend/architecture-designer/references/nfr-checklist.md +112 -112
- package/skills/backend/architecture-designer/references/system-design.md +100 -100
- package/skills/backend/code-documenter/SKILL.md +147 -147
- package/skills/backend/code-documenter/references/api-docs-fastapi-django.md +166 -166
- package/skills/backend/code-documenter/references/api-docs-nestjs-express.md +220 -220
- package/skills/backend/code-documenter/references/coverage-reports.md +125 -125
- package/skills/backend/code-documenter/references/documentation-systems.md +333 -333
- package/skills/backend/code-documenter/references/interactive-api-docs.md +531 -531
- package/skills/backend/code-documenter/references/python-docstrings.md +121 -121
- package/skills/backend/code-documenter/references/typescript-jsdoc.md +145 -145
- package/skills/backend/code-documenter/references/user-guides-tutorials.md +530 -530
- package/skills/backend/debugging-wizard/SKILL.md +105 -105
- package/skills/backend/debugging-wizard/references/common-patterns.md +132 -132
- package/skills/backend/debugging-wizard/references/debugging-tools.md +140 -140
- package/skills/backend/debugging-wizard/references/quick-fixes.md +177 -177
- package/skills/backend/debugging-wizard/references/strategies.md +142 -142
- package/skills/backend/debugging-wizard/references/systematic-debugging.md +367 -367
- package/skills/backend/feature-forge/SKILL.md +98 -98
- package/skills/backend/feature-forge/references/acceptance-criteria.md +104 -104
- package/skills/backend/feature-forge/references/ears-syntax.md +99 -99
- package/skills/backend/feature-forge/references/interview-questions.md +150 -150
- package/skills/backend/feature-forge/references/pre-discovery-subagents.md +54 -54
- package/skills/backend/feature-forge/references/specification-template.md +103 -103
- package/skills/backend/fullstack-guardian/SKILL.md +105 -105
- package/skills/backend/fullstack-guardian/references/api-design-standards.md +307 -307
- package/skills/backend/fullstack-guardian/references/architecture-decisions.md +350 -350
- package/skills/backend/fullstack-guardian/references/backend-patterns.md +237 -237
- package/skills/backend/fullstack-guardian/references/common-patterns.md +134 -134
- package/skills/backend/fullstack-guardian/references/deliverables-checklist.md +354 -354
- package/skills/backend/fullstack-guardian/references/design-template.md +91 -91
- package/skills/backend/fullstack-guardian/references/error-handling.md +135 -135
- package/skills/backend/fullstack-guardian/references/frontend-patterns.md +340 -340
- package/skills/backend/fullstack-guardian/references/integration-patterns.md +333 -333
- package/skills/backend/fullstack-guardian/references/security-checklist.md +106 -106
- package/skills/backend/graphql-architect/SKILL.md +146 -146
- package/skills/backend/graphql-architect/references/federation.md +418 -418
- package/skills/backend/graphql-architect/references/migration-from-rest.md +1141 -1141
- package/skills/backend/graphql-architect/references/resolvers.md +425 -425
- package/skills/backend/graphql-architect/references/schema-design.md +393 -393
- package/skills/backend/graphql-architect/references/security.md +569 -569
- package/skills/backend/graphql-architect/references/subscriptions.md +510 -510
- package/skills/backend/legacy-modernizer/SKILL.md +137 -137
- package/skills/backend/legacy-modernizer/references/legacy-testing.md +381 -381
- package/skills/backend/legacy-modernizer/references/migration-strategies.md +423 -423
- package/skills/backend/legacy-modernizer/references/refactoring-patterns.md +395 -395
- package/skills/backend/legacy-modernizer/references/strangler-fig-pattern.md +281 -281
- package/skills/backend/legacy-modernizer/references/system-assessment.md +487 -487
- package/skills/backend/microservices-architect/SKILL.md +164 -164
- package/skills/backend/microservices-architect/references/communication.md +499 -499
- package/skills/backend/microservices-architect/references/data.md +721 -721
- package/skills/backend/microservices-architect/references/decomposition.md +344 -344
- package/skills/backend/microservices-architect/references/observability.md +805 -805
- package/skills/backend/microservices-architect/references/patterns.md +603 -603
- package/skills/database/database-optimizer/SKILL.md +147 -147
- package/skills/database/database-optimizer/references/index-strategies.md +331 -331
- package/skills/database/database-optimizer/references/monitoring-analysis.md +501 -501
- package/skills/database/database-optimizer/references/mysql-tuning.md +452 -452
- package/skills/database/database-optimizer/references/postgresql-tuning.md +413 -413
- package/skills/database/database-optimizer/references/query-optimization.md +251 -251
- package/skills/database/postgres-pro/SKILL.md +152 -152
- package/skills/database/postgres-pro/references/extensions.md +404 -404
- package/skills/database/postgres-pro/references/jsonb.md +321 -321
- package/skills/database/postgres-pro/references/maintenance.md +481 -481
- package/skills/database/postgres-pro/references/performance.md +265 -265
- package/skills/database/postgres-pro/references/replication.md +446 -446
- package/skills/database/sql-pro/SKILL.md +129 -129
- package/skills/database/sql-pro/references/database-design.md +402 -402
- package/skills/database/sql-pro/references/dialect-differences.md +419 -419
- package/skills/database/sql-pro/references/optimization.md +384 -384
- package/skills/database/sql-pro/references/query-patterns.md +285 -285
- package/skills/database/sql-pro/references/window-functions.md +328 -328
- package/skills/dotnet/csharp-developer/SKILL.md +125 -125
- package/skills/dotnet/csharp-developer/references/aspnet-core.md +394 -394
- package/skills/dotnet/csharp-developer/references/blazor.md +553 -553
- package/skills/dotnet/csharp-developer/references/entity-framework.md +409 -409
- package/skills/dotnet/csharp-developer/references/modern-csharp.md +248 -248
- package/skills/dotnet/csharp-developer/references/performance.md +498 -498
- package/skills/dotnet/dotnet-core-expert/SKILL.md +138 -138
- package/skills/dotnet/dotnet-core-expert/references/authentication.md +546 -546
- package/skills/dotnet/dotnet-core-expert/references/clean-architecture.md +455 -455
- package/skills/dotnet/dotnet-core-expert/references/cloud-native.md +548 -548
- package/skills/dotnet/dotnet-core-expert/references/entity-framework.md +440 -440
- package/skills/dotnet/dotnet-core-expert/references/minimal-apis.md +319 -319
- package/skills/frontend/angular-architect/SKILL.md +152 -152
- package/skills/frontend/angular-architect/references/components.md +297 -297
- package/skills/frontend/angular-architect/references/ngrx.md +401 -401
- package/skills/frontend/angular-architect/references/routing.md +361 -361
- package/skills/frontend/angular-architect/references/rxjs.md +319 -319
- package/skills/frontend/angular-architect/references/testing.md +405 -405
- package/skills/frontend/design-commands/design.md +91 -91
- package/skills/frontend/design-commands/handoff.md +97 -97
- package/skills/frontend/design-commands/prototype.md +120 -120
- package/skills/frontend/design-commands/spec.md +160 -160
- package/skills/frontend/design-commands/style.md +78 -78
- package/skills/frontend/flutter-expert/SKILL.md +138 -138
- package/skills/frontend/flutter-expert/references/bloc-state.md +259 -259
- package/skills/frontend/flutter-expert/references/gorouter-navigation.md +119 -119
- package/skills/frontend/flutter-expert/references/performance.md +99 -99
- package/skills/frontend/flutter-expert/references/project-structure.md +118 -118
- package/skills/frontend/flutter-expert/references/riverpod-state.md +130 -130
- package/skills/frontend/flutter-expert/references/widget-patterns.md +123 -123
- package/skills/frontend/nextjs-developer/SKILL.md +143 -143
- package/skills/frontend/nextjs-developer/references/app-router.md +311 -311
- package/skills/frontend/nextjs-developer/references/data-fetching.md +482 -482
- package/skills/frontend/nextjs-developer/references/deployment.md +545 -545
- package/skills/frontend/nextjs-developer/references/server-actions.md +462 -462
- package/skills/frontend/nextjs-developer/references/server-components.md +384 -384
- package/skills/frontend/react-expert/SKILL.md +149 -149
- package/skills/frontend/react-expert/references/hooks-patterns.md +162 -162
- package/skills/frontend/react-expert/references/migration-class-to-modern.md +1119 -1119
- package/skills/frontend/react-expert/references/performance.md +168 -168
- package/skills/frontend/react-expert/references/react-19-features.md +174 -174
- package/skills/frontend/react-expert/references/server-components.md +143 -143
- package/skills/frontend/react-expert/references/state-management.md +171 -171
- package/skills/frontend/react-expert/references/testing-react.md +174 -174
- package/skills/frontend/react-native-expert/SKILL.md +185 -185
- package/skills/frontend/react-native-expert/references/expo-router.md +187 -187
- package/skills/frontend/react-native-expert/references/list-optimization.md +204 -204
- package/skills/frontend/react-native-expert/references/platform-handling.md +188 -188
- package/skills/frontend/react-native-expert/references/project-structure.md +171 -171
- package/skills/frontend/react-native-expert/references/storage-hooks.md +173 -173
- package/skills/frontend/senior-frontend/SKILL.md +477 -477
- package/skills/frontend/senior-frontend/references/frontend_best_practices.md +806 -806
- package/skills/frontend/senior-frontend/references/nextjs_optimization_guide.md +724 -724
- package/skills/frontend/senior-frontend/references/react_patterns.md +746 -746
- package/skills/frontend/senior-frontend/scripts/bundle_analyzer.py +407 -407
- package/skills/frontend/senior-frontend/scripts/component_generator.py +329 -329
- package/skills/frontend/senior-frontend/scripts/frontend_scaffolder.py +1005 -1005
- package/skills/frontend/ui-ux-pro-max/SKILL.md +386 -386
- package/skills/frontend/ui-ux-pro-max/data/charts.csv +26 -26
- package/skills/frontend/ui-ux-pro-max/data/colors.csv +97 -97
- package/skills/frontend/ui-ux-pro-max/data/icons.csv +101 -101
- package/skills/frontend/ui-ux-pro-max/data/landing.csv +31 -31
- package/skills/frontend/ui-ux-pro-max/data/products.csv +96 -96
- package/skills/frontend/ui-ux-pro-max/data/react-performance.csv +45 -45
- package/skills/frontend/ui-ux-pro-max/data/stacks/astro.csv +54 -54
- package/skills/frontend/ui-ux-pro-max/data/stacks/flutter.csv +53 -53
- package/skills/frontend/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -56
- package/skills/frontend/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -53
- package/skills/frontend/ui-ux-pro-max/data/stacks/nextjs.csv +53 -53
- package/skills/frontend/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -51
- package/skills/frontend/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -59
- package/skills/frontend/ui-ux-pro-max/data/stacks/react-native.csv +52 -52
- package/skills/frontend/ui-ux-pro-max/data/stacks/react.csv +54 -54
- package/skills/frontend/ui-ux-pro-max/data/stacks/shadcn.csv +61 -61
- package/skills/frontend/ui-ux-pro-max/data/stacks/svelte.csv +54 -54
- package/skills/frontend/ui-ux-pro-max/data/stacks/swiftui.csv +51 -51
- package/skills/frontend/ui-ux-pro-max/data/stacks/vue.csv +50 -50
- package/skills/frontend/ui-ux-pro-max/data/styles.csv +68 -68
- package/skills/frontend/ui-ux-pro-max/data/typography.csv +57 -57
- package/skills/frontend/ui-ux-pro-max/data/ui-reasoning.csv +101 -101
- package/skills/frontend/ui-ux-pro-max/data/ux-guidelines.csv +99 -99
- package/skills/frontend/ui-ux-pro-max/data/web-interface.csv +31 -31
- package/skills/frontend/ui-ux-pro-max/scripts/core.py +253 -253
- package/skills/frontend/ui-ux-pro-max/scripts/design_system.py +1067 -1067
- package/skills/frontend/ui-ux-pro-max/scripts/search.py +114 -114
- package/skills/frontend/vue-expert/SKILL.md +98 -98
- package/skills/frontend/vue-expert/references/build-tooling.md +480 -480
- package/skills/frontend/vue-expert/references/components.md +448 -448
- package/skills/frontend/vue-expert/references/composition-api.md +299 -299
- package/skills/frontend/vue-expert/references/mobile-hybrid.md +636 -636
- package/skills/frontend/vue-expert/references/nuxt.md +669 -669
- package/skills/frontend/vue-expert/references/state-management.md +449 -449
- package/skills/frontend/vue-expert/references/typescript.md +584 -584
- package/skills/frontend/vue-expert-js/SKILL.md +167 -167
- package/skills/frontend/vue-expert-js/references/component-architecture.md +219 -219
- package/skills/frontend/vue-expert-js/references/composables-patterns.md +183 -183
- package/skills/frontend/vue-expert-js/references/jsdoc-typing.md +535 -535
- package/skills/frontend/vue-expert-js/references/state-management.md +249 -249
- package/skills/frontend/vue-expert-js/references/testing-patterns.md +237 -237
- package/skills/go-rust-cpp/cpp-pro/SKILL.md +115 -115
- package/skills/go-rust-cpp/cpp-pro/references/build-tooling.md +440 -440
- package/skills/go-rust-cpp/cpp-pro/references/concurrency.md +437 -437
- package/skills/go-rust-cpp/cpp-pro/references/memory-performance.md +397 -397
- package/skills/go-rust-cpp/cpp-pro/references/modern-cpp.md +304 -304
- package/skills/go-rust-cpp/cpp-pro/references/templates.md +357 -357
- package/skills/go-rust-cpp/golang-pro/SKILL.md +122 -122
- package/skills/go-rust-cpp/golang-pro/references/concurrency.md +329 -329
- package/skills/go-rust-cpp/golang-pro/references/generics.md +442 -442
- package/skills/go-rust-cpp/golang-pro/references/interfaces.md +432 -432
- package/skills/go-rust-cpp/golang-pro/references/project-structure.md +477 -477
- package/skills/go-rust-cpp/golang-pro/references/testing.md +451 -451
- package/skills/go-rust-cpp/rust-engineer/SKILL.md +167 -167
- package/skills/go-rust-cpp/rust-engineer/references/async.md +458 -458
- package/skills/go-rust-cpp/rust-engineer/references/error-handling.md +334 -334
- package/skills/go-rust-cpp/rust-engineer/references/ownership.md +278 -278
- package/skills/go-rust-cpp/rust-engineer/references/testing.md +470 -470
- package/skills/go-rust-cpp/rust-engineer/references/traits.md +413 -413
- package/skills/infra/cli-developer/SKILL.md +113 -113
- package/skills/infra/cli-developer/references/design-patterns.md +221 -221
- package/skills/infra/cli-developer/references/go-cli.md +540 -540
- package/skills/infra/cli-developer/references/node-cli.md +383 -383
- package/skills/infra/cli-developer/references/python-cli.md +422 -422
- package/skills/infra/cli-developer/references/ux-patterns.md +448 -448
- package/skills/infra/cloud-architect/SKILL.md +216 -216
- package/skills/infra/cloud-architect/references/aws.md +394 -394
- package/skills/infra/cloud-architect/references/azure.md +562 -562
- package/skills/infra/cloud-architect/references/cost.md +582 -582
- package/skills/infra/cloud-architect/references/gcp.md +633 -633
- package/skills/infra/cloud-architect/references/multi-cloud.md +483 -483
- package/skills/infra/devops-engineer/SKILL.md +144 -144
- package/skills/infra/devops-engineer/references/deployment-strategies.md +241 -241
- package/skills/infra/devops-engineer/references/docker-patterns.md +113 -113
- package/skills/infra/devops-engineer/references/github-actions.md +139 -139
- package/skills/infra/devops-engineer/references/incident-response.md +331 -331
- package/skills/infra/devops-engineer/references/kubernetes.md +154 -154
- package/skills/infra/devops-engineer/references/platform-engineering.md +417 -417
- package/skills/infra/devops-engineer/references/release-automation.md +527 -527
- package/skills/infra/devops-engineer/references/terraform-iac.md +141 -141
- package/skills/infra/kubernetes-specialist/SKILL.md +241 -241
- package/skills/infra/kubernetes-specialist/references/configuration.md +452 -452
- package/skills/infra/kubernetes-specialist/references/cost-optimization.md +458 -458
- package/skills/infra/kubernetes-specialist/references/custom-operators.md +563 -563
- package/skills/infra/kubernetes-specialist/references/gitops.md +530 -530
- package/skills/infra/kubernetes-specialist/references/helm-charts.md +912 -912
- package/skills/infra/kubernetes-specialist/references/multi-cluster.md +507 -507
- package/skills/infra/kubernetes-specialist/references/networking.md +447 -447
- package/skills/infra/kubernetes-specialist/references/service-mesh.md +459 -459
- package/skills/infra/kubernetes-specialist/references/storage.md +535 -535
- package/skills/infra/kubernetes-specialist/references/troubleshooting.md +414 -414
- package/skills/infra/kubernetes-specialist/references/workloads.md +377 -377
- package/skills/infra/mcp-developer/SKILL.md +143 -143
- package/skills/infra/mcp-developer/references/protocol.md +244 -244
- package/skills/infra/mcp-developer/references/python-sdk.md +367 -367
- package/skills/infra/mcp-developer/references/resources.md +554 -554
- package/skills/infra/mcp-developer/references/tools.md +480 -480
- package/skills/infra/mcp-developer/references/typescript-sdk.md +350 -350
- package/skills/infra/monitoring-expert/SKILL.md +176 -176
- package/skills/infra/monitoring-expert/references/alerting-rules.md +141 -141
- package/skills/infra/monitoring-expert/references/application-profiling.md +331 -331
- package/skills/infra/monitoring-expert/references/capacity-planning.md +344 -344
- package/skills/infra/monitoring-expert/references/dashboards.md +126 -126
- package/skills/infra/monitoring-expert/references/opentelemetry.md +123 -123
- package/skills/infra/monitoring-expert/references/performance-testing.md +269 -269
- package/skills/infra/monitoring-expert/references/prometheus-metrics.md +136 -136
- package/skills/infra/monitoring-expert/references/structured-logging.md +142 -142
- package/skills/infra/sre-engineer/SKILL.md +181 -181
- package/skills/infra/sre-engineer/references/automation-toil.md +492 -492
- package/skills/infra/sre-engineer/references/error-budget-policy.md +334 -334
- package/skills/infra/sre-engineer/references/incident-chaos.md +576 -576
- package/skills/infra/sre-engineer/references/monitoring-alerting.md +424 -424
- package/skills/infra/sre-engineer/references/slo-sli-management.md +238 -238
- package/skills/infra/terraform-engineer/SKILL.md +143 -143
- package/skills/infra/terraform-engineer/references/best-practices.md +583 -583
- package/skills/infra/terraform-engineer/references/module-patterns.md +297 -297
- package/skills/infra/terraform-engineer/references/providers.md +452 -452
- package/skills/infra/terraform-engineer/references/state-management.md +371 -371
- package/skills/infra/terraform-engineer/references/testing.md +486 -486
- package/skills/infra/websocket-engineer/SKILL.md +168 -168
- package/skills/infra/websocket-engineer/references/alternatives.md +391 -391
- package/skills/infra/websocket-engineer/references/patterns.md +400 -400
- package/skills/infra/websocket-engineer/references/protocol.md +195 -195
- package/skills/infra/websocket-engineer/references/scaling.md +333 -333
- package/skills/infra/websocket-engineer/references/security.md +474 -474
- package/skills/java/java-architect/SKILL.md +132 -132
- package/skills/java/java-architect/references/jpa-optimization.md +393 -393
- package/skills/java/java-architect/references/reactive-webflux.md +356 -356
- package/skills/java/java-architect/references/spring-boot-setup.md +269 -269
- package/skills/java/java-architect/references/spring-security.md +445 -445
- package/skills/java/java-architect/references/testing-patterns.md +500 -500
- package/skills/java/kotlin-specialist/SKILL.md +147 -147
- package/skills/java/kotlin-specialist/references/android-compose.md +419 -419
- package/skills/java/kotlin-specialist/references/coroutines-flow.md +276 -276
- package/skills/java/kotlin-specialist/references/dsl-idioms.md +421 -421
- package/skills/java/kotlin-specialist/references/ktor-server.md +426 -426
- package/skills/java/kotlin-specialist/references/multiplatform-kmp.md +380 -380
- package/skills/java/spring-boot-engineer/SKILL.md +195 -195
- package/skills/java/spring-boot-engineer/references/cloud.md +498 -498
- package/skills/java/spring-boot-engineer/references/data.md +381 -381
- package/skills/java/spring-boot-engineer/references/security.md +459 -459
- package/skills/java/spring-boot-engineer/references/testing.md +545 -545
- package/skills/java/spring-boot-engineer/references/web.md +295 -295
- package/skills/javascript/javascript-pro/SKILL.md +132 -132
- package/skills/javascript/javascript-pro/references/async-patterns.md +334 -334
- package/skills/javascript/javascript-pro/references/browser-apis.md +398 -398
- package/skills/javascript/javascript-pro/references/modern-syntax.md +272 -272
- package/skills/javascript/javascript-pro/references/modules.md +357 -357
- package/skills/javascript/javascript-pro/references/node-essentials.md +471 -471
- package/skills/javascript/nestjs-expert/SKILL.md +206 -206
- package/skills/javascript/nestjs-expert/references/authentication.md +166 -166
- package/skills/javascript/nestjs-expert/references/controllers-routing.md +111 -111
- package/skills/javascript/nestjs-expert/references/dtos-validation.md +153 -153
- package/skills/javascript/nestjs-expert/references/migration-from-express.md +1237 -1237
- package/skills/javascript/nestjs-expert/references/services-di.md +140 -140
- package/skills/javascript/nestjs-expert/references/testing-patterns.md +186 -186
- package/skills/javascript/typescript-pro/SKILL.md +145 -145
- package/skills/javascript/typescript-pro/references/advanced-types.md +259 -259
- package/skills/javascript/typescript-pro/references/configuration.md +445 -445
- package/skills/javascript/typescript-pro/references/patterns.md +484 -484
- package/skills/javascript/typescript-pro/references/type-guards.md +352 -352
- package/skills/javascript/typescript-pro/references/utility-types.md +329 -329
- package/skills/php/laravel-specialist/SKILL.md +262 -262
- package/skills/php/laravel-specialist/references/eloquent.md +351 -351
- package/skills/php/laravel-specialist/references/livewire.md +512 -512
- package/skills/php/laravel-specialist/references/queues.md +423 -423
- package/skills/php/laravel-specialist/references/routing.md +362 -362
- package/skills/php/laravel-specialist/references/testing.md +522 -522
- package/skills/php/php-pro/SKILL.md +206 -206
- package/skills/php/php-pro/references/async-patterns.md +412 -412
- package/skills/php/php-pro/references/laravel-patterns.md +377 -377
- package/skills/php/php-pro/references/modern-php-features.md +323 -323
- package/skills/php/php-pro/references/symfony-patterns.md +466 -466
- package/skills/php/php-pro/references/testing-quality.md +466 -466
- package/skills/product/competitive-analysis/SKILL.md +257 -257
- package/skills/product/meeting-notes/SKILL.md +266 -266
- package/skills/product/prd-template/SKILL.md +150 -150
- package/skills/product/stakeholder-update/SKILL.md +225 -225
- package/skills/product/user-research-synthesis/SKILL.md +235 -235
- package/skills/python/django-expert/SKILL.md +162 -162
- package/skills/python/django-expert/references/authentication.md +145 -145
- package/skills/python/django-expert/references/drf-serializers.md +148 -148
- package/skills/python/django-expert/references/models-orm.md +151 -151
- package/skills/python/django-expert/references/testing-django.md +204 -204
- package/skills/python/django-expert/references/viewsets-views.md +153 -153
- package/skills/python/fastapi-expert/SKILL.md +185 -185
- package/skills/python/fastapi-expert/references/async-sqlalchemy.md +146 -146
- package/skills/python/fastapi-expert/references/authentication.md +159 -159
- package/skills/python/fastapi-expert/references/endpoints-routing.md +142 -142
- package/skills/python/fastapi-expert/references/migration-from-django.md +996 -996
- package/skills/python/fastapi-expert/references/pydantic-v2.md +135 -135
- package/skills/python/fastapi-expert/references/testing-async.md +159 -159
- package/skills/python/pandas-pro/SKILL.md +178 -178
- package/skills/python/pandas-pro/references/aggregation-groupby.md +545 -545
- package/skills/python/pandas-pro/references/data-cleaning.md +500 -500
- package/skills/python/pandas-pro/references/dataframe-operations.md +420 -420
- package/skills/python/pandas-pro/references/merging-joining.md +596 -596
- package/skills/python/pandas-pro/references/performance-optimization.md +597 -597
- package/skills/python/python-pro/SKILL.md +177 -177
- package/skills/python/python-pro/references/async-patterns.md +356 -356
- package/skills/python/python-pro/references/packaging.md +460 -460
- package/skills/python/python-pro/references/standard-library.md +378 -378
- package/skills/python/python-pro/references/testing.md +404 -404
- package/skills/python/python-pro/references/type-system.md +290 -290
- package/skills/quality/chaos-engineer/SKILL.md +182 -182
- package/skills/quality/chaos-engineer/references/chaos-tools.md +511 -511
- package/skills/quality/chaos-engineer/references/experiment-design.md +229 -229
- package/skills/quality/chaos-engineer/references/game-days.md +434 -434
- package/skills/quality/chaos-engineer/references/infrastructure-chaos.md +348 -348
- package/skills/quality/chaos-engineer/references/kubernetes-chaos.md +432 -432
- package/skills/quality/code-reviewer/SKILL.md +119 -119
- package/skills/quality/code-reviewer/references/common-issues.md +142 -142
- package/skills/quality/code-reviewer/references/feedback-examples.md +144 -144
- package/skills/quality/code-reviewer/references/receiving-feedback.md +238 -238
- package/skills/quality/code-reviewer/references/report-template.md +109 -109
- package/skills/quality/code-reviewer/references/review-checklist.md +88 -88
- package/skills/quality/code-reviewer/references/spec-compliance-review.md +258 -258
- package/skills/quality/playwright-expert/SKILL.md +169 -169
- package/skills/quality/playwright-expert/references/api-mocking.md +140 -140
- package/skills/quality/playwright-expert/references/configuration.md +155 -155
- package/skills/quality/playwright-expert/references/debugging-flaky.md +150 -150
- package/skills/quality/playwright-expert/references/page-object-model.md +152 -152
- package/skills/quality/playwright-expert/references/selectors-locators.md +119 -119
- package/skills/quality/secure-code-guardian/SKILL.md +191 -191
- package/skills/quality/secure-code-guardian/references/authentication.md +136 -136
- package/skills/quality/secure-code-guardian/references/input-validation.md +146 -146
- package/skills/quality/secure-code-guardian/references/owasp-prevention.md +135 -135
- package/skills/quality/secure-code-guardian/references/security-headers.md +133 -133
- package/skills/quality/secure-code-guardian/references/xss-csrf.md +157 -157
- package/skills/quality/security-reviewer/SKILL.md +103 -103
- package/skills/quality/security-reviewer/references/infrastructure-security.md +268 -268
- package/skills/quality/security-reviewer/references/penetration-testing.md +268 -268
- package/skills/quality/security-reviewer/references/report-template.md +170 -170
- package/skills/quality/security-reviewer/references/sast-tools.md +117 -117
- package/skills/quality/security-reviewer/references/secret-scanning.md +125 -125
- package/skills/quality/security-reviewer/references/vulnerability-patterns.md +152 -152
- package/skills/quality/senior-qa/README.md +196 -196
- package/skills/quality/senior-qa/SKILL.md +399 -399
- package/skills/quality/senior-qa/references/qa_best_practices.md +964 -964
- package/skills/quality/senior-qa/references/test_automation_patterns.md +1009 -1009
- package/skills/quality/senior-qa/references/testing_strategies.md +649 -649
- package/skills/quality/senior-qa/scripts/coverage_analyzer.py +836 -836
- package/skills/quality/senior-qa/scripts/e2e_test_scaffolder.py +820 -820
- package/skills/quality/senior-qa/scripts/test_suite_generator.py +605 -605
- package/skills/quality/tdd-guide/HOW_TO_USE.md +313 -313
- package/skills/quality/tdd-guide/README.md +680 -680
- package/skills/quality/tdd-guide/SKILL.md +122 -122
- package/skills/quality/tdd-guide/assets/expected_output.json +77 -77
- package/skills/quality/tdd-guide/assets/sample_input_python.json +39 -39
- package/skills/quality/tdd-guide/assets/sample_input_typescript.json +36 -36
- package/skills/quality/tdd-guide/references/ci-integration.md +195 -195
- package/skills/quality/tdd-guide/references/framework-guide.md +206 -206
- package/skills/quality/tdd-guide/references/tdd-best-practices.md +128 -128
- package/skills/quality/tdd-guide/scripts/coverage_analyzer.py +434 -434
- package/skills/quality/tdd-guide/scripts/fixture_generator.py +440 -440
- package/skills/quality/tdd-guide/scripts/format_detector.py +384 -384
- package/skills/quality/tdd-guide/scripts/framework_adapter.py +428 -428
- package/skills/quality/tdd-guide/scripts/metrics_calculator.py +456 -456
- package/skills/quality/tdd-guide/scripts/output_formatter.py +354 -354
- package/skills/quality/tdd-guide/scripts/tdd_workflow.py +474 -474
- package/skills/quality/tdd-guide/scripts/test_generator.py +438 -438
- package/skills/quality/test-master/SKILL.md +94 -94
- package/skills/quality/test-master/references/automation-frameworks.md +294 -294
- package/skills/quality/test-master/references/e2e-testing.md +128 -128
- package/skills/quality/test-master/references/integration-testing.md +120 -120
- package/skills/quality/test-master/references/performance-testing.md +118 -118
- package/skills/quality/test-master/references/qa-methodology.md +247 -247
- package/skills/quality/test-master/references/security-testing.md +127 -127
- package/skills/quality/test-master/references/tdd-iron-laws.md +174 -174
- package/skills/quality/test-master/references/test-reports.md +104 -104
- package/skills/quality/test-master/references/testing-anti-patterns.md +231 -231
- package/skills/quality/test-master/references/unit-testing.md +113 -113
- package/skills/ruby/rails-expert/SKILL.md +154 -154
- package/skills/ruby/rails-expert/references/active-record.md +244 -244
- package/skills/ruby/rails-expert/references/api-development.md +401 -401
- package/skills/ruby/rails-expert/references/background-jobs.md +272 -272
- package/skills/ruby/rails-expert/references/hotwire-turbo.md +228 -228
- package/skills/ruby/rails-expert/references/rspec-testing.md +367 -367
- package/skills/swift/swift-expert/SKILL.md +163 -163
- package/skills/swift/swift-expert/references/async-concurrency.md +360 -360
- package/skills/swift/swift-expert/references/memory-performance.md +377 -377
- package/skills/swift/swift-expert/references/protocol-oriented.md +354 -354
- package/skills/swift/swift-expert/references/swiftui-patterns.md +291 -291
- package/skills/swift/swift-expert/references/testing-patterns.md +399 -399
- package/skills/workflow/brainstorming/SKILL.md +164 -164
- package/skills/workflow/brainstorming/scripts/frame-template.html +214 -214
- package/skills/workflow/brainstorming/scripts/helper.js +88 -88
- package/skills/workflow/brainstorming/scripts/server.cjs +354 -354
- package/skills/workflow/brainstorming/scripts/start-server.sh +148 -148
- package/skills/workflow/brainstorming/scripts/stop-server.sh +56 -56
- package/skills/workflow/brainstorming/spec-document-reviewer-prompt.md +49 -49
- package/skills/workflow/brainstorming/visual-companion.md +287 -287
- package/skills/workflow/documentation/SKILL.md +45 -45
- package/skills/workflow/entropy-management/SKILL.md +115 -115
- package/skills/workflow/executing-plans/SKILL.md +70 -70
- package/skills/workflow/finishing-a-development-branch/SKILL.md +200 -200
- package/skills/workflow/receiving-code-review/SKILL.md +213 -213
- package/skills/workflow/requesting-code-review/SKILL.md +105 -105
- package/skills/workflow/requesting-code-review/code-reviewer.md +146 -146
- package/skills/workflow/requirement-engineering/SKILL.md +111 -111
- package/skills/workflow/systematic-debugging/CREATION-LOG.md +119 -119
- package/skills/workflow/systematic-debugging/SKILL.md +296 -296
- package/skills/workflow/systematic-debugging/condition-based-waiting-example.ts +158 -158
- package/skills/workflow/systematic-debugging/condition-based-waiting.md +115 -115
- package/skills/workflow/systematic-debugging/defense-in-depth.md +122 -122
- package/skills/workflow/systematic-debugging/find-polluter.sh +63 -63
- package/skills/workflow/systematic-debugging/root-cause-tracing.md +169 -169
- package/skills/workflow/systematic-debugging/test-academic.md +14 -14
- package/skills/workflow/systematic-debugging/test-pressure-1.md +58 -58
- package/skills/workflow/systematic-debugging/test-pressure-2.md +68 -68
- package/skills/workflow/systematic-debugging/test-pressure-3.md +69 -69
- package/skills/workflow/using-git-worktrees/SKILL.md +218 -218
- package/skills/workflow/verification-before-completion/SKILL.md +139 -139
- package/skills/workflow/writing-plans/SKILL.md +151 -151
- package/skills/workflow/writing-plans/plan-document-reviewer-prompt.md +49 -49
- package/skills/workflow/writing-skills/SKILL.md +655 -655
- package/skills/workflow/writing-skills/anthropic-best-practices.md +1150 -1150
- package/skills/workflow/writing-skills/examples/CLAUDE_MD_TESTING.md +189 -189
- package/skills/workflow/writing-skills/persuasion-principles.md +187 -187
- package/skills/workflow/writing-skills/render-graphs.js +168 -168
- package/skills/workflow/writing-skills/testing-skills-with-subagents.md +384 -384
|
@@ -1,605 +1,605 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
Test Suite Generator
|
|
4
|
-
|
|
5
|
-
Scans React/TypeScript components and generates Jest + React Testing Library
|
|
6
|
-
test stubs with proper structure, accessibility tests, and common patterns.
|
|
7
|
-
|
|
8
|
-
Usage:
|
|
9
|
-
python test_suite_generator.py src/components/ --output __tests__/
|
|
10
|
-
python test_suite_generator.py src/ --include-a11y --scan-only
|
|
11
|
-
"""
|
|
12
|
-
|
|
13
|
-
import os
|
|
14
|
-
import sys
|
|
15
|
-
import json
|
|
16
|
-
import argparse
|
|
17
|
-
import re
|
|
18
|
-
from pathlib import Path
|
|
19
|
-
from typing import Dict, List, Optional, Tuple, Set
|
|
20
|
-
from dataclasses import dataclass, field, asdict
|
|
21
|
-
from datetime import datetime
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
@dataclass
|
|
25
|
-
class ComponentInfo:
|
|
26
|
-
"""Information about a detected React component"""
|
|
27
|
-
name: str
|
|
28
|
-
file_path: str
|
|
29
|
-
component_type: str # 'functional', 'class', 'forwardRef', 'memo'
|
|
30
|
-
has_props: bool
|
|
31
|
-
props: List[str]
|
|
32
|
-
has_hooks: List[str]
|
|
33
|
-
has_context: bool
|
|
34
|
-
has_effects: bool
|
|
35
|
-
has_state: bool
|
|
36
|
-
has_callbacks: bool
|
|
37
|
-
exports: List[str]
|
|
38
|
-
imports: List[str]
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
@dataclass
|
|
42
|
-
class TestCase:
|
|
43
|
-
"""A single test case to generate"""
|
|
44
|
-
name: str
|
|
45
|
-
description: str
|
|
46
|
-
test_type: str # 'render', 'interaction', 'a11y', 'props', 'state'
|
|
47
|
-
code: str
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
@dataclass
|
|
51
|
-
class TestFile:
|
|
52
|
-
"""A complete test file to generate"""
|
|
53
|
-
component: ComponentInfo
|
|
54
|
-
test_cases: List[TestCase] = field(default_factory=list)
|
|
55
|
-
imports: Set[str] = field(default_factory=set)
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
class ComponentScanner:
|
|
59
|
-
"""Scans source files for React components"""
|
|
60
|
-
|
|
61
|
-
# Patterns for detecting React components
|
|
62
|
-
FUNCTIONAL_COMPONENT = re.compile(
|
|
63
|
-
r'^(?:export\s+)?(?:const|function)\s+([A-Z][a-zA-Z0-9]*)\s*[=:]?\s*(?:\([^)]*\)\s*(?::\s*[^=]+)?\s*=>|function\s*\([^)]*\))',
|
|
64
|
-
re.MULTILINE
|
|
65
|
-
)
|
|
66
|
-
|
|
67
|
-
ARROW_COMPONENT = re.compile(
|
|
68
|
-
r'^(?:export\s+)?const\s+([A-Z][a-zA-Z0-9]*)\s*=\s*(?:React\.)?(?:memo|forwardRef)?\s*\(',
|
|
69
|
-
re.MULTILINE
|
|
70
|
-
)
|
|
71
|
-
|
|
72
|
-
CLASS_COMPONENT = re.compile(
|
|
73
|
-
r'^(?:export\s+)?class\s+([A-Z][a-zA-Z0-9]*)\s+extends\s+(?:React\.)?(?:Component|PureComponent)',
|
|
74
|
-
re.MULTILINE
|
|
75
|
-
)
|
|
76
|
-
|
|
77
|
-
HOOK_PATTERN = re.compile(r'use([A-Z][a-zA-Z0-9]*)\s*\(')
|
|
78
|
-
PROPS_PATTERN = re.compile(r'(?:props\.|{\s*([^}]+)\s*}\s*=\s*props|:\s*([A-Z][a-zA-Z0-9]*Props))')
|
|
79
|
-
CONTEXT_PATTERN = re.compile(r'useContext\s*\(|\.Provider|\.Consumer')
|
|
80
|
-
EFFECT_PATTERN = re.compile(r'useEffect\s*\(|useLayoutEffect\s*\(')
|
|
81
|
-
STATE_PATTERN = re.compile(r'useState\s*\(|useReducer\s*\(|this\.state')
|
|
82
|
-
CALLBACK_PATTERN = re.compile(r'on[A-Z][a-zA-Z]*\s*[=:]|handle[A-Z][a-zA-Z]*\s*[=:]')
|
|
83
|
-
|
|
84
|
-
def __init__(self, source_path: Path, verbose: bool = False):
|
|
85
|
-
self.source_path = source_path
|
|
86
|
-
self.verbose = verbose
|
|
87
|
-
self.components: List[ComponentInfo] = []
|
|
88
|
-
|
|
89
|
-
def scan(self) -> List[ComponentInfo]:
|
|
90
|
-
"""Scan the source path for React components"""
|
|
91
|
-
extensions = {'.tsx', '.jsx', '.ts', '.js'}
|
|
92
|
-
|
|
93
|
-
for root, dirs, files in os.walk(self.source_path):
|
|
94
|
-
# Skip node_modules and test directories
|
|
95
|
-
dirs[:] = [d for d in dirs if d not in {'node_modules', '__tests__', 'test', 'tests', '.git'}]
|
|
96
|
-
|
|
97
|
-
for file in files:
|
|
98
|
-
if Path(file).suffix in extensions:
|
|
99
|
-
file_path = Path(root) / file
|
|
100
|
-
self._scan_file(file_path)
|
|
101
|
-
|
|
102
|
-
return self.components
|
|
103
|
-
|
|
104
|
-
def _scan_file(self, file_path: Path):
|
|
105
|
-
"""Scan a single file for components"""
|
|
106
|
-
try:
|
|
107
|
-
content = file_path.read_text(encoding='utf-8')
|
|
108
|
-
except Exception as e:
|
|
109
|
-
if self.verbose:
|
|
110
|
-
print(f"Warning: Could not read {file_path}: {e}")
|
|
111
|
-
return
|
|
112
|
-
|
|
113
|
-
# Skip test files
|
|
114
|
-
if '.test.' in file_path.name or '.spec.' in file_path.name:
|
|
115
|
-
return
|
|
116
|
-
|
|
117
|
-
# Skip files without JSX indicators
|
|
118
|
-
if 'return' not in content or ('<' not in content and 'jsx' not in content.lower()):
|
|
119
|
-
# Could still be a hook
|
|
120
|
-
if not self.HOOK_PATTERN.search(content):
|
|
121
|
-
return
|
|
122
|
-
|
|
123
|
-
# Find functional components
|
|
124
|
-
for match in self.FUNCTIONAL_COMPONENT.finditer(content):
|
|
125
|
-
name = match.group(1)
|
|
126
|
-
self._add_component(name, file_path, content, 'functional')
|
|
127
|
-
|
|
128
|
-
# Find arrow function components
|
|
129
|
-
for match in self.ARROW_COMPONENT.finditer(content):
|
|
130
|
-
name = match.group(1)
|
|
131
|
-
component_type = 'functional'
|
|
132
|
-
if 'memo(' in content:
|
|
133
|
-
component_type = 'memo'
|
|
134
|
-
elif 'forwardRef(' in content:
|
|
135
|
-
component_type = 'forwardRef'
|
|
136
|
-
self._add_component(name, file_path, content, component_type)
|
|
137
|
-
|
|
138
|
-
# Find class components
|
|
139
|
-
for match in self.CLASS_COMPONENT.finditer(content):
|
|
140
|
-
name = match.group(1)
|
|
141
|
-
self._add_component(name, file_path, content, 'class')
|
|
142
|
-
|
|
143
|
-
def _add_component(self, name: str, file_path: Path, content: str, component_type: str):
|
|
144
|
-
"""Add a component to the list if not already present"""
|
|
145
|
-
# Check if already added
|
|
146
|
-
for comp in self.components:
|
|
147
|
-
if comp.name == name and comp.file_path == str(file_path):
|
|
148
|
-
return
|
|
149
|
-
|
|
150
|
-
# Extract hooks used
|
|
151
|
-
hooks = list(set(self.HOOK_PATTERN.findall(content)))
|
|
152
|
-
|
|
153
|
-
# Extract prop names (simplified)
|
|
154
|
-
props = []
|
|
155
|
-
props_match = self.PROPS_PATTERN.search(content)
|
|
156
|
-
if props_match:
|
|
157
|
-
props_str = props_match.group(1) or ''
|
|
158
|
-
props = [p.strip().split(':')[0].strip() for p in props_str.split(',') if p.strip()]
|
|
159
|
-
|
|
160
|
-
# Extract imports
|
|
161
|
-
imports = re.findall(r"import\s+(?:{[^}]+}|[^;]+)\s+from\s+['\"]([^'\"]+)['\"]", content)
|
|
162
|
-
|
|
163
|
-
# Extract exports
|
|
164
|
-
exports = re.findall(r"export\s+(?:default\s+)?(?:const|function|class)\s+(\w+)", content)
|
|
165
|
-
|
|
166
|
-
component = ComponentInfo(
|
|
167
|
-
name=name,
|
|
168
|
-
file_path=str(file_path),
|
|
169
|
-
component_type=component_type,
|
|
170
|
-
has_props=bool(props) or 'props' in content.lower(),
|
|
171
|
-
props=props[:10], # Limit props
|
|
172
|
-
has_hooks=hooks[:10], # Limit hooks
|
|
173
|
-
has_context=bool(self.CONTEXT_PATTERN.search(content)),
|
|
174
|
-
has_effects=bool(self.EFFECT_PATTERN.search(content)),
|
|
175
|
-
has_state=bool(self.STATE_PATTERN.search(content)),
|
|
176
|
-
has_callbacks=bool(self.CALLBACK_PATTERN.search(content)),
|
|
177
|
-
exports=exports[:5],
|
|
178
|
-
imports=imports[:10]
|
|
179
|
-
)
|
|
180
|
-
|
|
181
|
-
self.components.append(component)
|
|
182
|
-
|
|
183
|
-
if self.verbose:
|
|
184
|
-
print(f" Found: {name} ({component_type}) in {file_path.name}")
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
class TestGenerator:
|
|
188
|
-
"""Generates Jest + React Testing Library test files"""
|
|
189
|
-
|
|
190
|
-
def __init__(self, include_a11y: bool = False, template: Optional[str] = None):
|
|
191
|
-
self.include_a11y = include_a11y
|
|
192
|
-
self.template = template
|
|
193
|
-
|
|
194
|
-
def generate(self, component: ComponentInfo) -> TestFile:
|
|
195
|
-
"""Generate a test file for a component"""
|
|
196
|
-
test_file = TestFile(component=component)
|
|
197
|
-
|
|
198
|
-
# Build imports
|
|
199
|
-
test_file.imports.add("import { render, screen } from '@testing-library/react';")
|
|
200
|
-
|
|
201
|
-
if component.has_callbacks:
|
|
202
|
-
test_file.imports.add("import userEvent from '@testing-library/user-event';")
|
|
203
|
-
|
|
204
|
-
if component.has_effects or component.has_state:
|
|
205
|
-
test_file.imports.add("import { waitFor } from '@testing-library/react';")
|
|
206
|
-
|
|
207
|
-
if self.include_a11y:
|
|
208
|
-
test_file.imports.add("import { axe, toHaveNoViolations } from 'jest-axe';")
|
|
209
|
-
|
|
210
|
-
# Add component import
|
|
211
|
-
relative_path = self._get_relative_import(component.file_path)
|
|
212
|
-
test_file.imports.add(f"import {{ {component.name} }} from '{relative_path}';")
|
|
213
|
-
|
|
214
|
-
# Generate test cases
|
|
215
|
-
test_file.test_cases.append(self._generate_render_test(component))
|
|
216
|
-
|
|
217
|
-
if component.has_props:
|
|
218
|
-
test_file.test_cases.append(self._generate_props_test(component))
|
|
219
|
-
|
|
220
|
-
if component.has_callbacks:
|
|
221
|
-
test_file.test_cases.append(self._generate_interaction_test(component))
|
|
222
|
-
|
|
223
|
-
if component.has_state:
|
|
224
|
-
test_file.test_cases.append(self._generate_state_test(component))
|
|
225
|
-
|
|
226
|
-
if self.include_a11y:
|
|
227
|
-
test_file.test_cases.append(self._generate_a11y_test(component))
|
|
228
|
-
|
|
229
|
-
return test_file
|
|
230
|
-
|
|
231
|
-
def _get_relative_import(self, file_path: str) -> str:
|
|
232
|
-
"""Get the relative import path for a component"""
|
|
233
|
-
path = Path(file_path)
|
|
234
|
-
# Remove extension
|
|
235
|
-
stem = path.stem
|
|
236
|
-
if stem == 'index':
|
|
237
|
-
return f"../{path.parent.name}"
|
|
238
|
-
return f"../{path.parent.name}/{stem}"
|
|
239
|
-
|
|
240
|
-
def _generate_render_test(self, component: ComponentInfo) -> TestCase:
|
|
241
|
-
"""Generate a basic render test"""
|
|
242
|
-
props_str = self._get_mock_props(component)
|
|
243
|
-
|
|
244
|
-
code = f''' it('renders without crashing', () => {{
|
|
245
|
-
render(<{component.name}{props_str} />);
|
|
246
|
-
}});
|
|
247
|
-
|
|
248
|
-
it('renders expected content', () => {{
|
|
249
|
-
render(<{component.name}{props_str} />);
|
|
250
|
-
// TODO: Add specific content assertions
|
|
251
|
-
// expect(screen.getByRole('...')).toBeInTheDocument();
|
|
252
|
-
}});'''
|
|
253
|
-
|
|
254
|
-
return TestCase(
|
|
255
|
-
name='render',
|
|
256
|
-
description='Basic render tests',
|
|
257
|
-
test_type='render',
|
|
258
|
-
code=code
|
|
259
|
-
)
|
|
260
|
-
|
|
261
|
-
def _generate_props_test(self, component: ComponentInfo) -> TestCase:
|
|
262
|
-
"""Generate props-related tests"""
|
|
263
|
-
props = component.props[:3] if component.props else ['prop1']
|
|
264
|
-
|
|
265
|
-
prop_tests = []
|
|
266
|
-
for prop in props:
|
|
267
|
-
prop_tests.append(f''' it('renders with {prop} prop', () => {{
|
|
268
|
-
render(<{component.name} {prop}="test-value" />);
|
|
269
|
-
// TODO: Assert that {prop} affects rendering
|
|
270
|
-
}});''')
|
|
271
|
-
|
|
272
|
-
code = '\n\n'.join(prop_tests)
|
|
273
|
-
|
|
274
|
-
return TestCase(
|
|
275
|
-
name='props',
|
|
276
|
-
description='Props handling tests',
|
|
277
|
-
test_type='props',
|
|
278
|
-
code=code
|
|
279
|
-
)
|
|
280
|
-
|
|
281
|
-
def _generate_interaction_test(self, component: ComponentInfo) -> TestCase:
|
|
282
|
-
"""Generate user interaction tests"""
|
|
283
|
-
code = f''' it('handles user interaction', async () => {{
|
|
284
|
-
const user = userEvent.setup();
|
|
285
|
-
const handleClick = jest.fn();
|
|
286
|
-
|
|
287
|
-
render(<{component.name} onClick={{handleClick}} />);
|
|
288
|
-
|
|
289
|
-
// TODO: Find the interactive element
|
|
290
|
-
const button = screen.getByRole('button');
|
|
291
|
-
await user.click(button);
|
|
292
|
-
|
|
293
|
-
expect(handleClick).toHaveBeenCalledTimes(1);
|
|
294
|
-
}});
|
|
295
|
-
|
|
296
|
-
it('handles keyboard navigation', async () => {{
|
|
297
|
-
const user = userEvent.setup();
|
|
298
|
-
render(<{component.name} />);
|
|
299
|
-
|
|
300
|
-
// TODO: Add keyboard interaction tests
|
|
301
|
-
// await user.tab();
|
|
302
|
-
// expect(screen.getByRole('...')).toHaveFocus();
|
|
303
|
-
}});'''
|
|
304
|
-
|
|
305
|
-
return TestCase(
|
|
306
|
-
name='interaction',
|
|
307
|
-
description='User interaction tests',
|
|
308
|
-
test_type='interaction',
|
|
309
|
-
code=code
|
|
310
|
-
)
|
|
311
|
-
|
|
312
|
-
def _generate_state_test(self, component: ComponentInfo) -> TestCase:
|
|
313
|
-
"""Generate state-related tests"""
|
|
314
|
-
code = f''' it('updates state correctly', async () => {{
|
|
315
|
-
const user = userEvent.setup();
|
|
316
|
-
render(<{component.name} />);
|
|
317
|
-
|
|
318
|
-
// TODO: Trigger state change
|
|
319
|
-
// await user.click(screen.getByRole('button'));
|
|
320
|
-
|
|
321
|
-
// TODO: Assert state change is reflected in UI
|
|
322
|
-
await waitFor(() => {{
|
|
323
|
-
// expect(screen.getByText('...')).toBeInTheDocument();
|
|
324
|
-
}});
|
|
325
|
-
}});'''
|
|
326
|
-
|
|
327
|
-
return TestCase(
|
|
328
|
-
name='state',
|
|
329
|
-
description='State management tests',
|
|
330
|
-
test_type='state',
|
|
331
|
-
code=code
|
|
332
|
-
)
|
|
333
|
-
|
|
334
|
-
def _generate_a11y_test(self, component: ComponentInfo) -> TestCase:
|
|
335
|
-
"""Generate accessibility test"""
|
|
336
|
-
props_str = self._get_mock_props(component)
|
|
337
|
-
|
|
338
|
-
code = f''' it('has no accessibility violations', async () => {{
|
|
339
|
-
const {{ container }} = render(<{component.name}{props_str} />);
|
|
340
|
-
const results = await axe(container);
|
|
341
|
-
expect(results).toHaveNoViolations();
|
|
342
|
-
}});'''
|
|
343
|
-
|
|
344
|
-
return TestCase(
|
|
345
|
-
name='accessibility',
|
|
346
|
-
description='Accessibility tests',
|
|
347
|
-
test_type='a11y',
|
|
348
|
-
code=code
|
|
349
|
-
)
|
|
350
|
-
|
|
351
|
-
def _get_mock_props(self, component: ComponentInfo) -> str:
|
|
352
|
-
"""Generate mock props string for a component"""
|
|
353
|
-
if not component.has_props or not component.props:
|
|
354
|
-
return ''
|
|
355
|
-
|
|
356
|
-
# Return empty for simplicity, user should fill in
|
|
357
|
-
return ' {...mockProps}'
|
|
358
|
-
|
|
359
|
-
def format_test_file(self, test_file: TestFile) -> str:
|
|
360
|
-
"""Format the complete test file content"""
|
|
361
|
-
lines = []
|
|
362
|
-
|
|
363
|
-
# Imports
|
|
364
|
-
lines.append("import '@testing-library/jest-dom';")
|
|
365
|
-
for imp in sorted(test_file.imports):
|
|
366
|
-
lines.append(imp)
|
|
367
|
-
|
|
368
|
-
lines.append('')
|
|
369
|
-
|
|
370
|
-
# A11y setup if needed
|
|
371
|
-
if self.include_a11y:
|
|
372
|
-
lines.append('expect.extend(toHaveNoViolations);')
|
|
373
|
-
lines.append('')
|
|
374
|
-
|
|
375
|
-
# Mock props if component has props
|
|
376
|
-
if test_file.component.has_props:
|
|
377
|
-
lines.append('// TODO: Define mock props')
|
|
378
|
-
lines.append('const mockProps = {};')
|
|
379
|
-
lines.append('')
|
|
380
|
-
|
|
381
|
-
# Describe block
|
|
382
|
-
lines.append(f"describe('{test_file.component.name}', () => {{")
|
|
383
|
-
|
|
384
|
-
# Test cases grouped by type
|
|
385
|
-
test_types = {}
|
|
386
|
-
for test_case in test_file.test_cases:
|
|
387
|
-
if test_case.test_type not in test_types:
|
|
388
|
-
test_types[test_case.test_type] = []
|
|
389
|
-
test_types[test_case.test_type].append(test_case)
|
|
390
|
-
|
|
391
|
-
for test_type, cases in test_types.items():
|
|
392
|
-
for case in cases:
|
|
393
|
-
lines.append('')
|
|
394
|
-
lines.append(f' // {case.description}')
|
|
395
|
-
lines.append(case.code)
|
|
396
|
-
|
|
397
|
-
lines.append('});')
|
|
398
|
-
lines.append('')
|
|
399
|
-
|
|
400
|
-
return '\n'.join(lines)
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
class TestSuiteGenerator:
|
|
404
|
-
"""Main class for generating test suites"""
|
|
405
|
-
|
|
406
|
-
def __init__(
|
|
407
|
-
self,
|
|
408
|
-
source_path: str,
|
|
409
|
-
output_path: Optional[str] = None,
|
|
410
|
-
include_a11y: bool = False,
|
|
411
|
-
scan_only: bool = False,
|
|
412
|
-
verbose: bool = False,
|
|
413
|
-
template: Optional[str] = None
|
|
414
|
-
):
|
|
415
|
-
self.source_path = Path(source_path)
|
|
416
|
-
self.output_path = Path(output_path) if output_path else None
|
|
417
|
-
self.include_a11y = include_a11y
|
|
418
|
-
self.scan_only = scan_only
|
|
419
|
-
self.verbose = verbose
|
|
420
|
-
self.template = template
|
|
421
|
-
self.results = {
|
|
422
|
-
'status': 'success',
|
|
423
|
-
'source': str(self.source_path),
|
|
424
|
-
'components': [],
|
|
425
|
-
'generated_files': [],
|
|
426
|
-
'summary': {}
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
def run(self) -> Dict:
|
|
430
|
-
"""Execute the test suite generation"""
|
|
431
|
-
print(f"Scanning: {self.source_path}")
|
|
432
|
-
|
|
433
|
-
# Validate source path
|
|
434
|
-
if not self.source_path.exists():
|
|
435
|
-
raise ValueError(f"Source path does not exist: {self.source_path}")
|
|
436
|
-
|
|
437
|
-
# Scan for components
|
|
438
|
-
scanner = ComponentScanner(self.source_path, self.verbose)
|
|
439
|
-
components = scanner.scan()
|
|
440
|
-
|
|
441
|
-
print(f"Found {len(components)} React components")
|
|
442
|
-
|
|
443
|
-
if self.scan_only:
|
|
444
|
-
self._report_scan_results(components)
|
|
445
|
-
return self.results
|
|
446
|
-
|
|
447
|
-
# Generate tests
|
|
448
|
-
if not self.output_path:
|
|
449
|
-
# Default to __tests__ in source directory
|
|
450
|
-
self.output_path = self.source_path / '__tests__'
|
|
451
|
-
|
|
452
|
-
self.output_path.mkdir(parents=True, exist_ok=True)
|
|
453
|
-
|
|
454
|
-
generator = TestGenerator(self.include_a11y, self.template)
|
|
455
|
-
|
|
456
|
-
total_tests = 0
|
|
457
|
-
for component in components:
|
|
458
|
-
test_file = generator.generate(component)
|
|
459
|
-
content = generator.format_test_file(test_file)
|
|
460
|
-
|
|
461
|
-
# Write test file
|
|
462
|
-
test_filename = f"{component.name}.test.tsx"
|
|
463
|
-
test_path = self.output_path / test_filename
|
|
464
|
-
|
|
465
|
-
test_path.write_text(content, encoding='utf-8')
|
|
466
|
-
|
|
467
|
-
test_count = len(test_file.test_cases)
|
|
468
|
-
total_tests += test_count
|
|
469
|
-
|
|
470
|
-
self.results['generated_files'].append({
|
|
471
|
-
'component': component.name,
|
|
472
|
-
'path': str(test_path),
|
|
473
|
-
'test_cases': test_count
|
|
474
|
-
})
|
|
475
|
-
|
|
476
|
-
print(f" {test_filename} ({test_count} test cases)")
|
|
477
|
-
|
|
478
|
-
# Store component info
|
|
479
|
-
self.results['components'] = [asdict(c) for c in components]
|
|
480
|
-
|
|
481
|
-
# Summary
|
|
482
|
-
self.results['summary'] = {
|
|
483
|
-
'total_components': len(components),
|
|
484
|
-
'total_files': len(self.results['generated_files']),
|
|
485
|
-
'total_test_cases': total_tests,
|
|
486
|
-
'output_directory': str(self.output_path)
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
print('')
|
|
490
|
-
print(f"Summary: {len(components)} test files, {total_tests} test cases")
|
|
491
|
-
|
|
492
|
-
return self.results
|
|
493
|
-
|
|
494
|
-
def _report_scan_results(self, components: List[ComponentInfo]):
|
|
495
|
-
"""Report scan results without generating tests"""
|
|
496
|
-
print('')
|
|
497
|
-
print("=" * 60)
|
|
498
|
-
print("COMPONENT SCAN RESULTS")
|
|
499
|
-
print("=" * 60)
|
|
500
|
-
|
|
501
|
-
# Group by type
|
|
502
|
-
by_type = {}
|
|
503
|
-
for comp in components:
|
|
504
|
-
comp_type = comp.component_type
|
|
505
|
-
if comp_type not in by_type:
|
|
506
|
-
by_type[comp_type] = []
|
|
507
|
-
by_type[comp_type].append(comp)
|
|
508
|
-
|
|
509
|
-
for comp_type, comps in sorted(by_type.items()):
|
|
510
|
-
print(f"\n{comp_type.upper()} COMPONENTS ({len(comps)}):")
|
|
511
|
-
for comp in comps:
|
|
512
|
-
hooks_str = f" [hooks: {', '.join(comp.has_hooks[:3])}]" if comp.has_hooks else ""
|
|
513
|
-
state_str = " [stateful]" if comp.has_state else ""
|
|
514
|
-
print(f" - {comp.name}{hooks_str}{state_str}")
|
|
515
|
-
print(f" {comp.file_path}")
|
|
516
|
-
|
|
517
|
-
print('')
|
|
518
|
-
print("=" * 60)
|
|
519
|
-
print(f"Total: {len(components)} components")
|
|
520
|
-
print("=" * 60)
|
|
521
|
-
|
|
522
|
-
self.results['components'] = [asdict(c) for c in components]
|
|
523
|
-
self.results['summary'] = {
|
|
524
|
-
'total_components': len(components),
|
|
525
|
-
'by_type': {k: len(v) for k, v in by_type.items()}
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
def main():
|
|
530
|
-
"""Main entry point"""
|
|
531
|
-
parser = argparse.ArgumentParser(
|
|
532
|
-
description="Generate Jest + React Testing Library test stubs for React components",
|
|
533
|
-
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
534
|
-
epilog="""
|
|
535
|
-
Examples:
|
|
536
|
-
# Scan and generate tests
|
|
537
|
-
python test_suite_generator.py src/components/ --output __tests__/
|
|
538
|
-
|
|
539
|
-
# Scan only (don't generate)
|
|
540
|
-
python test_suite_generator.py src/components/ --scan-only
|
|
541
|
-
|
|
542
|
-
# Include accessibility tests
|
|
543
|
-
python test_suite_generator.py src/ --include-a11y --output tests/
|
|
544
|
-
|
|
545
|
-
# Verbose output
|
|
546
|
-
python test_suite_generator.py src/components/ -v
|
|
547
|
-
"""
|
|
548
|
-
)
|
|
549
|
-
parser.add_argument(
|
|
550
|
-
'source',
|
|
551
|
-
help='Source directory containing React components'
|
|
552
|
-
)
|
|
553
|
-
parser.add_argument(
|
|
554
|
-
'--output', '-o',
|
|
555
|
-
help='Output directory for test files (default: <source>/__tests__/)'
|
|
556
|
-
)
|
|
557
|
-
parser.add_argument(
|
|
558
|
-
'--include-a11y',
|
|
559
|
-
action='store_true',
|
|
560
|
-
help='Include accessibility tests using jest-axe'
|
|
561
|
-
)
|
|
562
|
-
parser.add_argument(
|
|
563
|
-
'--scan-only',
|
|
564
|
-
action='store_true',
|
|
565
|
-
help='Scan and report components without generating tests'
|
|
566
|
-
)
|
|
567
|
-
parser.add_argument(
|
|
568
|
-
'--template',
|
|
569
|
-
help='Custom template file for test generation'
|
|
570
|
-
)
|
|
571
|
-
parser.add_argument(
|
|
572
|
-
'--verbose', '-v',
|
|
573
|
-
action='store_true',
|
|
574
|
-
help='Enable verbose output'
|
|
575
|
-
)
|
|
576
|
-
parser.add_argument(
|
|
577
|
-
'--json',
|
|
578
|
-
action='store_true',
|
|
579
|
-
help='Output results as JSON'
|
|
580
|
-
)
|
|
581
|
-
|
|
582
|
-
args = parser.parse_args()
|
|
583
|
-
|
|
584
|
-
try:
|
|
585
|
-
generator = TestSuiteGenerator(
|
|
586
|
-
args.source,
|
|
587
|
-
output_path=args.output,
|
|
588
|
-
include_a11y=args.include_a11y,
|
|
589
|
-
scan_only=args.scan_only,
|
|
590
|
-
verbose=args.verbose,
|
|
591
|
-
template=args.template
|
|
592
|
-
)
|
|
593
|
-
|
|
594
|
-
results = generator.run()
|
|
595
|
-
|
|
596
|
-
if args.json:
|
|
597
|
-
print(json.dumps(results, indent=2))
|
|
598
|
-
|
|
599
|
-
except Exception as e:
|
|
600
|
-
print(f"Error: {e}")
|
|
601
|
-
sys.exit(1)
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
if __name__ == '__main__':
|
|
605
|
-
main()
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Test Suite Generator
|
|
4
|
+
|
|
5
|
+
Scans React/TypeScript components and generates Jest + React Testing Library
|
|
6
|
+
test stubs with proper structure, accessibility tests, and common patterns.
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
python test_suite_generator.py src/components/ --output __tests__/
|
|
10
|
+
python test_suite_generator.py src/ --include-a11y --scan-only
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import os
|
|
14
|
+
import sys
|
|
15
|
+
import json
|
|
16
|
+
import argparse
|
|
17
|
+
import re
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import Dict, List, Optional, Tuple, Set
|
|
20
|
+
from dataclasses import dataclass, field, asdict
|
|
21
|
+
from datetime import datetime
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class ComponentInfo:
|
|
26
|
+
"""Information about a detected React component"""
|
|
27
|
+
name: str
|
|
28
|
+
file_path: str
|
|
29
|
+
component_type: str # 'functional', 'class', 'forwardRef', 'memo'
|
|
30
|
+
has_props: bool
|
|
31
|
+
props: List[str]
|
|
32
|
+
has_hooks: List[str]
|
|
33
|
+
has_context: bool
|
|
34
|
+
has_effects: bool
|
|
35
|
+
has_state: bool
|
|
36
|
+
has_callbacks: bool
|
|
37
|
+
exports: List[str]
|
|
38
|
+
imports: List[str]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class TestCase:
|
|
43
|
+
"""A single test case to generate"""
|
|
44
|
+
name: str
|
|
45
|
+
description: str
|
|
46
|
+
test_type: str # 'render', 'interaction', 'a11y', 'props', 'state'
|
|
47
|
+
code: str
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass
|
|
51
|
+
class TestFile:
|
|
52
|
+
"""A complete test file to generate"""
|
|
53
|
+
component: ComponentInfo
|
|
54
|
+
test_cases: List[TestCase] = field(default_factory=list)
|
|
55
|
+
imports: Set[str] = field(default_factory=set)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class ComponentScanner:
|
|
59
|
+
"""Scans source files for React components"""
|
|
60
|
+
|
|
61
|
+
# Patterns for detecting React components
|
|
62
|
+
FUNCTIONAL_COMPONENT = re.compile(
|
|
63
|
+
r'^(?:export\s+)?(?:const|function)\s+([A-Z][a-zA-Z0-9]*)\s*[=:]?\s*(?:\([^)]*\)\s*(?::\s*[^=]+)?\s*=>|function\s*\([^)]*\))',
|
|
64
|
+
re.MULTILINE
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
ARROW_COMPONENT = re.compile(
|
|
68
|
+
r'^(?:export\s+)?const\s+([A-Z][a-zA-Z0-9]*)\s*=\s*(?:React\.)?(?:memo|forwardRef)?\s*\(',
|
|
69
|
+
re.MULTILINE
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
CLASS_COMPONENT = re.compile(
|
|
73
|
+
r'^(?:export\s+)?class\s+([A-Z][a-zA-Z0-9]*)\s+extends\s+(?:React\.)?(?:Component|PureComponent)',
|
|
74
|
+
re.MULTILINE
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
HOOK_PATTERN = re.compile(r'use([A-Z][a-zA-Z0-9]*)\s*\(')
|
|
78
|
+
PROPS_PATTERN = re.compile(r'(?:props\.|{\s*([^}]+)\s*}\s*=\s*props|:\s*([A-Z][a-zA-Z0-9]*Props))')
|
|
79
|
+
CONTEXT_PATTERN = re.compile(r'useContext\s*\(|\.Provider|\.Consumer')
|
|
80
|
+
EFFECT_PATTERN = re.compile(r'useEffect\s*\(|useLayoutEffect\s*\(')
|
|
81
|
+
STATE_PATTERN = re.compile(r'useState\s*\(|useReducer\s*\(|this\.state')
|
|
82
|
+
CALLBACK_PATTERN = re.compile(r'on[A-Z][a-zA-Z]*\s*[=:]|handle[A-Z][a-zA-Z]*\s*[=:]')
|
|
83
|
+
|
|
84
|
+
def __init__(self, source_path: Path, verbose: bool = False):
|
|
85
|
+
self.source_path = source_path
|
|
86
|
+
self.verbose = verbose
|
|
87
|
+
self.components: List[ComponentInfo] = []
|
|
88
|
+
|
|
89
|
+
def scan(self) -> List[ComponentInfo]:
|
|
90
|
+
"""Scan the source path for React components"""
|
|
91
|
+
extensions = {'.tsx', '.jsx', '.ts', '.js'}
|
|
92
|
+
|
|
93
|
+
for root, dirs, files in os.walk(self.source_path):
|
|
94
|
+
# Skip node_modules and test directories
|
|
95
|
+
dirs[:] = [d for d in dirs if d not in {'node_modules', '__tests__', 'test', 'tests', '.git'}]
|
|
96
|
+
|
|
97
|
+
for file in files:
|
|
98
|
+
if Path(file).suffix in extensions:
|
|
99
|
+
file_path = Path(root) / file
|
|
100
|
+
self._scan_file(file_path)
|
|
101
|
+
|
|
102
|
+
return self.components
|
|
103
|
+
|
|
104
|
+
def _scan_file(self, file_path: Path):
|
|
105
|
+
"""Scan a single file for components"""
|
|
106
|
+
try:
|
|
107
|
+
content = file_path.read_text(encoding='utf-8')
|
|
108
|
+
except Exception as e:
|
|
109
|
+
if self.verbose:
|
|
110
|
+
print(f"Warning: Could not read {file_path}: {e}")
|
|
111
|
+
return
|
|
112
|
+
|
|
113
|
+
# Skip test files
|
|
114
|
+
if '.test.' in file_path.name or '.spec.' in file_path.name:
|
|
115
|
+
return
|
|
116
|
+
|
|
117
|
+
# Skip files without JSX indicators
|
|
118
|
+
if 'return' not in content or ('<' not in content and 'jsx' not in content.lower()):
|
|
119
|
+
# Could still be a hook
|
|
120
|
+
if not self.HOOK_PATTERN.search(content):
|
|
121
|
+
return
|
|
122
|
+
|
|
123
|
+
# Find functional components
|
|
124
|
+
for match in self.FUNCTIONAL_COMPONENT.finditer(content):
|
|
125
|
+
name = match.group(1)
|
|
126
|
+
self._add_component(name, file_path, content, 'functional')
|
|
127
|
+
|
|
128
|
+
# Find arrow function components
|
|
129
|
+
for match in self.ARROW_COMPONENT.finditer(content):
|
|
130
|
+
name = match.group(1)
|
|
131
|
+
component_type = 'functional'
|
|
132
|
+
if 'memo(' in content:
|
|
133
|
+
component_type = 'memo'
|
|
134
|
+
elif 'forwardRef(' in content:
|
|
135
|
+
component_type = 'forwardRef'
|
|
136
|
+
self._add_component(name, file_path, content, component_type)
|
|
137
|
+
|
|
138
|
+
# Find class components
|
|
139
|
+
for match in self.CLASS_COMPONENT.finditer(content):
|
|
140
|
+
name = match.group(1)
|
|
141
|
+
self._add_component(name, file_path, content, 'class')
|
|
142
|
+
|
|
143
|
+
def _add_component(self, name: str, file_path: Path, content: str, component_type: str):
|
|
144
|
+
"""Add a component to the list if not already present"""
|
|
145
|
+
# Check if already added
|
|
146
|
+
for comp in self.components:
|
|
147
|
+
if comp.name == name and comp.file_path == str(file_path):
|
|
148
|
+
return
|
|
149
|
+
|
|
150
|
+
# Extract hooks used
|
|
151
|
+
hooks = list(set(self.HOOK_PATTERN.findall(content)))
|
|
152
|
+
|
|
153
|
+
# Extract prop names (simplified)
|
|
154
|
+
props = []
|
|
155
|
+
props_match = self.PROPS_PATTERN.search(content)
|
|
156
|
+
if props_match:
|
|
157
|
+
props_str = props_match.group(1) or ''
|
|
158
|
+
props = [p.strip().split(':')[0].strip() for p in props_str.split(',') if p.strip()]
|
|
159
|
+
|
|
160
|
+
# Extract imports
|
|
161
|
+
imports = re.findall(r"import\s+(?:{[^}]+}|[^;]+)\s+from\s+['\"]([^'\"]+)['\"]", content)
|
|
162
|
+
|
|
163
|
+
# Extract exports
|
|
164
|
+
exports = re.findall(r"export\s+(?:default\s+)?(?:const|function|class)\s+(\w+)", content)
|
|
165
|
+
|
|
166
|
+
component = ComponentInfo(
|
|
167
|
+
name=name,
|
|
168
|
+
file_path=str(file_path),
|
|
169
|
+
component_type=component_type,
|
|
170
|
+
has_props=bool(props) or 'props' in content.lower(),
|
|
171
|
+
props=props[:10], # Limit props
|
|
172
|
+
has_hooks=hooks[:10], # Limit hooks
|
|
173
|
+
has_context=bool(self.CONTEXT_PATTERN.search(content)),
|
|
174
|
+
has_effects=bool(self.EFFECT_PATTERN.search(content)),
|
|
175
|
+
has_state=bool(self.STATE_PATTERN.search(content)),
|
|
176
|
+
has_callbacks=bool(self.CALLBACK_PATTERN.search(content)),
|
|
177
|
+
exports=exports[:5],
|
|
178
|
+
imports=imports[:10]
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
self.components.append(component)
|
|
182
|
+
|
|
183
|
+
if self.verbose:
|
|
184
|
+
print(f" Found: {name} ({component_type}) in {file_path.name}")
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
class TestGenerator:
|
|
188
|
+
"""Generates Jest + React Testing Library test files"""
|
|
189
|
+
|
|
190
|
+
def __init__(self, include_a11y: bool = False, template: Optional[str] = None):
|
|
191
|
+
self.include_a11y = include_a11y
|
|
192
|
+
self.template = template
|
|
193
|
+
|
|
194
|
+
def generate(self, component: ComponentInfo) -> TestFile:
|
|
195
|
+
"""Generate a test file for a component"""
|
|
196
|
+
test_file = TestFile(component=component)
|
|
197
|
+
|
|
198
|
+
# Build imports
|
|
199
|
+
test_file.imports.add("import { render, screen } from '@testing-library/react';")
|
|
200
|
+
|
|
201
|
+
if component.has_callbacks:
|
|
202
|
+
test_file.imports.add("import userEvent from '@testing-library/user-event';")
|
|
203
|
+
|
|
204
|
+
if component.has_effects or component.has_state:
|
|
205
|
+
test_file.imports.add("import { waitFor } from '@testing-library/react';")
|
|
206
|
+
|
|
207
|
+
if self.include_a11y:
|
|
208
|
+
test_file.imports.add("import { axe, toHaveNoViolations } from 'jest-axe';")
|
|
209
|
+
|
|
210
|
+
# Add component import
|
|
211
|
+
relative_path = self._get_relative_import(component.file_path)
|
|
212
|
+
test_file.imports.add(f"import {{ {component.name} }} from '{relative_path}';")
|
|
213
|
+
|
|
214
|
+
# Generate test cases
|
|
215
|
+
test_file.test_cases.append(self._generate_render_test(component))
|
|
216
|
+
|
|
217
|
+
if component.has_props:
|
|
218
|
+
test_file.test_cases.append(self._generate_props_test(component))
|
|
219
|
+
|
|
220
|
+
if component.has_callbacks:
|
|
221
|
+
test_file.test_cases.append(self._generate_interaction_test(component))
|
|
222
|
+
|
|
223
|
+
if component.has_state:
|
|
224
|
+
test_file.test_cases.append(self._generate_state_test(component))
|
|
225
|
+
|
|
226
|
+
if self.include_a11y:
|
|
227
|
+
test_file.test_cases.append(self._generate_a11y_test(component))
|
|
228
|
+
|
|
229
|
+
return test_file
|
|
230
|
+
|
|
231
|
+
def _get_relative_import(self, file_path: str) -> str:
|
|
232
|
+
"""Get the relative import path for a component"""
|
|
233
|
+
path = Path(file_path)
|
|
234
|
+
# Remove extension
|
|
235
|
+
stem = path.stem
|
|
236
|
+
if stem == 'index':
|
|
237
|
+
return f"../{path.parent.name}"
|
|
238
|
+
return f"../{path.parent.name}/{stem}"
|
|
239
|
+
|
|
240
|
+
def _generate_render_test(self, component: ComponentInfo) -> TestCase:
|
|
241
|
+
"""Generate a basic render test"""
|
|
242
|
+
props_str = self._get_mock_props(component)
|
|
243
|
+
|
|
244
|
+
code = f''' it('renders without crashing', () => {{
|
|
245
|
+
render(<{component.name}{props_str} />);
|
|
246
|
+
}});
|
|
247
|
+
|
|
248
|
+
it('renders expected content', () => {{
|
|
249
|
+
render(<{component.name}{props_str} />);
|
|
250
|
+
// TODO: Add specific content assertions
|
|
251
|
+
// expect(screen.getByRole('...')).toBeInTheDocument();
|
|
252
|
+
}});'''
|
|
253
|
+
|
|
254
|
+
return TestCase(
|
|
255
|
+
name='render',
|
|
256
|
+
description='Basic render tests',
|
|
257
|
+
test_type='render',
|
|
258
|
+
code=code
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
def _generate_props_test(self, component: ComponentInfo) -> TestCase:
|
|
262
|
+
"""Generate props-related tests"""
|
|
263
|
+
props = component.props[:3] if component.props else ['prop1']
|
|
264
|
+
|
|
265
|
+
prop_tests = []
|
|
266
|
+
for prop in props:
|
|
267
|
+
prop_tests.append(f''' it('renders with {prop} prop', () => {{
|
|
268
|
+
render(<{component.name} {prop}="test-value" />);
|
|
269
|
+
// TODO: Assert that {prop} affects rendering
|
|
270
|
+
}});''')
|
|
271
|
+
|
|
272
|
+
code = '\n\n'.join(prop_tests)
|
|
273
|
+
|
|
274
|
+
return TestCase(
|
|
275
|
+
name='props',
|
|
276
|
+
description='Props handling tests',
|
|
277
|
+
test_type='props',
|
|
278
|
+
code=code
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
def _generate_interaction_test(self, component: ComponentInfo) -> TestCase:
|
|
282
|
+
"""Generate user interaction tests"""
|
|
283
|
+
code = f''' it('handles user interaction', async () => {{
|
|
284
|
+
const user = userEvent.setup();
|
|
285
|
+
const handleClick = jest.fn();
|
|
286
|
+
|
|
287
|
+
render(<{component.name} onClick={{handleClick}} />);
|
|
288
|
+
|
|
289
|
+
// TODO: Find the interactive element
|
|
290
|
+
const button = screen.getByRole('button');
|
|
291
|
+
await user.click(button);
|
|
292
|
+
|
|
293
|
+
expect(handleClick).toHaveBeenCalledTimes(1);
|
|
294
|
+
}});
|
|
295
|
+
|
|
296
|
+
it('handles keyboard navigation', async () => {{
|
|
297
|
+
const user = userEvent.setup();
|
|
298
|
+
render(<{component.name} />);
|
|
299
|
+
|
|
300
|
+
// TODO: Add keyboard interaction tests
|
|
301
|
+
// await user.tab();
|
|
302
|
+
// expect(screen.getByRole('...')).toHaveFocus();
|
|
303
|
+
}});'''
|
|
304
|
+
|
|
305
|
+
return TestCase(
|
|
306
|
+
name='interaction',
|
|
307
|
+
description='User interaction tests',
|
|
308
|
+
test_type='interaction',
|
|
309
|
+
code=code
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
def _generate_state_test(self, component: ComponentInfo) -> TestCase:
|
|
313
|
+
"""Generate state-related tests"""
|
|
314
|
+
code = f''' it('updates state correctly', async () => {{
|
|
315
|
+
const user = userEvent.setup();
|
|
316
|
+
render(<{component.name} />);
|
|
317
|
+
|
|
318
|
+
// TODO: Trigger state change
|
|
319
|
+
// await user.click(screen.getByRole('button'));
|
|
320
|
+
|
|
321
|
+
// TODO: Assert state change is reflected in UI
|
|
322
|
+
await waitFor(() => {{
|
|
323
|
+
// expect(screen.getByText('...')).toBeInTheDocument();
|
|
324
|
+
}});
|
|
325
|
+
}});'''
|
|
326
|
+
|
|
327
|
+
return TestCase(
|
|
328
|
+
name='state',
|
|
329
|
+
description='State management tests',
|
|
330
|
+
test_type='state',
|
|
331
|
+
code=code
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
def _generate_a11y_test(self, component: ComponentInfo) -> TestCase:
|
|
335
|
+
"""Generate accessibility test"""
|
|
336
|
+
props_str = self._get_mock_props(component)
|
|
337
|
+
|
|
338
|
+
code = f''' it('has no accessibility violations', async () => {{
|
|
339
|
+
const {{ container }} = render(<{component.name}{props_str} />);
|
|
340
|
+
const results = await axe(container);
|
|
341
|
+
expect(results).toHaveNoViolations();
|
|
342
|
+
}});'''
|
|
343
|
+
|
|
344
|
+
return TestCase(
|
|
345
|
+
name='accessibility',
|
|
346
|
+
description='Accessibility tests',
|
|
347
|
+
test_type='a11y',
|
|
348
|
+
code=code
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
def _get_mock_props(self, component: ComponentInfo) -> str:
|
|
352
|
+
"""Generate mock props string for a component"""
|
|
353
|
+
if not component.has_props or not component.props:
|
|
354
|
+
return ''
|
|
355
|
+
|
|
356
|
+
# Return empty for simplicity, user should fill in
|
|
357
|
+
return ' {...mockProps}'
|
|
358
|
+
|
|
359
|
+
def format_test_file(self, test_file: TestFile) -> str:
|
|
360
|
+
"""Format the complete test file content"""
|
|
361
|
+
lines = []
|
|
362
|
+
|
|
363
|
+
# Imports
|
|
364
|
+
lines.append("import '@testing-library/jest-dom';")
|
|
365
|
+
for imp in sorted(test_file.imports):
|
|
366
|
+
lines.append(imp)
|
|
367
|
+
|
|
368
|
+
lines.append('')
|
|
369
|
+
|
|
370
|
+
# A11y setup if needed
|
|
371
|
+
if self.include_a11y:
|
|
372
|
+
lines.append('expect.extend(toHaveNoViolations);')
|
|
373
|
+
lines.append('')
|
|
374
|
+
|
|
375
|
+
# Mock props if component has props
|
|
376
|
+
if test_file.component.has_props:
|
|
377
|
+
lines.append('// TODO: Define mock props')
|
|
378
|
+
lines.append('const mockProps = {};')
|
|
379
|
+
lines.append('')
|
|
380
|
+
|
|
381
|
+
# Describe block
|
|
382
|
+
lines.append(f"describe('{test_file.component.name}', () => {{")
|
|
383
|
+
|
|
384
|
+
# Test cases grouped by type
|
|
385
|
+
test_types = {}
|
|
386
|
+
for test_case in test_file.test_cases:
|
|
387
|
+
if test_case.test_type not in test_types:
|
|
388
|
+
test_types[test_case.test_type] = []
|
|
389
|
+
test_types[test_case.test_type].append(test_case)
|
|
390
|
+
|
|
391
|
+
for test_type, cases in test_types.items():
|
|
392
|
+
for case in cases:
|
|
393
|
+
lines.append('')
|
|
394
|
+
lines.append(f' // {case.description}')
|
|
395
|
+
lines.append(case.code)
|
|
396
|
+
|
|
397
|
+
lines.append('});')
|
|
398
|
+
lines.append('')
|
|
399
|
+
|
|
400
|
+
return '\n'.join(lines)
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
class TestSuiteGenerator:
|
|
404
|
+
"""Main class for generating test suites"""
|
|
405
|
+
|
|
406
|
+
def __init__(
|
|
407
|
+
self,
|
|
408
|
+
source_path: str,
|
|
409
|
+
output_path: Optional[str] = None,
|
|
410
|
+
include_a11y: bool = False,
|
|
411
|
+
scan_only: bool = False,
|
|
412
|
+
verbose: bool = False,
|
|
413
|
+
template: Optional[str] = None
|
|
414
|
+
):
|
|
415
|
+
self.source_path = Path(source_path)
|
|
416
|
+
self.output_path = Path(output_path) if output_path else None
|
|
417
|
+
self.include_a11y = include_a11y
|
|
418
|
+
self.scan_only = scan_only
|
|
419
|
+
self.verbose = verbose
|
|
420
|
+
self.template = template
|
|
421
|
+
self.results = {
|
|
422
|
+
'status': 'success',
|
|
423
|
+
'source': str(self.source_path),
|
|
424
|
+
'components': [],
|
|
425
|
+
'generated_files': [],
|
|
426
|
+
'summary': {}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
def run(self) -> Dict:
|
|
430
|
+
"""Execute the test suite generation"""
|
|
431
|
+
print(f"Scanning: {self.source_path}")
|
|
432
|
+
|
|
433
|
+
# Validate source path
|
|
434
|
+
if not self.source_path.exists():
|
|
435
|
+
raise ValueError(f"Source path does not exist: {self.source_path}")
|
|
436
|
+
|
|
437
|
+
# Scan for components
|
|
438
|
+
scanner = ComponentScanner(self.source_path, self.verbose)
|
|
439
|
+
components = scanner.scan()
|
|
440
|
+
|
|
441
|
+
print(f"Found {len(components)} React components")
|
|
442
|
+
|
|
443
|
+
if self.scan_only:
|
|
444
|
+
self._report_scan_results(components)
|
|
445
|
+
return self.results
|
|
446
|
+
|
|
447
|
+
# Generate tests
|
|
448
|
+
if not self.output_path:
|
|
449
|
+
# Default to __tests__ in source directory
|
|
450
|
+
self.output_path = self.source_path / '__tests__'
|
|
451
|
+
|
|
452
|
+
self.output_path.mkdir(parents=True, exist_ok=True)
|
|
453
|
+
|
|
454
|
+
generator = TestGenerator(self.include_a11y, self.template)
|
|
455
|
+
|
|
456
|
+
total_tests = 0
|
|
457
|
+
for component in components:
|
|
458
|
+
test_file = generator.generate(component)
|
|
459
|
+
content = generator.format_test_file(test_file)
|
|
460
|
+
|
|
461
|
+
# Write test file
|
|
462
|
+
test_filename = f"{component.name}.test.tsx"
|
|
463
|
+
test_path = self.output_path / test_filename
|
|
464
|
+
|
|
465
|
+
test_path.write_text(content, encoding='utf-8')
|
|
466
|
+
|
|
467
|
+
test_count = len(test_file.test_cases)
|
|
468
|
+
total_tests += test_count
|
|
469
|
+
|
|
470
|
+
self.results['generated_files'].append({
|
|
471
|
+
'component': component.name,
|
|
472
|
+
'path': str(test_path),
|
|
473
|
+
'test_cases': test_count
|
|
474
|
+
})
|
|
475
|
+
|
|
476
|
+
print(f" {test_filename} ({test_count} test cases)")
|
|
477
|
+
|
|
478
|
+
# Store component info
|
|
479
|
+
self.results['components'] = [asdict(c) for c in components]
|
|
480
|
+
|
|
481
|
+
# Summary
|
|
482
|
+
self.results['summary'] = {
|
|
483
|
+
'total_components': len(components),
|
|
484
|
+
'total_files': len(self.results['generated_files']),
|
|
485
|
+
'total_test_cases': total_tests,
|
|
486
|
+
'output_directory': str(self.output_path)
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
print('')
|
|
490
|
+
print(f"Summary: {len(components)} test files, {total_tests} test cases")
|
|
491
|
+
|
|
492
|
+
return self.results
|
|
493
|
+
|
|
494
|
+
def _report_scan_results(self, components: List[ComponentInfo]):
|
|
495
|
+
"""Report scan results without generating tests"""
|
|
496
|
+
print('')
|
|
497
|
+
print("=" * 60)
|
|
498
|
+
print("COMPONENT SCAN RESULTS")
|
|
499
|
+
print("=" * 60)
|
|
500
|
+
|
|
501
|
+
# Group by type
|
|
502
|
+
by_type = {}
|
|
503
|
+
for comp in components:
|
|
504
|
+
comp_type = comp.component_type
|
|
505
|
+
if comp_type not in by_type:
|
|
506
|
+
by_type[comp_type] = []
|
|
507
|
+
by_type[comp_type].append(comp)
|
|
508
|
+
|
|
509
|
+
for comp_type, comps in sorted(by_type.items()):
|
|
510
|
+
print(f"\n{comp_type.upper()} COMPONENTS ({len(comps)}):")
|
|
511
|
+
for comp in comps:
|
|
512
|
+
hooks_str = f" [hooks: {', '.join(comp.has_hooks[:3])}]" if comp.has_hooks else ""
|
|
513
|
+
state_str = " [stateful]" if comp.has_state else ""
|
|
514
|
+
print(f" - {comp.name}{hooks_str}{state_str}")
|
|
515
|
+
print(f" {comp.file_path}")
|
|
516
|
+
|
|
517
|
+
print('')
|
|
518
|
+
print("=" * 60)
|
|
519
|
+
print(f"Total: {len(components)} components")
|
|
520
|
+
print("=" * 60)
|
|
521
|
+
|
|
522
|
+
self.results['components'] = [asdict(c) for c in components]
|
|
523
|
+
self.results['summary'] = {
|
|
524
|
+
'total_components': len(components),
|
|
525
|
+
'by_type': {k: len(v) for k, v in by_type.items()}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
def main():
|
|
530
|
+
"""Main entry point"""
|
|
531
|
+
parser = argparse.ArgumentParser(
|
|
532
|
+
description="Generate Jest + React Testing Library test stubs for React components",
|
|
533
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
534
|
+
epilog="""
|
|
535
|
+
Examples:
|
|
536
|
+
# Scan and generate tests
|
|
537
|
+
python test_suite_generator.py src/components/ --output __tests__/
|
|
538
|
+
|
|
539
|
+
# Scan only (don't generate)
|
|
540
|
+
python test_suite_generator.py src/components/ --scan-only
|
|
541
|
+
|
|
542
|
+
# Include accessibility tests
|
|
543
|
+
python test_suite_generator.py src/ --include-a11y --output tests/
|
|
544
|
+
|
|
545
|
+
# Verbose output
|
|
546
|
+
python test_suite_generator.py src/components/ -v
|
|
547
|
+
"""
|
|
548
|
+
)
|
|
549
|
+
parser.add_argument(
|
|
550
|
+
'source',
|
|
551
|
+
help='Source directory containing React components'
|
|
552
|
+
)
|
|
553
|
+
parser.add_argument(
|
|
554
|
+
'--output', '-o',
|
|
555
|
+
help='Output directory for test files (default: <source>/__tests__/)'
|
|
556
|
+
)
|
|
557
|
+
parser.add_argument(
|
|
558
|
+
'--include-a11y',
|
|
559
|
+
action='store_true',
|
|
560
|
+
help='Include accessibility tests using jest-axe'
|
|
561
|
+
)
|
|
562
|
+
parser.add_argument(
|
|
563
|
+
'--scan-only',
|
|
564
|
+
action='store_true',
|
|
565
|
+
help='Scan and report components without generating tests'
|
|
566
|
+
)
|
|
567
|
+
parser.add_argument(
|
|
568
|
+
'--template',
|
|
569
|
+
help='Custom template file for test generation'
|
|
570
|
+
)
|
|
571
|
+
parser.add_argument(
|
|
572
|
+
'--verbose', '-v',
|
|
573
|
+
action='store_true',
|
|
574
|
+
help='Enable verbose output'
|
|
575
|
+
)
|
|
576
|
+
parser.add_argument(
|
|
577
|
+
'--json',
|
|
578
|
+
action='store_true',
|
|
579
|
+
help='Output results as JSON'
|
|
580
|
+
)
|
|
581
|
+
|
|
582
|
+
args = parser.parse_args()
|
|
583
|
+
|
|
584
|
+
try:
|
|
585
|
+
generator = TestSuiteGenerator(
|
|
586
|
+
args.source,
|
|
587
|
+
output_path=args.output,
|
|
588
|
+
include_a11y=args.include_a11y,
|
|
589
|
+
scan_only=args.scan_only,
|
|
590
|
+
verbose=args.verbose,
|
|
591
|
+
template=args.template
|
|
592
|
+
)
|
|
593
|
+
|
|
594
|
+
results = generator.run()
|
|
595
|
+
|
|
596
|
+
if args.json:
|
|
597
|
+
print(json.dumps(results, indent=2))
|
|
598
|
+
|
|
599
|
+
except Exception as e:
|
|
600
|
+
print(f"Error: {e}")
|
|
601
|
+
sys.exit(1)
|
|
602
|
+
|
|
603
|
+
|
|
604
|
+
if __name__ == '__main__':
|
|
605
|
+
main()
|