import { Maybe, greaterThan, isPositive, isStringValue, makeBrand, makeResult, Result, } from 'vest-utils'; import matchingFieldName from '../../core/test/helpers/matchingFieldName'; import { Severity, SeverityCount } from '../Severity'; import { FailureMessages, GetFailuresResponse, SuiteResult, SuiteSummary, TFieldName, TGroupName, TSchema, } from '../SuiteResultTypes'; import { SummaryFailure } from '../SummaryFailure'; import { gatherFailures } from './collectFailures'; type InputFieldName = F; type InputGroupName = G; export function bindSuiteSelectors< F extends TFieldName, G extends TGroupName, S extends TSchema = undefined, >(get: () => SuiteResult): SuiteSelectors { return { getError: (...args: Parameters['getError']>) => get().getError(...args), getErrors: (...args: Parameters['getErrors']>) => get().getErrors(...args), getErrorsByGroup: ( ...args: Parameters['getErrorsByGroup']> ) => get().getErrorsByGroup(...args), getMessage: (...args: Parameters['getMessage']>) => get().getMessage(...args), getWarning: (...args: Parameters['getWarning']>) => get().getWarning(...args), getWarnings: (...args: Parameters['getWarnings']>) => get().getWarnings(...args), getWarningsByGroup: ( ...args: Parameters['getWarningsByGroup']> ) => get().getWarningsByGroup(...args), hasErrors: (...args: Parameters['hasErrors']>) => get().hasErrors(...args), hasErrorsByGroup: ( ...args: Parameters['hasErrorsByGroup']> ) => get().hasErrorsByGroup(...args), hasWarnings: (...args: Parameters['hasWarnings']>) => get().hasWarnings(...args), hasWarningsByGroup: ( ...args: Parameters['hasWarningsByGroup']> ) => get().hasWarningsByGroup(...args), isPending: (...args: Parameters['isPending']>) => get().isPending(...args), isTested: (...args: Parameters['isTested']>) => get().isTested(...args), isValid: (...args: Parameters['isValid']>) => get().isValid(...args), isValidByGroup: ( ...args: Parameters['isValidByGroup']> ) => get().isValidByGroup(...args), } as SuiteSelectors; } // eslint-disable-next-line max-lines-per-function, max-statements export function suiteSelectors( summary: SuiteSummary, ): SuiteSelectors { const selectors = { getError, getErrors, getErrorsByGroup, getMessage, getWarning, getWarnings, getWarningsByGroup, hasErrors, hasErrorsByGroup, hasWarnings, hasWarningsByGroup, isPending, isTested, isValid, isValidByGroup, }; return selectors; function asFieldName(fieldName?: InputFieldName): F | undefined { if (isStringValue(fieldName)) { return makeBrand(fieldName) as F; } return fieldName; } function asGroupName(groupName: InputGroupName): G { if (isStringValue(groupName)) { return makeBrand(groupName) as G; } return groupName; } // Booleans function isValid(fieldName?: InputFieldName): boolean { const targetFieldName = asFieldName(fieldName); return Boolean( targetFieldName ? summary.tests[targetFieldName]?.valid : summary.valid, ); } // eslint-disable-next-line max-statements, complexity function isValidByGroup( groupName: InputGroupName, fieldName?: InputFieldName, ): boolean { if ( summary.valid === null || (!summary.testCount && !Object.keys(summary.tests).length && !Object.keys(summary.groups).length) ) { return false; } const safeGroupName = asGroupName(groupName); const safeFieldName = asFieldName(fieldName); const group = summary.groups[safeGroupName]; // If the group doesn't exist, it is vacuously valid in this selector. if (!group) { return true; } // If checking a specific field within the group if (safeFieldName) { const fieldSummary = group[safeFieldName]; // If field doesn't exist in group, it's invalid (test was never run) if (!fieldSummary) { return false; } return !!fieldSummary.valid; } // Check all fields in the group let hasAnyFields = false; for (const fieldName of Object.keys(group) as F[]) { hasAnyFields = true; if (!group[fieldName]?.valid) { return false; } } return hasAnyFields; } function hasErrors(fieldName?: InputFieldName): boolean { return hasFailures( summary, SeverityCount.ERROR_COUNT, asFieldName(fieldName), ).unwrap(); } function hasWarnings(fieldName?: InputFieldName): boolean { return hasFailures( summary, SeverityCount.WARN_COUNT, asFieldName(fieldName), ).unwrap(); } function isTested(fieldName: InputFieldName): boolean { const safeFieldName = asFieldName(fieldName); return safeFieldName ? isPositive(summary.tests[safeFieldName]?.testCount) : false; } function hasWarningsByGroup( groupName: InputGroupName, fieldName?: InputFieldName, ): boolean { return hasFailuresByGroup( summary, SeverityCount.WARN_COUNT, asGroupName(groupName), asFieldName(fieldName), ).unwrap(); } function hasErrorsByGroup( groupName: InputGroupName, fieldName?: InputFieldName, ): boolean { return hasFailuresByGroup( summary, SeverityCount.ERROR_COUNT, asGroupName(groupName), asFieldName(fieldName), ).unwrap(); } // Responses function getWarnings(): FailureMessages; function getWarnings(fieldName: InputFieldName): string[]; function getWarnings(fieldName?: InputFieldName): GetFailuresResponse { return getFailures( summary, Severity.WARNINGS, asFieldName(fieldName), ).unwrap(); } function getWarning(): Maybe>; function getWarning(fieldName: InputFieldName): Maybe; function getWarning( fieldName?: InputFieldName, ): Maybe | string> { const safeFieldName = asFieldName(fieldName); return ( safeFieldName ? getFailure(Severity.WARNINGS, summary, safeFieldName) : getFailure(Severity.WARNINGS, summary) ).unwrap(); } function getErrors(): FailureMessages; function getErrors(fieldName: InputFieldName): string[]; function getErrors(fieldName?: InputFieldName): GetFailuresResponse { return getFailures( summary, Severity.ERRORS, asFieldName(fieldName), ).unwrap(); } function getError(): Maybe>; function getError(fieldName: InputFieldName): Maybe; function getError( fieldName?: InputFieldName, ): Maybe | string> { const safeFieldName = asFieldName(fieldName); return ( safeFieldName ? getFailure(Severity.ERRORS, summary, safeFieldName) : getFailure(Severity.ERRORS, summary) ).unwrap(); } function getErrorsByGroup(groupName: InputGroupName): FailureMessages; function getErrorsByGroup( groupName: InputGroupName, fieldName: InputFieldName, ): string[]; function getErrorsByGroup( groupName: InputGroupName, fieldName?: InputFieldName, ): GetFailuresResponse { return getFailuresByGroup( summary, Severity.ERRORS, asGroupName(groupName), asFieldName(fieldName), ).unwrap(); } function getMessage(fieldName: InputFieldName): Maybe { return getError(fieldName) || getWarning(fieldName); } function getWarningsByGroup(groupName: InputGroupName): FailureMessages; function getWarningsByGroup( groupName: InputGroupName, fieldName: InputFieldName, ): string[]; function getWarningsByGroup( groupName: InputGroupName, fieldName?: InputFieldName, ): GetFailuresResponse { return getFailuresByGroup( summary, Severity.WARNINGS, asGroupName(groupName), asFieldName(fieldName), ).unwrap(); } function isPending(fieldName?: InputFieldName): boolean { const safeFieldName = asFieldName(fieldName); return safeFieldName ? greaterThan(summary.tests[safeFieldName]?.pendingCount, 0) : greaterThan(summary.pendingCount, 0); } } export interface SuiteSelectors { getWarning(): SummaryFailure | undefined; getWarning(fieldName: InputFieldName): string | undefined; getWarning( fieldName?: InputFieldName, ): SummaryFailure | string | undefined; getError(): SummaryFailure | undefined; getError(fieldName: InputFieldName): string | undefined; getError( fieldName?: InputFieldName, ): SummaryFailure | string | undefined; getMessage(fieldName: InputFieldName): string | undefined; getErrors(): FailureMessages; getErrors(fieldName: InputFieldName): string[]; getErrors(fieldName?: InputFieldName): string[] | FailureMessages; getWarnings(): FailureMessages; getWarnings(fieldName: InputFieldName): string[]; getWarnings(fieldName?: InputFieldName): string[] | FailureMessages; getErrorsByGroup(groupName: InputGroupName): FailureMessages; getErrorsByGroup( groupName: InputGroupName, fieldName: InputFieldName, ): string[]; getErrorsByGroup( groupName: InputGroupName, fieldName?: InputFieldName, ): string[] | FailureMessages; getWarningsByGroup(groupName: InputGroupName): FailureMessages; getWarningsByGroup( groupName: InputGroupName, fieldName: InputFieldName, ): string[]; getWarningsByGroup( groupName: InputGroupName, fieldName?: InputFieldName, ): string[] | FailureMessages; hasErrors(fieldName?: InputFieldName): boolean; hasWarnings(fieldName?: InputFieldName): boolean; hasErrorsByGroup( groupName: InputGroupName, fieldName?: InputFieldName, ): boolean; hasWarningsByGroup( groupName: InputGroupName, fieldName?: InputFieldName, ): boolean; isTested(fieldName: InputFieldName): boolean; isPending(fieldName?: InputFieldName): boolean; isValid(fieldName?: InputFieldName): boolean; isValidByGroup( groupName: InputGroupName, fieldName?: InputFieldName, ): boolean; } // Gathers all failures of a given severity // With a fieldName, it will only gather failures for that field function getFailures( summary: SuiteSummary, severityKey: Severity, ): Result; function getFailures( summary: SuiteSummary, severityKey: Severity, fieldName?: TFieldName, ): Result; function getFailures( summary: SuiteSummary, severityKey: Severity, fieldName?: TFieldName, ): Result { return makeResult.Ok(gatherFailures(summary.tests, severityKey, fieldName)); } // Gathers all failures of a given severity within a group // With a fieldName, it will only gather failures for that field function getFailuresByGroup( summary: SuiteSummary, severityKey: Severity, groupName: TGroupName, fieldName?: TFieldName, ): Result { return makeResult.Ok( gatherFailures(summary.groups[groupName], severityKey, fieldName), ); } // Checks if a there are any failures of a given severity within a group // If a fieldName is provided, it will only check for failures within that field // eslint-disable-next-line complexity function hasFailuresByGroup( summary: SuiteSummary, severityCount: SeverityCount, groupName: G, fieldName?: F, ): Result { const group = summary.groups[groupName]; if (!group) { return makeResult.Ok(false); } if (fieldName) { return makeResult.Ok(isPositive(group[fieldName]?.[severityCount])); } for (const field in group) { if (isPositive(group[field as unknown as F]?.[severityCount])) { return makeResult.Ok(true); } } return makeResult.Ok(false); } // Checks if there are any failures of a given severity // If a fieldName is provided, it will only check for failures within that field function hasFailures( summary: SuiteSummary, countKey: SeverityCount, fieldName?: TFieldName, ): Result { const failureCount = fieldName ? summary.tests[fieldName]?.[countKey] : summary[countKey] || 0; return makeResult.Ok(isPositive(failureCount)); } function getFailure( severity: Severity, summary: SuiteSummary, ): Result>>; function getFailure( severity: Severity, summary: SuiteSummary, fieldName: F, ): Result>; function getFailure( severity: Severity, summary: SuiteSummary, fieldName?: F, ): Result | string>> { const summaryKey = summary[severity]; if (!fieldName) { return makeResult.Ok(summaryKey[0]); } return makeResult.Ok( summaryKey.find((summaryFailure: SummaryFailure) => matchingFieldName(summaryFailure, fieldName).unwrap(), )?.message, ); }