cfsa-antigravity 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.mjs +324 -0
- package/package.json +34 -0
- package/template/.agent/instructions/commands.md +48 -0
- package/template/.agent/instructions/patterns.md +61 -0
- package/template/.agent/instructions/structure.md +29 -0
- package/template/.agent/instructions/tech-stack.md +43 -0
- package/template/.agent/instructions/workflow.md +41 -0
- package/template/.agent/kit-sync.md +15 -0
- package/template/.agent/rules/boundary-not-placeholder.md +146 -0
- package/template/.agent/rules/completion-checklist.md +48 -0
- package/template/.agent/rules/decision-classification.md +103 -0
- package/template/.agent/rules/extensibility.md +47 -0
- package/template/.agent/rules/question-vs-command.md +81 -0
- package/template/.agent/rules/security-first.md +43 -0
- package/template/.agent/rules/specificity-standards.md +54 -0
- package/template/.agent/rules/tdd-contract-first.md +57 -0
- package/template/.agent/rules/vertical-slices.md +42 -0
- package/template/.agent/skill-library/MANIFEST.md +480 -0
- package/template/.agent/skill-library/README.md +38 -0
- package/template/.agent/skill-library/meta/brand-guidelines/SKILL.md +73 -0
- package/template/.agent/skill-library/meta/claude-code/README.md +9 -0
- package/template/.agent/skill-library/meta/claude-code/agent-development/SKILL.md +415 -0
- package/template/.agent/skill-library/meta/claude-code/hook-development/SKILL.md +712 -0
- package/template/.agent/skill-library/meta/claude-code/plugin-structure/SKILL.md +476 -0
- package/template/.agent/skill-library/meta/git-advanced/SKILL.md +972 -0
- package/template/.agent/skill-library/meta/mcp-builder/SKILL.md +236 -0
- package/template/.agent/skill-library/meta/product-marketing-context/SKILL.md +241 -0
- package/template/.agent/skill-library/meta/regex-patterns/SKILL.md +751 -0
- package/template/.agent/skill-library/meta/tmux-processes/SKILL.md +210 -0
- package/template/.agent/skill-library/meta/using-tmux-for-interactive-commands/SKILL.md +178 -0
- package/template/.agent/skill-library/stack/3d/threejs-pro/SKILL.md +300 -0
- package/template/.agent/skill-library/stack/ai/ai-sdk/SKILL.md +77 -0
- package/template/.agent/skill-library/stack/ai/langchain/SKILL.md +530 -0
- package/template/.agent/skill-library/stack/ai/ollama/SKILL.md +321 -0
- package/template/.agent/skill-library/stack/ai/openai-sdk/SKILL.md +549 -0
- package/template/.agent/skill-library/stack/analytics/google-analytics/SKILL.md +153 -0
- package/template/.agent/skill-library/stack/api/graphql/SKILL.md +1061 -0
- package/template/.agent/skill-library/stack/api/trpc/SKILL.md +576 -0
- package/template/.agent/skill-library/stack/auth/authjs/SKILL.md +569 -0
- package/template/.agent/skill-library/stack/auth/clerk/SKILL.md +590 -0
- package/template/.agent/skill-library/stack/auth/firebase-auth/SKILL.md +734 -0
- package/template/.agent/skill-library/stack/cms/payload-cms/SKILL.md +573 -0
- package/template/.agent/skill-library/stack/cms/shopify/SKILL.md +1193 -0
- package/template/.agent/skill-library/stack/cms/wordpress/SKILL.md +1104 -0
- package/template/.agent/skill-library/stack/css/sass-scss/SKILL.md +1121 -0
- package/template/.agent/skill-library/stack/css/tailwind-css-patterns/SKILL.md +863 -0
- package/template/.agent/skill-library/stack/css/tailwind-design-system/SKILL.md +490 -0
- package/template/.agent/skill-library/stack/css/vanilla-css/SKILL.md +1078 -0
- package/template/.agent/skill-library/stack/databases/clickhouse/SKILL.md +311 -0
- package/template/.agent/skill-library/stack/databases/influxdb/SKILL.md +280 -0
- package/template/.agent/skill-library/stack/databases/lancedb/SKILL.md +415 -0
- package/template/.agent/skill-library/stack/databases/mongodb/SKILL.md +1169 -0
- package/template/.agent/skill-library/stack/databases/neo4j/SKILL.md +839 -0
- package/template/.agent/skill-library/stack/databases/pgvector/SKILL.md +241 -0
- package/template/.agent/skill-library/stack/databases/pinecone/SKILL.md +212 -0
- package/template/.agent/skill-library/stack/databases/postgresql/SKILL.md +658 -0
- package/template/.agent/skill-library/stack/databases/qdrant/SKILL.md +312 -0
- package/template/.agent/skill-library/stack/databases/redis/SKILL.md +1079 -0
- package/template/.agent/skill-library/stack/databases/spacetimedb/SKILL.md +532 -0
- package/template/.agent/skill-library/stack/databases/sqlite/SKILL.md +1132 -0
- package/template/.agent/skill-library/stack/databases/supabase/SKILL.md +640 -0
- package/template/.agent/skill-library/stack/databases/surrealdb-expert/SKILL.md +945 -0
- package/template/.agent/skill-library/stack/databases/timescaledb/SKILL.md +745 -0
- package/template/.agent/skill-library/stack/databases/weaviate/SKILL.md +218 -0
- package/template/.agent/skill-library/stack/devops/github-actions/SKILL.md +554 -0
- package/template/.agent/skill-library/stack/devops/kubernetes/SKILL.md +950 -0
- package/template/.agent/skill-library/stack/devops/nginx/SKILL.md +841 -0
- package/template/.agent/skill-library/stack/devops/terraform/SKILL.md +860 -0
- package/template/.agent/skill-library/stack/email/resend/SKILL.md +391 -0
- package/template/.agent/skill-library/stack/engines/godot/SKILL.md +488 -0
- package/template/.agent/skill-library/stack/extensions/chrome-extension/SKILL.md +375 -0
- package/template/.agent/skill-library/stack/extensions/vscode-extension/SKILL.md +453 -0
- package/template/.agent/skill-library/stack/frameworks/astro-framework/SKILL.md +162 -0
- package/template/.agent/skill-library/stack/frameworks/electron/SKILL.md +1286 -0
- package/template/.agent/skill-library/stack/frameworks/fastapi/SKILL.md +650 -0
- package/template/.agent/skill-library/stack/frameworks/hono/SKILL.md +90 -0
- package/template/.agent/skill-library/stack/frameworks/nestjs/SKILL.md +878 -0
- package/template/.agent/skill-library/stack/frameworks/nextjs/SKILL.md +635 -0
- package/template/.agent/skill-library/stack/frameworks/nuxt/SKILL.md +564 -0
- package/template/.agent/skill-library/stack/frameworks/sveltekit/SKILL.md +614 -0
- package/template/.agent/skill-library/stack/frameworks/tauri/SKILL.md +920 -0
- package/template/.agent/skill-library/stack/gamedev/godot/SKILL.md +1032 -0
- package/template/.agent/skill-library/stack/gamedev/unity/SKILL.md +1175 -0
- package/template/.agent/skill-library/stack/hosting/aws/SKILL.md +467 -0
- package/template/.agent/skill-library/stack/hosting/cloudflare/SKILL.md +201 -0
- package/template/.agent/skill-library/stack/hosting/docker-expert/SKILL.md +409 -0
- package/template/.agent/skill-library/stack/hosting/vercel/SKILL.md +484 -0
- package/template/.agent/skill-library/stack/languages/bash-scripting/SKILL.md +773 -0
- package/template/.agent/skill-library/stack/languages/c-cpp/SKILL.md +712 -0
- package/template/.agent/skill-library/stack/languages/gdscript/SKILL.md +789 -0
- package/template/.agent/skill-library/stack/languages/go/SKILL.md +664 -0
- package/template/.agent/skill-library/stack/languages/java/SKILL.md +778 -0
- package/template/.agent/skill-library/stack/languages/kotlin/SKILL.md +665 -0
- package/template/.agent/skill-library/stack/languages/python/SKILL.md +678 -0
- package/template/.agent/skill-library/stack/languages/rust/SKILL.md +673 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/SKILL.md +141 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/advanced-generics.md +90 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/branded-types.md +57 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/builder-pattern.md +71 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/common-pitfalls.md +135 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/conditional-types.md +27 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/decorators.md +98 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/discriminated-unions.md +62 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/mapped-types.md +53 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/performance-best-practices.md +104 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/template-literal-types.md +49 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/testing-types.md +112 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/type-guards.md +70 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/type-inference.md +101 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/utility-types.md +98 -0
- package/template/.agent/skill-library/stack/languages/vanilla-javascript/SKILL.md +803 -0
- package/template/.agent/skill-library/stack/messaging/kafka/SKILL.md +235 -0
- package/template/.agent/skill-library/stack/mobile/expo-react-native/SKILL.md +665 -0
- package/template/.agent/skill-library/stack/mobile/flutter/SKILL.md +316 -0
- package/template/.agent/skill-library/stack/mobile/react-native/SKILL.md +337 -0
- package/template/.agent/skill-library/stack/monitoring/posthog/SKILL.md +396 -0
- package/template/.agent/skill-library/stack/monitoring/sentry/SKILL.md +509 -0
- package/template/.agent/skill-library/stack/observability/datadog/SKILL.md +179 -0
- package/template/.agent/skill-library/stack/observability/distributed-tracing/SKILL.md +140 -0
- package/template/.agent/skill-library/stack/observability/logging-best-practices/SKILL.md +168 -0
- package/template/.agent/skill-library/stack/observability/opentelemetry/SKILL.md +164 -0
- package/template/.agent/skill-library/stack/observability/prometheus-grafana/SKILL.md +246 -0
- package/template/.agent/skill-library/stack/observability/python-observability/SKILL.md +158 -0
- package/template/.agent/skill-library/stack/orm/drizzle-orm/SKILL.md +613 -0
- package/template/.agent/skill-library/stack/orm/prisma/SKILL.md +744 -0
- package/template/.agent/skill-library/stack/payments/lemonsqueezy/SKILL.md +393 -0
- package/template/.agent/skill-library/stack/payments/stripe-integration/SKILL.md +457 -0
- package/template/.agent/skill-library/stack/queue/bullmq/SKILL.md +385 -0
- package/template/.agent/skill-library/stack/queue/inngest/SKILL.md +438 -0
- package/template/.agent/skill-library/stack/realtime/socketio/SKILL.md +595 -0
- package/template/.agent/skill-library/stack/search/elasticsearch/SKILL.md +248 -0
- package/template/.agent/skill-library/stack/search/meilisearch/SKILL.md +385 -0
- package/template/.agent/skill-library/stack/security/crypto-patterns/SKILL.md +437 -0
- package/template/.agent/skill-library/stack/security/csp-cors-headers/SKILL.md +588 -0
- package/template/.agent/skill-library/stack/security/dependency-auditing/SKILL.md +560 -0
- package/template/.agent/skill-library/stack/security/input-sanitization/SKILL.md +430 -0
- package/template/.agent/skill-library/stack/security/owasp-web-security/SKILL.md +421 -0
- package/template/.agent/skill-library/stack/state/tanstack-query/SKILL.md +637 -0
- package/template/.agent/skill-library/stack/state/zustand/SKILL.md +483 -0
- package/template/.agent/skill-library/stack/storage/aws-s3/SKILL.md +415 -0
- package/template/.agent/skill-library/stack/testing/playwright/SKILL.md +641 -0
- package/template/.agent/skill-library/stack/testing/storybook/SKILL.md +923 -0
- package/template/.agent/skill-library/stack/testing/testing-library/SKILL.md +872 -0
- package/template/.agent/skill-library/stack/testing/vitest/SKILL.md +714 -0
- package/template/.agent/skill-library/stack/ui/react-best-practices/SKILL.md +877 -0
- package/template/.agent/skill-library/stack/ui/react-composition-patterns/SKILL.md +1107 -0
- package/template/.agent/skill-library/stack/ui/react-flow/SKILL.md +425 -0
- package/template/.agent/skill-library/stack/ui/shadcn-ui/SKILL.md +703 -0
- package/template/.agent/skill-library/surface/api/api-caching/SKILL.md +458 -0
- package/template/.agent/skill-library/surface/api/api-documentation-openapi/SKILL.md +697 -0
- package/template/.agent/skill-library/surface/api/api-error-handling/SKILL.md +478 -0
- package/template/.agent/skill-library/surface/api/api-security-checklist/SKILL.md +147 -0
- package/template/.agent/skill-library/surface/api/api-versioning/SKILL.md +420 -0
- package/template/.agent/skill-library/surface/api/email-best-practices/SKILL.md +59 -0
- package/template/.agent/skill-library/surface/api/rate-limiting-abuse-protection/SKILL.md +147 -0
- package/template/.agent/skill-library/surface/api/rest-api-design/SKILL.md +478 -0
- package/template/.agent/skill-library/surface/api/webhook-design/SKILL.md +752 -0
- package/template/.agent/skill-library/surface/cli/cli-configuration-management/SKILL.md +445 -0
- package/template/.agent/skill-library/surface/cli/cli-error-diagnostics/SKILL.md +515 -0
- package/template/.agent/skill-library/surface/cli/cli-shell-integration/SKILL.md +479 -0
- package/template/.agent/skill-library/surface/cli/cli-ux-design/SKILL.md +477 -0
- package/template/.agent/skill-library/surface/desktop/desktop-app-distribution/SKILL.md +416 -0
- package/template/.agent/skill-library/surface/desktop/desktop-security-sandboxing/SKILL.md +407 -0
- package/template/.agent/skill-library/surface/desktop/desktop-ux-conventions/SKILL.md +361 -0
- package/template/.agent/skill-library/surface/desktop/native-os-integration/SKILL.md +563 -0
- package/template/.agent/skill-library/surface/extension/browser-extension-patterns/SKILL.md +482 -0
- package/template/.agent/skill-library/surface/extension/plugin-architecture-design/SKILL.md +632 -0
- package/template/.agent/skill-library/surface/extension/vscode-extension-development/SKILL.md +728 -0
- package/template/.agent/skill-library/surface/mobile/app-store-submission/SKILL.md +304 -0
- package/template/.agent/skill-library/surface/mobile/mobile-offline-sync/SKILL.md +443 -0
- package/template/.agent/skill-library/surface/mobile/mobile-responsive-patterns/SKILL.md +432 -0
- package/template/.agent/skill-library/surface/mobile/push-notifications/SKILL.md +495 -0
- package/template/.agent/skill-library/surface/web/accessibility-compliance/SKILL.md +827 -0
- package/template/.agent/skill-library/surface/web/ai-seo/SKILL.md +398 -0
- package/template/.agent/skill-library/surface/web/ai-seo/references/content-patterns.md +285 -0
- package/template/.agent/skill-library/surface/web/ai-seo/references/platform-ranking-factors.md +152 -0
- package/template/.agent/skill-library/surface/web/analytics-tracking/SKILL.md +309 -0
- package/template/.agent/skill-library/surface/web/analytics-tracking/references/event-library.md +260 -0
- package/template/.agent/skill-library/surface/web/analytics-tracking/references/ga4-implementation.md +300 -0
- package/template/.agent/skill-library/surface/web/analytics-tracking/references/gtm-implementation.md +390 -0
- package/template/.agent/skill-library/surface/web/authentication-ui-flows/SKILL.md +530 -0
- package/template/.agent/skill-library/surface/web/dark-mode-theming/SKILL.md +516 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/SKILL.md +105 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/data/charts.csv +26 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/data/colors.csv +97 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/data/landing.csv +31 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/data/styles.csv +59 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/data/typography.csv +58 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/data/ux-guidelines.csv +100 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/scripts/core.py +258 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/scripts/design_system.py +1067 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/scripts/search.py +106 -0
- package/template/.agent/skill-library/surface/web/form-handling-validation/SKILL.md +675 -0
- package/template/.agent/skill-library/surface/web/frontend-design/SKILL.md +1393 -0
- package/template/.agent/skill-library/surface/web/frontend-design/templates/cppn-hero.tsx +299 -0
- package/template/.agent/skill-library/surface/web/frontend-design/templates/wave-hero.tsx +875 -0
- package/template/.agent/skill-library/surface/web/frontend-verification/SKILL.md +111 -0
- package/template/.agent/skill-library/surface/web/frontend-verification/scripts/ux_audit.py +739 -0
- package/template/.agent/skill-library/surface/web/i18n-localization/SKILL.md +154 -0
- package/template/.agent/skill-library/surface/web/offline-first-pwa/SKILL.md +657 -0
- package/template/.agent/skill-library/surface/web/page-cro/SKILL.md +182 -0
- package/template/.agent/skill-library/surface/web/page-cro/references/experiments.md +248 -0
- package/template/.agent/skill-library/surface/web/programmatic-seo/SKILL.md +238 -0
- package/template/.agent/skill-library/surface/web/programmatic-seo/references/playbooks.md +308 -0
- package/template/.agent/skill-library/surface/web/schema-markup/SKILL.md +179 -0
- package/template/.agent/skill-library/surface/web/schema-markup/references/schema-examples.md +398 -0
- package/template/.agent/skill-library/surface/web/seo-audit/SKILL.md +394 -0
- package/template/.agent/skill-library/surface/web/seo-audit/references/ai-writing-detection.md +200 -0
- package/template/.agent/skill-library/surface/web/web-performance-optimization/SKILL.md +646 -0
- package/template/.agent/skill-library/surface/web/web-scraping/SKILL.md +58 -0
- package/template/.agent/skills/accessibility/SKILL.md +522 -0
- package/template/.agent/skills/accessibility/references/WCAG.md +162 -0
- package/template/.agent/skills/adversarial-review/SKILL.md +90 -0
- package/template/.agent/skills/antigravity-workflows/SKILL.md +81 -0
- package/template/.agent/skills/antigravity-workflows/resources/implementation-playbook.md +36 -0
- package/template/.agent/skills/api-design-principles/SKILL.md +37 -0
- package/template/.agent/skills/api-design-principles/assets/api-design-checklist.md +155 -0
- package/template/.agent/skills/api-design-principles/assets/rest-api-template.py +182 -0
- package/template/.agent/skills/api-design-principles/references/graphql-schema-design.md +583 -0
- package/template/.agent/skills/api-design-principles/references/rest-best-practices.md +408 -0
- package/template/.agent/skills/api-design-principles/resources/implementation-playbook.md +513 -0
- package/template/.agent/skills/api-versioning/SKILL.md +420 -0
- package/template/.agent/skills/architecture-mapping/SKILL.md +219 -0
- package/template/.agent/skills/bootstrap-agents/SKILL.md +259 -0
- package/template/.agent/skills/brainstorming/SKILL.md +236 -0
- package/template/.agent/skills/brand-guidelines/SKILL.md +44 -0
- package/template/.agent/skills/clean-code/SKILL.md +94 -0
- package/template/.agent/skills/code-review-pro/SKILL.md +152 -0
- package/template/.agent/skills/concise-planning/SKILL.md +68 -0
- package/template/.agent/skills/cross-layer-consistency/SKILL.md +117 -0
- package/template/.agent/skills/database-schema-design/SKILL.md +429 -0
- package/template/.agent/skills/deployment-procedures/SKILL.md +241 -0
- package/template/.agent/skills/design-anti-cliche/SKILL.md +159 -0
- package/template/.agent/skills/design-direction/SKILL.md +45 -0
- package/template/.agent/skills/error-handling-patterns/SKILL.md +721 -0
- package/template/.agent/skills/find-skills/SKILL.md +145 -0
- package/template/.agent/skills/git-advanced/SKILL.md +972 -0
- package/template/.agent/skills/git-workflow/SKILL.md +420 -0
- package/template/.agent/skills/idea-extraction/SKILL.md +271 -0
- package/template/.agent/skills/logging-best-practices/SKILL.md +851 -0
- package/template/.agent/skills/migration-management/SKILL.md +384 -0
- package/template/.agent/skills/minimalist-surgical-development/SKILL.md +69 -0
- package/template/.agent/skills/parallel-agents/SKILL.md +165 -0
- package/template/.agent/skills/parallel-debugging/SKILL.md +135 -0
- package/template/.agent/skills/parallel-feature-development/SKILL.md +166 -0
- package/template/.agent/skills/performance-budgeting/SKILL.md +144 -0
- package/template/.agent/skills/pipeline-rubrics/SKILL.md +51 -0
- package/template/.agent/skills/pipeline-rubrics/references/architecture-rubric.md +19 -0
- package/template/.agent/skills/pipeline-rubrics/references/be-rubric.md +21 -0
- package/template/.agent/skills/pipeline-rubrics/references/fe-rubric.md +20 -0
- package/template/.agent/skills/pipeline-rubrics/references/ia-rubric.md +19 -0
- package/template/.agent/skills/pipeline-rubrics/references/scoring.md +28 -0
- package/template/.agent/skills/pipeline-rubrics/references/vision-rubric.md +11 -0
- package/template/.agent/skills/prd-templates/SKILL.md +88 -0
- package/template/.agent/skills/prd-templates/references/architecture-design-template.md +88 -0
- package/template/.agent/skills/prd-templates/references/be-spec-template.md +101 -0
- package/template/.agent/skills/prd-templates/references/data-placement-template.md +74 -0
- package/template/.agent/skills/prd-templates/references/decomposition-templates.md +211 -0
- package/template/.agent/skills/prd-templates/references/design-system-decisions.md +198 -0
- package/template/.agent/skills/prd-templates/references/engineering-standards-template.md +124 -0
- package/template/.agent/skills/prd-templates/references/fe-classification-procedures.md +47 -0
- package/template/.agent/skills/prd-templates/references/fe-spec-template.md +84 -0
- package/template/.agent/skills/prd-templates/references/infrastructure-report-template.md +71 -0
- package/template/.agent/skills/prd-templates/references/operational-templates.md +116 -0
- package/template/.agent/skills/prd-templates/references/placeholder-guard-template.md +21 -0
- package/template/.agent/skills/prd-templates/references/surface-model.md +61 -0
- package/template/.agent/skills/prd-templates/references/vision-template.md +66 -0
- package/template/.agent/skills/prompt-engineer/README.md +659 -0
- package/template/.agent/skills/prompt-engineer/SKILL.md +249 -0
- package/template/.agent/skills/regex-patterns/SKILL.md +751 -0
- package/template/.agent/skills/resolve-ambiguity/SKILL.md +278 -0
- package/template/.agent/skills/rest-api-design/SKILL.md +478 -0
- package/template/.agent/skills/security-scanning-security-hardening/SKILL.md +231 -0
- package/template/.agent/skills/session-continuity/SKILL.md +730 -0
- package/template/.agent/skills/session-continuity/protocols/01-session-resumption.md +38 -0
- package/template/.agent/skills/session-continuity/protocols/02-progress-generation.md +85 -0
- package/template/.agent/skills/session-continuity/protocols/03-progress-update.md +70 -0
- package/template/.agent/skills/session-continuity/protocols/04-pattern-extraction.md +60 -0
- package/template/.agent/skills/session-continuity/protocols/05-session-close.md +37 -0
- package/template/.agent/skills/session-continuity/protocols/06-decision-analysis.md +84 -0
- package/template/.agent/skills/session-continuity/protocols/07-spec-pipeline-generation.md +48 -0
- package/template/.agent/skills/session-continuity/protocols/08-spec-pipeline-update.md +43 -0
- package/template/.agent/skills/session-continuity/protocols/09-parallel-claim.md +122 -0
- package/template/.agent/skills/session-continuity/protocols/10-placeholder-verification-gate.md +104 -0
- package/template/.agent/skills/session-continuity/protocols/ambiguity-gates.md +48 -0
- package/template/.agent/skills/skill-creator/LICENSE.txt +202 -0
- package/template/.agent/skills/skill-creator/README.md +270 -0
- package/template/.agent/skills/skill-creator/SKILL.md +590 -0
- package/template/.agent/skills/skill-creator/references/output-patterns.md +82 -0
- package/template/.agent/skills/skill-creator/references/workflows.md +28 -0
- package/template/.agent/skills/skill-creator/scripts/init_skill.py +303 -0
- package/template/.agent/skills/skill-creator/scripts/package_skill.py +110 -0
- package/template/.agent/skills/skill-creator/scripts/quick_validate.py +95 -0
- package/template/.agent/skills/spec-writing/SKILL.md +110 -0
- package/template/.agent/skills/systematic-debugging/CREATION-LOG.md +119 -0
- package/template/.agent/skills/systematic-debugging/SKILL.md +297 -0
- package/template/.agent/skills/systematic-debugging/condition-based-waiting-example.ts +158 -0
- package/template/.agent/skills/systematic-debugging/condition-based-waiting.md +115 -0
- package/template/.agent/skills/systematic-debugging/defense-in-depth.md +122 -0
- package/template/.agent/skills/systematic-debugging/find-polluter.sh +63 -0
- package/template/.agent/skills/systematic-debugging/root-cause-tracing.md +169 -0
- package/template/.agent/skills/systematic-debugging/test-academic.md +14 -0
- package/template/.agent/skills/systematic-debugging/test-pressure-1.md +58 -0
- package/template/.agent/skills/systematic-debugging/test-pressure-2.md +68 -0
- package/template/.agent/skills/systematic-debugging/test-pressure-3.md +69 -0
- package/template/.agent/skills/tdd-workflow/SKILL.md +409 -0
- package/template/.agent/skills/tech-stack-catalog/SKILL.md +49 -0
- package/template/.agent/skills/tech-stack-catalog/references/constraint-questions.md +21 -0
- package/template/.agent/skills/tech-stack-catalog/references/dev-tooling-decisions.md +37 -0
- package/template/.agent/skills/tech-stack-catalog/references/surface-decision-tables.md +69 -0
- package/template/.agent/skills/technical-writer/SKILL.md +242 -0
- package/template/.agent/skills/testing-strategist/SKILL.md +932 -0
- package/template/.agent/skills/verification-before-completion/SKILL.md +145 -0
- package/template/.agent/skills/workflow-automation/SKILL.md +73 -0
- package/template/.agent/workflows/audit-ambiguity-execute.md +165 -0
- package/template/.agent/workflows/audit-ambiguity-rubrics.md +83 -0
- package/template/.agent/workflows/audit-ambiguity.md +64 -0
- package/template/.agent/workflows/bootstrap-agents-fill.md +201 -0
- package/template/.agent/workflows/bootstrap-agents-provision.md +197 -0
- package/template/.agent/workflows/bootstrap-agents.md +66 -0
- package/template/.agent/workflows/create-prd-architecture.md +119 -0
- package/template/.agent/workflows/create-prd-compile.md +138 -0
- package/template/.agent/workflows/create-prd-design-system.md +135 -0
- package/template/.agent/workflows/create-prd-security.md +113 -0
- package/template/.agent/workflows/create-prd-stack.md +91 -0
- package/template/.agent/workflows/create-prd.md +168 -0
- package/template/.agent/workflows/decompose-architecture-structure.md +82 -0
- package/template/.agent/workflows/decompose-architecture-validate.md +119 -0
- package/template/.agent/workflows/decompose-architecture.md +111 -0
- package/template/.agent/workflows/evolve-contract.md +98 -0
- package/template/.agent/workflows/evolve-feature-cascade.md +140 -0
- package/template/.agent/workflows/evolve-feature-classify.md +116 -0
- package/template/.agent/workflows/evolve-feature.md +56 -0
- package/template/.agent/workflows/ideate-discover.md +144 -0
- package/template/.agent/workflows/ideate-extract.md +129 -0
- package/template/.agent/workflows/ideate-validate.md +117 -0
- package/template/.agent/workflows/ideate.md +113 -0
- package/template/.agent/workflows/implement-slice-setup.md +113 -0
- package/template/.agent/workflows/implement-slice-tdd.md +198 -0
- package/template/.agent/workflows/implement-slice.md +50 -0
- package/template/.agent/workflows/plan-phase.md +202 -0
- package/template/.agent/workflows/propagate-decision-apply.md +135 -0
- package/template/.agent/workflows/propagate-decision-scan.md +147 -0
- package/template/.agent/workflows/propagate-decision.md +56 -0
- package/template/.agent/workflows/remediate-pipeline-assess.md +138 -0
- package/template/.agent/workflows/remediate-pipeline-execute.md +135 -0
- package/template/.agent/workflows/remediate-pipeline.md +55 -0
- package/template/.agent/workflows/resolve-ambiguity.md +82 -0
- package/template/.agent/workflows/sync-kit.md +209 -0
- package/template/.agent/workflows/update-architecture-map.md +74 -0
- package/template/.agent/workflows/validate-phase.md +219 -0
- package/template/.agent/workflows/verify-infrastructure.md +207 -0
- package/template/.agent/workflows/write-architecture-spec-deepen.md +139 -0
- package/template/.agent/workflows/write-architecture-spec-design.md +202 -0
- package/template/.agent/workflows/write-architecture-spec.md +63 -0
- package/template/.agent/workflows/write-be-spec-classify.md +165 -0
- package/template/.agent/workflows/write-be-spec-write.md +98 -0
- package/template/.agent/workflows/write-be-spec.md +76 -0
- package/template/.agent/workflows/write-fe-spec-classify.md +170 -0
- package/template/.agent/workflows/write-fe-spec-write.md +94 -0
- package/template/.agent/workflows/write-fe-spec.md +71 -0
- package/template/AGENTS.md +176 -0
- package/template/GEMINI.md +177 -0
- package/template/docs/README.md +187 -0
- package/template/docs/audits/.gitkeep +0 -0
- package/template/docs/audits/README.md +10 -0
- package/template/docs/plans/.gitkeep +0 -0
- package/template/docs/plans/README.md +21 -0
- package/template/docs/plans/be/.gitkeep +0 -0
- package/template/docs/plans/be/README.md +11 -0
- package/template/docs/plans/fe/.gitkeep +0 -0
- package/template/docs/plans/fe/README.md +11 -0
- package/template/docs/plans/ia/.gitkeep +0 -0
- package/template/docs/plans/ia/README.md +17 -0
- package/template/docs/plans/ia/deep-dives/.gitkeep +0 -0
- package/template/docs/plans/ia/deep-dives/README.md +5 -0
- package/template/docs/plans/phases/.gitkeep +0 -0
- package/template/docs/plans/phases/README.md +11 -0
|
@@ -0,0 +1,872 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: testing-library
|
|
3
|
+
description: "Comprehensive Testing Library guide for React, Vue, and Angular covering the user-centric testing philosophy, query priority (getByRole, getByLabelText, getByText), user events (userEvent over fireEvent), async utilities (waitFor, findBy), rendering patterns, testing forms and modals, accessibility-driven queries, MSW for API mocking, custom render with providers, testing hooks, and common mistakes. Use when writing component and integration tests."
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Testing Library (React / Vue / Angular)
|
|
8
|
+
|
|
9
|
+
## 1. Philosophy
|
|
10
|
+
|
|
11
|
+
Testing Library enforces one principle: **test the way users interact with your software, not implementation details.**
|
|
12
|
+
|
|
13
|
+
Users do not know about component state, hooks, or context providers. They see text, labels, buttons, and roles. Tests should reflect that.
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
Good: "Click the Submit button, then verify the success message appears."
|
|
17
|
+
Bad: "Assert that setState was called with { submitted: true }."
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 2. Query Priority
|
|
23
|
+
|
|
24
|
+
Queries are ordered by how accessible they are. Use the highest-priority query that works.
|
|
25
|
+
|
|
26
|
+
### Priority 1: Accessible to Everyone
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
// getByRole -- the BEST query. Uses ARIA roles.
|
|
30
|
+
screen.getByRole('button', { name: 'Submit' });
|
|
31
|
+
screen.getByRole('heading', { level: 2 });
|
|
32
|
+
screen.getByRole('textbox', { name: 'Email' });
|
|
33
|
+
screen.getByRole('checkbox', { name: 'Remember me' });
|
|
34
|
+
screen.getByRole('link', { name: 'Learn more' });
|
|
35
|
+
screen.getByRole('dialog', { name: 'Confirm deletion' });
|
|
36
|
+
screen.getByRole('tab', { selected: true });
|
|
37
|
+
screen.getByRole('alert');
|
|
38
|
+
screen.getByRole('navigation');
|
|
39
|
+
|
|
40
|
+
// getByLabelText -- form fields with proper labels
|
|
41
|
+
screen.getByLabelText('Email address');
|
|
42
|
+
screen.getByLabelText('Password');
|
|
43
|
+
screen.getByLabelText(/first name/i);
|
|
44
|
+
|
|
45
|
+
// getByPlaceholderText -- only if there is no label
|
|
46
|
+
screen.getByPlaceholderText('Search...');
|
|
47
|
+
|
|
48
|
+
// getByText -- non-interactive text content
|
|
49
|
+
screen.getByText('No results found');
|
|
50
|
+
screen.getByText(/welcome/i);
|
|
51
|
+
|
|
52
|
+
// getByDisplayValue -- current value of form elements
|
|
53
|
+
screen.getByDisplayValue('john@example.com');
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Priority 2: Semantic Queries
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
// getByAltText -- images
|
|
60
|
+
screen.getByAltText('User avatar');
|
|
61
|
+
|
|
62
|
+
// getByTitle -- title attribute (less common)
|
|
63
|
+
screen.getByTitle('Close');
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Priority 3: Last Resort
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
// getByTestId -- ONLY when no accessible query works
|
|
70
|
+
screen.getByTestId('custom-chart-widget');
|
|
71
|
+
|
|
72
|
+
// Configure the test ID attribute:
|
|
73
|
+
// configure({ testIdAttribute: 'data-testid' });
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Query Variants
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
// getBy -- throws if not found (synchronous, for elements that exist now)
|
|
80
|
+
screen.getByRole('button', { name: 'Save' });
|
|
81
|
+
|
|
82
|
+
// queryBy -- returns null if not found (for asserting absence)
|
|
83
|
+
expect(screen.queryByText('Error message')).not.toBeInTheDocument();
|
|
84
|
+
|
|
85
|
+
// findBy -- returns a Promise, waits for element to appear (async)
|
|
86
|
+
await screen.findByText('Data loaded');
|
|
87
|
+
|
|
88
|
+
// getAllBy -- returns array, throws if empty
|
|
89
|
+
screen.getAllByRole('listitem');
|
|
90
|
+
|
|
91
|
+
// queryAllBy -- returns array, empty if none found
|
|
92
|
+
screen.queryAllByRole('listitem');
|
|
93
|
+
|
|
94
|
+
// findAllBy -- returns Promise of array, waits
|
|
95
|
+
await screen.findAllByRole('row');
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## 3. User Events
|
|
101
|
+
|
|
102
|
+
### userEvent over fireEvent
|
|
103
|
+
|
|
104
|
+
`userEvent` simulates real user behavior (focus, keyboard, mouse). `fireEvent` dispatches a single DOM event. Always prefer `userEvent`.
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
import userEvent from '@testing-library/user-event';
|
|
108
|
+
|
|
109
|
+
// ALWAYS create a user instance per test
|
|
110
|
+
const user = userEvent.setup();
|
|
111
|
+
|
|
112
|
+
// Click
|
|
113
|
+
await user.click(screen.getByRole('button', { name: 'Submit' }));
|
|
114
|
+
|
|
115
|
+
// Double click
|
|
116
|
+
await user.dblClick(screen.getByRole('button', { name: 'Select' }));
|
|
117
|
+
|
|
118
|
+
// Type text (includes focus, keydown, keypress, keyup per character)
|
|
119
|
+
await user.type(screen.getByLabelText('Email'), 'test@example.com');
|
|
120
|
+
|
|
121
|
+
// Clear then type
|
|
122
|
+
await user.clear(screen.getByLabelText('Search'));
|
|
123
|
+
await user.type(screen.getByLabelText('Search'), 'new query');
|
|
124
|
+
|
|
125
|
+
// Tab navigation
|
|
126
|
+
await user.tab();
|
|
127
|
+
expect(screen.getByLabelText('Password')).toHaveFocus();
|
|
128
|
+
|
|
129
|
+
// Keyboard shortcuts
|
|
130
|
+
await user.keyboard('{Enter}');
|
|
131
|
+
await user.keyboard('{Shift>}{A}{/Shift}'); // Shift+A
|
|
132
|
+
await user.keyboard('{Control>}{a}{/Control}'); // Ctrl+A
|
|
133
|
+
|
|
134
|
+
// Select dropdown option
|
|
135
|
+
await user.selectOptions(screen.getByRole('combobox'), 'option-value');
|
|
136
|
+
|
|
137
|
+
// Upload file
|
|
138
|
+
const file = new File(['content'], 'test.png', { type: 'image/png' });
|
|
139
|
+
await user.upload(screen.getByLabelText('Upload'), file);
|
|
140
|
+
|
|
141
|
+
// Hover
|
|
142
|
+
await user.hover(screen.getByText('Tooltip trigger'));
|
|
143
|
+
expect(screen.getByRole('tooltip')).toBeInTheDocument();
|
|
144
|
+
await user.unhover(screen.getByText('Tooltip trigger'));
|
|
145
|
+
|
|
146
|
+
// Clipboard
|
|
147
|
+
await user.copy();
|
|
148
|
+
await user.paste();
|
|
149
|
+
|
|
150
|
+
// Pointer (advanced)
|
|
151
|
+
await user.pointer({ target: element, offset: 5, keys: '[MouseLeft]' });
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## 4. Async Utilities
|
|
157
|
+
|
|
158
|
+
### waitFor
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
import { waitFor } from '@testing-library/react';
|
|
162
|
+
|
|
163
|
+
// Wait for an assertion to pass (polls until timeout)
|
|
164
|
+
await waitFor(() => {
|
|
165
|
+
expect(screen.getByText('Success')).toBeInTheDocument();
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// With options
|
|
169
|
+
await waitFor(
|
|
170
|
+
() => {
|
|
171
|
+
expect(mockFn).toHaveBeenCalledTimes(1);
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
timeout: 3000, // Max wait time (default: 1000ms)
|
|
175
|
+
interval: 100, // Polling interval (default: 50ms)
|
|
176
|
+
}
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
// waitForElementToBeRemoved
|
|
180
|
+
await waitForElementToBeRemoved(() => screen.queryByText('Loading...'));
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### findBy (Built-in Async Queries)
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
// findBy = getBy + waitFor (returns a Promise)
|
|
187
|
+
const successMessage = await screen.findByText('Operation complete');
|
|
188
|
+
expect(successMessage).toBeInTheDocument();
|
|
189
|
+
|
|
190
|
+
// findAllBy
|
|
191
|
+
const rows = await screen.findAllByRole('row');
|
|
192
|
+
expect(rows).toHaveLength(5);
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Debugging
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
// Print the current DOM tree
|
|
199
|
+
screen.debug();
|
|
200
|
+
|
|
201
|
+
// Print a specific element
|
|
202
|
+
screen.debug(screen.getByRole('form'));
|
|
203
|
+
|
|
204
|
+
// Log testing playground URL (paste into browser to visualize)
|
|
205
|
+
screen.logTestingPlaygroundURL();
|
|
206
|
+
|
|
207
|
+
// Increase debug output length
|
|
208
|
+
screen.debug(undefined, 30000);
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## 5. Rendering Patterns
|
|
214
|
+
|
|
215
|
+
### React
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
import { render, screen } from '@testing-library/react';
|
|
219
|
+
import userEvent from '@testing-library/user-event';
|
|
220
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
221
|
+
import { Button } from './Button';
|
|
222
|
+
|
|
223
|
+
describe('Button', () => {
|
|
224
|
+
it('calls onClick when clicked', async () => {
|
|
225
|
+
const user = userEvent.setup();
|
|
226
|
+
const handleClick = vi.fn();
|
|
227
|
+
render(<Button onClick={handleClick}>Save</Button>);
|
|
228
|
+
|
|
229
|
+
await user.click(screen.getByRole('button', { name: 'Save' }));
|
|
230
|
+
|
|
231
|
+
expect(handleClick).toHaveBeenCalledTimes(1);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('renders as disabled when disabled prop is true', () => {
|
|
235
|
+
render(<Button disabled>Save</Button>);
|
|
236
|
+
|
|
237
|
+
expect(screen.getByRole('button', { name: 'Save' })).toBeDisabled();
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### Vue
|
|
243
|
+
|
|
244
|
+
```typescript
|
|
245
|
+
import { render, screen } from '@testing-library/vue';
|
|
246
|
+
import userEvent from '@testing-library/user-event';
|
|
247
|
+
import Counter from './Counter.vue';
|
|
248
|
+
|
|
249
|
+
test('increments counter on click', async () => {
|
|
250
|
+
const user = userEvent.setup();
|
|
251
|
+
render(Counter, {
|
|
252
|
+
props: { initialCount: 0 },
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
await user.click(screen.getByRole('button', { name: 'Increment' }));
|
|
256
|
+
|
|
257
|
+
expect(screen.getByText('Count: 1')).toBeInTheDocument();
|
|
258
|
+
});
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### Angular
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
import { render, screen } from '@testing-library/angular';
|
|
265
|
+
import userEvent from '@testing-library/user-event';
|
|
266
|
+
import { CounterComponent } from './counter.component';
|
|
267
|
+
|
|
268
|
+
test('increments counter on click', async () => {
|
|
269
|
+
const user = userEvent.setup();
|
|
270
|
+
await render(CounterComponent, {
|
|
271
|
+
componentProperties: { initialCount: 0 },
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
await user.click(screen.getByRole('button', { name: 'Increment' }));
|
|
275
|
+
|
|
276
|
+
expect(screen.getByText('Count: 1')).toBeInTheDocument();
|
|
277
|
+
});
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
282
|
+
## 6. Custom Render with Providers
|
|
283
|
+
|
|
284
|
+
```typescript
|
|
285
|
+
// test-utils.tsx
|
|
286
|
+
import { render, RenderOptions } from '@testing-library/react';
|
|
287
|
+
import { ReactElement } from 'react';
|
|
288
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
289
|
+
import { BrowserRouter } from 'react-router-dom';
|
|
290
|
+
import { ThemeProvider } from './theme';
|
|
291
|
+
|
|
292
|
+
function createTestQueryClient(): QueryClient {
|
|
293
|
+
return new QueryClient({
|
|
294
|
+
defaultOptions: {
|
|
295
|
+
queries: {
|
|
296
|
+
retry: false,
|
|
297
|
+
gcTime: 0,
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
interface CustomRenderOptions extends Omit<RenderOptions, 'wrapper'> {
|
|
304
|
+
queryClient?: QueryClient;
|
|
305
|
+
route?: string;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function customRender(
|
|
309
|
+
ui: ReactElement,
|
|
310
|
+
options: CustomRenderOptions = {}
|
|
311
|
+
): ReturnType<typeof render> {
|
|
312
|
+
const {
|
|
313
|
+
queryClient = createTestQueryClient(),
|
|
314
|
+
route = '/',
|
|
315
|
+
...renderOptions
|
|
316
|
+
} = options;
|
|
317
|
+
|
|
318
|
+
window.history.pushState({}, 'Test', route);
|
|
319
|
+
|
|
320
|
+
function AllProviders({ children }: { children: React.ReactNode }) {
|
|
321
|
+
return (
|
|
322
|
+
<QueryClientProvider client={queryClient}>
|
|
323
|
+
<BrowserRouter>
|
|
324
|
+
<ThemeProvider>
|
|
325
|
+
{children}
|
|
326
|
+
</ThemeProvider>
|
|
327
|
+
</BrowserRouter>
|
|
328
|
+
</QueryClientProvider>
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return render(ui, { wrapper: AllProviders, ...renderOptions });
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Re-export everything from testing-library
|
|
336
|
+
export * from '@testing-library/react';
|
|
337
|
+
export { customRender as render };
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
Usage:
|
|
341
|
+
|
|
342
|
+
```typescript
|
|
343
|
+
// Import from your test-utils instead of @testing-library/react
|
|
344
|
+
import { render, screen } from '../test-utils';
|
|
345
|
+
import { UserProfile } from './UserProfile';
|
|
346
|
+
|
|
347
|
+
test('renders user profile', async () => {
|
|
348
|
+
render(<UserProfile userId="123" />, { route: '/profile/123' });
|
|
349
|
+
|
|
350
|
+
expect(await screen.findByRole('heading', { name: 'User Profile' }))
|
|
351
|
+
.toBeInTheDocument();
|
|
352
|
+
});
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
---
|
|
356
|
+
|
|
357
|
+
## 7. Testing Forms
|
|
358
|
+
|
|
359
|
+
```typescript
|
|
360
|
+
import { render, screen, waitFor } from '../test-utils';
|
|
361
|
+
import userEvent from '@testing-library/user-event';
|
|
362
|
+
import { RegistrationForm } from './RegistrationForm';
|
|
363
|
+
|
|
364
|
+
describe('RegistrationForm', () => {
|
|
365
|
+
it('submits valid form data', async () => {
|
|
366
|
+
const user = userEvent.setup();
|
|
367
|
+
const onSubmit = vi.fn();
|
|
368
|
+
render(<RegistrationForm onSubmit={onSubmit} />);
|
|
369
|
+
|
|
370
|
+
await user.type(screen.getByLabelText('Full Name'), 'Jane Doe');
|
|
371
|
+
await user.type(screen.getByLabelText('Email'), 'jane@example.com');
|
|
372
|
+
await user.type(screen.getByLabelText('Password'), 'SecurePass123!');
|
|
373
|
+
await user.type(screen.getByLabelText('Confirm Password'), 'SecurePass123!');
|
|
374
|
+
await user.click(screen.getByRole('checkbox', { name: /terms/i }));
|
|
375
|
+
await user.click(screen.getByRole('button', { name: 'Register' }));
|
|
376
|
+
|
|
377
|
+
await waitFor(() => {
|
|
378
|
+
expect(onSubmit).toHaveBeenCalledWith({
|
|
379
|
+
name: 'Jane Doe',
|
|
380
|
+
email: 'jane@example.com',
|
|
381
|
+
password: 'SecurePass123!',
|
|
382
|
+
confirmPassword: 'SecurePass123!',
|
|
383
|
+
termsAccepted: true,
|
|
384
|
+
});
|
|
385
|
+
});
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
it('shows validation errors for empty required fields', async () => {
|
|
389
|
+
const user = userEvent.setup();
|
|
390
|
+
render(<RegistrationForm onSubmit={vi.fn()} />);
|
|
391
|
+
|
|
392
|
+
await user.click(screen.getByRole('button', { name: 'Register' }));
|
|
393
|
+
|
|
394
|
+
expect(await screen.findByText('Name is required')).toBeInTheDocument();
|
|
395
|
+
expect(screen.getByText('Email is required')).toBeInTheDocument();
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
it('shows error when passwords do not match', async () => {
|
|
399
|
+
const user = userEvent.setup();
|
|
400
|
+
render(<RegistrationForm onSubmit={vi.fn()} />);
|
|
401
|
+
|
|
402
|
+
await user.type(screen.getByLabelText('Password'), 'Password1');
|
|
403
|
+
await user.type(screen.getByLabelText('Confirm Password'), 'Password2');
|
|
404
|
+
await user.click(screen.getByRole('button', { name: 'Register' }));
|
|
405
|
+
|
|
406
|
+
expect(await screen.findByText('Passwords must match')).toBeInTheDocument();
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
it('disables submit button while submitting', async () => {
|
|
410
|
+
const user = userEvent.setup();
|
|
411
|
+
const onSubmit = vi.fn(
|
|
412
|
+
() => new Promise((resolve) => setTimeout(resolve, 1000))
|
|
413
|
+
);
|
|
414
|
+
render(<RegistrationForm onSubmit={onSubmit} />);
|
|
415
|
+
|
|
416
|
+
await user.type(screen.getByLabelText('Full Name'), 'Jane');
|
|
417
|
+
await user.type(screen.getByLabelText('Email'), 'jane@test.com');
|
|
418
|
+
await user.type(screen.getByLabelText('Password'), 'Pass123!');
|
|
419
|
+
await user.type(screen.getByLabelText('Confirm Password'), 'Pass123!');
|
|
420
|
+
await user.click(screen.getByRole('checkbox', { name: /terms/i }));
|
|
421
|
+
await user.click(screen.getByRole('button', { name: 'Register' }));
|
|
422
|
+
|
|
423
|
+
expect(screen.getByRole('button', { name: /registering/i })).toBeDisabled();
|
|
424
|
+
});
|
|
425
|
+
});
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
---
|
|
429
|
+
|
|
430
|
+
## 8. Testing Modals and Dialogs
|
|
431
|
+
|
|
432
|
+
```typescript
|
|
433
|
+
import { render, screen, waitForElementToBeRemoved } from '../test-utils';
|
|
434
|
+
import userEvent from '@testing-library/user-event';
|
|
435
|
+
import { ConfirmDialog } from './ConfirmDialog';
|
|
436
|
+
|
|
437
|
+
describe('ConfirmDialog', () => {
|
|
438
|
+
it('opens when trigger is clicked', async () => {
|
|
439
|
+
const user = userEvent.setup();
|
|
440
|
+
render(<ConfirmDialog trigger="Delete Item" onConfirm={vi.fn()} />);
|
|
441
|
+
|
|
442
|
+
// Dialog should not be in DOM initially
|
|
443
|
+
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
|
|
444
|
+
|
|
445
|
+
await user.click(screen.getByRole('button', { name: 'Delete Item' }));
|
|
446
|
+
|
|
447
|
+
// Dialog should appear
|
|
448
|
+
expect(screen.getByRole('dialog', { name: 'Confirm Deletion' }))
|
|
449
|
+
.toBeInTheDocument();
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
it('calls onConfirm and closes when confirmed', async () => {
|
|
453
|
+
const user = userEvent.setup();
|
|
454
|
+
const onConfirm = vi.fn();
|
|
455
|
+
render(<ConfirmDialog trigger="Delete" onConfirm={onConfirm} />);
|
|
456
|
+
|
|
457
|
+
await user.click(screen.getByRole('button', { name: 'Delete' }));
|
|
458
|
+
await user.click(screen.getByRole('button', { name: 'Confirm' }));
|
|
459
|
+
|
|
460
|
+
expect(onConfirm).toHaveBeenCalledTimes(1);
|
|
461
|
+
await waitForElementToBeRemoved(() => screen.queryByRole('dialog'));
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
it('closes without calling onConfirm when cancelled', async () => {
|
|
465
|
+
const user = userEvent.setup();
|
|
466
|
+
const onConfirm = vi.fn();
|
|
467
|
+
render(<ConfirmDialog trigger="Delete" onConfirm={onConfirm} />);
|
|
468
|
+
|
|
469
|
+
await user.click(screen.getByRole('button', { name: 'Delete' }));
|
|
470
|
+
await user.click(screen.getByRole('button', { name: 'Cancel' }));
|
|
471
|
+
|
|
472
|
+
expect(onConfirm).not.toHaveBeenCalled();
|
|
473
|
+
await waitForElementToBeRemoved(() => screen.queryByRole('dialog'));
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
it('closes when Escape key is pressed', async () => {
|
|
477
|
+
const user = userEvent.setup();
|
|
478
|
+
render(<ConfirmDialog trigger="Delete" onConfirm={vi.fn()} />);
|
|
479
|
+
|
|
480
|
+
await user.click(screen.getByRole('button', { name: 'Delete' }));
|
|
481
|
+
expect(screen.getByRole('dialog')).toBeInTheDocument();
|
|
482
|
+
|
|
483
|
+
await user.keyboard('{Escape}');
|
|
484
|
+
await waitForElementToBeRemoved(() => screen.queryByRole('dialog'));
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
it('traps focus within the dialog', async () => {
|
|
488
|
+
const user = userEvent.setup();
|
|
489
|
+
render(<ConfirmDialog trigger="Delete" onConfirm={vi.fn()} />);
|
|
490
|
+
|
|
491
|
+
await user.click(screen.getByRole('button', { name: 'Delete' }));
|
|
492
|
+
|
|
493
|
+
// First focusable element should receive focus
|
|
494
|
+
expect(screen.getByRole('button', { name: 'Cancel' })).toHaveFocus();
|
|
495
|
+
|
|
496
|
+
// Tab should cycle within the dialog
|
|
497
|
+
await user.tab();
|
|
498
|
+
expect(screen.getByRole('button', { name: 'Confirm' })).toHaveFocus();
|
|
499
|
+
|
|
500
|
+
await user.tab();
|
|
501
|
+
expect(screen.getByRole('button', { name: 'Cancel' })).toHaveFocus();
|
|
502
|
+
});
|
|
503
|
+
});
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
---
|
|
507
|
+
|
|
508
|
+
## 9. Testing Navigation and Routing
|
|
509
|
+
|
|
510
|
+
```typescript
|
|
511
|
+
import { render, screen } from '../test-utils';
|
|
512
|
+
import userEvent from '@testing-library/user-event';
|
|
513
|
+
import { App } from './App';
|
|
514
|
+
|
|
515
|
+
describe('Navigation', () => {
|
|
516
|
+
it('navigates to profile page when link is clicked', async () => {
|
|
517
|
+
const user = userEvent.setup();
|
|
518
|
+
render(<App />, { route: '/' });
|
|
519
|
+
|
|
520
|
+
await user.click(screen.getByRole('link', { name: 'Profile' }));
|
|
521
|
+
|
|
522
|
+
expect(await screen.findByRole('heading', { name: 'Your Profile' }))
|
|
523
|
+
.toBeInTheDocument();
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
it('shows 404 page for unknown routes', () => {
|
|
527
|
+
render(<App />, { route: '/unknown-page' });
|
|
528
|
+
|
|
529
|
+
expect(screen.getByText('Page not found')).toBeInTheDocument();
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
it('redirects unauthenticated users to login', async () => {
|
|
533
|
+
render(<App />, { route: '/dashboard' });
|
|
534
|
+
|
|
535
|
+
// Should redirect to login
|
|
536
|
+
expect(await screen.findByRole('heading', { name: 'Sign In' }))
|
|
537
|
+
.toBeInTheDocument();
|
|
538
|
+
});
|
|
539
|
+
});
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
---
|
|
543
|
+
|
|
544
|
+
## 10. MSW for API Mocking
|
|
545
|
+
|
|
546
|
+
```typescript
|
|
547
|
+
// mocks/handlers.ts
|
|
548
|
+
import { http, HttpResponse } from 'msw';
|
|
549
|
+
|
|
550
|
+
export const handlers = [
|
|
551
|
+
http.get('/api/users', () => {
|
|
552
|
+
return HttpResponse.json([
|
|
553
|
+
{ id: '1', name: 'Alice', email: 'alice@example.com' },
|
|
554
|
+
{ id: '2', name: 'Bob', email: 'bob@example.com' },
|
|
555
|
+
]);
|
|
556
|
+
}),
|
|
557
|
+
|
|
558
|
+
http.post('/api/users', async ({ request }) => {
|
|
559
|
+
const body = await request.json();
|
|
560
|
+
return HttpResponse.json(
|
|
561
|
+
{ id: '3', ...body },
|
|
562
|
+
{ status: 201 }
|
|
563
|
+
);
|
|
564
|
+
}),
|
|
565
|
+
|
|
566
|
+
http.delete('/api/users/:id', ({ params }) => {
|
|
567
|
+
return new HttpResponse(null, { status: 204 });
|
|
568
|
+
}),
|
|
569
|
+
];
|
|
570
|
+
|
|
571
|
+
// mocks/server.ts
|
|
572
|
+
import { setupServer } from 'msw/node';
|
|
573
|
+
import { handlers } from './handlers';
|
|
574
|
+
|
|
575
|
+
export const server = setupServer(...handlers);
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
### Test Setup
|
|
579
|
+
|
|
580
|
+
```typescript
|
|
581
|
+
// test/setup.ts
|
|
582
|
+
import { server } from '../mocks/server';
|
|
583
|
+
import { afterAll, afterEach, beforeAll } from 'vitest';
|
|
584
|
+
|
|
585
|
+
beforeAll(() => server.listen({ onUnhandledRequest: 'error' }));
|
|
586
|
+
afterEach(() => server.resetHandlers());
|
|
587
|
+
afterAll(() => server.close());
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
### Per-Test Overrides
|
|
591
|
+
|
|
592
|
+
```typescript
|
|
593
|
+
import { http, HttpResponse } from 'msw';
|
|
594
|
+
import { server } from '../mocks/server';
|
|
595
|
+
|
|
596
|
+
test('shows error message when API fails', async () => {
|
|
597
|
+
// Override for this specific test
|
|
598
|
+
server.use(
|
|
599
|
+
http.get('/api/users', () => {
|
|
600
|
+
return HttpResponse.json(
|
|
601
|
+
{ error: 'Internal Server Error' },
|
|
602
|
+
{ status: 500 }
|
|
603
|
+
);
|
|
604
|
+
})
|
|
605
|
+
);
|
|
606
|
+
|
|
607
|
+
render(<UserList />);
|
|
608
|
+
|
|
609
|
+
expect(await screen.findByRole('alert'))
|
|
610
|
+
.toHaveTextContent('Failed to load users');
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
test('shows empty state when no users exist', async () => {
|
|
614
|
+
server.use(
|
|
615
|
+
http.get('/api/users', () => {
|
|
616
|
+
return HttpResponse.json([]);
|
|
617
|
+
})
|
|
618
|
+
);
|
|
619
|
+
|
|
620
|
+
render(<UserList />);
|
|
621
|
+
|
|
622
|
+
expect(await screen.findByText('No users found')).toBeInTheDocument();
|
|
623
|
+
});
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
---
|
|
627
|
+
|
|
628
|
+
## 11. Testing Hooks
|
|
629
|
+
|
|
630
|
+
```typescript
|
|
631
|
+
import { renderHook, act, waitFor } from '@testing-library/react';
|
|
632
|
+
import { useCounter } from './useCounter';
|
|
633
|
+
import { useDebounce } from './useDebounce';
|
|
634
|
+
|
|
635
|
+
describe('useCounter', () => {
|
|
636
|
+
it('initializes with the given value', () => {
|
|
637
|
+
const { result } = renderHook(() => useCounter(5));
|
|
638
|
+
|
|
639
|
+
expect(result.current.count).toBe(5);
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
it('increments the counter', () => {
|
|
643
|
+
const { result } = renderHook(() => useCounter(0));
|
|
644
|
+
|
|
645
|
+
act(() => {
|
|
646
|
+
result.current.increment();
|
|
647
|
+
});
|
|
648
|
+
|
|
649
|
+
expect(result.current.count).toBe(1);
|
|
650
|
+
});
|
|
651
|
+
|
|
652
|
+
it('resets to initial value', () => {
|
|
653
|
+
const { result } = renderHook(() => useCounter(0));
|
|
654
|
+
|
|
655
|
+
act(() => {
|
|
656
|
+
result.current.increment();
|
|
657
|
+
result.current.increment();
|
|
658
|
+
result.current.reset();
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
expect(result.current.count).toBe(0);
|
|
662
|
+
});
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
// Testing hooks with props that change
|
|
666
|
+
describe('useDebounce', () => {
|
|
667
|
+
it('returns debounced value after delay', async () => {
|
|
668
|
+
const { result, rerender } = renderHook(
|
|
669
|
+
({ value, delay }) => useDebounce(value, delay),
|
|
670
|
+
{ initialProps: { value: 'initial', delay: 300 } }
|
|
671
|
+
);
|
|
672
|
+
|
|
673
|
+
expect(result.current).toBe('initial');
|
|
674
|
+
|
|
675
|
+
rerender({ value: 'updated', delay: 300 });
|
|
676
|
+
|
|
677
|
+
// Value should not change immediately
|
|
678
|
+
expect(result.current).toBe('initial');
|
|
679
|
+
|
|
680
|
+
// Wait for debounce
|
|
681
|
+
await waitFor(() => {
|
|
682
|
+
expect(result.current).toBe('updated');
|
|
683
|
+
});
|
|
684
|
+
});
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
// Testing hooks that need providers
|
|
688
|
+
describe('useAuth', () => {
|
|
689
|
+
it('returns current user', () => {
|
|
690
|
+
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
|
691
|
+
<AuthProvider user={{ id: '1', name: 'Test' }}>
|
|
692
|
+
{children}
|
|
693
|
+
</AuthProvider>
|
|
694
|
+
);
|
|
695
|
+
|
|
696
|
+
const { result } = renderHook(() => useAuth(), { wrapper });
|
|
697
|
+
|
|
698
|
+
expect(result.current.user).toEqual({ id: '1', name: 'Test' });
|
|
699
|
+
});
|
|
700
|
+
});
|
|
701
|
+
```
|
|
702
|
+
|
|
703
|
+
---
|
|
704
|
+
|
|
705
|
+
## 12. Accessibility-Driven Testing
|
|
706
|
+
|
|
707
|
+
```typescript
|
|
708
|
+
// Testing Library queries naturally enforce accessibility
|
|
709
|
+
describe('Accessible Form', () => {
|
|
710
|
+
it('has properly labeled inputs', () => {
|
|
711
|
+
render(<LoginForm />);
|
|
712
|
+
|
|
713
|
+
// These queries FAIL if labels are not properly associated
|
|
714
|
+
expect(screen.getByLabelText('Email')).toBeInTheDocument();
|
|
715
|
+
expect(screen.getByLabelText('Password')).toBeInTheDocument();
|
|
716
|
+
});
|
|
717
|
+
|
|
718
|
+
it('has proper ARIA roles', () => {
|
|
719
|
+
render(<LoginForm />);
|
|
720
|
+
|
|
721
|
+
expect(screen.getByRole('form', { name: 'Login' })).toBeInTheDocument();
|
|
722
|
+
expect(screen.getByRole('button', { name: 'Sign in' })).toBeInTheDocument();
|
|
723
|
+
});
|
|
724
|
+
|
|
725
|
+
it('announces errors to screen readers', async () => {
|
|
726
|
+
const user = userEvent.setup();
|
|
727
|
+
render(<LoginForm />);
|
|
728
|
+
|
|
729
|
+
await user.click(screen.getByRole('button', { name: 'Sign in' }));
|
|
730
|
+
|
|
731
|
+
// Error messages should have role="alert"
|
|
732
|
+
expect(screen.getByRole('alert')).toHaveTextContent('Email is required');
|
|
733
|
+
});
|
|
734
|
+
|
|
735
|
+
it('manages focus after submission error', async () => {
|
|
736
|
+
const user = userEvent.setup();
|
|
737
|
+
render(<LoginForm />);
|
|
738
|
+
|
|
739
|
+
await user.click(screen.getByRole('button', { name: 'Sign in' }));
|
|
740
|
+
|
|
741
|
+
// Focus should move to the first error field
|
|
742
|
+
await waitFor(() => {
|
|
743
|
+
expect(screen.getByLabelText('Email')).toHaveFocus();
|
|
744
|
+
});
|
|
745
|
+
});
|
|
746
|
+
});
|
|
747
|
+
```
|
|
748
|
+
|
|
749
|
+
---
|
|
750
|
+
|
|
751
|
+
## 13. Common Mistakes
|
|
752
|
+
|
|
753
|
+
### Testing Implementation Details
|
|
754
|
+
|
|
755
|
+
```typescript
|
|
756
|
+
// BAD: Testing internal state
|
|
757
|
+
test('sets isLoading to true', () => {
|
|
758
|
+
const { result } = renderHook(() => useData());
|
|
759
|
+
expect(result.current.isLoading).toBe(true); // Implementation detail
|
|
760
|
+
});
|
|
761
|
+
|
|
762
|
+
// GOOD: Testing user-visible behavior
|
|
763
|
+
test('shows loading spinner while fetching', () => {
|
|
764
|
+
render(<DataList />);
|
|
765
|
+
expect(screen.getByRole('progressbar')).toBeInTheDocument();
|
|
766
|
+
});
|
|
767
|
+
|
|
768
|
+
// BAD: Testing component internals
|
|
769
|
+
test('calls handleSubmit', () => {
|
|
770
|
+
const wrapper = shallow(<Form />);
|
|
771
|
+
wrapper.instance().handleSubmit(); // Never do this
|
|
772
|
+
});
|
|
773
|
+
|
|
774
|
+
// GOOD: Testing as user would
|
|
775
|
+
test('submits form data', async () => {
|
|
776
|
+
const user = userEvent.setup();
|
|
777
|
+
render(<Form />);
|
|
778
|
+
await user.type(screen.getByLabelText('Name'), 'Test');
|
|
779
|
+
await user.click(screen.getByRole('button', { name: 'Submit' }));
|
|
780
|
+
expect(await screen.findByText('Submitted!')).toBeInTheDocument();
|
|
781
|
+
});
|
|
782
|
+
```
|
|
783
|
+
|
|
784
|
+
### Wrong Query Choice
|
|
785
|
+
|
|
786
|
+
```typescript
|
|
787
|
+
// BAD: Using test IDs when accessible queries exist
|
|
788
|
+
screen.getByTestId('submit-button'); // Users do not see test IDs
|
|
789
|
+
|
|
790
|
+
// GOOD: Using role-based queries
|
|
791
|
+
screen.getByRole('button', { name: 'Submit' });
|
|
792
|
+
|
|
793
|
+
// BAD: Using container.querySelector
|
|
794
|
+
const { container } = render(<Component />);
|
|
795
|
+
container.querySelector('.my-class'); // CSS class is an implementation detail
|
|
796
|
+
|
|
797
|
+
// GOOD: Using semantic queries
|
|
798
|
+
screen.getByRole('heading', { level: 1 });
|
|
799
|
+
```
|
|
800
|
+
|
|
801
|
+
### Incorrect Async Handling
|
|
802
|
+
|
|
803
|
+
```typescript
|
|
804
|
+
// BAD: No await on async operations
|
|
805
|
+
test('loads data', () => {
|
|
806
|
+
render(<DataList />);
|
|
807
|
+
expect(screen.getByText('Data loaded')).toBeInTheDocument(); // Race condition
|
|
808
|
+
});
|
|
809
|
+
|
|
810
|
+
// GOOD: Await async results
|
|
811
|
+
test('loads data', async () => {
|
|
812
|
+
render(<DataList />);
|
|
813
|
+
expect(await screen.findByText('Data loaded')).toBeInTheDocument();
|
|
814
|
+
});
|
|
815
|
+
|
|
816
|
+
// BAD: Using setTimeout to wait
|
|
817
|
+
test('shows toast', async () => {
|
|
818
|
+
render(<App />);
|
|
819
|
+
await new Promise((r) => setTimeout(r, 1000)); // Fragile timing
|
|
820
|
+
expect(screen.getByText('Saved')).toBeInTheDocument();
|
|
821
|
+
});
|
|
822
|
+
|
|
823
|
+
// GOOD: Using waitFor
|
|
824
|
+
test('shows toast', async () => {
|
|
825
|
+
render(<App />);
|
|
826
|
+
await waitFor(() => {
|
|
827
|
+
expect(screen.getByText('Saved')).toBeInTheDocument();
|
|
828
|
+
});
|
|
829
|
+
});
|
|
830
|
+
```
|
|
831
|
+
|
|
832
|
+
### Excessive Mocking
|
|
833
|
+
|
|
834
|
+
```typescript
|
|
835
|
+
// BAD: Mocking your own components
|
|
836
|
+
vi.mock('./ChildComponent', () => ({
|
|
837
|
+
ChildComponent: () => <div>mocked</div>, // Defeats the purpose
|
|
838
|
+
}));
|
|
839
|
+
|
|
840
|
+
// GOOD: Render real component tree, mock external dependencies
|
|
841
|
+
server.use(
|
|
842
|
+
http.get('/api/data', () => HttpResponse.json({ items: [] }))
|
|
843
|
+
);
|
|
844
|
+
render(<ParentComponent />);
|
|
845
|
+
```
|
|
846
|
+
|
|
847
|
+
---
|
|
848
|
+
|
|
849
|
+
## 14. Critical Reminders
|
|
850
|
+
|
|
851
|
+
### ALWAYS
|
|
852
|
+
|
|
853
|
+
- Use `userEvent.setup()` at the start of each test (not the global import)
|
|
854
|
+
- Use `getByRole` as the first choice for all queries
|
|
855
|
+
- Use `findBy` for elements that appear asynchronously
|
|
856
|
+
- Use `queryBy` only when asserting something is NOT in the DOM
|
|
857
|
+
- Use `waitFor` for async state changes (not `setTimeout`)
|
|
858
|
+
- Use `screen` instead of destructuring from `render()`
|
|
859
|
+
- Clean up mocks and server handlers after each test
|
|
860
|
+
- Test loading, error, and empty states -- not just happy path
|
|
861
|
+
- Create a custom `render` that wraps providers once
|
|
862
|
+
|
|
863
|
+
### NEVER
|
|
864
|
+
|
|
865
|
+
- Use `fireEvent` when `userEvent` works (userEvent simulates real behavior)
|
|
866
|
+
- Use `container.querySelector` or `container.innerHTML` (breaks the abstraction)
|
|
867
|
+
- Test CSS classes, state variables, or internal methods (implementation details)
|
|
868
|
+
- Use `act()` warnings as an excuse to wrap everything in `act` (find root cause)
|
|
869
|
+
- Use snapshot tests as a substitute for behavioral assertions
|
|
870
|
+
- Use `getByTestId` when a role or label query exists
|
|
871
|
+
- Leave `screen.debug()` in committed test code
|
|
872
|
+
- Use `waitFor` with side effects inside the callback (only assertions)
|