Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 36 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"@cucumber/html-formatter": "23.0.0",
"@cucumber/junit-xml-formatter": "0.9.0",
"@cucumber/messages": "32.0.1",
"@cucumber/pretty-formatter": "3.0.0",
"@cucumber/query": "15.0.1",
"@cucumber/tag-expressions": "9.0.0",
"@teppeis/multimaps": "3.0.0",
Expand Down Expand Up @@ -70,6 +71,7 @@
"eslint-plugin-simple-import-sort": "12.1.1",
"globals": "^17.0.0",
"mocha": "^11.0.1",
"node-pty": "^1.2.0-beta.10",
"prettier": "^3.4.2",
"tsx": "^4.19.2",
"type-fest": "^5.3.1",
Expand Down
26 changes: 26 additions & 0 deletions src/reporters/builtin/pretty.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { TestEvent } from 'node:test/reporters'

import { PrettyOptions, PrettyPrinter } from '@cucumber/pretty-formatter'

import { enrichMessages } from '../enrichMessages.js'
import { proxyStream } from '../proxyStream.js'

const options: PrettyOptions = {
summarise: true,
}

export default async function* (events: AsyncIterable<TestEvent>): AsyncGenerator<string> {
const buffer: Array<string> = []
const stream = proxyStream(process.stdout, (content: string) => buffer.push(content))
const printer = new PrettyPrinter({ stream, options })
const envelopes = enrichMessages(events)
for await (const envelope of envelopes) {
printer.update(envelope)
if (buffer.length) {
const togo = buffer.splice(0)
for (const content of togo) {
yield content
}
}
}
}
22 changes: 22 additions & 0 deletions src/reporters/builtin/progress-bar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { TestEvent } from 'node:test/reporters'

import { ProgressBarOptions, ProgressBarPrinter } from '@cucumber/pretty-formatter'

import { enrichMessages } from '../enrichMessages.js'

const options: ProgressBarOptions = {}

export default async function* (events: AsyncIterable<TestEvent>): AsyncGenerator<string> {
const buffer: Array<string> = []
const printer = new ProgressBarPrinter({ stream: process.stdout, options })
const envelopes = enrichMessages(events)
for await (const envelope of envelopes) {
printer.update(envelope)
if (buffer.length) {
const togo = buffer.splice(0)
for (const content of togo) {
yield content
}
}
}
}
26 changes: 26 additions & 0 deletions src/reporters/builtin/progress.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { TestEvent } from 'node:test/reporters'

import { ProgressOptions, ProgressPrinter } from '@cucumber/pretty-formatter'

import { enrichMessages } from '../enrichMessages.js'
import { proxyStream } from '../proxyStream.js'

const options: ProgressOptions = {
summarise: true,
}

export default async function* (events: AsyncIterable<TestEvent>): AsyncGenerator<string> {
const buffer: Array<string> = []
const stream = proxyStream(process.stdout, (content: string) => buffer.push(content))
const printer = new ProgressPrinter({ stream, options })
const envelopes = enrichMessages(events)
for await (const envelope of envelopes) {
printer.update(envelope)
if (buffer.length) {
const togo = buffer.splice(0)
for (const content of togo) {
yield content
}
}
}
}
21 changes: 21 additions & 0 deletions src/reporters/proxyStream.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { WriteStream } from 'node:tty'

/**
* Captures attempts to write to a stream, but otherwise passes through all accessors. Useful for
* exposing the underlying stream for feature detection while still controlling writes.
* @param stream
* @param write
*/
export function proxyStream(stream: WriteStream, write: (chunk: string) => void): WriteStream {
return new Proxy(stream, {
get(target, prop, receiver) {
if (prop === 'write') {
return (chunk: string) => {
write(chunk)
return true
}
}
return Reflect.get(target, prop, receiver)
},
})
}
124 changes: 104 additions & 20 deletions test/integration/reporters.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,26 +31,6 @@ describe('Reporters', () => {
expect(sanitised).to.include(`test at ${path.join('features', 'foo.feature')}:2:3`)
})

it('does not emit messages as diagnostics if no cucumber reporters', async () => {
const harness = await makeTestHarness()
await harness.writeFile(
'features/first.feature',
`Feature:
Scenario:
Given a step
`
)
await harness.writeFile(
'features/steps.js',
`import { Given } from '@cucumber/node'
Given('a step', () => {})
`
)
const [output] = await harness.run('spec')
const sanitised = stripVTControlCharacters(output.trim())
expect(sanitised).not.to.include('@cucumber/messages:')
})

it('provides a useful error for an ambiguous step', async () => {
const harness = await makeTestHarness()
await harness.writeFile(
Expand Down Expand Up @@ -95,6 +75,110 @@ Given('a step', () => {})`
})
})

describe('pretty', () => {
it('outputs the pretty format', async () => {
const harness = await makeTestHarness()
await harness.writeFile(
'features/first.feature',
`Feature:
Scenario:
Given a step
And a step

Scenario:
Given a step
But a step
`
)
await harness.writeFile(
'features/steps.js',
`import { Given } from '@cucumber/node'
Given('a step', () => {})
`
)

const [output] = await harness.run('@cucumber/node/reporters/pretty')
const sanitised = stripVTControlCharacters(output.trim())

expect(sanitised).to.include(`Feature:

Scenario: # features/first.feature:2
βœ” Given a step # features/steps.js:2
βœ” And a step # features/steps.js:2

Scenario: # features/first.feature:6
βœ” Given a step # features/steps.js:2
βœ” But a step # features/steps.js:2

2 scenarios (2 passed)
4 steps (4 passed)`)
})
})

describe('progress', () => {
it('outputs the progress format', async () => {
const harness = await makeTestHarness()
await harness.writeFile(
'features/first.feature',
`Feature:
Scenario:
Given a step
And a step

Scenario:
Given a step
But a step
`
)
await harness.writeFile(
'features/steps.js',
`import { Given } from '@cucumber/node'
Given('a step', () => {})
`
)

const [output] = await harness.run('@cucumber/node/reporters/progress')
const sanitised = stripVTControlCharacters(output.trim())

expect(sanitised).to.include(
'....\n' + '\n' + '2 scenarios (2 passed)\n' + '4 steps (4 passed)\n'
)
})
})

describe('progress-bar', () => {
it('outputs the progress-bar format', async () => {
const harness = await makeTestHarness()
await harness.writeFile(
'features/first.feature',
`Feature:
Scenario:
Given a step
And a step

Scenario:
Given a step
But a step
`
)
await harness.writeFile(
'features/steps.js',
`import { Given } from '@cucumber/node'
Given('a step', () => {})
`
)

const [output] = await harness.run('@cucumber/node/reporters/progress-bar')
const sanitised = stripVTControlCharacters(output.trim())

expect(sanitised).to.include(
'β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ 2/2 scenarios\n' +
'β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ 4/4 steps\n' +
'Done'
)
})
})

describe('junit', () => {
it('outputs a junit xml report', async () => {
const harness = await makeTestHarness()
Expand Down
Loading
Loading