import * as ui from "../../ui"; import * as csx from "../../base/csx"; import * as React from "react"; import * as tab from "./tab"; import { server, cast } from "../../../socket/socketClient"; import * as commands from "../../commands/commands"; import * as utils from "../../../common/utils"; import * as d3 from "d3"; import { Types } from "../../../socket/socketContract"; import * as types from "../../../common/types"; import { IconType } from "../../../common/types"; import * as $ from "jquery"; import * as styles from "../../styles/styles"; import * as onresize from "onresize"; import { Clipboard } from "../../components/clipboard"; import * as typeIcon from "../../components/typeIcon"; import * as gls from "../../base/gls"; import * as typestyle from "typestyle"; import { MarkDown } from "../../markdown/markdown"; import { testResultsCache } from "../../clientTestResultsCache"; import { Icon } from "../../components/icon"; import * as state from "../../state/state"; import { blackHighlightColor } from "../../styles/styles"; export interface Props extends tab.TabProps { } export interface State { tests?: types.TestSuitesByFilePath; testResultsStats?: types.TestContainerStats; selected?: string | null; testedWorking?: types.Working; } export namespace TestedViewStyles { export const headerClassName = typestyle.style({ fontWeight: 'bold', cursor: 'pointer', $nest: { '&:hover': { textDecoration: 'underline' } } }); export const clickable = typestyle.style({ cursor: 'pointer', $nest: { '&:hover': { textDecoration: 'underline' } } }); } /** Utility */ const makeReactKeyOutOfPosition = (position: EditorPosition) => { return position.line + ':' + position.ch; } const formatStats = (stats: types.TestContainerStats): string => { return `Σ: ${stats.testCount} (✓: ${stats.passCount}, ✘: ${stats.failCount}, ◎: ${stats.skipCount}) ${utils.formatMilliseconds(stats.durationMs)}` } export class TestedView extends ui.BaseComponent { constructor(props: Props) { super(props); this.state = { tests: Object.create(null), selected: null }; } componentDidMount() { /** * Initial load + load on result change */ this.loadData(); this.disposible.add( testResultsCache.testResultsDelta.on(() => { this.loadData(); }) ); this.disposible.add(state.subscribeSub(s => s.testedWorking, (testedWorking) => { this.setState({ testedWorking }); })); // Listen to tab events const api = this.props.api; this.disposible.add(api.resize.on(this.resize)); this.disposible.add(api.focus.on(this.focus)); this.disposible.add(api.save.on(this.save)); this.disposible.add(api.close.on(this.close)); this.disposible.add(api.gotoPosition.on(this.gotoPosition)); // Listen to search tab events this.disposible.add(api.search.doSearch.on(this.search.doSearch)); this.disposible.add(api.search.hideSearch.on(this.search.hideSearch)); this.disposible.add(api.search.findNext.on(this.search.findNext)); this.disposible.add(api.search.findPrevious.on(this.search.findPrevious)); this.disposible.add(api.search.replaceNext.on(this.search.replaceNext)); this.disposible.add(api.search.replacePrevious.on(this.search.replacePrevious)); this.disposible.add(api.search.replaceAll.on(this.search.replaceAll)); } ctrls: { root?: HTMLDivElement } = {}; render() { return (
this.ctrls.root = root} onFocus={this.props.onFocused} tabIndex={0} style={csx.extend(csx.vertical, csx.flex, csx.newLayerParent, styles.someChildWillScroll, { color: styles.textColor })} onKeyPress={this.handleKey}> {this.renderHeader()} { this.renderFiles() } { this.state.selected ? this.renderSelectedNode() : 'Select a module from the left to view results 🌹' }
); } renderHeader() { if (!this.state.testResultsStats) { return
No test runs yet.
} const testResultsStats = testResultsCache.getStats(); const failing = !!testResultsStats.failCount; const totalThatRan = testResultsStats.passCount + testResultsStats.failCount; const working = this.state.testedWorking && this.state.testedWorking.working; const summary = formatStats(testResultsStats); const testStatsRendered = !!testResultsStats.testCount && { failing ? {testResultsStats.failCount}/{totalThatRan} Tests Failing : {testResultsStats.passCount}/{totalThatRan} Tests Passed } return ( {testStatsRendered} {summary} ); } renderFiles() { return Object.keys(this.state.tests).map((fp, i) => { const item = this.state.tests[fp]; const fileName = utils.getFileName(fp); const failing = !!item.stats.failCount; const totalThatRan = item.stats.passCount + item.stats.failCount; return (
this.handleModuleSelected(item)}> ({failing ? item.stats.failCount : item.stats.passCount}/{totalThatRan}) {fileName}
) }); } renderSelectedNode() { const filePath = this.state.selected; const test = this.state.tests[filePath]; if (!test) { return
The selected filePath: {filePath} is no longer in the test restuls
} const someFailing = !!test.stats.failCount; return
{formatStats(test.stats)}
{ commands.doOpenOrFocusFile.emit({ filePath: filePath, }); } }>{filePath}
{test.suites.map(s => this.renderSuite(s))}
} renderSuite(suite: types.TestSuiteResult) { const color = !!suite.stats.failCount ? styles.errorColor : !!suite.stats.passCount ? styles.successColor : styles.highlightColor; return
this.openTestLogPositionInSelectedModule(suite.testLogPosition)}> {suite.description} {formatStats(suite.stats)} {suite.tests.map(s => this.renderTest(s))} {suite.suites.map(s => this.renderSuite(s))}
} renderTest(test: types.TestResult) { return
this.openTestLogPositionInSelectedModule(test.testLogPosition)}> {test.description}   {test.durationMs != undefined ? utils.formatMilliseconds(test.durationMs) : ''} { test.status === types.TestStatus.Fail &&
{test.error.message}
{!!test.error.testLogPosition.stack.length && test.error.testLogPosition.stack.map(s => { return
this.openFilePathPosition(s)}>   {s.filePath}:{s.position.line + 1}:{s.position.ch + 1}
})}
}
} openTestLogPositionInSelectedModule = (pos: types.TestLogPosition) => { const filePath = this.state.selected; commands.doOpenOrFocusFile.emit({ filePath, position: pos.lastPositionInFile, }) } openFilePathPosition = (fpPos: types.FilePathPosition) => { commands.doOpenOrFocusFile.emit(fpPos); } handleNodeClick = (node: types.DocumentedType) => { commands.doOpenOrFocusFile.emit({ filePath: node.location.filePath, position: node.location.position }); } handleModuleSelected = (node: types.TestModule) => { this.setState({ selected: node.filePath }); } handleKey = (e: any) => { let unicode = e.charCode; if (String.fromCharCode(unicode).toLowerCase() === "r") { this.loadData(); } } loadData = () => { const results = testResultsCache.getResults(); const testResultsStats = testResultsCache.getStats(); this.setState({ tests: results, testResultsStats }); } /** * TAB implementation */ resize = () => { // Not needed } focus = () => { this.ctrls.root.focus(); } save = () => { } close = () => { } gotoPosition = (position: EditorPosition) => { } search = { doSearch: (options: FindOptions) => { }, hideSearch: () => { }, findNext: (options: FindOptions) => { }, findPrevious: (options: FindOptions) => { }, replaceNext: ({newText}: { newText: string }) => { }, replacePrevious: ({newText}: { newText: string }) => { }, replaceAll: ({newText}: { newText: string }) => { } } }