aigroup-workflow 2.2.0 → 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 +11 -10
- package/package.json +40 -39
- package/scripts/hooks/checks/orchestration-artifacts.cjs +28 -23
- package/scripts/hooks/checks/workflow-state.cjs +4 -5
- package/scripts/orchestration/lib/orchestrator.cjs +344 -117
- package/scripts/orchestration/lib/validate.cjs +145 -0
- package/scripts/orchestration/session.cjs +88 -44
- 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,964 +1,964 @@
|
|
|
1
|
-
# QA Best Practices for React and Next.js
|
|
2
|
-
|
|
3
|
-
Guidelines for writing maintainable tests, debugging failures, and measuring test quality.
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## Table of Contents
|
|
8
|
-
|
|
9
|
-
- [Writing Testable Code](#writing-testable-code)
|
|
10
|
-
- [Test Naming Conventions](#test-naming-conventions)
|
|
11
|
-
- [Arrange-Act-Assert Pattern](#arrange-act-assert-pattern)
|
|
12
|
-
- [Test Isolation Principles](#test-isolation-principles)
|
|
13
|
-
- [Handling Flaky Tests](#handling-flaky-tests)
|
|
14
|
-
- [Code Review for Testability](#code-review-for-testability)
|
|
15
|
-
- [Test Maintenance Strategies](#test-maintenance-strategies)
|
|
16
|
-
- [Debugging Failed Tests](#debugging-failed-tests)
|
|
17
|
-
- [Quality Metrics and KPIs](#quality-metrics-and-kpis)
|
|
18
|
-
|
|
19
|
-
---
|
|
20
|
-
|
|
21
|
-
## Writing Testable Code
|
|
22
|
-
|
|
23
|
-
Testable code is easy to understand, has clear boundaries, and minimizes dependencies.
|
|
24
|
-
|
|
25
|
-
### Dependency Injection
|
|
26
|
-
|
|
27
|
-
Instead of creating dependencies inside functions, pass them as parameters.
|
|
28
|
-
|
|
29
|
-
**Hard to Test:**
|
|
30
|
-
|
|
31
|
-
```typescript
|
|
32
|
-
// src/services/userService.ts
|
|
33
|
-
import { prisma } from '../lib/prisma';
|
|
34
|
-
import { sendEmail } from '../lib/email';
|
|
35
|
-
|
|
36
|
-
export async function createUser(data: UserInput) {
|
|
37
|
-
const user = await prisma.user.create({ data });
|
|
38
|
-
await sendEmail(user.email, 'Welcome!');
|
|
39
|
-
return user;
|
|
40
|
-
}
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
**Easy to Test:**
|
|
44
|
-
|
|
45
|
-
```typescript
|
|
46
|
-
// src/services/userService.ts
|
|
47
|
-
export function createUserService(
|
|
48
|
-
db: PrismaClient,
|
|
49
|
-
emailService: EmailService
|
|
50
|
-
) {
|
|
51
|
-
return {
|
|
52
|
-
async createUser(data: UserInput) {
|
|
53
|
-
const user = await db.user.create({ data });
|
|
54
|
-
await emailService.send(user.email, 'Welcome!');
|
|
55
|
-
return user;
|
|
56
|
-
},
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Usage in app
|
|
61
|
-
const userService = createUserService(prisma, emailService);
|
|
62
|
-
|
|
63
|
-
// Usage in tests
|
|
64
|
-
const mockDb = { user: { create: jest.fn() } };
|
|
65
|
-
const mockEmail = { send: jest.fn() };
|
|
66
|
-
const testService = createUserService(mockDb, mockEmail);
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
### Pure Functions
|
|
70
|
-
|
|
71
|
-
Pure functions are deterministic and have no side effects, making them trivial to test.
|
|
72
|
-
|
|
73
|
-
**Impure (Hard to Test):**
|
|
74
|
-
|
|
75
|
-
```typescript
|
|
76
|
-
function formatTimestamp() {
|
|
77
|
-
const now = new Date();
|
|
78
|
-
return `${now.getFullYear()}-${now.getMonth() + 1}-${now.getDate()}`;
|
|
79
|
-
}
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
**Pure (Easy to Test):**
|
|
83
|
-
|
|
84
|
-
```typescript
|
|
85
|
-
function formatTimestamp(date: Date): string {
|
|
86
|
-
return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Test
|
|
90
|
-
expect(formatTimestamp(new Date('2024-03-15'))).toBe('2024-3-15');
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
### Separation of Concerns
|
|
94
|
-
|
|
95
|
-
Separate business logic from UI and I/O operations.
|
|
96
|
-
|
|
97
|
-
**Mixed Concerns (Hard to Test):**
|
|
98
|
-
|
|
99
|
-
```typescript
|
|
100
|
-
// Component with embedded business logic
|
|
101
|
-
function CheckoutForm() {
|
|
102
|
-
const [total, setTotal] = useState(0);
|
|
103
|
-
|
|
104
|
-
const handleSubmit = async (items: CartItem[]) => {
|
|
105
|
-
// Business logic mixed with UI
|
|
106
|
-
let sum = 0;
|
|
107
|
-
for (const item of items) {
|
|
108
|
-
sum += item.price * item.quantity;
|
|
109
|
-
if (item.category === 'electronics') {
|
|
110
|
-
sum *= 0.9; // 10% discount
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
const tax = sum * 0.08;
|
|
114
|
-
const finalTotal = sum + tax;
|
|
115
|
-
|
|
116
|
-
// API call
|
|
117
|
-
await fetch('/api/orders', {
|
|
118
|
-
method: 'POST',
|
|
119
|
-
body: JSON.stringify({ items, total: finalTotal }),
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
setTotal(finalTotal);
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
return <form onSubmit={handleSubmit}>...</form>;
|
|
126
|
-
}
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
**Separated Concerns (Easy to Test):**
|
|
130
|
-
|
|
131
|
-
```typescript
|
|
132
|
-
// Pure business logic (easy to unit test)
|
|
133
|
-
export function calculateOrderTotal(items: CartItem[]): number {
|
|
134
|
-
return items.reduce((sum, item) => {
|
|
135
|
-
const subtotal = item.price * item.quantity;
|
|
136
|
-
const discount = item.category === 'electronics' ? 0.9 : 1;
|
|
137
|
-
return sum + subtotal * discount;
|
|
138
|
-
}, 0);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
export function calculateTax(subtotal: number, rate = 0.08): number {
|
|
142
|
-
return subtotal * rate;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// Custom hook for order logic (testable with renderHook)
|
|
146
|
-
export function useCheckout() {
|
|
147
|
-
const [total, setTotal] = useState(0);
|
|
148
|
-
const mutation = useMutation(createOrder);
|
|
149
|
-
|
|
150
|
-
const checkout = async (items: CartItem[]) => {
|
|
151
|
-
const subtotal = calculateOrderTotal(items);
|
|
152
|
-
const tax = calculateTax(subtotal);
|
|
153
|
-
const finalTotal = subtotal + tax;
|
|
154
|
-
|
|
155
|
-
await mutation.mutateAsync({ items, total: finalTotal });
|
|
156
|
-
setTotal(finalTotal);
|
|
157
|
-
};
|
|
158
|
-
|
|
159
|
-
return { checkout, total, isLoading: mutation.isLoading };
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// Component (integration testable)
|
|
163
|
-
function CheckoutForm() {
|
|
164
|
-
const { checkout, total, isLoading } = useCheckout();
|
|
165
|
-
return <form onSubmit={() => checkout(items)}>...</form>;
|
|
166
|
-
}
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
### Component Design for Testability
|
|
170
|
-
|
|
171
|
-
| Pattern | Testability | Example |
|
|
172
|
-
|---------|-------------|---------|
|
|
173
|
-
| Props over context | High | `<Button disabled={!valid}>` |
|
|
174
|
-
| Callbacks over side effects | High | `onSubmit={handleSubmit}` |
|
|
175
|
-
| Controlled components | High | `<Input value={value} onChange={...}>` |
|
|
176
|
-
| Render props | Medium | `<DataProvider render={data => ...}>` |
|
|
177
|
-
| Internal state | Low | `const [x, setX] = useState()` |
|
|
178
|
-
| Global state | Low | `useGlobalStore()` |
|
|
179
|
-
|
|
180
|
-
---
|
|
181
|
-
|
|
182
|
-
## Test Naming Conventions
|
|
183
|
-
|
|
184
|
-
Good test names document expected behavior and help diagnose failures.
|
|
185
|
-
|
|
186
|
-
### Naming Patterns
|
|
187
|
-
|
|
188
|
-
**Pattern 1: should [expected behavior] when [condition]**
|
|
189
|
-
|
|
190
|
-
```typescript
|
|
191
|
-
describe('LoginForm', () => {
|
|
192
|
-
it('should display error message when credentials are invalid', () => {});
|
|
193
|
-
it('should redirect to dashboard when login succeeds', () => {});
|
|
194
|
-
it('should disable submit button when form is submitting', () => {});
|
|
195
|
-
});
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
**Pattern 2: [method/action] [expected result]**
|
|
199
|
-
|
|
200
|
-
```typescript
|
|
201
|
-
describe('calculateDiscount', () => {
|
|
202
|
-
it('returns 0 for orders under $50', () => {});
|
|
203
|
-
it('returns 10% for orders $50-$99', () => {});
|
|
204
|
-
it('returns 20% for orders $100+', () => {});
|
|
205
|
-
});
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
**Pattern 3: given [context], when [action], then [result]**
|
|
209
|
-
|
|
210
|
-
```typescript
|
|
211
|
-
describe('ShoppingCart', () => {
|
|
212
|
-
it('given an empty cart, when adding an item, then cart count is 1', () => {});
|
|
213
|
-
it('given items in cart, when removing all, then cart is empty', () => {});
|
|
214
|
-
});
|
|
215
|
-
```
|
|
216
|
-
|
|
217
|
-
### Describe Block Organization
|
|
218
|
-
|
|
219
|
-
```typescript
|
|
220
|
-
describe('UserService', () => {
|
|
221
|
-
describe('createUser', () => {
|
|
222
|
-
describe('with valid input', () => {
|
|
223
|
-
it('creates user in database', () => {});
|
|
224
|
-
it('sends welcome email', () => {});
|
|
225
|
-
it('returns user with id', () => {});
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
describe('with invalid input', () => {
|
|
229
|
-
it('throws ValidationError for missing email', () => {});
|
|
230
|
-
it('throws ValidationError for invalid email format', () => {});
|
|
231
|
-
it('throws ConflictError for duplicate email', () => {});
|
|
232
|
-
});
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
describe('deleteUser', () => {
|
|
236
|
-
it('removes user from database', () => {});
|
|
237
|
-
it('throws NotFoundError for non-existent user', () => {});
|
|
238
|
-
});
|
|
239
|
-
});
|
|
240
|
-
```
|
|
241
|
-
|
|
242
|
-
### Anti-patterns to Avoid
|
|
243
|
-
|
|
244
|
-
| Bad | Good | Why |
|
|
245
|
-
|-----|------|-----|
|
|
246
|
-
| `it('works')` | `it('returns sum of two numbers')` | Describes behavior |
|
|
247
|
-
| `it('test 1')` | `it('handles empty array')` | Specific scenario |
|
|
248
|
-
| `it('should do stuff')` | `it('should validate email format')` | Clear expectation |
|
|
249
|
-
| Duplicating code in name | Describing behavior | Readable output |
|
|
250
|
-
|
|
251
|
-
---
|
|
252
|
-
|
|
253
|
-
## Arrange-Act-Assert Pattern
|
|
254
|
-
|
|
255
|
-
The AAA pattern structures tests into three clear phases.
|
|
256
|
-
|
|
257
|
-
### Structure
|
|
258
|
-
|
|
259
|
-
```typescript
|
|
260
|
-
it('calculates total with discount', () => {
|
|
261
|
-
// Arrange - Set up test data and conditions
|
|
262
|
-
const items = [
|
|
263
|
-
{ name: 'Widget', price: 100, quantity: 2 },
|
|
264
|
-
{ name: 'Gadget', price: 50, quantity: 1 },
|
|
265
|
-
];
|
|
266
|
-
const discountRate = 0.1;
|
|
267
|
-
|
|
268
|
-
// Act - Execute the code being tested
|
|
269
|
-
const result = calculateTotal(items, discountRate);
|
|
270
|
-
|
|
271
|
-
// Assert - Verify the outcome
|
|
272
|
-
expect(result).toBe(225); // (200 + 50) * 0.9
|
|
273
|
-
});
|
|
274
|
-
```
|
|
275
|
-
|
|
276
|
-
### Async Example
|
|
277
|
-
|
|
278
|
-
```typescript
|
|
279
|
-
it('fetches user profile', async () => {
|
|
280
|
-
// Arrange
|
|
281
|
-
const userId = '123';
|
|
282
|
-
server.use(
|
|
283
|
-
rest.get('/api/users/:id', (req, res, ctx) =>
|
|
284
|
-
res(ctx.json({ id: userId, name: 'John' }))
|
|
285
|
-
)
|
|
286
|
-
);
|
|
287
|
-
|
|
288
|
-
// Act
|
|
289
|
-
render(<UserProfile userId={userId} />);
|
|
290
|
-
|
|
291
|
-
// Assert
|
|
292
|
-
await expect(screen.findByText('John')).resolves.toBeInTheDocument();
|
|
293
|
-
});
|
|
294
|
-
```
|
|
295
|
-
|
|
296
|
-
### Component Testing Example
|
|
297
|
-
|
|
298
|
-
```typescript
|
|
299
|
-
it('submits form with user input', async () => {
|
|
300
|
-
// Arrange
|
|
301
|
-
const user = userEvent.setup();
|
|
302
|
-
const onSubmit = jest.fn();
|
|
303
|
-
render(<ContactForm onSubmit={onSubmit} />);
|
|
304
|
-
|
|
305
|
-
// Act
|
|
306
|
-
await user.type(screen.getByLabelText('Name'), 'John Doe');
|
|
307
|
-
await user.type(screen.getByLabelText('Email'), 'john@example.com');
|
|
308
|
-
await user.type(screen.getByLabelText('Message'), 'Hello!');
|
|
309
|
-
await user.click(screen.getByRole('button', { name: 'Send' }));
|
|
310
|
-
|
|
311
|
-
// Assert
|
|
312
|
-
expect(onSubmit).toHaveBeenCalledWith({
|
|
313
|
-
name: 'John Doe',
|
|
314
|
-
email: 'john@example.com',
|
|
315
|
-
message: 'Hello!',
|
|
316
|
-
});
|
|
317
|
-
});
|
|
318
|
-
```
|
|
319
|
-
|
|
320
|
-
### Guidelines
|
|
321
|
-
|
|
322
|
-
1. **One Act per test** - Test one behavior at a time
|
|
323
|
-
2. **Multiple assertions OK** - If they verify the same behavior
|
|
324
|
-
3. **Avoid logic in tests** - No if/else, loops in test code
|
|
325
|
-
4. **Setup in Arrange, not beforeEach** - Unless truly shared
|
|
326
|
-
|
|
327
|
-
---
|
|
328
|
-
|
|
329
|
-
## Test Isolation Principles
|
|
330
|
-
|
|
331
|
-
Isolated tests are independent, repeatable, and can run in any order.
|
|
332
|
-
|
|
333
|
-
### State Isolation
|
|
334
|
-
|
|
335
|
-
```typescript
|
|
336
|
-
describe('CartService', () => {
|
|
337
|
-
let cartService: CartService;
|
|
338
|
-
|
|
339
|
-
// Fresh instance for each test
|
|
340
|
-
beforeEach(() => {
|
|
341
|
-
cartService = new CartService();
|
|
342
|
-
});
|
|
343
|
-
|
|
344
|
-
it('adds item to empty cart', () => {
|
|
345
|
-
cartService.addItem({ id: '1', quantity: 1 });
|
|
346
|
-
expect(cartService.getItems()).toHaveLength(1);
|
|
347
|
-
});
|
|
348
|
-
|
|
349
|
-
it('starts with empty cart', () => {
|
|
350
|
-
// Not affected by previous test
|
|
351
|
-
expect(cartService.getItems()).toHaveLength(0);
|
|
352
|
-
});
|
|
353
|
-
});
|
|
354
|
-
```
|
|
355
|
-
|
|
356
|
-
### Database Isolation
|
|
357
|
-
|
|
358
|
-
```typescript
|
|
359
|
-
describe('UserRepository', () => {
|
|
360
|
-
beforeAll(async () => {
|
|
361
|
-
// Connect to test database
|
|
362
|
-
await db.connect(process.env.TEST_DATABASE_URL);
|
|
363
|
-
});
|
|
364
|
-
|
|
365
|
-
beforeEach(async () => {
|
|
366
|
-
// Clean database before each test
|
|
367
|
-
await db.query('TRUNCATE users CASCADE');
|
|
368
|
-
});
|
|
369
|
-
|
|
370
|
-
afterAll(async () => {
|
|
371
|
-
await db.disconnect();
|
|
372
|
-
});
|
|
373
|
-
|
|
374
|
-
it('creates user', async () => {
|
|
375
|
-
const user = await userRepo.create({ email: 'test@example.com' });
|
|
376
|
-
expect(user.id).toBeDefined();
|
|
377
|
-
});
|
|
378
|
-
});
|
|
379
|
-
```
|
|
380
|
-
|
|
381
|
-
### API Mocking Isolation
|
|
382
|
-
|
|
383
|
-
```typescript
|
|
384
|
-
describe('ProductList', () => {
|
|
385
|
-
// Reset handlers after each test
|
|
386
|
-
afterEach(() => server.resetHandlers());
|
|
387
|
-
|
|
388
|
-
it('shows products from API', async () => {
|
|
389
|
-
// Default handler returns products
|
|
390
|
-
render(<ProductList />);
|
|
391
|
-
await expect(screen.findByText('Widget')).resolves.toBeInTheDocument();
|
|
392
|
-
});
|
|
393
|
-
|
|
394
|
-
it('shows error on API failure', async () => {
|
|
395
|
-
// Override handler for this test only
|
|
396
|
-
server.use(
|
|
397
|
-
rest.get('/api/products', (req, res, ctx) =>
|
|
398
|
-
res(ctx.status(500))
|
|
399
|
-
)
|
|
400
|
-
);
|
|
401
|
-
|
|
402
|
-
render(<ProductList />);
|
|
403
|
-
await expect(screen.findByText('Error')).resolves.toBeInTheDocument();
|
|
404
|
-
});
|
|
405
|
-
|
|
406
|
-
it('shows products again', async () => {
|
|
407
|
-
// Back to default handler (server.resetHandlers ran)
|
|
408
|
-
render(<ProductList />);
|
|
409
|
-
await expect(screen.findByText('Widget')).resolves.toBeInTheDocument();
|
|
410
|
-
});
|
|
411
|
-
});
|
|
412
|
-
```
|
|
413
|
-
|
|
414
|
-
### Isolation Checklist
|
|
415
|
-
|
|
416
|
-
| Aspect | Solution |
|
|
417
|
-
|--------|----------|
|
|
418
|
-
| Global state | Reset in beforeEach |
|
|
419
|
-
| Timers | jest.useFakeTimers() + jest.useRealTimers() |
|
|
420
|
-
| DOM | RTL's cleanup (automatic) |
|
|
421
|
-
| Database | Truncate tables or use transactions |
|
|
422
|
-
| API mocks | server.resetHandlers() |
|
|
423
|
-
| File system | Use temp directories, clean up in afterEach |
|
|
424
|
-
| Environment vars | Restore in afterEach |
|
|
425
|
-
|
|
426
|
-
---
|
|
427
|
-
|
|
428
|
-
## Handling Flaky Tests
|
|
429
|
-
|
|
430
|
-
Flaky tests pass and fail intermittently without code changes.
|
|
431
|
-
|
|
432
|
-
### Common Causes and Fixes
|
|
433
|
-
|
|
434
|
-
**1. Timing Issues**
|
|
435
|
-
|
|
436
|
-
```typescript
|
|
437
|
-
// Flaky - race condition
|
|
438
|
-
it('shows loading then data', () => {
|
|
439
|
-
render(<UserProfile />);
|
|
440
|
-
expect(screen.getByText('Loading')).toBeInTheDocument();
|
|
441
|
-
expect(screen.getByText('John')).toBeInTheDocument(); // May fail
|
|
442
|
-
});
|
|
443
|
-
|
|
444
|
-
// Fixed - proper async handling
|
|
445
|
-
it('shows loading then data', async () => {
|
|
446
|
-
render(<UserProfile />);
|
|
447
|
-
expect(screen.getByText('Loading')).toBeInTheDocument();
|
|
448
|
-
await waitFor(() => {
|
|
449
|
-
expect(screen.getByText('John')).toBeInTheDocument();
|
|
450
|
-
});
|
|
451
|
-
});
|
|
452
|
-
```
|
|
453
|
-
|
|
454
|
-
**2. Non-deterministic Data**
|
|
455
|
-
|
|
456
|
-
```typescript
|
|
457
|
-
// Flaky - random data
|
|
458
|
-
it('sorts users alphabetically', () => {
|
|
459
|
-
const users = [createUser(), createUser(), createUser()];
|
|
460
|
-
// Names are random, order unpredictable
|
|
461
|
-
});
|
|
462
|
-
|
|
463
|
-
// Fixed - deterministic data
|
|
464
|
-
it('sorts users alphabetically', () => {
|
|
465
|
-
const users = [
|
|
466
|
-
createUser({ name: 'Charlie' }),
|
|
467
|
-
createUser({ name: 'Alice' }),
|
|
468
|
-
createUser({ name: 'Bob' }),
|
|
469
|
-
];
|
|
470
|
-
const sorted = sortUsers(users);
|
|
471
|
-
expect(sorted.map(u => u.name)).toEqual(['Alice', 'Bob', 'Charlie']);
|
|
472
|
-
});
|
|
473
|
-
```
|
|
474
|
-
|
|
475
|
-
**3. Test Order Dependencies**
|
|
476
|
-
|
|
477
|
-
```typescript
|
|
478
|
-
// Flaky - relies on previous test
|
|
479
|
-
describe('Counter', () => {
|
|
480
|
-
const counter = new Counter(); // Shared instance!
|
|
481
|
-
|
|
482
|
-
it('increments', () => {
|
|
483
|
-
counter.increment();
|
|
484
|
-
expect(counter.value).toBe(1);
|
|
485
|
-
});
|
|
486
|
-
|
|
487
|
-
it('starts at zero', () => {
|
|
488
|
-
expect(counter.value).toBe(0); // Fails! Value is 1
|
|
489
|
-
});
|
|
490
|
-
});
|
|
491
|
-
|
|
492
|
-
// Fixed - fresh instance per test
|
|
493
|
-
describe('Counter', () => {
|
|
494
|
-
let counter: Counter;
|
|
495
|
-
|
|
496
|
-
beforeEach(() => {
|
|
497
|
-
counter = new Counter();
|
|
498
|
-
});
|
|
499
|
-
|
|
500
|
-
it('increments', () => {
|
|
501
|
-
counter.increment();
|
|
502
|
-
expect(counter.value).toBe(1);
|
|
503
|
-
});
|
|
504
|
-
|
|
505
|
-
it('starts at zero', () => {
|
|
506
|
-
expect(counter.value).toBe(0); // Passes
|
|
507
|
-
});
|
|
508
|
-
});
|
|
509
|
-
```
|
|
510
|
-
|
|
511
|
-
**4. Network/External Dependencies**
|
|
512
|
-
|
|
513
|
-
```typescript
|
|
514
|
-
// Flaky - real network call
|
|
515
|
-
it('fetches data', async () => {
|
|
516
|
-
const data = await fetch('https://api.example.com/data');
|
|
517
|
-
expect(data).toBeDefined();
|
|
518
|
-
});
|
|
519
|
-
|
|
520
|
-
// Fixed - mock the network
|
|
521
|
-
it('fetches data', async () => {
|
|
522
|
-
server.use(
|
|
523
|
-
rest.get('https://api.example.com/data', (req, res, ctx) =>
|
|
524
|
-
res(ctx.json({ value: 42 }))
|
|
525
|
-
)
|
|
526
|
-
);
|
|
527
|
-
|
|
528
|
-
const data = await fetchData();
|
|
529
|
-
expect(data.value).toBe(42);
|
|
530
|
-
});
|
|
531
|
-
```
|
|
532
|
-
|
|
533
|
-
### Flaky Test Detection
|
|
534
|
-
|
|
535
|
-
```javascript
|
|
536
|
-
// jest.config.js
|
|
537
|
-
module.exports = {
|
|
538
|
-
// Run each test multiple times to detect flakiness
|
|
539
|
-
testEnvironment: 'jsdom',
|
|
540
|
-
|
|
541
|
-
// Add reporters to track flaky tests
|
|
542
|
-
reporters: [
|
|
543
|
-
'default',
|
|
544
|
-
['jest-junit', { outputDirectory: './reports' }],
|
|
545
|
-
],
|
|
546
|
-
};
|
|
547
|
-
|
|
548
|
-
// Run tests multiple times
|
|
549
|
-
// npx jest --runInBand --testTimeout=10000 --repeat=5
|
|
550
|
-
```
|
|
551
|
-
|
|
552
|
-
### Quarantine Strategy
|
|
553
|
-
|
|
554
|
-
1. **Identify** - Track tests that fail randomly
|
|
555
|
-
2. **Quarantine** - Move to separate suite, run separately
|
|
556
|
-
3. **Fix** - Investigate and fix root cause
|
|
557
|
-
4. **Restore** - Move back to main suite
|
|
558
|
-
|
|
559
|
-
```typescript
|
|
560
|
-
// Temporarily skip flaky test
|
|
561
|
-
it.skip('flaky test to fix', () => {
|
|
562
|
-
// TODO: Fix timing issue in #123
|
|
563
|
-
});
|
|
564
|
-
|
|
565
|
-
// Or run only when investigating
|
|
566
|
-
it.todo('investigate flaky behavior');
|
|
567
|
-
```
|
|
568
|
-
|
|
569
|
-
---
|
|
570
|
-
|
|
571
|
-
## Code Review for Testability
|
|
572
|
-
|
|
573
|
-
Questions to ask during code review to ensure testable code.
|
|
574
|
-
|
|
575
|
-
### Testability Checklist
|
|
576
|
-
|
|
577
|
-
**Functions and Methods:**
|
|
578
|
-
- [ ] Does it have a single responsibility?
|
|
579
|
-
- [ ] Are dependencies injected?
|
|
580
|
-
- [ ] Can it be tested without mocking internals?
|
|
581
|
-
- [ ] Does it return a value or have observable side effects?
|
|
582
|
-
|
|
583
|
-
**Components:**
|
|
584
|
-
- [ ] Are props descriptive and minimal?
|
|
585
|
-
- [ ] Can behavior be triggered via user events?
|
|
586
|
-
- [ ] Are loading/error states exposed?
|
|
587
|
-
- [ ] Can it be rendered without a full app context?
|
|
588
|
-
|
|
589
|
-
**State Management:**
|
|
590
|
-
- [ ] Is state minimal and derived where possible?
|
|
591
|
-
- [ ] Can state changes be triggered and observed?
|
|
592
|
-
- [ ] Are side effects separated from reducers?
|
|
593
|
-
|
|
594
|
-
### Review Comments
|
|
595
|
-
|
|
596
|
-
**Before:**
|
|
597
|
-
```typescript
|
|
598
|
-
// Hard to test - embedded dependency
|
|
599
|
-
function processPayment(order: Order) {
|
|
600
|
-
const stripe = new Stripe(process.env.STRIPE_KEY);
|
|
601
|
-
return stripe.charges.create({
|
|
602
|
-
amount: order.total,
|
|
603
|
-
currency: 'usd',
|
|
604
|
-
});
|
|
605
|
-
}
|
|
606
|
-
```
|
|
607
|
-
|
|
608
|
-
**Review Comment:**
|
|
609
|
-
> Consider injecting the payment processor to improve testability:
|
|
610
|
-
> ```typescript
|
|
611
|
-
> function processPayment(order: Order, processor: PaymentProcessor) {
|
|
612
|
-
> return processor.charge(order.total, 'usd');
|
|
613
|
-
> }
|
|
614
|
-
> ```
|
|
615
|
-
> This allows testing with a mock processor without hitting Stripe's API.
|
|
616
|
-
|
|
617
|
-
---
|
|
618
|
-
|
|
619
|
-
## Test Maintenance Strategies
|
|
620
|
-
|
|
621
|
-
Keep tests maintainable as the codebase evolves.
|
|
622
|
-
|
|
623
|
-
### Reducing Duplication
|
|
624
|
-
|
|
625
|
-
**Use helpers for common assertions:**
|
|
626
|
-
|
|
627
|
-
```typescript
|
|
628
|
-
// __tests__/helpers/assertions.ts
|
|
629
|
-
export function expectLoadingState(container: HTMLElement) {
|
|
630
|
-
expect(within(container).getByRole('progressbar')).toBeInTheDocument();
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
export function expectErrorState(container: HTMLElement, message: string) {
|
|
634
|
-
expect(within(container).getByRole('alert')).toHaveTextContent(message);
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
// Usage
|
|
638
|
-
it('shows loading state', () => {
|
|
639
|
-
render(<DataList />);
|
|
640
|
-
expectLoadingState(screen.getByTestId('data-list'));
|
|
641
|
-
});
|
|
642
|
-
```
|
|
643
|
-
|
|
644
|
-
**Use factory functions:**
|
|
645
|
-
|
|
646
|
-
```typescript
|
|
647
|
-
// Instead of repeating setup
|
|
648
|
-
function renderWithUser(ui: ReactElement, user = createUser()) {
|
|
649
|
-
return {
|
|
650
|
-
user,
|
|
651
|
-
...render(<AuthProvider user={user}>{ui}</AuthProvider>),
|
|
652
|
-
};
|
|
653
|
-
}
|
|
654
|
-
```
|
|
655
|
-
|
|
656
|
-
### Updating Tests When Code Changes
|
|
657
|
-
|
|
658
|
-
**Scenario: Renaming a prop**
|
|
659
|
-
|
|
660
|
-
```typescript
|
|
661
|
-
// Old component
|
|
662
|
-
<Button onClick={handleClick} />
|
|
663
|
-
|
|
664
|
-
// New component
|
|
665
|
-
<Button onPress={handleClick} />
|
|
666
|
-
|
|
667
|
-
// Find and update all tests
|
|
668
|
-
// grep -r "onClick" __tests__/ --include="*.test.tsx"
|
|
669
|
-
```
|
|
670
|
-
|
|
671
|
-
**Scenario: Changing API response shape**
|
|
672
|
-
|
|
673
|
-
```typescript
|
|
674
|
-
// Update factory first
|
|
675
|
-
export function createUserResponse(overrides = {}) {
|
|
676
|
-
return {
|
|
677
|
-
user: { // New nested structure
|
|
678
|
-
id: '1',
|
|
679
|
-
name: 'Test User',
|
|
680
|
-
...overrides,
|
|
681
|
-
},
|
|
682
|
-
};
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
// Tests automatically get new shape
|
|
686
|
-
```
|
|
687
|
-
|
|
688
|
-
### When to Delete Tests
|
|
689
|
-
|
|
690
|
-
- **Redundant coverage** - Multiple tests testing the same thing
|
|
691
|
-
- **Testing implementation** - Tests that break on refactor
|
|
692
|
-
- **Obsolete features** - Tests for removed functionality
|
|
693
|
-
- **Flaky beyond repair** - Tests that can't be stabilized
|
|
694
|
-
|
|
695
|
-
### Test Documentation
|
|
696
|
-
|
|
697
|
-
```typescript
|
|
698
|
-
/**
|
|
699
|
-
* @group integration
|
|
700
|
-
* @requires database
|
|
701
|
-
*
|
|
702
|
-
* Tests for the order processing workflow.
|
|
703
|
-
* These tests require a running PostgreSQL instance.
|
|
704
|
-
*
|
|
705
|
-
* Setup: docker-compose up -d postgres
|
|
706
|
-
*/
|
|
707
|
-
describe('OrderProcessor', () => {
|
|
708
|
-
/**
|
|
709
|
-
* Verifies that orders with backordered items
|
|
710
|
-
* are split into separate fulfillment batches.
|
|
711
|
-
*
|
|
712
|
-
* Related: JIRA-1234
|
|
713
|
-
*/
|
|
714
|
-
it('splits orders with backordered items', () => {});
|
|
715
|
-
});
|
|
716
|
-
```
|
|
717
|
-
|
|
718
|
-
---
|
|
719
|
-
|
|
720
|
-
## Debugging Failed Tests
|
|
721
|
-
|
|
722
|
-
Techniques for investigating test failures.
|
|
723
|
-
|
|
724
|
-
### Jest Debugging
|
|
725
|
-
|
|
726
|
-
**Run single test:**
|
|
727
|
-
```bash
|
|
728
|
-
# By name pattern
|
|
729
|
-
npx jest -t "should validate email"
|
|
730
|
-
|
|
731
|
-
# By file
|
|
732
|
-
npx jest src/utils/__tests__/validation.test.ts
|
|
733
|
-
|
|
734
|
-
# Watch mode for iteration
|
|
735
|
-
npx jest --watch
|
|
736
|
-
```
|
|
737
|
-
|
|
738
|
-
**Debug with Node inspector:**
|
|
739
|
-
```bash
|
|
740
|
-
node --inspect-brk node_modules/.bin/jest --runInBand
|
|
741
|
-
# Open chrome://inspect in Chrome
|
|
742
|
-
```
|
|
743
|
-
|
|
744
|
-
**Verbose output:**
|
|
745
|
-
```bash
|
|
746
|
-
npx jest --verbose --no-coverage
|
|
747
|
-
```
|
|
748
|
-
|
|
749
|
-
### React Testing Library Debugging
|
|
750
|
-
|
|
751
|
-
```typescript
|
|
752
|
-
it('renders user profile', async () => {
|
|
753
|
-
render(<UserProfile userId="123" />);
|
|
754
|
-
|
|
755
|
-
// Print current DOM
|
|
756
|
-
screen.debug();
|
|
757
|
-
|
|
758
|
-
// Print specific element
|
|
759
|
-
screen.debug(screen.getByRole('heading'));
|
|
760
|
-
|
|
761
|
-
// Log accessible roles
|
|
762
|
-
screen.logTestingPlaygroundURL(); // Opens interactive playground
|
|
763
|
-
|
|
764
|
-
// Check what queries would match
|
|
765
|
-
const element = screen.getByRole('button');
|
|
766
|
-
console.log(prettyDOM(element));
|
|
767
|
-
});
|
|
768
|
-
```
|
|
769
|
-
|
|
770
|
-
### Playwright Debugging
|
|
771
|
-
|
|
772
|
-
```bash
|
|
773
|
-
# Debug mode - opens browser with inspector
|
|
774
|
-
npx playwright test --debug
|
|
775
|
-
|
|
776
|
-
# UI mode - visual test runner
|
|
777
|
-
npx playwright test --ui
|
|
778
|
-
|
|
779
|
-
# Headed mode - see browser
|
|
780
|
-
npx playwright test --headed
|
|
781
|
-
|
|
782
|
-
# Trace viewer after failure
|
|
783
|
-
npx playwright show-trace trace.zip
|
|
784
|
-
```
|
|
785
|
-
|
|
786
|
-
**Pause in test:**
|
|
787
|
-
```typescript
|
|
788
|
-
test('debug this', async ({ page }) => {
|
|
789
|
-
await page.goto('/');
|
|
790
|
-
await page.pause(); // Opens inspector
|
|
791
|
-
await page.click('button');
|
|
792
|
-
});
|
|
793
|
-
```
|
|
794
|
-
|
|
795
|
-
### Common Failure Patterns
|
|
796
|
-
|
|
797
|
-
| Symptom | Likely Cause | Debug Approach |
|
|
798
|
-
|---------|--------------|----------------|
|
|
799
|
-
| "Unable to find element" | Wrong query or element not rendered | `screen.debug()`, check async |
|
|
800
|
-
| "Expected X, received Y" | Logic error or stale mock | Log intermediate values |
|
|
801
|
-
| "Timeout exceeded" | Slow async or missing await | Increase timeout, check promises |
|
|
802
|
-
| "Cannot read property of undefined" | Missing mock or setup | Check beforeEach, mock returns |
|
|
803
|
-
| Passes locally, fails in CI | Environment difference | Check env vars, timing |
|
|
804
|
-
|
|
805
|
-
### Investigating Flaky Failures
|
|
806
|
-
|
|
807
|
-
```typescript
|
|
808
|
-
// Add logging for intermittent failures
|
|
809
|
-
it('processes order', async () => {
|
|
810
|
-
console.log('Test started at', Date.now());
|
|
811
|
-
|
|
812
|
-
const order = await createOrder();
|
|
813
|
-
console.log('Order created:', order.id);
|
|
814
|
-
|
|
815
|
-
const result = await processOrder(order);
|
|
816
|
-
console.log('Process result:', result);
|
|
817
|
-
|
|
818
|
-
expect(result.status).toBe('completed');
|
|
819
|
-
});
|
|
820
|
-
```
|
|
821
|
-
|
|
822
|
-
---
|
|
823
|
-
|
|
824
|
-
## Quality Metrics and KPIs
|
|
825
|
-
|
|
826
|
-
Measure test suite effectiveness and track quality improvements.
|
|
827
|
-
|
|
828
|
-
### Key Metrics
|
|
829
|
-
|
|
830
|
-
**Coverage Metrics:**
|
|
831
|
-
|
|
832
|
-
| Metric | Target | Measurement |
|
|
833
|
-
|--------|--------|-------------|
|
|
834
|
-
| Line coverage | 80% | `jest --coverage` |
|
|
835
|
-
| Branch coverage | 75% | `jest --coverage` |
|
|
836
|
-
| Function coverage | 80% | `jest --coverage` |
|
|
837
|
-
| Critical path coverage | 95% | Custom tracking |
|
|
838
|
-
|
|
839
|
-
**Test Suite Health:**
|
|
840
|
-
|
|
841
|
-
| Metric | Target | Measurement |
|
|
842
|
-
|--------|--------|-------------|
|
|
843
|
-
| Test pass rate | 100% | CI reports |
|
|
844
|
-
| Flaky test rate | <1% | Track retries |
|
|
845
|
-
| Test execution time | <5 min | CI timing |
|
|
846
|
-
| Tests per component | ≥3 | Test count / components |
|
|
847
|
-
|
|
848
|
-
**Defect Metrics:**
|
|
849
|
-
|
|
850
|
-
| Metric | Target | Measurement |
|
|
851
|
-
|--------|--------|-------------|
|
|
852
|
-
| Defects found in testing | >70% | Bug tracking |
|
|
853
|
-
| Defects escaped to prod | <10% | Production bugs |
|
|
854
|
-
| Regression rate | <5% | Bugs reintroduced |
|
|
855
|
-
| Mean time to detect | <1 day | Bug timestamps |
|
|
856
|
-
|
|
857
|
-
### Dashboard Example
|
|
858
|
-
|
|
859
|
-
```typescript
|
|
860
|
-
// scripts/test-metrics.ts
|
|
861
|
-
import { readCoverageReport } from './utils';
|
|
862
|
-
|
|
863
|
-
const coverage = readCoverageReport('./coverage/coverage-summary.json');
|
|
864
|
-
const testResults = readTestReport('./reports/jest-results.json');
|
|
865
|
-
|
|
866
|
-
const metrics = {
|
|
867
|
-
coverage: {
|
|
868
|
-
lines: coverage.total.lines.pct,
|
|
869
|
-
branches: coverage.total.branches.pct,
|
|
870
|
-
functions: coverage.total.functions.pct,
|
|
871
|
-
},
|
|
872
|
-
tests: {
|
|
873
|
-
total: testResults.numTotalTests,
|
|
874
|
-
passed: testResults.numPassedTests,
|
|
875
|
-
failed: testResults.numFailedTests,
|
|
876
|
-
passRate: (testResults.numPassedTests / testResults.numTotalTests) * 100,
|
|
877
|
-
},
|
|
878
|
-
execution: {
|
|
879
|
-
duration: testResults.testResults.reduce((sum, r) => sum + r.duration, 0),
|
|
880
|
-
},
|
|
881
|
-
};
|
|
882
|
-
|
|
883
|
-
console.log('Test Metrics:', JSON.stringify(metrics, null, 2));
|
|
884
|
-
```
|
|
885
|
-
|
|
886
|
-
### CI Quality Gates
|
|
887
|
-
|
|
888
|
-
```yaml
|
|
889
|
-
# .github/workflows/quality.yml
|
|
890
|
-
name: Quality Gates
|
|
891
|
-
|
|
892
|
-
on: [push, pull_request]
|
|
893
|
-
|
|
894
|
-
jobs:
|
|
895
|
-
test:
|
|
896
|
-
runs-on: ubuntu-latest
|
|
897
|
-
steps:
|
|
898
|
-
- uses: actions/checkout@v4
|
|
899
|
-
- uses: actions/setup-node@v4
|
|
900
|
-
|
|
901
|
-
- run: npm ci
|
|
902
|
-
- run: npm test -- --coverage
|
|
903
|
-
|
|
904
|
-
# Coverage gate
|
|
905
|
-
- name: Check coverage
|
|
906
|
-
run: |
|
|
907
|
-
coverage=$(jq '.total.lines.pct' coverage/coverage-summary.json)
|
|
908
|
-
if (( $(echo "$coverage < 80" | bc -l) )); then
|
|
909
|
-
echo "Coverage $coverage% is below 80% threshold"
|
|
910
|
-
exit 1
|
|
911
|
-
fi
|
|
912
|
-
|
|
913
|
-
# Test count gate
|
|
914
|
-
- name: Check test count
|
|
915
|
-
run: |
|
|
916
|
-
tests=$(jq '.numTotalTests' reports/test-results.json)
|
|
917
|
-
if [ "$tests" -lt 100 ]; then
|
|
918
|
-
echo "Test count $tests is below minimum of 100"
|
|
919
|
-
exit 1
|
|
920
|
-
fi
|
|
921
|
-
```
|
|
922
|
-
|
|
923
|
-
### Trend Tracking
|
|
924
|
-
|
|
925
|
-
Track metrics over time to identify trends:
|
|
926
|
-
|
|
927
|
-
```typescript
|
|
928
|
-
// Weekly metrics collection
|
|
929
|
-
{
|
|
930
|
-
"week": "2024-W03",
|
|
931
|
-
"coverage": {
|
|
932
|
-
"lines": 82.4,
|
|
933
|
-
"branches": 76.1,
|
|
934
|
-
"trend": "+1.2%" // vs previous week
|
|
935
|
-
},
|
|
936
|
-
"tests": {
|
|
937
|
-
"total": 487,
|
|
938
|
-
"new": 23,
|
|
939
|
-
"removed": 5
|
|
940
|
-
},
|
|
941
|
-
"execution": {
|
|
942
|
-
"avgDuration": 245, // seconds
|
|
943
|
-
"trend": "-12s"
|
|
944
|
-
},
|
|
945
|
-
"flaky": {
|
|
946
|
-
"count": 3,
|
|
947
|
-
"rate": 0.6
|
|
948
|
-
}
|
|
949
|
-
}
|
|
950
|
-
```
|
|
951
|
-
|
|
952
|
-
---
|
|
953
|
-
|
|
954
|
-
## Summary
|
|
955
|
-
|
|
956
|
-
1. **Write testable code** - Inject dependencies, use pure functions, separate concerns
|
|
957
|
-
2. **Name tests clearly** - Describe behavior, not implementation
|
|
958
|
-
3. **Follow AAA pattern** - Arrange, Act, Assert for clear structure
|
|
959
|
-
4. **Isolate tests** - Fresh state, reset mocks, no dependencies between tests
|
|
960
|
-
5. **Fix flaky tests** - Handle timing, use deterministic data, mock externals
|
|
961
|
-
6. **Review for testability** - Check during code review, not after
|
|
962
|
-
7. **Maintain tests** - Reduce duplication, update with code changes
|
|
963
|
-
8. **Debug systematically** - Use debug tools, log strategically
|
|
964
|
-
9. **Measure quality** - Track coverage, pass rate, execution time
|
|
1
|
+
# QA Best Practices for React and Next.js
|
|
2
|
+
|
|
3
|
+
Guidelines for writing maintainable tests, debugging failures, and measuring test quality.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Table of Contents
|
|
8
|
+
|
|
9
|
+
- [Writing Testable Code](#writing-testable-code)
|
|
10
|
+
- [Test Naming Conventions](#test-naming-conventions)
|
|
11
|
+
- [Arrange-Act-Assert Pattern](#arrange-act-assert-pattern)
|
|
12
|
+
- [Test Isolation Principles](#test-isolation-principles)
|
|
13
|
+
- [Handling Flaky Tests](#handling-flaky-tests)
|
|
14
|
+
- [Code Review for Testability](#code-review-for-testability)
|
|
15
|
+
- [Test Maintenance Strategies](#test-maintenance-strategies)
|
|
16
|
+
- [Debugging Failed Tests](#debugging-failed-tests)
|
|
17
|
+
- [Quality Metrics and KPIs](#quality-metrics-and-kpis)
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Writing Testable Code
|
|
22
|
+
|
|
23
|
+
Testable code is easy to understand, has clear boundaries, and minimizes dependencies.
|
|
24
|
+
|
|
25
|
+
### Dependency Injection
|
|
26
|
+
|
|
27
|
+
Instead of creating dependencies inside functions, pass them as parameters.
|
|
28
|
+
|
|
29
|
+
**Hard to Test:**
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
// src/services/userService.ts
|
|
33
|
+
import { prisma } from '../lib/prisma';
|
|
34
|
+
import { sendEmail } from '../lib/email';
|
|
35
|
+
|
|
36
|
+
export async function createUser(data: UserInput) {
|
|
37
|
+
const user = await prisma.user.create({ data });
|
|
38
|
+
await sendEmail(user.email, 'Welcome!');
|
|
39
|
+
return user;
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**Easy to Test:**
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
// src/services/userService.ts
|
|
47
|
+
export function createUserService(
|
|
48
|
+
db: PrismaClient,
|
|
49
|
+
emailService: EmailService
|
|
50
|
+
) {
|
|
51
|
+
return {
|
|
52
|
+
async createUser(data: UserInput) {
|
|
53
|
+
const user = await db.user.create({ data });
|
|
54
|
+
await emailService.send(user.email, 'Welcome!');
|
|
55
|
+
return user;
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Usage in app
|
|
61
|
+
const userService = createUserService(prisma, emailService);
|
|
62
|
+
|
|
63
|
+
// Usage in tests
|
|
64
|
+
const mockDb = { user: { create: jest.fn() } };
|
|
65
|
+
const mockEmail = { send: jest.fn() };
|
|
66
|
+
const testService = createUserService(mockDb, mockEmail);
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Pure Functions
|
|
70
|
+
|
|
71
|
+
Pure functions are deterministic and have no side effects, making them trivial to test.
|
|
72
|
+
|
|
73
|
+
**Impure (Hard to Test):**
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
function formatTimestamp() {
|
|
77
|
+
const now = new Date();
|
|
78
|
+
return `${now.getFullYear()}-${now.getMonth() + 1}-${now.getDate()}`;
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**Pure (Easy to Test):**
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
function formatTimestamp(date: Date): string {
|
|
86
|
+
return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Test
|
|
90
|
+
expect(formatTimestamp(new Date('2024-03-15'))).toBe('2024-3-15');
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Separation of Concerns
|
|
94
|
+
|
|
95
|
+
Separate business logic from UI and I/O operations.
|
|
96
|
+
|
|
97
|
+
**Mixed Concerns (Hard to Test):**
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
// Component with embedded business logic
|
|
101
|
+
function CheckoutForm() {
|
|
102
|
+
const [total, setTotal] = useState(0);
|
|
103
|
+
|
|
104
|
+
const handleSubmit = async (items: CartItem[]) => {
|
|
105
|
+
// Business logic mixed with UI
|
|
106
|
+
let sum = 0;
|
|
107
|
+
for (const item of items) {
|
|
108
|
+
sum += item.price * item.quantity;
|
|
109
|
+
if (item.category === 'electronics') {
|
|
110
|
+
sum *= 0.9; // 10% discount
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const tax = sum * 0.08;
|
|
114
|
+
const finalTotal = sum + tax;
|
|
115
|
+
|
|
116
|
+
// API call
|
|
117
|
+
await fetch('/api/orders', {
|
|
118
|
+
method: 'POST',
|
|
119
|
+
body: JSON.stringify({ items, total: finalTotal }),
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
setTotal(finalTotal);
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
return <form onSubmit={handleSubmit}>...</form>;
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
**Separated Concerns (Easy to Test):**
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
// Pure business logic (easy to unit test)
|
|
133
|
+
export function calculateOrderTotal(items: CartItem[]): number {
|
|
134
|
+
return items.reduce((sum, item) => {
|
|
135
|
+
const subtotal = item.price * item.quantity;
|
|
136
|
+
const discount = item.category === 'electronics' ? 0.9 : 1;
|
|
137
|
+
return sum + subtotal * discount;
|
|
138
|
+
}, 0);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function calculateTax(subtotal: number, rate = 0.08): number {
|
|
142
|
+
return subtotal * rate;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Custom hook for order logic (testable with renderHook)
|
|
146
|
+
export function useCheckout() {
|
|
147
|
+
const [total, setTotal] = useState(0);
|
|
148
|
+
const mutation = useMutation(createOrder);
|
|
149
|
+
|
|
150
|
+
const checkout = async (items: CartItem[]) => {
|
|
151
|
+
const subtotal = calculateOrderTotal(items);
|
|
152
|
+
const tax = calculateTax(subtotal);
|
|
153
|
+
const finalTotal = subtotal + tax;
|
|
154
|
+
|
|
155
|
+
await mutation.mutateAsync({ items, total: finalTotal });
|
|
156
|
+
setTotal(finalTotal);
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
return { checkout, total, isLoading: mutation.isLoading };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Component (integration testable)
|
|
163
|
+
function CheckoutForm() {
|
|
164
|
+
const { checkout, total, isLoading } = useCheckout();
|
|
165
|
+
return <form onSubmit={() => checkout(items)}>...</form>;
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Component Design for Testability
|
|
170
|
+
|
|
171
|
+
| Pattern | Testability | Example |
|
|
172
|
+
|---------|-------------|---------|
|
|
173
|
+
| Props over context | High | `<Button disabled={!valid}>` |
|
|
174
|
+
| Callbacks over side effects | High | `onSubmit={handleSubmit}` |
|
|
175
|
+
| Controlled components | High | `<Input value={value} onChange={...}>` |
|
|
176
|
+
| Render props | Medium | `<DataProvider render={data => ...}>` |
|
|
177
|
+
| Internal state | Low | `const [x, setX] = useState()` |
|
|
178
|
+
| Global state | Low | `useGlobalStore()` |
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## Test Naming Conventions
|
|
183
|
+
|
|
184
|
+
Good test names document expected behavior and help diagnose failures.
|
|
185
|
+
|
|
186
|
+
### Naming Patterns
|
|
187
|
+
|
|
188
|
+
**Pattern 1: should [expected behavior] when [condition]**
|
|
189
|
+
|
|
190
|
+
```typescript
|
|
191
|
+
describe('LoginForm', () => {
|
|
192
|
+
it('should display error message when credentials are invalid', () => {});
|
|
193
|
+
it('should redirect to dashboard when login succeeds', () => {});
|
|
194
|
+
it('should disable submit button when form is submitting', () => {});
|
|
195
|
+
});
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
**Pattern 2: [method/action] [expected result]**
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
describe('calculateDiscount', () => {
|
|
202
|
+
it('returns 0 for orders under $50', () => {});
|
|
203
|
+
it('returns 10% for orders $50-$99', () => {});
|
|
204
|
+
it('returns 20% for orders $100+', () => {});
|
|
205
|
+
});
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
**Pattern 3: given [context], when [action], then [result]**
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
describe('ShoppingCart', () => {
|
|
212
|
+
it('given an empty cart, when adding an item, then cart count is 1', () => {});
|
|
213
|
+
it('given items in cart, when removing all, then cart is empty', () => {});
|
|
214
|
+
});
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Describe Block Organization
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
describe('UserService', () => {
|
|
221
|
+
describe('createUser', () => {
|
|
222
|
+
describe('with valid input', () => {
|
|
223
|
+
it('creates user in database', () => {});
|
|
224
|
+
it('sends welcome email', () => {});
|
|
225
|
+
it('returns user with id', () => {});
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
describe('with invalid input', () => {
|
|
229
|
+
it('throws ValidationError for missing email', () => {});
|
|
230
|
+
it('throws ValidationError for invalid email format', () => {});
|
|
231
|
+
it('throws ConflictError for duplicate email', () => {});
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
describe('deleteUser', () => {
|
|
236
|
+
it('removes user from database', () => {});
|
|
237
|
+
it('throws NotFoundError for non-existent user', () => {});
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### Anti-patterns to Avoid
|
|
243
|
+
|
|
244
|
+
| Bad | Good | Why |
|
|
245
|
+
|-----|------|-----|
|
|
246
|
+
| `it('works')` | `it('returns sum of two numbers')` | Describes behavior |
|
|
247
|
+
| `it('test 1')` | `it('handles empty array')` | Specific scenario |
|
|
248
|
+
| `it('should do stuff')` | `it('should validate email format')` | Clear expectation |
|
|
249
|
+
| Duplicating code in name | Describing behavior | Readable output |
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## Arrange-Act-Assert Pattern
|
|
254
|
+
|
|
255
|
+
The AAA pattern structures tests into three clear phases.
|
|
256
|
+
|
|
257
|
+
### Structure
|
|
258
|
+
|
|
259
|
+
```typescript
|
|
260
|
+
it('calculates total with discount', () => {
|
|
261
|
+
// Arrange - Set up test data and conditions
|
|
262
|
+
const items = [
|
|
263
|
+
{ name: 'Widget', price: 100, quantity: 2 },
|
|
264
|
+
{ name: 'Gadget', price: 50, quantity: 1 },
|
|
265
|
+
];
|
|
266
|
+
const discountRate = 0.1;
|
|
267
|
+
|
|
268
|
+
// Act - Execute the code being tested
|
|
269
|
+
const result = calculateTotal(items, discountRate);
|
|
270
|
+
|
|
271
|
+
// Assert - Verify the outcome
|
|
272
|
+
expect(result).toBe(225); // (200 + 50) * 0.9
|
|
273
|
+
});
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### Async Example
|
|
277
|
+
|
|
278
|
+
```typescript
|
|
279
|
+
it('fetches user profile', async () => {
|
|
280
|
+
// Arrange
|
|
281
|
+
const userId = '123';
|
|
282
|
+
server.use(
|
|
283
|
+
rest.get('/api/users/:id', (req, res, ctx) =>
|
|
284
|
+
res(ctx.json({ id: userId, name: 'John' }))
|
|
285
|
+
)
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
// Act
|
|
289
|
+
render(<UserProfile userId={userId} />);
|
|
290
|
+
|
|
291
|
+
// Assert
|
|
292
|
+
await expect(screen.findByText('John')).resolves.toBeInTheDocument();
|
|
293
|
+
});
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### Component Testing Example
|
|
297
|
+
|
|
298
|
+
```typescript
|
|
299
|
+
it('submits form with user input', async () => {
|
|
300
|
+
// Arrange
|
|
301
|
+
const user = userEvent.setup();
|
|
302
|
+
const onSubmit = jest.fn();
|
|
303
|
+
render(<ContactForm onSubmit={onSubmit} />);
|
|
304
|
+
|
|
305
|
+
// Act
|
|
306
|
+
await user.type(screen.getByLabelText('Name'), 'John Doe');
|
|
307
|
+
await user.type(screen.getByLabelText('Email'), 'john@example.com');
|
|
308
|
+
await user.type(screen.getByLabelText('Message'), 'Hello!');
|
|
309
|
+
await user.click(screen.getByRole('button', { name: 'Send' }));
|
|
310
|
+
|
|
311
|
+
// Assert
|
|
312
|
+
expect(onSubmit).toHaveBeenCalledWith({
|
|
313
|
+
name: 'John Doe',
|
|
314
|
+
email: 'john@example.com',
|
|
315
|
+
message: 'Hello!',
|
|
316
|
+
});
|
|
317
|
+
});
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### Guidelines
|
|
321
|
+
|
|
322
|
+
1. **One Act per test** - Test one behavior at a time
|
|
323
|
+
2. **Multiple assertions OK** - If they verify the same behavior
|
|
324
|
+
3. **Avoid logic in tests** - No if/else, loops in test code
|
|
325
|
+
4. **Setup in Arrange, not beforeEach** - Unless truly shared
|
|
326
|
+
|
|
327
|
+
---
|
|
328
|
+
|
|
329
|
+
## Test Isolation Principles
|
|
330
|
+
|
|
331
|
+
Isolated tests are independent, repeatable, and can run in any order.
|
|
332
|
+
|
|
333
|
+
### State Isolation
|
|
334
|
+
|
|
335
|
+
```typescript
|
|
336
|
+
describe('CartService', () => {
|
|
337
|
+
let cartService: CartService;
|
|
338
|
+
|
|
339
|
+
// Fresh instance for each test
|
|
340
|
+
beforeEach(() => {
|
|
341
|
+
cartService = new CartService();
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
it('adds item to empty cart', () => {
|
|
345
|
+
cartService.addItem({ id: '1', quantity: 1 });
|
|
346
|
+
expect(cartService.getItems()).toHaveLength(1);
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
it('starts with empty cart', () => {
|
|
350
|
+
// Not affected by previous test
|
|
351
|
+
expect(cartService.getItems()).toHaveLength(0);
|
|
352
|
+
});
|
|
353
|
+
});
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
### Database Isolation
|
|
357
|
+
|
|
358
|
+
```typescript
|
|
359
|
+
describe('UserRepository', () => {
|
|
360
|
+
beforeAll(async () => {
|
|
361
|
+
// Connect to test database
|
|
362
|
+
await db.connect(process.env.TEST_DATABASE_URL);
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
beforeEach(async () => {
|
|
366
|
+
// Clean database before each test
|
|
367
|
+
await db.query('TRUNCATE users CASCADE');
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
afterAll(async () => {
|
|
371
|
+
await db.disconnect();
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
it('creates user', async () => {
|
|
375
|
+
const user = await userRepo.create({ email: 'test@example.com' });
|
|
376
|
+
expect(user.id).toBeDefined();
|
|
377
|
+
});
|
|
378
|
+
});
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
### API Mocking Isolation
|
|
382
|
+
|
|
383
|
+
```typescript
|
|
384
|
+
describe('ProductList', () => {
|
|
385
|
+
// Reset handlers after each test
|
|
386
|
+
afterEach(() => server.resetHandlers());
|
|
387
|
+
|
|
388
|
+
it('shows products from API', async () => {
|
|
389
|
+
// Default handler returns products
|
|
390
|
+
render(<ProductList />);
|
|
391
|
+
await expect(screen.findByText('Widget')).resolves.toBeInTheDocument();
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
it('shows error on API failure', async () => {
|
|
395
|
+
// Override handler for this test only
|
|
396
|
+
server.use(
|
|
397
|
+
rest.get('/api/products', (req, res, ctx) =>
|
|
398
|
+
res(ctx.status(500))
|
|
399
|
+
)
|
|
400
|
+
);
|
|
401
|
+
|
|
402
|
+
render(<ProductList />);
|
|
403
|
+
await expect(screen.findByText('Error')).resolves.toBeInTheDocument();
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
it('shows products again', async () => {
|
|
407
|
+
// Back to default handler (server.resetHandlers ran)
|
|
408
|
+
render(<ProductList />);
|
|
409
|
+
await expect(screen.findByText('Widget')).resolves.toBeInTheDocument();
|
|
410
|
+
});
|
|
411
|
+
});
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
### Isolation Checklist
|
|
415
|
+
|
|
416
|
+
| Aspect | Solution |
|
|
417
|
+
|--------|----------|
|
|
418
|
+
| Global state | Reset in beforeEach |
|
|
419
|
+
| Timers | jest.useFakeTimers() + jest.useRealTimers() |
|
|
420
|
+
| DOM | RTL's cleanup (automatic) |
|
|
421
|
+
| Database | Truncate tables or use transactions |
|
|
422
|
+
| API mocks | server.resetHandlers() |
|
|
423
|
+
| File system | Use temp directories, clean up in afterEach |
|
|
424
|
+
| Environment vars | Restore in afterEach |
|
|
425
|
+
|
|
426
|
+
---
|
|
427
|
+
|
|
428
|
+
## Handling Flaky Tests
|
|
429
|
+
|
|
430
|
+
Flaky tests pass and fail intermittently without code changes.
|
|
431
|
+
|
|
432
|
+
### Common Causes and Fixes
|
|
433
|
+
|
|
434
|
+
**1. Timing Issues**
|
|
435
|
+
|
|
436
|
+
```typescript
|
|
437
|
+
// Flaky - race condition
|
|
438
|
+
it('shows loading then data', () => {
|
|
439
|
+
render(<UserProfile />);
|
|
440
|
+
expect(screen.getByText('Loading')).toBeInTheDocument();
|
|
441
|
+
expect(screen.getByText('John')).toBeInTheDocument(); // May fail
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
// Fixed - proper async handling
|
|
445
|
+
it('shows loading then data', async () => {
|
|
446
|
+
render(<UserProfile />);
|
|
447
|
+
expect(screen.getByText('Loading')).toBeInTheDocument();
|
|
448
|
+
await waitFor(() => {
|
|
449
|
+
expect(screen.getByText('John')).toBeInTheDocument();
|
|
450
|
+
});
|
|
451
|
+
});
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
**2. Non-deterministic Data**
|
|
455
|
+
|
|
456
|
+
```typescript
|
|
457
|
+
// Flaky - random data
|
|
458
|
+
it('sorts users alphabetically', () => {
|
|
459
|
+
const users = [createUser(), createUser(), createUser()];
|
|
460
|
+
// Names are random, order unpredictable
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
// Fixed - deterministic data
|
|
464
|
+
it('sorts users alphabetically', () => {
|
|
465
|
+
const users = [
|
|
466
|
+
createUser({ name: 'Charlie' }),
|
|
467
|
+
createUser({ name: 'Alice' }),
|
|
468
|
+
createUser({ name: 'Bob' }),
|
|
469
|
+
];
|
|
470
|
+
const sorted = sortUsers(users);
|
|
471
|
+
expect(sorted.map(u => u.name)).toEqual(['Alice', 'Bob', 'Charlie']);
|
|
472
|
+
});
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
**3. Test Order Dependencies**
|
|
476
|
+
|
|
477
|
+
```typescript
|
|
478
|
+
// Flaky - relies on previous test
|
|
479
|
+
describe('Counter', () => {
|
|
480
|
+
const counter = new Counter(); // Shared instance!
|
|
481
|
+
|
|
482
|
+
it('increments', () => {
|
|
483
|
+
counter.increment();
|
|
484
|
+
expect(counter.value).toBe(1);
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
it('starts at zero', () => {
|
|
488
|
+
expect(counter.value).toBe(0); // Fails! Value is 1
|
|
489
|
+
});
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
// Fixed - fresh instance per test
|
|
493
|
+
describe('Counter', () => {
|
|
494
|
+
let counter: Counter;
|
|
495
|
+
|
|
496
|
+
beforeEach(() => {
|
|
497
|
+
counter = new Counter();
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
it('increments', () => {
|
|
501
|
+
counter.increment();
|
|
502
|
+
expect(counter.value).toBe(1);
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
it('starts at zero', () => {
|
|
506
|
+
expect(counter.value).toBe(0); // Passes
|
|
507
|
+
});
|
|
508
|
+
});
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
**4. Network/External Dependencies**
|
|
512
|
+
|
|
513
|
+
```typescript
|
|
514
|
+
// Flaky - real network call
|
|
515
|
+
it('fetches data', async () => {
|
|
516
|
+
const data = await fetch('https://api.example.com/data');
|
|
517
|
+
expect(data).toBeDefined();
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
// Fixed - mock the network
|
|
521
|
+
it('fetches data', async () => {
|
|
522
|
+
server.use(
|
|
523
|
+
rest.get('https://api.example.com/data', (req, res, ctx) =>
|
|
524
|
+
res(ctx.json({ value: 42 }))
|
|
525
|
+
)
|
|
526
|
+
);
|
|
527
|
+
|
|
528
|
+
const data = await fetchData();
|
|
529
|
+
expect(data.value).toBe(42);
|
|
530
|
+
});
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
### Flaky Test Detection
|
|
534
|
+
|
|
535
|
+
```javascript
|
|
536
|
+
// jest.config.js
|
|
537
|
+
module.exports = {
|
|
538
|
+
// Run each test multiple times to detect flakiness
|
|
539
|
+
testEnvironment: 'jsdom',
|
|
540
|
+
|
|
541
|
+
// Add reporters to track flaky tests
|
|
542
|
+
reporters: [
|
|
543
|
+
'default',
|
|
544
|
+
['jest-junit', { outputDirectory: './reports' }],
|
|
545
|
+
],
|
|
546
|
+
};
|
|
547
|
+
|
|
548
|
+
// Run tests multiple times
|
|
549
|
+
// npx jest --runInBand --testTimeout=10000 --repeat=5
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
### Quarantine Strategy
|
|
553
|
+
|
|
554
|
+
1. **Identify** - Track tests that fail randomly
|
|
555
|
+
2. **Quarantine** - Move to separate suite, run separately
|
|
556
|
+
3. **Fix** - Investigate and fix root cause
|
|
557
|
+
4. **Restore** - Move back to main suite
|
|
558
|
+
|
|
559
|
+
```typescript
|
|
560
|
+
// Temporarily skip flaky test
|
|
561
|
+
it.skip('flaky test to fix', () => {
|
|
562
|
+
// TODO: Fix timing issue in #123
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
// Or run only when investigating
|
|
566
|
+
it.todo('investigate flaky behavior');
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
---
|
|
570
|
+
|
|
571
|
+
## Code Review for Testability
|
|
572
|
+
|
|
573
|
+
Questions to ask during code review to ensure testable code.
|
|
574
|
+
|
|
575
|
+
### Testability Checklist
|
|
576
|
+
|
|
577
|
+
**Functions and Methods:**
|
|
578
|
+
- [ ] Does it have a single responsibility?
|
|
579
|
+
- [ ] Are dependencies injected?
|
|
580
|
+
- [ ] Can it be tested without mocking internals?
|
|
581
|
+
- [ ] Does it return a value or have observable side effects?
|
|
582
|
+
|
|
583
|
+
**Components:**
|
|
584
|
+
- [ ] Are props descriptive and minimal?
|
|
585
|
+
- [ ] Can behavior be triggered via user events?
|
|
586
|
+
- [ ] Are loading/error states exposed?
|
|
587
|
+
- [ ] Can it be rendered without a full app context?
|
|
588
|
+
|
|
589
|
+
**State Management:**
|
|
590
|
+
- [ ] Is state minimal and derived where possible?
|
|
591
|
+
- [ ] Can state changes be triggered and observed?
|
|
592
|
+
- [ ] Are side effects separated from reducers?
|
|
593
|
+
|
|
594
|
+
### Review Comments
|
|
595
|
+
|
|
596
|
+
**Before:**
|
|
597
|
+
```typescript
|
|
598
|
+
// Hard to test - embedded dependency
|
|
599
|
+
function processPayment(order: Order) {
|
|
600
|
+
const stripe = new Stripe(process.env.STRIPE_KEY);
|
|
601
|
+
return stripe.charges.create({
|
|
602
|
+
amount: order.total,
|
|
603
|
+
currency: 'usd',
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
**Review Comment:**
|
|
609
|
+
> Consider injecting the payment processor to improve testability:
|
|
610
|
+
> ```typescript
|
|
611
|
+
> function processPayment(order: Order, processor: PaymentProcessor) {
|
|
612
|
+
> return processor.charge(order.total, 'usd');
|
|
613
|
+
> }
|
|
614
|
+
> ```
|
|
615
|
+
> This allows testing with a mock processor without hitting Stripe's API.
|
|
616
|
+
|
|
617
|
+
---
|
|
618
|
+
|
|
619
|
+
## Test Maintenance Strategies
|
|
620
|
+
|
|
621
|
+
Keep tests maintainable as the codebase evolves.
|
|
622
|
+
|
|
623
|
+
### Reducing Duplication
|
|
624
|
+
|
|
625
|
+
**Use helpers for common assertions:**
|
|
626
|
+
|
|
627
|
+
```typescript
|
|
628
|
+
// __tests__/helpers/assertions.ts
|
|
629
|
+
export function expectLoadingState(container: HTMLElement) {
|
|
630
|
+
expect(within(container).getByRole('progressbar')).toBeInTheDocument();
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
export function expectErrorState(container: HTMLElement, message: string) {
|
|
634
|
+
expect(within(container).getByRole('alert')).toHaveTextContent(message);
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// Usage
|
|
638
|
+
it('shows loading state', () => {
|
|
639
|
+
render(<DataList />);
|
|
640
|
+
expectLoadingState(screen.getByTestId('data-list'));
|
|
641
|
+
});
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
**Use factory functions:**
|
|
645
|
+
|
|
646
|
+
```typescript
|
|
647
|
+
// Instead of repeating setup
|
|
648
|
+
function renderWithUser(ui: ReactElement, user = createUser()) {
|
|
649
|
+
return {
|
|
650
|
+
user,
|
|
651
|
+
...render(<AuthProvider user={user}>{ui}</AuthProvider>),
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
### Updating Tests When Code Changes
|
|
657
|
+
|
|
658
|
+
**Scenario: Renaming a prop**
|
|
659
|
+
|
|
660
|
+
```typescript
|
|
661
|
+
// Old component
|
|
662
|
+
<Button onClick={handleClick} />
|
|
663
|
+
|
|
664
|
+
// New component
|
|
665
|
+
<Button onPress={handleClick} />
|
|
666
|
+
|
|
667
|
+
// Find and update all tests
|
|
668
|
+
// grep -r "onClick" __tests__/ --include="*.test.tsx"
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
**Scenario: Changing API response shape**
|
|
672
|
+
|
|
673
|
+
```typescript
|
|
674
|
+
// Update factory first
|
|
675
|
+
export function createUserResponse(overrides = {}) {
|
|
676
|
+
return {
|
|
677
|
+
user: { // New nested structure
|
|
678
|
+
id: '1',
|
|
679
|
+
name: 'Test User',
|
|
680
|
+
...overrides,
|
|
681
|
+
},
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
// Tests automatically get new shape
|
|
686
|
+
```
|
|
687
|
+
|
|
688
|
+
### When to Delete Tests
|
|
689
|
+
|
|
690
|
+
- **Redundant coverage** - Multiple tests testing the same thing
|
|
691
|
+
- **Testing implementation** - Tests that break on refactor
|
|
692
|
+
- **Obsolete features** - Tests for removed functionality
|
|
693
|
+
- **Flaky beyond repair** - Tests that can't be stabilized
|
|
694
|
+
|
|
695
|
+
### Test Documentation
|
|
696
|
+
|
|
697
|
+
```typescript
|
|
698
|
+
/**
|
|
699
|
+
* @group integration
|
|
700
|
+
* @requires database
|
|
701
|
+
*
|
|
702
|
+
* Tests for the order processing workflow.
|
|
703
|
+
* These tests require a running PostgreSQL instance.
|
|
704
|
+
*
|
|
705
|
+
* Setup: docker-compose up -d postgres
|
|
706
|
+
*/
|
|
707
|
+
describe('OrderProcessor', () => {
|
|
708
|
+
/**
|
|
709
|
+
* Verifies that orders with backordered items
|
|
710
|
+
* are split into separate fulfillment batches.
|
|
711
|
+
*
|
|
712
|
+
* Related: JIRA-1234
|
|
713
|
+
*/
|
|
714
|
+
it('splits orders with backordered items', () => {});
|
|
715
|
+
});
|
|
716
|
+
```
|
|
717
|
+
|
|
718
|
+
---
|
|
719
|
+
|
|
720
|
+
## Debugging Failed Tests
|
|
721
|
+
|
|
722
|
+
Techniques for investigating test failures.
|
|
723
|
+
|
|
724
|
+
### Jest Debugging
|
|
725
|
+
|
|
726
|
+
**Run single test:**
|
|
727
|
+
```bash
|
|
728
|
+
# By name pattern
|
|
729
|
+
npx jest -t "should validate email"
|
|
730
|
+
|
|
731
|
+
# By file
|
|
732
|
+
npx jest src/utils/__tests__/validation.test.ts
|
|
733
|
+
|
|
734
|
+
# Watch mode for iteration
|
|
735
|
+
npx jest --watch
|
|
736
|
+
```
|
|
737
|
+
|
|
738
|
+
**Debug with Node inspector:**
|
|
739
|
+
```bash
|
|
740
|
+
node --inspect-brk node_modules/.bin/jest --runInBand
|
|
741
|
+
# Open chrome://inspect in Chrome
|
|
742
|
+
```
|
|
743
|
+
|
|
744
|
+
**Verbose output:**
|
|
745
|
+
```bash
|
|
746
|
+
npx jest --verbose --no-coverage
|
|
747
|
+
```
|
|
748
|
+
|
|
749
|
+
### React Testing Library Debugging
|
|
750
|
+
|
|
751
|
+
```typescript
|
|
752
|
+
it('renders user profile', async () => {
|
|
753
|
+
render(<UserProfile userId="123" />);
|
|
754
|
+
|
|
755
|
+
// Print current DOM
|
|
756
|
+
screen.debug();
|
|
757
|
+
|
|
758
|
+
// Print specific element
|
|
759
|
+
screen.debug(screen.getByRole('heading'));
|
|
760
|
+
|
|
761
|
+
// Log accessible roles
|
|
762
|
+
screen.logTestingPlaygroundURL(); // Opens interactive playground
|
|
763
|
+
|
|
764
|
+
// Check what queries would match
|
|
765
|
+
const element = screen.getByRole('button');
|
|
766
|
+
console.log(prettyDOM(element));
|
|
767
|
+
});
|
|
768
|
+
```
|
|
769
|
+
|
|
770
|
+
### Playwright Debugging
|
|
771
|
+
|
|
772
|
+
```bash
|
|
773
|
+
# Debug mode - opens browser with inspector
|
|
774
|
+
npx playwright test --debug
|
|
775
|
+
|
|
776
|
+
# UI mode - visual test runner
|
|
777
|
+
npx playwright test --ui
|
|
778
|
+
|
|
779
|
+
# Headed mode - see browser
|
|
780
|
+
npx playwright test --headed
|
|
781
|
+
|
|
782
|
+
# Trace viewer after failure
|
|
783
|
+
npx playwright show-trace trace.zip
|
|
784
|
+
```
|
|
785
|
+
|
|
786
|
+
**Pause in test:**
|
|
787
|
+
```typescript
|
|
788
|
+
test('debug this', async ({ page }) => {
|
|
789
|
+
await page.goto('/');
|
|
790
|
+
await page.pause(); // Opens inspector
|
|
791
|
+
await page.click('button');
|
|
792
|
+
});
|
|
793
|
+
```
|
|
794
|
+
|
|
795
|
+
### Common Failure Patterns
|
|
796
|
+
|
|
797
|
+
| Symptom | Likely Cause | Debug Approach |
|
|
798
|
+
|---------|--------------|----------------|
|
|
799
|
+
| "Unable to find element" | Wrong query or element not rendered | `screen.debug()`, check async |
|
|
800
|
+
| "Expected X, received Y" | Logic error or stale mock | Log intermediate values |
|
|
801
|
+
| "Timeout exceeded" | Slow async or missing await | Increase timeout, check promises |
|
|
802
|
+
| "Cannot read property of undefined" | Missing mock or setup | Check beforeEach, mock returns |
|
|
803
|
+
| Passes locally, fails in CI | Environment difference | Check env vars, timing |
|
|
804
|
+
|
|
805
|
+
### Investigating Flaky Failures
|
|
806
|
+
|
|
807
|
+
```typescript
|
|
808
|
+
// Add logging for intermittent failures
|
|
809
|
+
it('processes order', async () => {
|
|
810
|
+
console.log('Test started at', Date.now());
|
|
811
|
+
|
|
812
|
+
const order = await createOrder();
|
|
813
|
+
console.log('Order created:', order.id);
|
|
814
|
+
|
|
815
|
+
const result = await processOrder(order);
|
|
816
|
+
console.log('Process result:', result);
|
|
817
|
+
|
|
818
|
+
expect(result.status).toBe('completed');
|
|
819
|
+
});
|
|
820
|
+
```
|
|
821
|
+
|
|
822
|
+
---
|
|
823
|
+
|
|
824
|
+
## Quality Metrics and KPIs
|
|
825
|
+
|
|
826
|
+
Measure test suite effectiveness and track quality improvements.
|
|
827
|
+
|
|
828
|
+
### Key Metrics
|
|
829
|
+
|
|
830
|
+
**Coverage Metrics:**
|
|
831
|
+
|
|
832
|
+
| Metric | Target | Measurement |
|
|
833
|
+
|--------|--------|-------------|
|
|
834
|
+
| Line coverage | 80% | `jest --coverage` |
|
|
835
|
+
| Branch coverage | 75% | `jest --coverage` |
|
|
836
|
+
| Function coverage | 80% | `jest --coverage` |
|
|
837
|
+
| Critical path coverage | 95% | Custom tracking |
|
|
838
|
+
|
|
839
|
+
**Test Suite Health:**
|
|
840
|
+
|
|
841
|
+
| Metric | Target | Measurement |
|
|
842
|
+
|--------|--------|-------------|
|
|
843
|
+
| Test pass rate | 100% | CI reports |
|
|
844
|
+
| Flaky test rate | <1% | Track retries |
|
|
845
|
+
| Test execution time | <5 min | CI timing |
|
|
846
|
+
| Tests per component | ≥3 | Test count / components |
|
|
847
|
+
|
|
848
|
+
**Defect Metrics:**
|
|
849
|
+
|
|
850
|
+
| Metric | Target | Measurement |
|
|
851
|
+
|--------|--------|-------------|
|
|
852
|
+
| Defects found in testing | >70% | Bug tracking |
|
|
853
|
+
| Defects escaped to prod | <10% | Production bugs |
|
|
854
|
+
| Regression rate | <5% | Bugs reintroduced |
|
|
855
|
+
| Mean time to detect | <1 day | Bug timestamps |
|
|
856
|
+
|
|
857
|
+
### Dashboard Example
|
|
858
|
+
|
|
859
|
+
```typescript
|
|
860
|
+
// scripts/test-metrics.ts
|
|
861
|
+
import { readCoverageReport } from './utils';
|
|
862
|
+
|
|
863
|
+
const coverage = readCoverageReport('./coverage/coverage-summary.json');
|
|
864
|
+
const testResults = readTestReport('./reports/jest-results.json');
|
|
865
|
+
|
|
866
|
+
const metrics = {
|
|
867
|
+
coverage: {
|
|
868
|
+
lines: coverage.total.lines.pct,
|
|
869
|
+
branches: coverage.total.branches.pct,
|
|
870
|
+
functions: coverage.total.functions.pct,
|
|
871
|
+
},
|
|
872
|
+
tests: {
|
|
873
|
+
total: testResults.numTotalTests,
|
|
874
|
+
passed: testResults.numPassedTests,
|
|
875
|
+
failed: testResults.numFailedTests,
|
|
876
|
+
passRate: (testResults.numPassedTests / testResults.numTotalTests) * 100,
|
|
877
|
+
},
|
|
878
|
+
execution: {
|
|
879
|
+
duration: testResults.testResults.reduce((sum, r) => sum + r.duration, 0),
|
|
880
|
+
},
|
|
881
|
+
};
|
|
882
|
+
|
|
883
|
+
console.log('Test Metrics:', JSON.stringify(metrics, null, 2));
|
|
884
|
+
```
|
|
885
|
+
|
|
886
|
+
### CI Quality Gates
|
|
887
|
+
|
|
888
|
+
```yaml
|
|
889
|
+
# .github/workflows/quality.yml
|
|
890
|
+
name: Quality Gates
|
|
891
|
+
|
|
892
|
+
on: [push, pull_request]
|
|
893
|
+
|
|
894
|
+
jobs:
|
|
895
|
+
test:
|
|
896
|
+
runs-on: ubuntu-latest
|
|
897
|
+
steps:
|
|
898
|
+
- uses: actions/checkout@v4
|
|
899
|
+
- uses: actions/setup-node@v4
|
|
900
|
+
|
|
901
|
+
- run: npm ci
|
|
902
|
+
- run: npm test -- --coverage
|
|
903
|
+
|
|
904
|
+
# Coverage gate
|
|
905
|
+
- name: Check coverage
|
|
906
|
+
run: |
|
|
907
|
+
coverage=$(jq '.total.lines.pct' coverage/coverage-summary.json)
|
|
908
|
+
if (( $(echo "$coverage < 80" | bc -l) )); then
|
|
909
|
+
echo "Coverage $coverage% is below 80% threshold"
|
|
910
|
+
exit 1
|
|
911
|
+
fi
|
|
912
|
+
|
|
913
|
+
# Test count gate
|
|
914
|
+
- name: Check test count
|
|
915
|
+
run: |
|
|
916
|
+
tests=$(jq '.numTotalTests' reports/test-results.json)
|
|
917
|
+
if [ "$tests" -lt 100 ]; then
|
|
918
|
+
echo "Test count $tests is below minimum of 100"
|
|
919
|
+
exit 1
|
|
920
|
+
fi
|
|
921
|
+
```
|
|
922
|
+
|
|
923
|
+
### Trend Tracking
|
|
924
|
+
|
|
925
|
+
Track metrics over time to identify trends:
|
|
926
|
+
|
|
927
|
+
```typescript
|
|
928
|
+
// Weekly metrics collection
|
|
929
|
+
{
|
|
930
|
+
"week": "2024-W03",
|
|
931
|
+
"coverage": {
|
|
932
|
+
"lines": 82.4,
|
|
933
|
+
"branches": 76.1,
|
|
934
|
+
"trend": "+1.2%" // vs previous week
|
|
935
|
+
},
|
|
936
|
+
"tests": {
|
|
937
|
+
"total": 487,
|
|
938
|
+
"new": 23,
|
|
939
|
+
"removed": 5
|
|
940
|
+
},
|
|
941
|
+
"execution": {
|
|
942
|
+
"avgDuration": 245, // seconds
|
|
943
|
+
"trend": "-12s"
|
|
944
|
+
},
|
|
945
|
+
"flaky": {
|
|
946
|
+
"count": 3,
|
|
947
|
+
"rate": 0.6
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
```
|
|
951
|
+
|
|
952
|
+
---
|
|
953
|
+
|
|
954
|
+
## Summary
|
|
955
|
+
|
|
956
|
+
1. **Write testable code** - Inject dependencies, use pure functions, separate concerns
|
|
957
|
+
2. **Name tests clearly** - Describe behavior, not implementation
|
|
958
|
+
3. **Follow AAA pattern** - Arrange, Act, Assert for clear structure
|
|
959
|
+
4. **Isolate tests** - Fresh state, reset mocks, no dependencies between tests
|
|
960
|
+
5. **Fix flaky tests** - Handle timing, use deterministic data, mock externals
|
|
961
|
+
6. **Review for testability** - Check during code review, not after
|
|
962
|
+
7. **Maintain tests** - Reduce duplication, update with code changes
|
|
963
|
+
8. **Debug systematically** - Use debug tools, log strategically
|
|
964
|
+
9. **Measure quality** - Track coverage, pass rate, execution time
|