Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

build: switch to using typescript #3

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Expand Up @@ -5,3 +5,5 @@ build
coverage
.DS_Store
/package-lock.json
dist/
.idea/
83 changes: 0 additions & 83 deletions lib/CleanConsoleReporter.js

This file was deleted.

90 changes: 90 additions & 0 deletions lib/CleanConsoleReporter.ts
@@ -0,0 +1,90 @@
/* global require module */

import { Options, Rule} from "./types";
import type {Config} from '@jest/types';
import type {AggregatedResult, TestResult} from '@jest/test-result';

import {DefaultReporter} from "@jest/reporters";
import {LogEntry, LogType} from "@jest/console/build/types";
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reaching into build is not supported, if tsc supports package exports it'll actually fail from Jest 27.

I'd be happy to merge a PR in Jest that exports the types you need to build this reporter in TS

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh that is good to know! I'll add it to my list of things to do today. Thanks!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

import {ReporterOnStartOptions} from "@jest/reporters/build/types";
import {getLogGroupKey} from "./getLogGroupKey";
import {getLogGroupSummary} from "./getLogGroupSummary";

/**
* Overrides Jest's default reporter to filter out known console messages,
* and prints a summary at the end of the test run.
*/
export class CleanConsoleReporter extends DefaultReporter {

private readonly rules: Rule[];
private readonly levels: LogType[];
private readonly logs: Map<LogType, Map<string, number>>;
private ignored: number;

constructor(globalConfig: any, options: Options = {}) {
super(globalConfig);
this.rules = options.rules || [];
this.levels = options.levels || ["error", "warn", "info", "debug", "log"];
this.logs = new Map<LogType, Map<string, number>>();
this.ignored = 0;
}

// Override DefaultReporter method
printTestFileHeader(testPath: Config.Path, config: Config.ProjectConfig,
result: TestResult) {
// Strip out known console messages before passing to base implementation
const filteredResult = {
...result,
console: this.filterOutKnownMessages(result.console),
};

super.printTestFileHeader(testPath, config, filteredResult);
}

filterOutKnownMessages(consoleBuffer: LogEntry[] = []) {
const rules = this.rules;
const retain: LogEntry[] = [];

for (const frame of consoleBuffer) {
// Check if this a known type message
const [key, keep] = getLogGroupKey(rules, frame);
if (key) {
this.groupMessageByKey(frame.type, key);
if (keep) {
retain.push(frame);
}
} else if (key === null) {
this.ignored++;
} else {
retain.push(frame);
}
}

// Based implementation expects undefined instead of empty array
return retain.length ? retain : undefined;
}

groupMessageByKey(type: LogType, key: string): void {
// this.logs : Map<string, Map<string, number>>
let level = this.logs.get(type);
if (!level) {
this.logs.set(type, (level = new Map<string, number>()));
}

level.set(key, (level.get(key) || 0) + 1);
}

onRunStart(aggregatedResults: AggregatedResult,
options: ReporterOnStartOptions): void {
super.onRunStart(aggregatedResults, options);
}

onRunComplete() {
const summary = getLogGroupSummary(this.logs, this.levels, this.ignored);
if (summary) {
summary.forEach(this.log);
}

super.onRunComplete();
}
}
20 changes: 9 additions & 11 deletions lib/getLogGroupHeader.js → lib/getLogGroupHeader.ts
@@ -1,5 +1,7 @@
/* global require, module */

import {LogType} from "@jest/console";

const chalk = require("chalk");

// Explicitly reset for these messages since they can get written out in the
Expand All @@ -10,7 +12,7 @@ const LOG_TEXT = "LOG";
const INFO_TEXT = "INFO";
const DEBUG_TEXT = "DEBUG";

const statusByType = {
const statusByType: Partial<Record<LogType, string>> = {
error: chalk.supportsColor
? chalk.reset.bold.red(` ${ERROR_TEXT}`.padEnd(7))
: ERROR_TEXT,
Expand All @@ -28,30 +30,26 @@ const statusByType = {
: DEBUG_TEXT,
};

const formatCount = (count) => {
export const formatCount = (count: number): string => {
const chars = count.toString();
const chalked = chalk.bold(chars);
const formatChars = chalked.length - chars.length;
return `${chalked}`.padEnd(5 + formatChars, " ");
};

const formatMessage = (key) => {
export const formatMessage = (key: string): string => {
const truncated = key.length > 100 ? `${key.substring(0, 100)}...` : key;
const singleline = truncated.replace(/[\r\n]+/g, " ");
const highlighted = singleline.replace("@TODO", chalk.bold("@TODO"));

return highlighted;
return singleline.replace("@TODO", chalk.bold("@TODO"));
};

const getLogGroupHeader = (type, key, count) => {
const status = statusByType[type] || type.toUpperCase();
export const getLogGroupHeader = (type: LogType, key: string, count: number): string => {
const status: string = statusByType[type] || type.toUpperCase();
return `${status} ${formatCount(count)} ${formatMessage(key)}`;
};

const getSkippedHeader = (count) => {
export const getSkippedHeader = (count: number): string => {
const label = " SKIP".padEnd(7);
const message = `${label} ${formatCount(count)} messages were filtered`;
return chalk.supportsColor ? chalk.reset.gray(message) : message;
};

module.exports = { getLogGroupHeader, getSkippedHeader };
12 changes: 7 additions & 5 deletions lib/getLogGroupKey.js → lib/getLogGroupKey.ts
@@ -1,5 +1,9 @@
/* global module */
const matchWith = (matcher, message, type, origin) => {
import {Group, Matcher, Rule} from "./types";
import {LogType} from "@jest/console";
import {LogEntry} from "@jest/console/build/types";

export const matchWith = (matcher: Matcher, message: string, type: LogType, origin: string) => {
if (matcher instanceof RegExp) {
return matcher.test(message);
}
Expand All @@ -17,7 +21,7 @@ const matchWith = (matcher, message, type, origin) => {
throw new Error("Filter must be a string, function or a regular expression");
};

const formatMessage = (formatter, message, type, matcher) => {
const formatMessage = (formatter: Group, message: string, type: LogType, matcher: Matcher): string | null => {
if (typeof formatter === "undefined") {
return null;
}
Expand All @@ -37,7 +41,7 @@ const formatMessage = (formatter, message, type, matcher) => {
return message;
};

const getLogGroupKey = (rules, { message, type, origin }) => {
export const getLogGroupKey = (rules: Rule[], { message, type, origin }: LogEntry): [string | null, boolean] | [] => {
for (let { match: matcher, group: formatter, keep = false } of rules) {
if (matchWith(matcher, message, type, origin)) {
return [formatMessage(formatter, message, type, matcher), keep];
Expand All @@ -46,5 +50,3 @@ const getLogGroupKey = (rules, { message, type, origin }) => {

return [];
};

module.exports = getLogGroupKey;
20 changes: 10 additions & 10 deletions lib/getLogGroupSummary.js → lib/getLogGroupSummary.ts
@@ -1,9 +1,11 @@
/* global require module */

import {getLogGroupHeader, getSkippedHeader} from "./getLogGroupHeader";
import {LogType} from "@jest/console";

const chalk = require("chalk");
const { getLogGroupHeader, getSkippedHeader } = require("./getLogGroupHeader");

const orderBy = ([aKey, aCount], [bKey, bCount]) => {
const orderBy = ([aKey, aCount]: [string, number], [bKey, bCount]: [string, number]): number => {
// count descending
if (aCount > bCount) return -1;
if (bCount > aCount) return 1;
Expand All @@ -16,18 +18,18 @@ const orderBy = ([aKey, aCount], [bKey, bCount]) => {
return 0;
};

const getLogGroupSummary = (logs, levels, ignored) => {
export const getLogGroupSummary = (logs: Map<LogType, Map<string, number>>, levels: LogType[], ignored: number) => {
if (logs.size === 0 && !ignored) {
return null;
}

const lines = [`\n ${chalk.bold("\u25cf ")} Suppressed console messages:\n`];
levels.forEach((type) => {
const level = logs.get(type);
if (level) {
const entries = [...level.entries()].sort(orderBy);
levels.forEach((level) => {
const levelMap = logs.get(level);
if (levelMap) {
const entries = [...levelMap.entries()].sort(orderBy);
for (let [key, count] of entries) {
lines.push(getLogGroupHeader(type, key, count));
lines.push(getLogGroupHeader(level, key, count));
}
}
});
Expand All @@ -38,5 +40,3 @@ const getLogGroupSummary = (logs, levels, ignored) => {

return lines;
};

module.exports = getLogGroupSummary;
2 changes: 0 additions & 2 deletions lib/index.js

This file was deleted.

2 changes: 2 additions & 0 deletions lib/index.ts
@@ -0,0 +1,2 @@
/* global require module */
export { CleanConsoleReporter } from './CleanConsoleReporter'
16 changes: 16 additions & 0 deletions lib/types.ts
@@ -0,0 +1,16 @@
import {LogType} from "@jest/console";

export type Matcher = RegExp | string | ((message: string, type: LogType, origin: string) => boolean)

export type Group = string | null | ((message: string, type: LogType, matcher: Matcher) => string | null)

export interface Rule {
match: Matcher
group: Group
keep?: boolean
}

export interface Options {
rules?: Rule[],
levels?: LogType[]
}
6 changes: 4 additions & 2 deletions package.json
Expand Up @@ -13,9 +13,11 @@
"chalk": ">=3.0.0"
},
"peerDependencies": {
"jest": ">=25.1.0"
"jest": "^25.1.0"
},
"devDependencies": {
"npm-install-peers": "^1.2.1"
"@types/chalk": "^2.2.0",
"npm-install-peers": "^1.2.1",
"typescript": "^4.1.3"
}
}