Skip to content

Commit

Permalink
refactor to allow custom formatter plugins
Browse files Browse the repository at this point in the history
  • Loading branch information
davidjgoss committed May 3, 2024
1 parent 358abbb commit 3c93cbf
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 12 deletions.
21 changes: 21 additions & 0 deletions features/custom_formatter.feature
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,27 @@ Feature: custom formatter
<duration-stat>
"""

Scenario: formatter plugins
Given a file named "simple_formatter.js" with:
"""
module.exports = {
type: 'formatter',
formatter({ on, write }) {
on('message', (message) => {
if (message.testRunFinished) {
write('Test run finished!')
}
})
}
}
"""
When I run cucumber-js with `--format ./simple_formatter.js`
Then it fails
And it outputs the text:
"""
Test run finished!
"""

Scenario Outline: supported module formats
Given a file named "features/step_definitions/cucumber_steps.js" with:
"""
Expand Down
14 changes: 4 additions & 10 deletions src/formatter/builder.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import path from 'node:path'
import { EventEmitter } from 'node:events'
import { Writable as WritableStream } from 'node:stream'
import { pathToFileURL } from 'node:url'
import { doesHaveValue, doesNotHaveValue } from '../value_checker'
import { SupportCodeLibrary } from '../support_code_library_builder/types'
import { SnippetInterface } from './step_definition_snippet_builder/snippet_syntax'
Expand All @@ -10,6 +8,7 @@ import StepDefinitionSnippetBuilder from './step_definition_snippet_builder'
import JavascriptSnippetSyntax from './step_definition_snippet_builder/javascript_snippet_syntax'
import getColorFns from './get_color_fns'
import Formatters from './helpers/formatters'
import { importCode } from './import_code'
import Formatter, {
FormatOptions,
IFormatterCleanupFn,
Expand Down Expand Up @@ -105,14 +104,9 @@ const FormatterBuilder = {
descriptor: string,
cwd: string
) {
let normalized: URL | string = descriptor
if (descriptor.startsWith('.')) {
normalized = pathToFileURL(path.resolve(cwd, descriptor))
} else if (descriptor.startsWith('file://')) {
normalized = new URL(descriptor)
}
let CustomClass = await FormatterBuilder.loadFile(normalized)
CustomClass = FormatterBuilder.resolveConstructor(CustomClass)
const CustomClass = FormatterBuilder.resolveConstructor(
await importCode(descriptor, cwd)
)
if (doesHaveValue(CustomClass)) {
return CustomClass
} else {
Expand Down
12 changes: 12 additions & 0 deletions src/formatter/import_code.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { pathToFileURL } from 'node:url'
import path from 'node:path'

export async function importCode(specifier: string, cwd: string): Promise<any> {
let normalized: URL | string = specifier
if (specifier.startsWith('.')) {
normalized = pathToFileURL(path.resolve(cwd, specifier))
} else if (specifier.startsWith('file://')) {
normalized = new URL(specifier)
}
return await import(normalized.toString())
}
28 changes: 28 additions & 0 deletions src/formatter/resolve_class_or_plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { doesNotHaveValue } from '../value_checker'

export function resolveClassOrPlugin(imported: any) {
if (doesNotHaveValue(imported)) {
return null
}
if (typeof imported === 'function') {
return imported
} else if (typeof imported === 'object' && imported.type === 'formatter') {
return imported
} else if (
typeof imported === 'object' &&
typeof imported.default === 'function'
) {
return imported.default
} else if (
typeof imported.default === 'object' &&
imported.default.type === 'formatter'
) {
return imported.default
} else if (
typeof imported.default === 'object' &&
typeof imported.default.default === 'function'
) {
return imported.default.default
}
return null
}
12 changes: 10 additions & 2 deletions src/formatter/resolve_implementation.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import FormatterBuilder from './builder'
import builtin from './builtin'
import { importCode } from './import_code'
import { resolveClassOrPlugin } from './resolve_class_or_plugin'
import { FormatterImplementation } from './index'

export async function resolveImplementation(
Expand All @@ -9,6 +10,13 @@ export async function resolveImplementation(
if (builtin[specifier]) {
return builtin[specifier]
} else {
return await FormatterBuilder.loadCustomClass('formatter', specifier, cwd)
const imported = await importCode(specifier, cwd)
const resolved = resolveClassOrPlugin(imported)
if (!resolved) {
throw new Error(
`Custom formatter (${specifier}) does not export a function/class`
)
}
return resolved
}
}

0 comments on commit 3c93cbf

Please sign in to comment.