@memberjunction/react-test-harness
Automated test harness for MemberJunction interactive React components. Provides static analysis (linting) with 57 extensible rules, type inference, constraint validation, browser-based rendering via Playwright, and a CLI for running test suites.
Quick Start
Section titled “Quick Start”Linting a Component
Section titled “Linting a Component”import { ComponentLinter } from '@memberjunction/react-test-harness';
const result = await ComponentLinter.lintComponent( code, // JavaScript/JSX source 'MyComponent', // Component name componentSpec, // ComponentSpec with metadata true, // isRootComponent);
if (!result.success) { for (const v of result.violations) { console.log(`[${v.severity}] ${v.rule}: ${v.message} (line ${v.line})`); }}Browser-Based Testing
Section titled “Browser-Based Testing”import { ComponentRunner } from '@memberjunction/react-test-harness';
const runner = new ComponentRunner();const result = await runner.executeComponent(componentSpec, { contextUser, props: { data: testData },});
console.log('Rendered:', result.success);console.log('Errors:', result.errors);Connecting to an existing browser
Section titled “Connecting to an existing browser”By default the harness launches its own throwaway Chromium. You can instead attach to an already-running browser — useful for watching component runs in a browser you control, or for reusing a warm/remote browser (pool, Docker) instead of paying a cold launch each run.
// Attach to a real Chrome started with --remote-debugging-port=9222 (CDP)const harness = new ReactTestHarness({ connect: 'http://localhost:9222' });
// Attach to a Playwright server started via chromium.launchServer() (ws endpoint)const harness = new ReactTestHarness({ connect: 'ws://localhost:55001/<id>' });- Auto-detect:
http(s)://endpoints use CDP (connectOverCDP);ws(s)://endpoints use a Playwright server (connect). A raw CDP websocket also starts withws://— passconnectType: 'cdp'to force CDP in that case. - Env-var fallback: set
MJ_REACT_TEST_HARNESS_CONNECTto the endpoint instead of passingconnect(so themj-react-testCLI can attach without a new flag). - Session reuse: by default a fresh isolated context is created inside the
attached browser. Set
reuseExistingContext: true(orMJ_REACT_TEST_HARNESS_REUSE_CONTEXT=true) to reuse the browser’s existing default context and share its cookies/auth/session. This breaks per-test isolation, so keep the default for parallel runs. - Lifecycle safety: when attached,
harness.close()only closes the pages and contexts the harness created — it never closes a browser it did not launch, nor a reused/shared context. The external browser’s lifecycle is the caller’s. headlessis ignored when attaching (the external browser already decided).
Writing Custom Rules
Section titled “Writing Custom Rules”Rules extend BaseLintRule and auto-register via @RegisterClass:
import { RegisterClass } from '@memberjunction/global';import { BaseLintRule } from '@memberjunction/react-test-harness';
@RegisterClass(BaseLintRule, 'my-custom-rule')export class MyCustomRule extends BaseLintRule { get Name() { return 'my-custom-rule'; } get AppliesTo(): 'all' | 'child' | 'root' { return 'all'; }
Test(ast, componentName, componentSpec, options, typeContext) { const violations = []; // Babel AST traversal and validation logic return violations; }}External packages (e.g., Skip-Brain) can define custom rules — the linter discovers them automatically via MJGlobal’s ClassFactory.
Testing
Section titled “Testing”cd packages/React/test-harness
# Build (required before testing)npm run build
# Run all 425 testsnpx vitest run
# Run with verbose output (see individual fixture results)npx vitest run --reporter=verbose
# Watch modenpx vitestDatabase Connection (Optional)
Section titled “Database Connection (Optional)”Some rules validate against entity metadata from the database. Create .env:
DB_HOST=your-hostDB_DATABASE=your-dbDB_USERNAME=your-userDB_PASSWORD=your-passwordDB_PORT=1433DB_TRUST_SERVER_CERTIFICATE=1Without a database, DB-dependent fixtures still run but rules that need entity metadata emit low-severity warnings instead of violations.
Architecture
Section titled “Architecture”See LINTER-ARCHITECTURE.md for comprehensive documentation including:
- How rules work and how to add new ones
- Rule categories and what each validates
- Type inference engine capabilities
- Metadata fallback strategy
- SQL dialect configuration
- Test fixture organization
Lint Rules (57)
Section titled “Lint Rules (57)”Data Access (5 rules)
Section titled “Data Access (5 rules)”runview-call-validation— RunView/RunViews call-site validationrunquery-call-validation— RunQuery call-site validation with parameter type checkingdata-result-validation— RunView/RunQuery/Search result usage patternssearch-availability-check—utilities.searchnull guardsearch-call-validation— Search/PreviewSearch parameter validation
Entity & Query Fields (4 rules)
Section titled “Entity & Query Fields (4 rules)”entity-field-access-validation— Field access on RunView results (typos, case, type coercion)query-result-field-access-validation— Field access on RunQuery resultschart-field-validation— Chart prop field referencesdatagrid-field-validation— Grid column field references
Runtime Constraints (8 rules)
Section titled “Runtime Constraints (8 rules)”no-import-statements,no-export-statements,no-require-statementsno-iife-wrapper,no-return-component,no-window-accessuse-function-declaration,single-function-only
Component Structure (9 rules)
Section titled “Component Structure (9 rules)”react-component-naming,component-name-mismatch,pass-standard-propsno-react-destructuring,no-data-prop,no-child-implementationcomponent-props-validation,child-component-prop-validationcomponent-usage-without-destructuring
Dependencies (7 rules)
Section titled “Dependencies (7 rules)”component-not-in-dependencies,undefined-component-usageunused-libraries,unused-component-dependencieslibrary-variable-names,dependency-shadowing,validate-component-references
Callbacks & Events (1 consolidated rule)
Section titled “Callbacks & Events (1 consolidated rule)”callback-event-validation— Method usage, parameter signatures, passthrough, event null-checks
Best Practices (8 rules)
Section titled “Best Practices (8 rules)”prefer-async-await,prefer-jsx-syntax,react-hooks-rulesuseeffect-unstable-dependencies,unsafe-array-operations,unsafe-formatting-methodsstring-replace-all-occurrences,string-template-validation
Type Safety (2 rules)
Section titled “Type Safety (2 rules)”type-inference-errors,type-mismatch-operation
Styles (1 consolidated rule)
Section titled “Styles (1 consolidated rule)”styles-validation— Invalid path access, unsafe patterns
Utilities (3 rules)
Section titled “Utilities (3 rules)”utilities-api-validation,utilities-no-direct-instantiation,ai-tools-availability-check
State & Settings (5 rules)
Section titled “State & Settings (5 rules)”saved-user-settings-pattern,noisy-settings-updates,prop-state-syncproperty-name-consistency,server-reload-on-client-operation
Other (4 rules)
Section titled “Other (4 rules)”no-use-reducer,required-queries-not-calledundefined-jsx-component,no-child-implementation
Exports
Section titled “Exports”// Core lintingexport { ComponentLinter, LintResult, Violation } from './lib/component-linter';export { BaseLintRule } from './lib/lint-rule';
// Test harnessexport { ReactTestHarness, TestHarnessOptions } from './lib/test-harness';export { ComponentRunner, ComponentExecutionOptions, ComponentExecutionResult } from './lib/component-runner';export { BrowserManager, BrowserContextOptions } from './lib/browser-context';
// Utilitiesexport { LibraryLintCache, CompiledLibraryRules, CompiledValidator } from './lib/library-lint-cache';export { ComponentSpec } from '@memberjunction/interactive-component-types';License
Section titled “License”ISC