All files tap-bark.tsx

100% Statements 57/57
100% Branches 35/35
100% Functions 13/13
100% Lines 56/56
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176    1x   1x 1x 1x 1x   1x                                         1x     2x     68x 68x 68x     68x   68x         68x         12x   12x 9x 9x 9x   9x 3x   3x         6x     3x       95x 95x   95x 2x                   93x 15x     78x       6x           68x     9x   9x 3x         6x   6x     3x 3x               9x             30x               30x 29x   29x     3x         68x 54x 54x     68x 68x       1x   1x     68x 68x   68x      
import { Results } from "./results";
import { Assertion as TAPAssertion, Results as TAPResults, Assertion, Plan } from "./external/tap-parser";
import chalk from "chalk";
 
const through = require("through2");
const parser = require("tap-parser");
const duplexer = require("duplexer");
import { h, render, Color, Component, Indent } from "ink";
 
const TAP_PARSER: { on: (eventName: string, callback: Function) => void } = parser();
 
export interface TapBarkOutputState {
    logs: Array<string>;
    warnings: Array<string>;
    fixtureName?: string;
    results?: {
        pass: number;
        fail: number;
        ignore: number;
        failures: Array<Assertion>;
    };
    totalTests?: number;
    currentTest?: number;
    testName?: string;
}
 
export interface TapBarkOutputProps {
    showProgress: boolean;
}
 
export class TapBarkOutputComponent extends Component<TapBarkOutputProps, TapBarkOutputState> {
    
    public getPipeable(): any {
        return duplexer(TAP_PARSER, through());
    }
 
    private FIXTURE_REGEXP: RegExp = /^# FIXTURE (.*)/;
    private CONSOLE_WARNING_REGEXP: RegExp = /^# WARN: (.*)/;
    private _completeCalled = false;
 
    public constructor(props) {
        super(props);
 
        this.state = {
            logs: [],
            warnings: []
        };
 
        this.setupListeners();
    }
 
    public getFailureMessage(assertion: Assertion): string {
 
        const failureTitle = chalk.red("FAIL: ") + chalk.bold(assertion.name) + "\n";
 
        if (assertion.diag) {
            const data = assertion.diag.data;
            const details = data.details;
            const title = `${failureTitle} ${assertion.diag.message}\n`;
 
            if (details && Object.keys(details).length > 0) {
                return `${title}${
                    Object.keys(details)
                            .map(key => `\n${chalk.underline(key)}:\n${details[key]}`)
                            .join("\n")
                    }`;
            }
 
            return `${title}\nexpected:\n${data.expect}\nactual:\n${data.got}`;
        }
 
        return failureTitle + "Failure reason unknown.";
    }
 
    public render() {
        const results = this.state.results;
        const total = this.state.totalTests;
 
        if (this.state.results) {
            return <Indent>
                        {this.state.warnings.join("\n")}
                        {results.failures.map(this.getFailureMessage.bind(this)).join("\n")}
                        {"\n"}
                        <Color green>Pass: {results.pass} / {total}{"\n"}</Color>
                        <Color red>Fail: {results.fail} / {total}{"\n"}</Color>
                        <Color yellow>Ignore: {results.ignore} / {total}{"\n"}{"\n"}</Color>
                    </Indent>;
        }
 
        if (this.props.showProgress === false) {
            return <Indent>running alsatian tests</Indent>
        }
 
        return <Indent>{Math.floor(this.state.currentTest / total * 100 || 0)}% complete</Indent>;
    }
 
    private handleNewPlan(plan: Plan) {
        this.setState({
            totalTests: plan.end
        });
    }
 
    // temporary while https://github.com/vadimdemedes/ink/issues/97 is still an issue
    private readonly warnings = [] as Array<string>;
 
    private handleComment(comment: string) {
        let fixtureParse = this.FIXTURE_REGEXP.exec(comment);
 
        if (fixtureParse !== null) {
            this.setState({
                fixtureName: fixtureParse[1]
            });
        }
        else {
            const message = comment.replace("# ", "");
 
            if (this.CONSOLE_WARNING_REGEXP.test(comment)) {
                
                // temporary while https://github.com/vadimdemedes/ink/issues/97 is still an issue
                this.warnings.push(chalk.yellow(message));
                this.setState({
                    warnings: this.warnings
                });
            }
        }
    }
 
    private handleAssert(assertion: TAPAssertion) {
        this.setState({
            currentTest: assertion.id,
            testName: assertion.name
        });
    }
 
    private handleComplete(results: TAPResults) {
        let _results: Results = {
            pass: results.pass || 0,
            fail: (results.fail || (results.failures || []).length),
            ignore: (results.skip || 0) + (results.todo || 0),
            failures: results.failures || []
        };
 
        // getting called multiple times for whatever reason
        if (this._completeCalled === false) {
            this._completeCalled = true;
            
            this.setState({
                ... this.state,
                results: _results
            }, () => setTimeout(() => process.exit(results.ok ? 0 : 1), 100));
        }
    }
 
    private setupListeners(): void {
        if (this.props.showProgress) {
            TAP_PARSER.on("comment", this.handleComment.bind(this));
            TAP_PARSER.on("assert", this.handleAssert.bind(this));
        }
        
        TAP_PARSER.on("plan", this.handleNewPlan.bind(this));
        TAP_PARSER.on("complete", this.handleComplete.bind(this));
    }
}
 
export class TapBark {
    
    public static readonly tapParser = TAP_PARSER;
 
    public static create(showProgress: boolean = true) {
        const tapBarkOutput = <TapBarkOutputComponent showProgress={showProgress} />;
        render(tapBarkOutput);
        
        return tapBarkOutput.instance as TapBarkOutputComponent;
    }
}