@codyswann/lisa 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/LICENSE +21 -0
- package/README.md +867 -0
- package/all/copy-overwrite/.claude/README.md +205 -0
- package/all/copy-overwrite/.claude/agents/agent-architect.md +311 -0
- package/all/copy-overwrite/.claude/agents/codebase-analyzer.md +146 -0
- package/all/copy-overwrite/.claude/agents/codebase-locator.md +125 -0
- package/all/copy-overwrite/.claude/agents/codebase-pattern-finder.md +237 -0
- package/all/copy-overwrite/.claude/agents/git-history-analyzer.md +183 -0
- package/all/copy-overwrite/.claude/agents/hooks-expert.md +74 -0
- package/all/copy-overwrite/.claude/agents/skill-evaluator.md +246 -0
- package/all/copy-overwrite/.claude/agents/slash-command-architect.md +87 -0
- package/all/copy-overwrite/.claude/agents/web-search-researcher.md +112 -0
- package/all/copy-overwrite/.claude/commands/git/commit-and-submit-pr.md +8 -0
- package/all/copy-overwrite/.claude/commands/git/commit.md +44 -0
- package/all/copy-overwrite/.claude/commands/git/prune.md +34 -0
- package/all/copy-overwrite/.claude/commands/git/submit-pr.md +50 -0
- package/all/copy-overwrite/.claude/commands/jira/create.md +50 -0
- package/all/copy-overwrite/.claude/commands/jira/verify.md +34 -0
- package/all/copy-overwrite/.claude/commands/project/archive.md +8 -0
- package/all/copy-overwrite/.claude/commands/project/bootstrap.md +49 -0
- package/all/copy-overwrite/.claude/commands/project/complete-task.md +7 -0
- package/all/copy-overwrite/.claude/commands/project/debrief.md +65 -0
- package/all/copy-overwrite/.claude/commands/project/execute.md +94 -0
- package/all/copy-overwrite/.claude/commands/project/implement.md +42 -0
- package/all/copy-overwrite/.claude/commands/project/local-code-review.md +88 -0
- package/all/copy-overwrite/.claude/commands/project/lower-code-complexity.md +74 -0
- package/all/copy-overwrite/.claude/commands/project/plan.md +314 -0
- package/all/copy-overwrite/.claude/commands/project/research.md +248 -0
- package/all/copy-overwrite/.claude/commands/project/review.md +63 -0
- package/all/copy-overwrite/.claude/commands/project/setup.md +19 -0
- package/all/copy-overwrite/.claude/commands/project/verify.md +38 -0
- package/all/copy-overwrite/.claude/commands/pull-request/review.md +12 -0
- package/all/copy-overwrite/.claude/commands/rules/format-md.md +72 -0
- package/all/copy-overwrite/.claude/commands/sonarqube/check.md +6 -0
- package/all/copy-overwrite/.claude/commands/sonarqube/fix.md +3 -0
- package/all/copy-overwrite/.claude/hooks/README.md +301 -0
- package/all/copy-overwrite/.claude/hooks/notify-ntfy.sh +181 -0
- package/all/copy-overwrite/.claude/settings.json +41 -0
- package/all/copy-overwrite/.claude/settings.local.json.example +14 -0
- package/all/copy-overwrite/.claude/skills/coding-philosophy/SKILL.md +405 -0
- package/all/copy-overwrite/.claude/skills/coding-philosophy/references/function-structure.md +416 -0
- package/all/copy-overwrite/.claude/skills/coding-philosophy/references/immutable-patterns.md +316 -0
- package/all/copy-overwrite/.claude/skills/prompt-complexity-scorer/SKILL.md +118 -0
- package/all/copy-overwrite/.claude/skills/skill-creator/LICENSE.txt +202 -0
- package/all/copy-overwrite/.claude/skills/skill-creator/SKILL.md +210 -0
- package/all/copy-overwrite/.claude/skills/skill-creator/scripts/__pycache__/quick_validate.cpython-312.pyc +0 -0
- package/all/copy-overwrite/.claude/skills/skill-creator/scripts/init_skill.py +303 -0
- package/all/copy-overwrite/.claude/skills/skill-creator/scripts/package_skill.py +110 -0
- package/all/copy-overwrite/.claude/skills/skill-creator/scripts/quick_validate.py +65 -0
- package/all/copy-overwrite/CLAUDE.md +77 -0
- package/all/copy-overwrite/HUMAN.md +17 -0
- package/all/copy-overwrite/specs/.keep +0 -0
- package/all/create-only/PROJECT_RULES.md +0 -0
- package/cdk/merge/package.json +20 -0
- package/dist/cli/index.d.ts +7 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +107 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/prompts.d.ts +45 -0
- package/dist/cli/prompts.d.ts.map +1 -0
- package/dist/cli/prompts.js +58 -0
- package/dist/cli/prompts.js.map +1 -0
- package/dist/core/config.d.ts +73 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +36 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/index.d.ts +4 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +4 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/lisa.d.ts +81 -0
- package/dist/core/lisa.d.ts.map +1 -0
- package/dist/core/lisa.js +459 -0
- package/dist/core/lisa.js.map +1 -0
- package/dist/core/manifest.d.ts +58 -0
- package/dist/core/manifest.d.ts.map +1 -0
- package/dist/core/manifest.js +104 -0
- package/dist/core/manifest.js.map +1 -0
- package/dist/detection/detector.interface.d.ts +15 -0
- package/dist/detection/detector.interface.d.ts.map +1 -0
- package/dist/detection/detector.interface.js +2 -0
- package/dist/detection/detector.interface.js.map +1 -0
- package/dist/detection/detectors/cdk.d.ts +10 -0
- package/dist/detection/detectors/cdk.d.ts.map +1 -0
- package/dist/detection/detectors/cdk.js +34 -0
- package/dist/detection/detectors/cdk.js.map +1 -0
- package/dist/detection/detectors/expo.d.ts +10 -0
- package/dist/detection/detectors/expo.d.ts.map +1 -0
- package/dist/detection/detectors/expo.js +30 -0
- package/dist/detection/detectors/expo.js.map +1 -0
- package/dist/detection/detectors/nestjs.d.ts +10 -0
- package/dist/detection/detectors/nestjs.d.ts.map +1 -0
- package/dist/detection/detectors/nestjs.js +34 -0
- package/dist/detection/detectors/nestjs.js.map +1 -0
- package/dist/detection/detectors/npm-package.d.ts +13 -0
- package/dist/detection/detectors/npm-package.d.ts.map +1 -0
- package/dist/detection/detectors/npm-package.js +30 -0
- package/dist/detection/detectors/npm-package.js.map +1 -0
- package/dist/detection/detectors/typescript.d.ts +10 -0
- package/dist/detection/detectors/typescript.d.ts.map +1 -0
- package/dist/detection/detectors/typescript.js +25 -0
- package/dist/detection/detectors/typescript.js.map +1 -0
- package/dist/detection/index.d.ts +24 -0
- package/dist/detection/index.d.ts.map +1 -0
- package/dist/detection/index.js +57 -0
- package/dist/detection/index.js.map +1 -0
- package/dist/errors/index.d.ts +69 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +110 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/logging/console-logger.d.ts +12 -0
- package/dist/logging/console-logger.d.ts.map +1 -0
- package/dist/logging/console-logger.js +22 -0
- package/dist/logging/console-logger.js.map +1 -0
- package/dist/logging/index.d.ts +4 -0
- package/dist/logging/index.d.ts.map +1 -0
- package/dist/logging/index.js +3 -0
- package/dist/logging/index.js.map +1 -0
- package/dist/logging/logger.interface.d.ts +20 -0
- package/dist/logging/logger.interface.d.ts.map +1 -0
- package/dist/logging/logger.interface.js +2 -0
- package/dist/logging/logger.interface.js.map +1 -0
- package/dist/logging/silent-logger.d.ts +12 -0
- package/dist/logging/silent-logger.d.ts.map +1 -0
- package/dist/logging/silent-logger.js +21 -0
- package/dist/logging/silent-logger.js.map +1 -0
- package/dist/strategies/copy-contents.d.ts +14 -0
- package/dist/strategies/copy-contents.d.ts.map +1 -0
- package/dist/strategies/copy-contents.js +69 -0
- package/dist/strategies/copy-contents.js.map +1 -0
- package/dist/strategies/copy-overwrite.d.ts +14 -0
- package/dist/strategies/copy-overwrite.d.ts.map +1 -0
- package/dist/strategies/copy-overwrite.js +47 -0
- package/dist/strategies/copy-overwrite.js.map +1 -0
- package/dist/strategies/create-only.d.ts +13 -0
- package/dist/strategies/create-only.d.ts.map +1 -0
- package/dist/strategies/create-only.js +30 -0
- package/dist/strategies/create-only.js.map +1 -0
- package/dist/strategies/index.d.ts +31 -0
- package/dist/strategies/index.d.ts.map +1 -0
- package/dist/strategies/index.js +52 -0
- package/dist/strategies/index.js.map +1 -0
- package/dist/strategies/merge.d.ts +13 -0
- package/dist/strategies/merge.d.ts.map +1 -0
- package/dist/strategies/merge.js +60 -0
- package/dist/strategies/merge.js.map +1 -0
- package/dist/strategies/strategy.interface.d.ts +31 -0
- package/dist/strategies/strategy.interface.d.ts.map +1 -0
- package/dist/strategies/strategy.interface.js +2 -0
- package/dist/strategies/strategy.interface.js.map +1 -0
- package/dist/transaction/backup.d.ts +38 -0
- package/dist/transaction/backup.d.ts.map +1 -0
- package/dist/transaction/backup.js +97 -0
- package/dist/transaction/backup.js.map +1 -0
- package/dist/transaction/index.d.ts +4 -0
- package/dist/transaction/index.d.ts.map +1 -0
- package/dist/transaction/index.js +3 -0
- package/dist/transaction/index.js.map +1 -0
- package/dist/transaction/transaction.d.ts +34 -0
- package/dist/transaction/transaction.d.ts.map +1 -0
- package/dist/transaction/transaction.js +68 -0
- package/dist/transaction/transaction.js.map +1 -0
- package/dist/utils/file-operations.d.ts +29 -0
- package/dist/utils/file-operations.d.ts.map +1 -0
- package/dist/utils/file-operations.js +84 -0
- package/dist/utils/file-operations.js.map +1 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +4 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/json-utils.d.ts +22 -0
- package/dist/utils/json-utils.d.ts.map +1 -0
- package/dist/utils/json-utils.js +57 -0
- package/dist/utils/json-utils.js.map +1 -0
- package/dist/utils/path-utils.d.ts +21 -0
- package/dist/utils/path-utils.d.ts.map +1 -0
- package/dist/utils/path-utils.js +35 -0
- package/dist/utils/path-utils.js.map +1 -0
- package/eslint-plugin-code-organization/README.md +149 -0
- package/eslint-plugin-code-organization/__tests__/enforce-statement-order.test.js +468 -0
- package/eslint-plugin-code-organization/index.js +23 -0
- package/eslint-plugin-code-organization/package.json +10 -0
- package/eslint-plugin-code-organization/rules/enforce-statement-order.js +157 -0
- package/expo/copy-overwrite/.claude/skills/apollo-client/SKILL.md +238 -0
- package/expo/copy-overwrite/.claude/skills/apollo-client/references/mutation-patterns.md +360 -0
- package/expo/copy-overwrite/.claude/skills/atomic-design-gluestack/SKILL.md +360 -0
- package/expo/copy-overwrite/.claude/skills/atomic-design-gluestack/references/atomic-levels.md +417 -0
- package/expo/copy-overwrite/.claude/skills/atomic-design-gluestack/references/folder-structure.md +257 -0
- package/expo/copy-overwrite/.claude/skills/atomic-design-gluestack/references/gluestack-mapping.md +233 -0
- package/expo/copy-overwrite/.claude/skills/atomic-design-gluestack/scripts/validate_atomic_structure.py +327 -0
- package/expo/copy-overwrite/.claude/skills/container-view-pattern/SKILL.md +299 -0
- package/expo/copy-overwrite/.claude/skills/container-view-pattern/references/examples.md +749 -0
- package/expo/copy-overwrite/.claude/skills/container-view-pattern/references/patterns.md +318 -0
- package/expo/copy-overwrite/.claude/skills/container-view-pattern/scripts/create_component.py +198 -0
- package/expo/copy-overwrite/.claude/skills/container-view-pattern/scripts/validate_component.py +207 -0
- package/expo/copy-overwrite/.claude/skills/cross-platform-compatibility/SKILL.md +268 -0
- package/expo/copy-overwrite/.claude/skills/cross-platform-compatibility/references/common-issues.md +619 -0
- package/expo/copy-overwrite/.claude/skills/cross-platform-compatibility/references/file-extensions.md +340 -0
- package/expo/copy-overwrite/.claude/skills/cross-platform-compatibility/references/platform-api.md +276 -0
- package/expo/copy-overwrite/.claude/skills/cross-platform-compatibility/scripts/validate_cross_platform.py +414 -0
- package/expo/copy-overwrite/.claude/skills/directory-structure/SKILL.md +202 -0
- package/expo/copy-overwrite/.claude/skills/directory-structure/scripts/validate_structure.py +443 -0
- package/expo/copy-overwrite/.claude/skills/expo-env-config/SKILL.md +309 -0
- package/expo/copy-overwrite/.claude/skills/expo-env-config/references/validation-patterns.md +417 -0
- package/expo/copy-overwrite/.claude/skills/expo-router-best-practices/SKILL.md +431 -0
- package/expo/copy-overwrite/.claude/skills/expo-router-best-practices/references/official-docs.md +290 -0
- package/expo/copy-overwrite/.claude/skills/expo-router-best-practices/scripts/generate-route.py +169 -0
- package/expo/copy-overwrite/.claude/skills/gluestack-nativewind/SKILL.md +411 -0
- package/expo/copy-overwrite/.claude/skills/gluestack-nativewind/references/color-tokens.md +343 -0
- package/expo/copy-overwrite/.claude/skills/gluestack-nativewind/references/component-mapping.md +307 -0
- package/expo/copy-overwrite/.claude/skills/gluestack-nativewind/references/spacing-scale.md +300 -0
- package/expo/copy-overwrite/.claude/skills/gluestack-nativewind/scripts/validate_styling.py +354 -0
- package/expo/copy-overwrite/.claude/skills/local-state/SKILL.md +362 -0
- package/expo/copy-overwrite/.claude/skills/local-state/references/async-storage.md +505 -0
- package/expo/copy-overwrite/.claude/skills/local-state/references/persistence-patterns.md +711 -0
- package/expo/copy-overwrite/.claude/skills/local-state/references/reactive-variables.md +446 -0
- package/expo/copy-overwrite/.claude/skills/playwright-selectors/SKILL.md +223 -0
- package/expo/copy-overwrite/.claude/skills/testing-library/SKILL.md +319 -0
- package/expo/copy-overwrite/.claude/skills/testing-library/references/async-patterns.md +420 -0
- package/expo/copy-overwrite/.claude/skills/testing-library/references/expo-router-testing.md +556 -0
- package/expo/copy-overwrite/.claude/skills/testing-library/references/mocking-patterns.md +590 -0
- package/expo/copy-overwrite/.claude/skills/testing-library/references/query-priority.md +291 -0
- package/expo/copy-overwrite/.easignore.extra +2 -0
- package/expo/copy-overwrite/.mcp.json +33 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/README.md +234 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/__tests__/plugin-index.test.js +84 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/__tests__/require-memo-in-view.test.js +196 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/__tests__/single-component-per-file.test.js +289 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/index.js +32 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/package.json +10 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/rules/enforce-component-structure.js +230 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/rules/no-return-in-view.js +91 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/rules/require-memo-in-view.js +178 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/rules/single-component-per-file.js +238 -0
- package/expo/copy-overwrite/eslint-plugin-ui-standards/README.md +260 -0
- package/expo/copy-overwrite/eslint-plugin-ui-standards/index.js +29 -0
- package/expo/copy-overwrite/eslint-plugin-ui-standards/package.json +10 -0
- package/expo/copy-overwrite/eslint-plugin-ui-standards/rules/no-classname-outside-ui.js +51 -0
- package/expo/copy-overwrite/eslint-plugin-ui-standards/rules/no-direct-rn-imports.js +55 -0
- package/expo/copy-overwrite/eslint-plugin-ui-standards/rules/no-inline-styles.js +73 -0
- package/expo/copy-overwrite/eslint.config.mjs +560 -0
- package/expo/copy-overwrite/lighthouserc.js +194 -0
- package/expo/create-only/lighthouserc-config.json +28 -0
- package/expo/merge/package.json +132 -0
- package/lisa.sh +35 -0
- package/nestjs/copy-overwrite/.claude/skills/nestjs-graphql/SKILL.md +176 -0
- package/nestjs/copy-overwrite/.claude/skills/nestjs-graphql/references/advanced-features.md +527 -0
- package/nestjs/copy-overwrite/.claude/skills/nestjs-graphql/references/project-patterns.md +483 -0
- package/nestjs/copy-overwrite/.claude/skills/nestjs-graphql/references/quick-start.md +257 -0
- package/nestjs/copy-overwrite/.claude/skills/nestjs-graphql/references/resolvers-mutations.md +413 -0
- package/nestjs/copy-overwrite/.claude/skills/nestjs-graphql/references/types-scalars.md +513 -0
- package/nestjs/copy-overwrite/.claude/skills/nestjs-rules/SKILL.md +536 -0
- package/nestjs/copy-overwrite/.claude/skills/typeorm-patterns/SKILL.md +275 -0
- package/nestjs/copy-overwrite/.claude/skills/typeorm-patterns/references/configuration-patterns.md +487 -0
- package/nestjs/copy-overwrite/.claude/skills/typeorm-patterns/references/entity-patterns.md +450 -0
- package/nestjs/copy-overwrite/.claude/skills/typeorm-patterns/references/observability-patterns.md +536 -0
- package/nestjs/merge/package.json +75 -0
- package/package.json +124 -0
- package/typescript/copy-contents/.husky/commit-msg +91 -0
- package/typescript/copy-contents/.husky/pre-commit +96 -0
- package/typescript/copy-contents/.husky/pre-push +211 -0
- package/typescript/copy-overwrite/.claude/hooks/format-on-edit.sh +74 -0
- package/typescript/copy-overwrite/.claude/hooks/install_pkgs.sh +59 -0
- package/typescript/copy-overwrite/.claude/hooks/lint-on-edit.sh +103 -0
- package/typescript/copy-overwrite/.claude/skills/jsdoc-best-practices/SKILL.md +388 -0
- package/typescript/copy-overwrite/.github/README.md +455 -0
- package/typescript/copy-overwrite/.github/dependabot.yml +40 -0
- package/typescript/copy-overwrite/.github/k6/BROWSER_TESTING_NOTE.md +129 -0
- package/typescript/copy-overwrite/.github/k6/INTEGRATION_GUIDE.md +354 -0
- package/typescript/copy-overwrite/.github/k6/README.md +386 -0
- package/typescript/copy-overwrite/.github/k6/SCENARIO_SELECTION_GUIDE.md +264 -0
- package/typescript/copy-overwrite/.github/k6/examples/customer-deploy-integration.yml +115 -0
- package/typescript/copy-overwrite/.github/k6/examples/data-driven-test.js +268 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/load.js +142 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/load.json +27 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/smoke.js +26 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/smoke.json +20 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/soak.js +244 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/soak.json +29 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/spike.js +180 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/spike.json +32 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/stress.js +206 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/stress.json +38 -0
- package/typescript/copy-overwrite/.github/k6/scripts/api-test.js +452 -0
- package/typescript/copy-overwrite/.github/k6/scripts/default-test.js +185 -0
- package/typescript/copy-overwrite/.github/k6/thresholds/normal.json +30 -0
- package/typescript/copy-overwrite/.github/k6/thresholds/relaxed.json +21 -0
- package/typescript/copy-overwrite/.github/k6/thresholds/strict.json +29 -0
- package/typescript/copy-overwrite/.github/workflows/build.yml +72 -0
- package/typescript/copy-overwrite/.github/workflows/ci.yml +49 -0
- package/typescript/copy-overwrite/.github/workflows/claude.yml +51 -0
- package/typescript/copy-overwrite/.github/workflows/create-github-issue-on-failure.yml +113 -0
- package/typescript/copy-overwrite/.github/workflows/create-jira-issue-on-failure.yml +195 -0
- package/typescript/copy-overwrite/.github/workflows/create-sentry-issue-on-failure.yml +267 -0
- package/typescript/copy-overwrite/.github/workflows/deploy.yml +228 -0
- package/typescript/copy-overwrite/.github/workflows/k6-load-test-README.md +230 -0
- package/typescript/copy-overwrite/.github/workflows/lighthouse.yml +68 -0
- package/typescript/copy-overwrite/.github/workflows/load-test.yml +282 -0
- package/typescript/copy-overwrite/.github/workflows/quality.yml +1737 -0
- package/typescript/copy-overwrite/.github/workflows/release.yml +1599 -0
- package/typescript/copy-overwrite/.gitleaksignore +28 -0
- package/typescript/copy-overwrite/.nvmrc +1 -0
- package/typescript/copy-overwrite/.prettierignore +23 -0
- package/typescript/copy-overwrite/.prettierrc.json +22 -0
- package/typescript/copy-overwrite/.versionrc +42 -0
- package/typescript/copy-overwrite/.yamllint +20 -0
- package/typescript/copy-overwrite/commitlint.config.js +11 -0
- package/typescript/copy-overwrite/eslint-plugin-code-organization/README.md +149 -0
- package/typescript/copy-overwrite/eslint-plugin-code-organization/__tests__/enforce-statement-order.test.js +468 -0
- package/typescript/copy-overwrite/eslint-plugin-code-organization/index.js +23 -0
- package/typescript/copy-overwrite/eslint-plugin-code-organization/package.json +10 -0
- package/typescript/copy-overwrite/eslint-plugin-code-organization/rules/enforce-statement-order.js +157 -0
- package/typescript/copy-overwrite/eslint.config.mjs +390 -0
- package/typescript/copy-overwrite/eslint.ignore.config.json +57 -0
- package/typescript/copy-overwrite/eslint.thresholds.config.json +5 -0
- package/typescript/github-rulesets/base.json +106 -0
- package/typescript/merge/.claude/settings.json +28 -0
- package/typescript/merge/package.json +71 -0
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
# Reactive Variables Reference
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
Reactive variables are Apollo Client 3's mechanism for managing local state outside the GraphQL cache. They enable seamless reactivity without requiring GraphQL syntax and can store any data type.
|
|
6
|
+
|
|
7
|
+
## Creating Reactive Variables
|
|
8
|
+
|
|
9
|
+
### Basic Types
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
import { makeVar } from "@apollo/client";
|
|
13
|
+
|
|
14
|
+
// Boolean
|
|
15
|
+
export const isLoadingVar = makeVar<boolean>(false);
|
|
16
|
+
|
|
17
|
+
// String
|
|
18
|
+
export const selectedIdVar = makeVar<string | null>(null);
|
|
19
|
+
|
|
20
|
+
// Number
|
|
21
|
+
export const countVar = makeVar<number>(0);
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Complex Types
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
// Interface-based object
|
|
28
|
+
interface IFilterValues {
|
|
29
|
+
readonly minAge: number;
|
|
30
|
+
readonly maxAge: number;
|
|
31
|
+
readonly positions: readonly string[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const DEFAULT_FILTERS: IFilterValues = {
|
|
35
|
+
minAge: 18,
|
|
36
|
+
maxAge: 40,
|
|
37
|
+
positions: [],
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export const filterValuesVar = makeVar<IFilterValues>(DEFAULT_FILTERS);
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Record Types for Lookups
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
// Key-value lookup
|
|
47
|
+
export const selectedPlayersVar = makeVar<Record<string, boolean>>({});
|
|
48
|
+
|
|
49
|
+
// Complex value lookup
|
|
50
|
+
interface PlayerNote {
|
|
51
|
+
readonly playerId: string;
|
|
52
|
+
readonly note: string;
|
|
53
|
+
readonly updatedAt: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const playerNotesVar = makeVar<Record<string, PlayerNote>>({});
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Array Types
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
interface Todo {
|
|
63
|
+
readonly id: string;
|
|
64
|
+
readonly text: string;
|
|
65
|
+
readonly completed: boolean;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
type Todos = readonly Todo[];
|
|
69
|
+
|
|
70
|
+
export const todosVar = makeVar<Todos>([]);
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Reading Values
|
|
74
|
+
|
|
75
|
+
### Direct Read (Non-Reactive)
|
|
76
|
+
|
|
77
|
+
Call without arguments to read current value. Does NOT trigger component re-renders:
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
const currentFilters = filterValuesVar();
|
|
81
|
+
const selectedPlayers = selectedPlayersVar();
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Use for:
|
|
85
|
+
|
|
86
|
+
- Event handlers
|
|
87
|
+
- Utility functions
|
|
88
|
+
- Async operations
|
|
89
|
+
- Conditional logic outside JSX
|
|
90
|
+
|
|
91
|
+
### Reactive Read with Hook
|
|
92
|
+
|
|
93
|
+
Use `useReactiveVar` for components that should re-render on changes:
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
import { useReactiveVar } from "@apollo/client";
|
|
97
|
+
|
|
98
|
+
const FilterDisplay = () => {
|
|
99
|
+
const filters = useReactiveVar(filterValuesVar);
|
|
100
|
+
|
|
101
|
+
return (
|
|
102
|
+
<View>
|
|
103
|
+
<Text>Age: {filters.minAge} - {filters.maxAge}</Text>
|
|
104
|
+
<Text>Positions: {filters.positions.join(", ")}</Text>
|
|
105
|
+
</View>
|
|
106
|
+
);
|
|
107
|
+
};
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Updating Values
|
|
111
|
+
|
|
112
|
+
### Object Updates
|
|
113
|
+
|
|
114
|
+
Always create new object references:
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
// Single field update
|
|
118
|
+
filterValuesVar({
|
|
119
|
+
...filterValuesVar(),
|
|
120
|
+
minAge: 21,
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// Multiple field update
|
|
124
|
+
filterValuesVar({
|
|
125
|
+
...filterValuesVar(),
|
|
126
|
+
minAge: 21,
|
|
127
|
+
maxAge: 35,
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// Nested object update
|
|
131
|
+
settingsVar({
|
|
132
|
+
...settingsVar(),
|
|
133
|
+
display: {
|
|
134
|
+
...settingsVar().display,
|
|
135
|
+
showGrid: true,
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Array Updates
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
// Add item
|
|
144
|
+
todosVar([
|
|
145
|
+
...todosVar(),
|
|
146
|
+
{ id: crypto.randomUUID(), text: "New todo", completed: false },
|
|
147
|
+
]);
|
|
148
|
+
|
|
149
|
+
// Remove item
|
|
150
|
+
todosVar(todosVar().filter(todo => todo.id !== idToRemove));
|
|
151
|
+
|
|
152
|
+
// Update item
|
|
153
|
+
todosVar(
|
|
154
|
+
todosVar().map(todo =>
|
|
155
|
+
todo.id === idToUpdate ? { ...todo, completed: true } : todo
|
|
156
|
+
)
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
// Clear all
|
|
160
|
+
todosVar([]);
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Record Updates
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
// Add/update entry
|
|
167
|
+
selectedPlayersVar({
|
|
168
|
+
...selectedPlayersVar(),
|
|
169
|
+
[playerId]: true,
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// Remove entry (using destructuring)
|
|
173
|
+
const { [playerIdToRemove]: _, ...remaining } = selectedPlayersVar();
|
|
174
|
+
selectedPlayersVar(remaining);
|
|
175
|
+
|
|
176
|
+
// Toggle entry
|
|
177
|
+
const current = selectedPlayersVar();
|
|
178
|
+
selectedPlayersVar({
|
|
179
|
+
...current,
|
|
180
|
+
[playerId]: !current[playerId],
|
|
181
|
+
});
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Custom Hooks Pattern
|
|
185
|
+
|
|
186
|
+
Encapsulate reactive variable logic in custom hooks for better testability:
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
import { makeVar, ReactiveVar, useReactiveVar } from "@apollo/client";
|
|
190
|
+
import { useCallback } from "react";
|
|
191
|
+
|
|
192
|
+
interface IFilterValues {
|
|
193
|
+
readonly minAge: number;
|
|
194
|
+
readonly maxAge: number;
|
|
195
|
+
readonly positions: readonly string[];
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const DEFAULT_FILTERS: IFilterValues = {
|
|
199
|
+
minAge: 18,
|
|
200
|
+
maxAge: 40,
|
|
201
|
+
positions: [],
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
export const filterValuesVar = makeVar<IFilterValues>(DEFAULT_FILTERS);
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Custom hook for filter operations
|
|
208
|
+
* Encapsulates all filter state logic
|
|
209
|
+
*/
|
|
210
|
+
export const useFilters = () => {
|
|
211
|
+
const filters = useReactiveVar(filterValuesVar);
|
|
212
|
+
|
|
213
|
+
const setMinAge = useCallback((minAge: number) => {
|
|
214
|
+
filterValuesVar({ ...filterValuesVar(), minAge });
|
|
215
|
+
}, []);
|
|
216
|
+
|
|
217
|
+
const setMaxAge = useCallback((maxAge: number) => {
|
|
218
|
+
filterValuesVar({ ...filterValuesVar(), maxAge });
|
|
219
|
+
}, []);
|
|
220
|
+
|
|
221
|
+
const togglePosition = useCallback((position: string) => {
|
|
222
|
+
const current = filterValuesVar();
|
|
223
|
+
const hasPosition = current.positions.includes(position);
|
|
224
|
+
const positions = hasPosition
|
|
225
|
+
? current.positions.filter(p => p !== position)
|
|
226
|
+
: [...current.positions, position];
|
|
227
|
+
filterValuesVar({ ...current, positions });
|
|
228
|
+
}, []);
|
|
229
|
+
|
|
230
|
+
const resetFilters = useCallback(() => {
|
|
231
|
+
filterValuesVar(DEFAULT_FILTERS);
|
|
232
|
+
}, []);
|
|
233
|
+
|
|
234
|
+
return {
|
|
235
|
+
filters,
|
|
236
|
+
setMinAge,
|
|
237
|
+
setMaxAge,
|
|
238
|
+
togglePosition,
|
|
239
|
+
resetFilters,
|
|
240
|
+
};
|
|
241
|
+
};
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
## Dependency Injection for Testing
|
|
245
|
+
|
|
246
|
+
Pass reactive variables as parameters for testable hooks:
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
/**
|
|
250
|
+
* Testable hook with dependency injection
|
|
251
|
+
* @param filtersVar - Reactive variable (can be mocked in tests)
|
|
252
|
+
*/
|
|
253
|
+
export const useFiltersWithDI = (
|
|
254
|
+
filtersVar: ReactiveVar<IFilterValues> = filterValuesVar
|
|
255
|
+
) => {
|
|
256
|
+
const filters = useReactiveVar(filtersVar);
|
|
257
|
+
|
|
258
|
+
const setMinAge = useCallback(
|
|
259
|
+
(minAge: number) => {
|
|
260
|
+
filtersVar({ ...filtersVar(), minAge });
|
|
261
|
+
},
|
|
262
|
+
[filtersVar]
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
return { filters, setMinAge };
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
// Test usage
|
|
269
|
+
describe("useFiltersWithDI", () => {
|
|
270
|
+
it("should update minAge", () => {
|
|
271
|
+
const testVar = makeVar<IFilterValues>(DEFAULT_FILTERS);
|
|
272
|
+
const { result } = renderHook(() => useFiltersWithDI(testVar));
|
|
273
|
+
|
|
274
|
+
act(() => {
|
|
275
|
+
result.current.setMinAge(25);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
expect(testVar().minAge).toBe(25);
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
## Cache Type Policy Integration
|
|
284
|
+
|
|
285
|
+
Connect reactive variables to GraphQL queries using field policies:
|
|
286
|
+
|
|
287
|
+
```typescript
|
|
288
|
+
import { InMemoryCache, makeVar } from "@apollo/client";
|
|
289
|
+
import { gql } from "@apollo/client";
|
|
290
|
+
|
|
291
|
+
// Reactive variable
|
|
292
|
+
export const filterValuesVar = makeVar<IFilterValues>(DEFAULT_FILTERS);
|
|
293
|
+
|
|
294
|
+
// Cache configuration
|
|
295
|
+
export const cache = new InMemoryCache({
|
|
296
|
+
typePolicies: {
|
|
297
|
+
Query: {
|
|
298
|
+
fields: {
|
|
299
|
+
filterValues: {
|
|
300
|
+
read() {
|
|
301
|
+
return filterValuesVar();
|
|
302
|
+
},
|
|
303
|
+
},
|
|
304
|
+
},
|
|
305
|
+
},
|
|
306
|
+
},
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
// Query local state with @client directive
|
|
310
|
+
export const GET_FILTER_VALUES = gql`
|
|
311
|
+
query GetFilterValues {
|
|
312
|
+
filterValues @client {
|
|
313
|
+
minAge
|
|
314
|
+
maxAge
|
|
315
|
+
positions
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
`;
|
|
319
|
+
|
|
320
|
+
// Use in component
|
|
321
|
+
const FilterComponent = () => {
|
|
322
|
+
const { data } = useQuery(GET_FILTER_VALUES);
|
|
323
|
+
// data.filterValues is reactive
|
|
324
|
+
};
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
## Performance Considerations
|
|
328
|
+
|
|
329
|
+
### Split Large State
|
|
330
|
+
|
|
331
|
+
Instead of one large reactive variable, use multiple smaller ones:
|
|
332
|
+
|
|
333
|
+
```typescript
|
|
334
|
+
// AVOID - single large state
|
|
335
|
+
const appStateVar = makeVar({
|
|
336
|
+
user: {
|
|
337
|
+
/* ... */
|
|
338
|
+
},
|
|
339
|
+
settings: {
|
|
340
|
+
/* ... */
|
|
341
|
+
},
|
|
342
|
+
filters: {
|
|
343
|
+
/* ... */
|
|
344
|
+
},
|
|
345
|
+
selectedItems: {
|
|
346
|
+
/* ... */
|
|
347
|
+
},
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
// PREFER - separate concerns
|
|
351
|
+
export const userVar = makeVar<User | null>(null);
|
|
352
|
+
export const settingsVar = makeVar<Settings>(DEFAULT_SETTINGS);
|
|
353
|
+
export const filtersVar = makeVar<Filters>(DEFAULT_FILTERS);
|
|
354
|
+
export const selectedItemsVar = makeVar<Record<string, boolean>>({});
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
### Batch Related Updates
|
|
358
|
+
|
|
359
|
+
Update multiple fields in a single call:
|
|
360
|
+
|
|
361
|
+
```typescript
|
|
362
|
+
// CORRECT - single update
|
|
363
|
+
filterValuesVar({
|
|
364
|
+
...filterValuesVar(),
|
|
365
|
+
minAge: 21,
|
|
366
|
+
maxAge: 35,
|
|
367
|
+
positions: ["QB", "WR"],
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
// AVOID - multiple sequential updates
|
|
371
|
+
filterValuesVar({ ...filterValuesVar(), minAge: 21 });
|
|
372
|
+
filterValuesVar({ ...filterValuesVar(), maxAge: 35 });
|
|
373
|
+
filterValuesVar({ ...filterValuesVar(), positions: ["QB", "WR"] });
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
### Selector Pattern for Complex State
|
|
377
|
+
|
|
378
|
+
Create selector wrappers to minimize re-renders:
|
|
379
|
+
|
|
380
|
+
```typescript
|
|
381
|
+
// Store
|
|
382
|
+
export const allPlayersVar = makeVar<Record<string, Player>>({});
|
|
383
|
+
|
|
384
|
+
// Selector hook - only re-renders when specific player changes
|
|
385
|
+
export const usePlayer = (playerId: string): Player | undefined => {
|
|
386
|
+
const allPlayers = useReactiveVar(allPlayersVar);
|
|
387
|
+
return allPlayers[playerId];
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
// Component only re-renders when its specific player changes
|
|
391
|
+
const PlayerCard = ({ playerId }: { playerId: string }) => {
|
|
392
|
+
const player = usePlayer(playerId);
|
|
393
|
+
return player ? <Text>{player.name}</Text> : null;
|
|
394
|
+
};
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
## TypeScript Best Practices
|
|
398
|
+
|
|
399
|
+
### Use Readonly Types
|
|
400
|
+
|
|
401
|
+
Enforce immutability at the type level:
|
|
402
|
+
|
|
403
|
+
```typescript
|
|
404
|
+
interface IState {
|
|
405
|
+
readonly items: readonly Item[];
|
|
406
|
+
readonly metadata: Readonly<Metadata>;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
export const stateVar = makeVar<IState>({
|
|
410
|
+
items: [],
|
|
411
|
+
metadata: { count: 0 },
|
|
412
|
+
});
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
### Use Generic Constraints
|
|
416
|
+
|
|
417
|
+
```typescript
|
|
418
|
+
// Generic updater function
|
|
419
|
+
const updateVar = <T extends Record<string, unknown>>(
|
|
420
|
+
reactiveVar: ReactiveVar<T>,
|
|
421
|
+
updates: Partial<T>
|
|
422
|
+
): void => {
|
|
423
|
+
reactiveVar({ ...reactiveVar(), ...updates });
|
|
424
|
+
};
|
|
425
|
+
|
|
426
|
+
// Usage
|
|
427
|
+
updateVar(settingsVar, { theme: "dark" });
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
### Export Types with Variables
|
|
431
|
+
|
|
432
|
+
```typescript
|
|
433
|
+
// Export both type and variable
|
|
434
|
+
export interface IFilterValues {
|
|
435
|
+
readonly minAge: number;
|
|
436
|
+
readonly maxAge: number;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
export const filterValuesVar = makeVar<IFilterValues>({
|
|
440
|
+
minAge: 18,
|
|
441
|
+
maxAge: 40,
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
// Re-export type for consumers
|
|
445
|
+
export type { IFilterValues };
|
|
446
|
+
```
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: playwright-selectors
|
|
3
|
+
description: Best practices for adding testID and aria-label selectors for Playwright E2E testing in Expo web applications. This skill should be used when adding E2E test coverage, creating new components that need test selectors, or reviewing code for testability.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Playwright Selectors Best Practices
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
This skill provides guidance for adding effective selectors (testID, aria-labels) to Expo/React Native components for Playwright E2E testing. Proper selector strategy ensures tests are reliable, maintainable, and accessible.
|
|
11
|
+
|
|
12
|
+
## Selector Priority
|
|
13
|
+
|
|
14
|
+
Choose selectors based on their reliability and accessibility impact, in this priority order:
|
|
15
|
+
|
|
16
|
+
| Priority | Selector Method | When to Use |
|
|
17
|
+
| -------- | --------------- | ----------------------------------------- |
|
|
18
|
+
| 1 | `getByRole` | Interactive elements with semantic roles |
|
|
19
|
+
| 2 | `getByText` | Visible text content |
|
|
20
|
+
| 3 | `getByLabel` | Form elements with labels |
|
|
21
|
+
| 4 | `getByTestId` | Fallback for elements without semantics |
|
|
22
|
+
|
|
23
|
+
### getByRole (Preferred)
|
|
24
|
+
|
|
25
|
+
Use semantic roles when possible - they improve both accessibility and test resilience.
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
// E2E test - preferred selector
|
|
29
|
+
const submitButton = page.getByRole("button", { name: "Submit" });
|
|
30
|
+
await expect(submitButton).toBeVisible();
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### getByTestId (Fallback)
|
|
34
|
+
|
|
35
|
+
Use testID when semantic selectors are not available, particularly for structural containers.
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
// E2E test - fallback selector
|
|
39
|
+
const container = page.getByTestId("home:container");
|
|
40
|
+
await expect(container).toBeVisible();
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## testID Naming Convention
|
|
44
|
+
|
|
45
|
+
Use a namespaced pattern with colons as separators: `screen:element`
|
|
46
|
+
|
|
47
|
+
### Format
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
{screen}:{element}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
- **screen**: lowercase screen/feature name (e.g., `home`, `profile`, `settings`)
|
|
54
|
+
- **element**: lowercase element identifier (e.g., `container`, `title`, `submit-button`)
|
|
55
|
+
|
|
56
|
+
### Examples
|
|
57
|
+
|
|
58
|
+
| testID | Description |
|
|
59
|
+
| -------------------------- | -------------------------------- |
|
|
60
|
+
| `home:container` | Main container on home screen |
|
|
61
|
+
| `home:title` | Title text on home screen |
|
|
62
|
+
| `profile:avatar` | User avatar on profile screen |
|
|
63
|
+
| `settings:dark-mode-toggle`| Dark mode toggle in settings |
|
|
64
|
+
| `auth:login-button` | Login button on auth screen |
|
|
65
|
+
|
|
66
|
+
### Rules
|
|
67
|
+
|
|
68
|
+
1. Use lowercase only
|
|
69
|
+
2. Use colons (`:`) to separate screen from element
|
|
70
|
+
3. Use hyphens (`-`) for multi-word elements
|
|
71
|
+
4. Be descriptive but concise
|
|
72
|
+
5. Avoid redundant words (e.g., `home:home-title` should be `home:title`)
|
|
73
|
+
|
|
74
|
+
## React Native to HTML Mapping
|
|
75
|
+
|
|
76
|
+
Understanding how testID propagates from React Native to the web is essential.
|
|
77
|
+
|
|
78
|
+
### How It Works
|
|
79
|
+
|
|
80
|
+
1. React Native's `testID` prop is for native testing (XCUITest, Espresso)
|
|
81
|
+
2. On web (via react-native-web), `testID` renders as `data-testid` in HTML
|
|
82
|
+
3. Playwright's `getByTestId()` queries `data-testid` by default
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
// React Native component
|
|
86
|
+
<Box testID="home:container">...</Box>
|
|
87
|
+
|
|
88
|
+
// Rendered HTML on web
|
|
89
|
+
<div data-testid="home:container">...</div>
|
|
90
|
+
|
|
91
|
+
// Playwright locator
|
|
92
|
+
page.getByTestId("home:container")
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Gluestack UI Components
|
|
96
|
+
|
|
97
|
+
Gluestack UI web components (Box, Text, etc.) require explicit testID handling because they use native HTML elements instead of react-native-web components. The web versions have been updated to:
|
|
98
|
+
|
|
99
|
+
1. Accept a `testID` prop in the TypeScript type
|
|
100
|
+
2. Map `testID` to `data-testid` on the rendered HTML element
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
// Gluestack Box web implementation
|
|
104
|
+
const Box = ({ testID, ...props }) => (
|
|
105
|
+
<div data-testid={testID} {...props} />
|
|
106
|
+
);
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## When to Add testID
|
|
110
|
+
|
|
111
|
+
### Add testID To
|
|
112
|
+
|
|
113
|
+
1. **Interactive elements** that E2E tests will click/interact with
|
|
114
|
+
2. **Key structural containers** for page load verification
|
|
115
|
+
3. **Dynamic content areas** that change based on state
|
|
116
|
+
4. **Form elements** that lack semantic labels
|
|
117
|
+
|
|
118
|
+
### Do Not Add testID To
|
|
119
|
+
|
|
120
|
+
1. Every element (over-testing creates maintenance burden)
|
|
121
|
+
2. Elements with good semantic selectors (use getByRole instead)
|
|
122
|
+
3. Decorative elements not needed for testing
|
|
123
|
+
4. Elements inside third-party components (may not propagate)
|
|
124
|
+
|
|
125
|
+
## Accessibility Best Practices
|
|
126
|
+
|
|
127
|
+
Prefer semantic selectors and aria-labels over testID when possible.
|
|
128
|
+
|
|
129
|
+
### aria-label for Testing and Accessibility
|
|
130
|
+
|
|
131
|
+
When adding labels for testing, use `aria-label` or `accessibilityLabel` to benefit screen reader users too.
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
// Correct - benefits both testing and accessibility
|
|
135
|
+
<Pressable
|
|
136
|
+
accessibilityLabel="Close dialog"
|
|
137
|
+
onPress={handleClose}
|
|
138
|
+
>
|
|
139
|
+
<XIcon />
|
|
140
|
+
</Pressable>
|
|
141
|
+
|
|
142
|
+
// E2E test uses accessible name
|
|
143
|
+
await page.getByRole("button", { name: "Close dialog" }).click();
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### accessibilityRole for Semantic Elements
|
|
147
|
+
|
|
148
|
+
Use `accessibilityRole` to provide semantic meaning on web.
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
// Correct - semantic role for assistive technology
|
|
152
|
+
<Box accessibilityRole="banner" testID="header:container">
|
|
153
|
+
<Text accessibilityRole="heading">Welcome</Text>
|
|
154
|
+
</Box>
|
|
155
|
+
|
|
156
|
+
// E2E test can use role
|
|
157
|
+
await expect(page.getByRole("banner")).toBeVisible();
|
|
158
|
+
await expect(page.getByRole("heading", { name: "Welcome" })).toBeVisible();
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Implementation Checklist
|
|
162
|
+
|
|
163
|
+
When adding E2E test coverage to a component:
|
|
164
|
+
|
|
165
|
+
- [ ] Identify elements that need selectors for testing
|
|
166
|
+
- [ ] Prefer semantic selectors (role, text, label) when available
|
|
167
|
+
- [ ] Use namespaced testID pattern for elements without semantics
|
|
168
|
+
- [ ] Verify testID propagates to `data-testid` on web (check Gluestack components)
|
|
169
|
+
- [ ] Add accessibility labels where beneficial
|
|
170
|
+
- [ ] Document testIDs in component JSDoc preamble
|
|
171
|
+
|
|
172
|
+
## Example Component
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
/**
|
|
176
|
+
* Profile screen component.
|
|
177
|
+
*
|
|
178
|
+
* Test IDs for E2E testing:
|
|
179
|
+
* - `profile:container` - Main container
|
|
180
|
+
* - `profile:avatar` - User avatar image
|
|
181
|
+
* - `profile:name` - User display name
|
|
182
|
+
*
|
|
183
|
+
* @module features/profile/screens/Main
|
|
184
|
+
*/
|
|
185
|
+
export const ProfileScreen = () => (
|
|
186
|
+
<Box testID="profile:container" className="flex-1 p-4">
|
|
187
|
+
<Image
|
|
188
|
+
testID="profile:avatar"
|
|
189
|
+
source={{ uri: user.avatarUrl }}
|
|
190
|
+
accessibilityLabel={`${user.name}'s profile photo`}
|
|
191
|
+
/>
|
|
192
|
+
<Text testID="profile:name" accessibilityRole="heading">
|
|
193
|
+
{user.name}
|
|
194
|
+
</Text>
|
|
195
|
+
<Pressable
|
|
196
|
+
accessibilityLabel="Edit profile"
|
|
197
|
+
onPress={handleEdit}
|
|
198
|
+
>
|
|
199
|
+
<Text>Edit</Text>
|
|
200
|
+
</Pressable>
|
|
201
|
+
</Box>
|
|
202
|
+
);
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## Corresponding E2E Test
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
test.describe("Profile Screen", () => {
|
|
209
|
+
test("displays user information", async ({ page }) => {
|
|
210
|
+
await page.goto("/profile");
|
|
211
|
+
|
|
212
|
+
// Verify structural container
|
|
213
|
+
await expect(page.getByTestId("profile:container")).toBeVisible();
|
|
214
|
+
|
|
215
|
+
// Prefer accessible queries when available
|
|
216
|
+
await expect(page.getByRole("heading")).toHaveText("John Doe");
|
|
217
|
+
await expect(page.getByRole("button", { name: "Edit profile" })).toBeVisible();
|
|
218
|
+
|
|
219
|
+
// Use testID for elements without semantic roles
|
|
220
|
+
await expect(page.getByTestId("profile:avatar")).toBeVisible();
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
```
|