- {report &&
-
Ran {report.stats.total} tests
-
+ {report &&
}
- {report && (report.files || []).map((file, i) =>
)}
+ {report && (report.files || []).filter(f => !!f.tests.find(t => filter.matches(t))).map((file, i) => {
+ return
{
+ const value = expandedFiles.get(fileId);
+ if (value === undefined)
+ return i === 0;
+ return !!value;
+ }}
+ setFileExpanded={(fileId, expanded) => {
+ const newExpanded = new Map(expandedFiles);
+ newExpanded.set(fileId, expanded);
+ setExpandedFiles(newExpanded);
+ }}
+ filter={filter}>
+ ;
+ })}
;
};
@@ -80,24 +116,28 @@ const TestFileSummaryView: React.FC<{
file: TestFileSummary;
isFileExpanded: (fileId: string) => boolean;
setFileExpanded: (fileId: string, expanded: boolean) => void;
-}> = ({ file, report, isFileExpanded, setFileExpanded }) => {
+ filter: Filter;
+}> = ({ file, report, isFileExpanded, setFileExpanded, filter }) => {
return
setFileExpanded(file.fileId, expanded))}
header={
{msToString(file.stats.duration)}
{file.fileName}
-
+
}>
- {file.tests.map((test, i) =>
-
+ {file.tests.filter(t => filter.matches(t)).map(test =>
+
{msToString(test.duration)}
{statusIcon(test.outcome)}
- {test.title}
-
— {test.path.join(' › ')}
- {report.projectNames.length > 1 && !!test.projectName &&
{test.projectName}}
+
+ {test.title}
+
— {test.path.join(' › ')}
+
+ {report.projectNames.length > 1 && !!test.projectName &&
+
}
- )}
+ )}
;
};
@@ -128,7 +168,7 @@ const TestCaseView: React.FC<{
return
{test &&
{test?.title}
}
{test &&
{test.path.join(' › ')}
}
- {test && !!test.projectName &&
{test.projectName}
}
+ {test && !!test.projectName &&
}
{test &&
({
id: String(index),
@@ -226,18 +266,38 @@ const StepTreeItem: React.FC<{
} : undefined} depth={depth}>;
};
-const StatsView: React.FC<{
+const StatsInlineView: React.FC<{
stats: Stats
}> = ({ stats }) => {
return
- —
- {!!stats.unexpected && {stats.unexpected} failed}
- {!!stats.flaky && {stats.flaky} flaky}
- {!!stats.expected && {stats.expected} passed}
- {!!stats.skipped && {stats.skipped} skipped}
+ {!!stats.expected && Passed {stats.expected}}
+ {!!stats.unexpected && Failed {stats.unexpected}}
+ {!!stats.flaky && Flaky {stats.flaky}}
;
};
+const StatsNavView: React.FC<{
+ stats: Stats
+}> = ({ stats }) => {
+ return ;
+};
+
const AttachmentLink: React.FunctionComponent<{
attachment: TestAttachment,
href?: string,
@@ -372,14 +432,27 @@ function navigate(href: string) {
window.dispatchEvent(navEvent);
}
+const ProjectLink: React.FunctionComponent<{
+ report: HTMLReport,
+ projectName: string,
+}> = ({ report, projectName }) => {
+ return
+
+ {projectName}
+
+ ;
+};
+
const Link: React.FunctionComponent<{
href: string,
- children: any
-}> = ({ href, children }) => {
- return {
+ className?: string,
+ children: any,
+}> = ({ href, className, children }) => {
+ return {
event.preventDefault();
+ event.stopPropagation();
navigate(href);
- }} className='no-decorations' href={href}>{children};
+ }} href={href}>{children};
};
const Route: React.FunctionComponent<{
@@ -398,3 +471,48 @@ const Route: React.FunctionComponent<{
}, []);
return currentParams === params ? children : null;
};
+
+class Filter {
+ project = new Set();
+ outcome = new Set();
+ text: string[] = [];
+ expression: string;
+ private static regex = /(".*?"|[\w]+:|[^"\s:]+)(?=\s*|\s*$)/g;
+
+ private constructor(expression: string) {
+ this.expression = expression;
+ }
+
+ static parse(expression: string): Filter {
+ const filter = new Filter(expression);
+ const match = (expression.match(Filter.regex) || []).map(t => {
+ return t.startsWith('"') && t.endsWith('"') ? t.substring(1, t.length - 1) : t;
+ });
+ for (let i = 0; i < match.length; ++i) {
+ if (match[i] === 'project:' && match[i + 1]) {
+ filter.project.add(match[++i]);
+ continue;
+ }
+ if (match[i] === 'outcome:' && match[i + 1]) {
+ filter.outcome.add(match[++i]);
+ continue;
+ }
+ filter.text.push(match[i]);
+ }
+ return filter;
+ }
+
+ matches(test: TestCaseSummary): boolean {
+ if (this.project.size && !this.project.has(test.projectName))
+ return false;
+ if (this.outcome.size && !this.outcome.has(test.outcome))
+ return false;
+ if (this.text.length) {
+ const fullTitle = test.path.join(' ') + test.title;
+ const matches = !!this.text.find(t => fullTitle.includes(t));
+ if (!matches)
+ return false;
+ }
+ return true;
+ }
+}