diff --git a/bin/cli.js b/bin/cli.js index 0e33360..35d8b59 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -3,7 +3,7 @@ const program = require('commander'); const orpcCoverage = require('../build').default; const { parseOpenRPCDocument } = require('@open-rpc/schema-utils-js'); -const getMethodsArray = (input) => { +const getArrayFromCommaSeparated = (input) => { if (input && input.split(',').length > 0) { return input.split(','); } else { @@ -15,10 +15,10 @@ program .version(require('./get-version')) .usage('[options]') .option('-s, --schema [schema]', 'JSON string or a Path/Url pointing to an open rpc schema') - .option('-r, --reporter ', 'Use the specified reporter [console] [json] [empty]') + .option('-r, --reporters ', 'Use the specified reporter [console] [json] [empty]. Can be a comma separated list of reporters.') .option('-t, --transport ', 'Use the specified transport [http]') - .option('--skip ', 'Methods to skip') - .option('--only ', 'Methods to only run') + .option('--skip ', 'Methods to skip. Comma separated list of method names') + .option('--only ', 'Methods to only run. Comma separated list of method names') .action(async (options) => { let schema; try { @@ -32,9 +32,9 @@ program await orpcCoverage({ openrpcDocument: schema, transport: options.transport, - reporter: options.reporter, - skip: getMethodsArray(options.skip), - only: getMethodsArray(options.only), + reporters: getArrayFromCommaSeparated(options.reporters), + skip: getArrayFromCommaSeparated(options.skip), + only: getArrayFromCommaSeparated(options.only), }); } catch (e) { console.error(e); diff --git a/src/coverage.test.ts b/src/coverage.test.ts index 1499bd5..f55fe3b 100644 --- a/src/coverage.test.ts +++ b/src/coverage.test.ts @@ -1,6 +1,7 @@ import coverage, { ExampleCall, IOptions } from "./coverage"; import { OpenrpcDocument } from "@open-rpc/meta-schema"; import EmptyReporter from "./reporters/emptyReporter"; +import ConsoleReporter from "./reporters/console"; const mockSchema = { openrpc: "1.0.0", @@ -127,7 +128,7 @@ describe("coverage", () => { } const transport = () => Promise.resolve(); coverage({ - reporter: new CustomReporter(), + reporters: [new CustomReporter()], transport, openrpcDocument: mockSchema, skip: [], @@ -148,7 +149,7 @@ describe("coverage", () => { return { result: true }; }; coverage({ - reporter: new CustomReporter(), + reporters: [new CustomReporter()], transport, openrpcDocument: mockSchema, skip: [], @@ -168,7 +169,7 @@ describe("coverage", () => { const transport = () => Promise.resolve({}); const openrpcDocument = mockSchema; const options = { - reporter, + reporters: [reporter], transport, openrpcDocument, skip: ['foo', 'bar', 'baz'], @@ -189,14 +190,14 @@ describe("coverage", () => { const openrpcDocument = {...mockSchema}; openrpcDocument.servers = undefined; const options = { - reporter, + reporters: [reporter], transport, openrpcDocument, skip: [], only: ['baz'], }; - await expect(coverage(options)).resolves.toBeUndefined(); + await expect(coverage(options)).resolves.toBeDefined(); }); }); describe("transport", () => { @@ -206,7 +207,7 @@ describe("coverage", () => { return Promise.resolve({}); }; coverage({ - reporter: new EmptyReporter(), + reporters: [new EmptyReporter()], transport, openrpcDocument: mockSchema, skip: [], @@ -222,7 +223,7 @@ describe("coverage", () => { const transport = () => Promise.resolve({}); const openrpcDocument = mockSchema; const options = { - reporter, + reporters: [reporter], transport, openrpcDocument, skip: [], @@ -244,7 +245,7 @@ describe("coverage", () => { const transport = () => Promise.resolve({}); const openrpcDocument = mockSchema; const options = { - reporter, + reporters: [reporter], transport, openrpcDocument, skip: [], @@ -254,5 +255,40 @@ describe("coverage", () => { await coverage(options); expect(spy).toHaveBeenCalledTimes(12); }); + it("can handle multiple reporters", async () => { + const reporter = new EmptyReporter(); + const reporter2 = new EmptyReporter(); + const transport = () => Promise.resolve({}); + const openrpcDocument = mockSchema; + + const onBeginSpy = jest.spyOn(reporter, "onBegin"); + const onTestBeginSpy = jest.spyOn(reporter, "onTestBegin"); + const onTestEndSpy = jest.spyOn(reporter, "onTestEnd"); + const onEndSpy = jest.spyOn(reporter, "onEnd"); + + const onBeginSpy2 = jest.spyOn(reporter2, "onBegin"); + const onTestBeginSpy2 = jest.spyOn(reporter2, "onTestBegin"); + const onTestEndSpy2 = jest.spyOn(reporter2, "onTestEnd"); + const onEndSpy2 = jest.spyOn(reporter2, "onEnd"); + + const options = { + reporters: [reporter, reporter2], + transport, + openrpcDocument, + skip: [], + only: [], + }; + await coverage(options); + + expect(onBeginSpy).toHaveBeenCalledTimes(1); + expect(onTestBeginSpy).toHaveBeenCalledTimes(12); + expect(onTestEndSpy).toHaveBeenCalledTimes(12); + expect(onEndSpy).toHaveBeenCalledTimes(1); + + expect(onBeginSpy2).toHaveBeenCalledTimes(1); + expect(onTestBeginSpy2).toHaveBeenCalledTimes(12); + expect(onTestEndSpy2).toHaveBeenCalledTimes(12); + expect(onEndSpy2).toHaveBeenCalledTimes(1); + }) }); }); diff --git a/src/coverage.ts b/src/coverage.ts index fc5007a..1d0e979 100644 --- a/src/coverage.ts +++ b/src/coverage.ts @@ -22,7 +22,7 @@ export interface IOptions { skip: string[]; only: string[]; transport(url: string, method: string, params: any[]): PromiseLike; - reporter: Reporter; + reporters: Reporter[]; } export interface ExampleCall { @@ -104,10 +104,14 @@ export default async (options: IOptions) => { }); }); - options.reporter.onBegin(options, exampleCalls); + for (const reporter of options.reporters) { + reporter.onBegin(options, exampleCalls); + } for (const exampleCall of exampleCalls) { - options.reporter.onTestBegin(options, exampleCall); + for (const reporter of options.reporters) { + reporter.onTestBegin(options, exampleCall); + } try { const callResult = await options.transport( exampleCall.url, @@ -135,8 +139,13 @@ export default async (options: IOptions) => { exampleCall.valid = false; exampleCall.requestError = e; } - options.reporter.onTestEnd(options, exampleCall); + for (const reporter of options.reporters) { + reporter.onTestEnd(options, exampleCall); + } } - return options.reporter.onEnd(options, exampleCalls); + for (const reporter of options.reporters) { + reporter.onEnd(options, exampleCalls); + } + return exampleCalls; }; diff --git a/src/index.ts b/src/index.ts index cff8505..980e4a0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,32 +4,34 @@ import { OpenrpcDocument } from "@open-rpc/meta-schema"; import { ITransport } from "./transports/ITransport"; import ConsoleReporter from "./reporters/console"; import JsonReporter from "./reporters/json"; -import RawReporter from "./reporters/raw"; import EmptyReporter from "./reporters/emptyReporter"; const reporters = { console: ConsoleReporter, json: JsonReporter, - raw: RawReporter, empty: EmptyReporter, }; const transports = { http: HTTPTransport, }; - +type ReporterString = "console" | "json" | "empty"; interface IOptions { openrpcDocument: OpenrpcDocument; skip?: string[]; only?: string[]; - reporter: "console" | "json" | "raw" | "empty"; + reporters: ReporterString[]; transport: "http" | ITransport; } export default async (options: IOptions) => { const transport = typeof options.transport === "function" ? options.transport : transports[options.transport || "http"]; + let reporterInstances = options.reporters.map((reporter) => new reporters[reporter]); + if (reporterInstances.length === 0) { + reporterInstances = [new reporters["console"]()]; + } return coverage({ - reporter: new reporters[options.reporter || "console"], + reporters: reporterInstances, openrpcDocument: options.openrpcDocument, skip: options.skip || [], only: options.only || [], diff --git a/src/reporters/raw.ts b/src/reporters/raw.ts deleted file mode 100644 index 437d5c9..0000000 --- a/src/reporters/raw.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { ExampleCall, IOptions } from '../coverage'; -import Reporter from './reporter'; - -class RawReporter implements Reporter { - onBegin(options: IOptions, exampleCalls: ExampleCall[]) {} - onTestBegin(options: IOptions, exampleCall: ExampleCall) {} - - onTestEnd(options: IOptions, exampleCall: ExampleCall) {} - - onEnd(options: IOptions, exampleCalls: ExampleCall[]) { - return exampleCalls; - } -} - -export default RawReporter;