Skip to content

Commit d8222b6

Browse files
committed
Add files
1 parent 2d36257 commit d8222b6

File tree

10 files changed

+2589
-0
lines changed

10 files changed

+2589
-0
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
name: Publish Package
2+
on:
3+
release:
4+
types: [published]
5+
jobs:
6+
publish:
7+
runs-on: ubuntu-18.04
8+
steps:
9+
- uses: actions/checkout@v2
10+
- uses: actions/setup-node@v1
11+
with:
12+
node-version: "12.x"
13+
registry-url: https://registry.npmjs.org
14+
- run: yarn
15+
- run: yarn publish --access public
16+
env:
17+
NODE_AUTH_TOKEN: ${{secrets.NODE_AUTH_TOKEN}}
18+
- uses: actions/setup-node@v1
19+
with:
20+
node-version: "12.x"
21+
registry-url: https://npm.pkg.github.com
22+
- run: yarn publish --access public
23+
env:
24+
NODE_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}}

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules/
2+
lib/

LICENSE.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2020 Qualified Inc.
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
## @codewars/jest-reporter
2+
3+
Jest reporter producing Codewars output.
4+
5+
> NOTE: Jest is currently only available on [Qualified](https://www.qualified.io/)
6+
7+
<img src="images/dark.png" width="500px" />
8+
<img src="images/light.png" width="500px" />

images/dark.png

58.5 KB
Loading

images/light.png

62.1 KB
Loading

package.json

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{
2+
"name": "@codewars/jest-reporter",
3+
"version": "1.0.0",
4+
"description": "Codewars reporter for Jest",
5+
"files": [
6+
"lib/",
7+
"src/"
8+
],
9+
"main": "lib/jest-reporter.js",
10+
"scripts": {
11+
"prepare": "tsc"
12+
},
13+
"license": "MIT",
14+
"dependencies": {
15+
"anser": "^1.4.9"
16+
},
17+
"devDependencies": {
18+
"@jest/console": "^25.5.0",
19+
"@jest/reporters": "^25.5.1",
20+
"@jest/test-result": "^25.5.0",
21+
"husky": "^4.2.5",
22+
"prettier": "^2.0.5",
23+
"pretty-quick": "^2.0.1",
24+
"typescript": "^3.8.3"
25+
},
26+
"peerDependencies": {
27+
"jest": "25.x"
28+
},
29+
"husky": {
30+
"hooks": {
31+
"pre-commit": "pretty-quick --staged"
32+
}
33+
}
34+
}

src/jest-reporter.ts

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
import Anser from "anser";
2+
3+
// Get types from @jest packages.
4+
// These can be `devDependencies` because no JS is emitted and we're currently
5+
// not emitting declarations.
6+
import { ConsoleBuffer } from "@jest/console";
7+
import {
8+
Test,
9+
Reporter,
10+
Config,
11+
Context,
12+
ReporterOnStartOptions,
13+
} from "@jest/reporters";
14+
import {
15+
AssertionResult,
16+
TestResult,
17+
AggregatedResult,
18+
} from "@jest/test-result";
19+
20+
export type CodewarsReporterOptions = {};
21+
22+
export default class CodewarsReporter implements Reporter {
23+
_globalConfig: Config.GlobalConfig;
24+
_options: CodewarsReporterOptions;
25+
_shouldFail: boolean | undefined;
26+
rootDir: string;
27+
constructor(
28+
globalConfig: Config.GlobalConfig,
29+
options: CodewarsReporterOptions
30+
) {
31+
this._globalConfig = globalConfig;
32+
this._options = options;
33+
this.rootDir = globalConfig.rootDir;
34+
}
35+
36+
onRunStart(results: AggregatedResult, options: ReporterOnStartOptions): void {
37+
//
38+
}
39+
40+
onTestStart(test: Test): void {
41+
//
42+
}
43+
44+
onTestResult(
45+
test: Test,
46+
testResult: TestResult,
47+
aggregatedResult: AggregatedResult
48+
): void {
49+
const results = testResult.testResults;
50+
_logSuite(groupTestsBySuites(results));
51+
_showConsoleOutput(testResult.console, this.rootDir);
52+
// Show failure message showing more details like source.
53+
// Maybe this needs to be configurable.
54+
if (!testResult.failureMessage) return;
55+
56+
const failureMsg = testResult.failureMessage.trim();
57+
if (!failureMsg) return;
58+
59+
const msg = escapeLF(ansi2html(failureMsg));
60+
console.log(
61+
`\n<LOG:HTML:Failures><pre class="ansi"><code>${msg}</code></pre>`
62+
);
63+
}
64+
65+
onRunComplete(contexts: Set<Context>, results: AggregatedResult): void {
66+
//
67+
}
68+
69+
getLastError() {
70+
if (this._shouldFail) {
71+
// Force exit code non zero
72+
return new Error("Test Failed");
73+
}
74+
}
75+
}
76+
77+
// https://github.com/sindresorhus/extract-stack
78+
function extractStack(stack: string) {
79+
if (!stack) return "";
80+
const match = stack.match(/(?:\n {4}at .*)+/);
81+
if (!match) return "";
82+
return match[0].slice(1);
83+
}
84+
85+
const escapeLF = (s: string) => s.replace(/\n/g, "<:LF:>");
86+
const escapeHtml = (s: string) =>
87+
s.replace(/[&<>]/g, (c) => (c == "&" ? "&amp;" : c == "<" ? "&lt;" : "&gt;"));
88+
const ansi2html = (s: string) =>
89+
Anser.ansiToHtml(escapeHtml(s), { use_classes: true });
90+
91+
type TestSuiteGroup = {
92+
suites: TestSuiteGroup[];
93+
tests: AssertionResult[];
94+
title: string;
95+
};
96+
97+
// https://github.com/facebook/jest/blob/179b3e8cd9838789254e2a2d63b32a699ca236d3/packages/jest-cli/src/reporters/verbose_reporter.js#L35
98+
// Note that if suites with same names exist, they're merged.
99+
function groupTestsBySuites(testResults: AssertionResult[]) {
100+
const root: TestSuiteGroup = { suites: [], tests: [], title: "" };
101+
for (const testResult of testResults) {
102+
let targetSuite = root;
103+
// Find the target suite for this test, creating nested suites as necessary.
104+
for (const title of testResult.ancestorTitles) {
105+
let matchingSuite = targetSuite.suites.find((s) => s.title === title);
106+
if (!matchingSuite) {
107+
matchingSuite = { suites: [], tests: [], title };
108+
targetSuite.suites.push(matchingSuite);
109+
}
110+
targetSuite = matchingSuite;
111+
}
112+
targetSuite.tests.push(testResult);
113+
}
114+
return root;
115+
}
116+
117+
function _logSuite(suite: TestSuiteGroup) {
118+
const hasTitle = suite.title !== "";
119+
if (hasTitle) console.log(`\n<DESCRIBE::>${suite.title}`);
120+
for (const t of suite.tests) _logTest(t);
121+
for (const s of suite.suites) _logSuite(s);
122+
if (hasTitle) console.log(`\n<COMPLETEDIN::>`);
123+
}
124+
125+
function _logTest(test: AssertionResult) {
126+
console.log(`\n<IT::>${test.title}`);
127+
switch (test.status) {
128+
case "passed":
129+
console.log(`\n<PASSED::>Test Passed`);
130+
break;
131+
case "failed": {
132+
console.log(`\n<FAILED::>Test Failed`);
133+
// We need to separate this because `<FAILED::>` doesn't support HTML mode at the moment.
134+
const msg = escapeLF(
135+
ansi2html(_collectFailureMessages(test.failureMessages).trim())
136+
);
137+
console.log(
138+
`\n<LOG:HTML:Failure><pre class="ansi"><code>${msg}</code></pre>`
139+
);
140+
break;
141+
}
142+
case "pending":
143+
console.log(`\n<LOG::>Test Pending`);
144+
break;
145+
case "todo":
146+
console.log(`\n<LOG::>Test TODO`);
147+
break;
148+
}
149+
const time = test.duration ? test.duration.toFixed(0) : "";
150+
console.log(`\n<COMPLETEDIN::>${time}`);
151+
}
152+
153+
function _collectFailureMessages(messages: string[]) {
154+
const lines: string[] = [];
155+
for (const m of messages) {
156+
lines.push(m.replace(extractStack(m), ""));
157+
}
158+
return lines.join("\n\n");
159+
}
160+
161+
function _showConsoleOutput(cs: ConsoleBuffer | undefined, rootDir: string) {
162+
if (!cs) return;
163+
for (const c of cs) {
164+
const message = c.message;
165+
if (c.type === "error") {
166+
console.error(message);
167+
continue;
168+
}
169+
170+
// Don't prepend location information when output command is used
171+
if (/^<(?:LOG|TAB|PROP|OUT|SWAP|TAG):(?:[A-Z]*):(?:[^>]*)>/.test(message)) {
172+
console.log(message);
173+
} else {
174+
const from = c.origin.replace(rootDir, "").replace(/^\//, "");
175+
console.log(`\n<LOG::console.${c.type} ${from}>${escapeLF(message)}`);
176+
}
177+
}
178+
}

tsconfig.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"compilerOptions": {
3+
"strict": true,
4+
"esModuleInterop": true,
5+
"target": "ES2019",
6+
"module": "commonjs",
7+
"outDir": "./lib",
8+
"rootDir": "./src",
9+
"declaration": false,
10+
"sourceMap": true,
11+
"removeComments": true
12+
}
13+
}

0 commit comments

Comments
 (0)