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.
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})`);
}
}
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);
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>' });
http(s):// endpoints use CDP (connectOverCDP); ws(s)://
endpoints use a Playwright server (connect). A raw CDP websocket also starts
with ws:// — pass connectType: 'cdp' to force CDP in that case.MJ_REACT_TEST_HARNESS_CONNECT to the endpoint instead
of passing connect (so the mj-react-test CLI can attach without a new flag).reuseExistingContext: true (or
MJ_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.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.headless is ignored when attaching (the external browser already decided).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.
cd packages/React/test-harness
# Build (required before testing)
npm run build
# Run all 425 tests
npx vitest run
# Run with verbose output (see individual fixture results)
npx vitest run --reporter=verbose
# Watch mode
npx vitest
Some rules validate against entity metadata from the database. Create .env:
DB_HOST=your-host
DB_DATABASE=your-db
DB_USERNAME=your-user
DB_PASSWORD=your-password
DB_PORT=1433
DB_TRUST_SERVER_CERTIFICATE=1
Without a database, DB-dependent fixtures still run but rules that need entity metadata emit low-severity warnings instead of violations.
See LINTER-ARCHITECTURE.md for comprehensive documentation including:
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.search null guardsearch-call-validation — Search/PreviewSearch parameter validationentity-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 referencesno-import-statements, no-export-statements, no-require-statementsno-iife-wrapper, no-return-component, no-window-accessuse-function-declaration, single-function-onlyreact-component-naming, component-name-mismatch, pass-standard-propsno-react-destructuring, no-data-prop, no-child-implementationcomponent-props-validation, child-component-prop-validationcomponent-usage-without-destructuringcomponent-not-in-dependencies, undefined-component-usageunused-libraries, unused-component-dependencieslibrary-variable-names, dependency-shadowing, validate-component-referencescallback-event-validation — Method usage, parameter signatures, passthrough, event null-checksprefer-async-await, prefer-jsx-syntax, react-hooks-rulesuseeffect-unstable-dependencies, unsafe-array-operations, unsafe-formatting-methodsstring-replace-all-occurrences, string-template-validationtype-inference-errors, type-mismatch-operationstyles-validation — Invalid path access, unsafe patternsutilities-api-validation, utilities-no-direct-instantiation, ai-tools-availability-checksaved-user-settings-pattern, noisy-settings-updates, prop-state-syncproperty-name-consistency, server-reload-on-client-operationno-use-reducer, required-queries-not-calledundefined-jsx-component, no-child-implementation// Core linting
export { ComponentLinter, LintResult, Violation } from './lib/component-linter';
export { BaseLintRule } from './lib/lint-rule';
// Test harness
export { ReactTestHarness, TestHarnessOptions } from './lib/test-harness';
export { ComponentRunner, ComponentExecutionOptions, ComponentExecutionResult } from './lib/component-runner';
export { BrowserManager, BrowserContextOptions } from './lib/browser-context';
// Utilities
export { LibraryLintCache, CompiledLibraryRules, CompiledValidator } from './lib/library-lint-cache';
export { ComponentSpec } from '@memberjunction/interactive-component-types';
ISC