UNPKG

covertable

Version:

Efficient TypeScript library for pairwise testing, generating minimal covering arrays with constraint support.

200 lines (199 loc) 5.69 kB
import type { Controller } from "./controller"; export type ScalarType = number | string; export type DictType = { [s: string]: any; }; export type ListType = { [index: number]: any[]; }; export type SerialsType = Map<ScalarType, PairType>; export type ParentsType = Map<number, ScalarType>; export type IndicesType = Map<number, number>; export type FilterRowType = { [key: string]: any; [index: number]: any; }; export type ArrayTupleType = any[][]; export type ArrayObjectType = { [s: string]: any[]; }; export type FactorsType = ArrayTupleType | ArrayObjectType; export type SuggestRowType<T extends FactorsType> = T extends ArrayTupleType ? T[number][number][] : T extends ArrayObjectType ? { [K in keyof T]: T[K][number]; } : unknown; export type PairType = number[]; export type PairByKeyType = Map<ScalarType, PairType>; export type CandidateType = [ScalarType, number][]; export interface RowType { } export type SorterType = (pairs: PairType[], sortArgs: SortArgsType) => PairType[]; export interface SortArgsType { salt: ScalarType; indices: IndicesType; } export interface CriterionArgsType { row: RowType; parents: ParentsType; strength: number; tolerance: number; } export interface SubModelType { fields: ScalarType[]; strength: number; } export type WeightsType = { [factorKey: string]: { [index: number]: number; }; }; export type PresetRowType = { [key: string]: any; [index: number]: any; }; /** * An operand is either a field reference (string, supports dot notation * like `"payment.method"`) or an arithmetic expression. */ export type Operand = string | ArithmeticExpression; /** * Arithmetic expressions compute a value from two operands. * Both `left` and `right` can be field references or nested expressions. * When `right` is omitted, `value` provides a literal operand. */ export type ArithmeticExpression = { operator: 'add' | 'sub' | 'mul' | 'div' | 'mod' | 'pow'; left: Operand; right: Operand; } | { operator: 'add' | 'sub' | 'mul' | 'div' | 'mod' | 'pow'; left: Operand; value: any; }; /** * A comparison condition. `left` is the first operand (field reference or * expression). The second operand is either `right` (another field/expression) * or `value` (a literal). For `in`, use `values` (an array of literals). */ export type ComparisonExpression = { operator: 'eq'; left: Operand; value: any; } | { operator: 'eq'; left: Operand; right: Operand; } | { operator: 'ne'; left: Operand; value: any; } | { operator: 'ne'; left: Operand; right: Operand; } | { operator: 'gt'; left: Operand; value: any; } | { operator: 'gt'; left: Operand; right: Operand; } | { operator: 'lt'; left: Operand; value: any; } | { operator: 'lt'; left: Operand; right: Operand; } | { operator: 'gte'; left: Operand; value: any; } | { operator: 'gte'; left: Operand; right: Operand; } | { operator: 'lte'; left: Operand; value: any; } | { operator: 'lte'; left: Operand; right: Operand; } | { operator: 'in'; left: Operand; values: any[]; }; export type LogicalExpression = { operator: 'not'; condition: Expression; } | { operator: 'and'; conditions: Expression[]; } | { operator: 'or'; conditions: Expression[]; }; /** * Escape hatch for constraints that cannot be expressed declaratively. * The engine cannot perform three-valued reasoning on these — when a * dependency key is missing the condition is treated as `null`. * Provide `requires` so the engine knows when it is safe to call `evaluate`. */ export type FnExpression = { operator: 'fn'; requires: string[]; evaluate: (row: { [key: string]: any; }) => boolean; }; export type Expression = ComparisonExpression | LogicalExpression | FnExpression; /** * Custom comparison functions. Each key matches a comparison operator name. * When provided, the corresponding function is called instead of the default * JS comparison. The function receives two resolved (non-undefined) values * and must return a boolean. * * The `in` comparer receives the field value and the values array. * * `undefined` values (= field not yet set in the row) are **never** passed * to a comparer — the engine returns `null` before reaching the * comparer in that case. */ export interface Comparer { eq?: (a: any, b: any) => boolean; ne?: (a: any, b: any) => boolean; gt?: (a: any, b: any) => boolean; lt?: (a: any, b: any) => boolean; gte?: (a: any, b: any) => boolean; lte?: (a: any, b: any) => boolean; in?: (value: any, values: Set<any>) => boolean; } export interface OptionsType<T extends FactorsType> { strength?: number; subModels?: SubModelType[]; weights?: WeightsType; presets?: PresetRowType[]; sorter?: SorterType; criterion?: (ctrl: Controller<T>) => IterableIterator<PairType>; salt?: ScalarType; tolerance?: number; /** * Declarative constraints. Each entry is a `Condition` tree that the * generator evaluates under Kleene three-valued logic: when a referenced * field is not yet present in the row the result is `null` (deferred) * rather than `false`, so the generator can prune early without * discarding viable combinations. * * The top-level array is an implicit AND: every condition must be * satisfied for a row to be accepted. */ constraints?: Expression[]; /** * Custom comparison functions. See `Comparer` for details. */ comparer?: Comparer; }