Skip to content

Commit bf6fc8f

Browse files
committed
fix more tests
1 parent 7e5373d commit bf6fc8f

File tree

7 files changed

+147
-80
lines changed

7 files changed

+147
-80
lines changed

lib/command/gherkin/init.js

Lines changed: 31 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@ import { mkdirp } from 'mkdirp'
33

44
import output from '../../output.js'
55
import { fileExists } from '../../utils.js'
6-
import {
7-
getConfig, getTestRoot, updateConfig, safeFileWrite, findConfigFile,
8-
} from '../utils.js'
6+
import { getConfig, getTestRoot, updateConfig, safeFileWrite, findConfigFile } from '../utils.js'
97

108
const featureFile = `Feature: Business rules
119
In order to achieve my goals
@@ -14,68 +12,68 @@ const featureFile = `Feature: Business rules
1412
1513
Scenario: do something
1614
Given I have a defined step
17-
`;
15+
`
1816

1917
const stepsFile = `const { I } = inject();
2018
// Add in your custom step files
2119
2220
Given('I have a defined step', () => {
2321
// TODO: replace with your own step
2422
});
25-
`;
23+
`
2624

2725
export default async function (genPath) {
28-
const testsPath = getTestRoot(genPath);
29-
const configFile = findConfigFile(testsPath);
26+
const testsPath = getTestRoot(genPath)
27+
const configFile = findConfigFile(testsPath)
3028

3129
if (!configFile) {
32-
output.error(
33-
"Can't initialize Gherkin. This command must be run in an already initialized project.",
34-
);
35-
process.exit(1);
30+
output.error("Can't initialize Gherkin. This command must be run in an already initialized project.")
31+
process.exit(1)
3632
}
3733

38-
const config = await getConfig(testsPath);
39-
const extension = path.extname(configFile).substring(1);
34+
const config = await getConfig(testsPath)
35+
const extension = path.extname(configFile).substring(1)
4036

41-
output.print('Initializing Gherkin (Cucumber BDD) for CodeceptJS');
42-
output.print('--------------------------');
37+
output.print('Initializing Gherkin (Cucumber BDD) for CodeceptJS')
38+
output.print('--------------------------')
4339

4440
if (config.gherkin && config.gherkin.steps) {
45-
output.error('Gherkin is already initialized in this project. See `gherkin` section in the config');
46-
process.exit(1);
41+
output.error('Gherkin is already initialized in this project. See `gherkin` section in the config')
42+
process.exit(1)
4743
}
4844

49-
let dir;
50-
dir = path.join(testsPath, 'features');
45+
let dir
46+
dir = path.join(testsPath, 'features')
5147
if (!fileExists(dir)) {
52-
mkdirp.sync(dir);
53-
output.success(`Created ${dir}, place your *.feature files in it`);
48+
mkdirp.sync(dir)
49+
// Use relative path for output
50+
const relativeDir = path.relative(process.cwd(), dir)
51+
output.success(`Created ${relativeDir}, place your *.feature files in it`)
5452
}
5553

5654
if (safeFileWrite(path.join(dir, 'basic.feature'), featureFile)) {
57-
output.success('Created sample feature file: features/basic.feature');
55+
output.success('Created sample feature file: features/basic.feature')
5856
}
5957

60-
dir = path.join(testsPath, 'step_definitions');
58+
dir = path.join(testsPath, 'step_definitions')
6159
if (!fileExists(dir)) {
62-
mkdirp.sync(dir);
63-
output.success(`Created ${dir}, place step definitions into it`);
60+
mkdirp.sync(dir)
61+
// Use relative path for output
62+
const relativeDir = path.relative(process.cwd(), dir)
63+
output.success(`Created ${relativeDir}, place step definitions into it`)
6464
}
6565

6666
if (safeFileWrite(path.join(dir, `steps.${extension}`), stepsFile)) {
67-
output.success(
68-
`Created sample steps file: step_definitions/steps.${extension}`,
69-
);
67+
output.success(`Created sample steps file: step_definitions/steps.${extension}`)
7068
}
7169

7270
config.gherkin = {
7371
features: './features/*.feature',
7472
steps: [`./step_definitions/steps.${extension}`],
75-
};
73+
}
7674

77-
updateConfig(testsPath, config, extension);
75+
updateConfig(testsPath, config, extension)
7876

79-
output.success('Gherkin setup is done.');
80-
output.success('Start writing feature files and implement corresponding steps.');
81-
};
77+
output.success('Gherkin setup is done.')
78+
output.success('Start writing feature files and implement corresponding steps.')
79+
}

lib/config.js

Lines changed: 50 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -138,29 +138,66 @@ async function loadConfigFile(configFile) {
138138
const require = createRequire(import.meta.url)
139139
const extensionName = path.extname(configFile)
140140

141-
if (extensionName === '.ts') {
142-
try {
143-
require('ts-node/register')
144-
} catch (err) {
145-
console.log('ts-node package is required to parse codecept.conf.ts config correctly')
146-
}
147-
}
148-
149141
// .conf.js config file
150142
if (extensionName === '.js' || extensionName === '.ts' || extensionName === '.cjs') {
151143
let configModule
152144
try {
153-
// Try ESM import first
154-
configModule = await import(configFile)
145+
// For .ts files, try to compile and load as JavaScript
146+
if (extensionName === '.ts') {
147+
try {
148+
// Try to load ts-node and compile the file
149+
const { transpile } = require('typescript')
150+
const tsContent = fs.readFileSync(configFile, 'utf8')
151+
152+
// Transpile TypeScript to JavaScript with ES module output
153+
const jsContent = transpile(tsContent, {
154+
module: 99, // ModuleKind.ESNext
155+
target: 99, // ScriptTarget.ESNext
156+
esModuleInterop: true,
157+
allowSyntheticDefaultImports: true,
158+
})
159+
160+
// Create a temporary JS file with .mjs extension to force ES module treatment
161+
const tempJsFile = configFile.replace('.ts', '.temp.mjs')
162+
fs.writeFileSync(tempJsFile, jsContent)
163+
164+
try {
165+
configModule = await import(tempJsFile)
166+
// Clean up temp file
167+
fs.unlinkSync(tempJsFile)
168+
} catch (err) {
169+
// Clean up temp file even on error
170+
if (fs.existsSync(tempJsFile)) {
171+
fs.unlinkSync(tempJsFile)
172+
}
173+
throw err
174+
}
175+
} catch (tsError) {
176+
// If TypeScript compilation fails, fallback to ts-node
177+
try {
178+
require('ts-node/register')
179+
configModule = require(configFile)
180+
} catch (tsNodeError) {
181+
throw new Error(`Failed to load TypeScript config: ${tsError.message}`)
182+
}
183+
}
184+
} else {
185+
// Try ESM import first for JS files
186+
configModule = await import(configFile)
187+
}
155188
} catch (importError) {
156189
try {
157-
// Fall back to CommonJS require
158-
configModule = require(configFile)
190+
// Fall back to CommonJS require for .js/.cjs files
191+
if (extensionName !== '.ts') {
192+
configModule = require(configFile)
193+
} else {
194+
throw importError
195+
}
159196
} catch (requireError) {
160197
throw new Error(`Failed to load config file ${configFile}: ${importError.message}`)
161198
}
162199
}
163-
200+
164201
const rawConfig = configModule.config || configModule.default?.config || configModule
165202

166203
// Process helpers to extract imported classes

lib/mocha/asyncWrapper.js

Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import assertThrown from '../assert/throws.js'
55
import { ucfirst, isAsyncFunction } from '../utils.js'
66
import { getInjectedArguments } from './inject.js'
77
import { fireHook } from './hooks.js'
8+
import Config from '../config.js'
89

910
const injectHook = function (inject, suite) {
1011
// Run the hook body inside recorder queue to ensure async parts complete before returning
@@ -59,6 +60,7 @@ function test(test) {
5960
const doneFn = makeDoneCallableOnce(done)
6061
let testPassed = false
6162
let testFailed = false
63+
let testAfterEmitted = false
6264

6365
// Ensure recorder is running so any steps added inside test function are executed
6466
recorder.startUnlessRunning()
@@ -82,7 +84,10 @@ function test(test) {
8284
// Add test.after emission after event listeners have added their operations
8385
process.nextTick(() => {
8486
recorder.add('fire test.after', () => {
85-
event.emit(event.test.after, test)
87+
if (!testAfterEmitted) {
88+
testAfterEmitted = true
89+
event.emit(event.test.after, test)
90+
}
8691
})
8792
})
8893
recorder.add(doneFn)
@@ -95,11 +100,12 @@ function test(test) {
95100
test.err = err
96101
event.emit(event.test.failed, test, err)
97102
event.emit(event.test.finished, test)
98-
// Add test.after emission after event listeners have added their operations
99-
setImmediate(() => {
100-
recorder.add('fire test.after for failed test', () => {
103+
// Add test.after emission immediately after helper events
104+
recorder.add('fire test.after for failed test', () => {
105+
if (!testAfterEmitted) {
106+
testAfterEmitted = true
101107
event.emit(event.test.after, test)
102-
})
108+
}
103109
})
104110
recorder.add(() => doneFn(err))
105111
})
@@ -127,7 +133,10 @@ function test(test) {
127133
// Add test.after to the queue after event listeners have added their operations
128134
process.nextTick(() => {
129135
recorder.add('fire test.after', () => {
130-
event.emit(event.test.after, test)
136+
if (!testAfterEmitted) {
137+
testAfterEmitted = true
138+
event.emit(event.test.after, test)
139+
}
131140
})
132141
})
133142
})
@@ -152,7 +161,10 @@ function test(test) {
152161
// Add test.after to the queue after event listeners have added their operations
153162
process.nextTick(() => {
154163
recorder.add('fire test.after', () => {
155-
event.emit(event.test.after, test)
164+
if (!testAfterEmitted) {
165+
testAfterEmitted = true
166+
event.emit(event.test.after, test)
167+
}
156168
})
157169
})
158170
})
@@ -206,7 +218,18 @@ function injected(fn, suite, hookName) {
206218
}
207219

208220
const opts = suite.opts || {}
209-
const retries = opts[`retry${ucfirst(hookName)}`] || 0
221+
let retries = opts[`retry${ucfirst(hookName)}`] || 0
222+
223+
// If no retries set and this is a hook, check the global retry config directly
224+
if (retries === 0 && (hookName === 'beforeSuite' || hookName === 'afterSuite' || hookName === 'before' || hookName === 'after')) {
225+
const retryConfig = Config.get('retry')
226+
if (retryConfig && typeof retryConfig === 'object' && !Array.isArray(retryConfig)) {
227+
const hookRetryKey = ucfirst(hookName)
228+
if (retryConfig[hookRetryKey]) {
229+
retries = retryConfig[hookRetryKey]
230+
}
231+
}
232+
}
210233

211234
const currentTest = hookName === 'before' || hookName === 'after' ? suite?.ctx?.currentTest : null
212235

@@ -275,19 +298,10 @@ function teardown(suite) {
275298
}
276299

277300
function suiteSetup(suite) {
278-
// Execute immediately to ensure config is applied before tests run
279-
;(async () => {
301+
return () => {
280302
recorder.startUnlessRunning()
281-
const suiteModule = await import('./suite.js')
282-
const { enhanceMochaSuite } = suiteModule.default || suiteModule
283-
const enhancedSuite = enhanceMochaSuite(suite)
284-
event.emit(event.suite.before, enhancedSuite)
285-
})().catch(err => {
286-
console.error('Suite setup error:', err)
287-
})
288-
289-
// Return empty function for Mocha hook compatibility
290-
return () => {}
303+
event.emit(event.suite.before, suite)
304+
}
291305
}
292306

293307
function suiteTeardown(suite) {

lib/mocha/gherkin.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ const gherkinParser = (text, file) => {
4343

4444
suite.beforeEach('codeceptjs.before', () => setup(suite))
4545
suite.afterEach('codeceptjs.after', () => teardown(suite))
46-
suite.beforeAll('codeceptjs.beforeSuite', () => suiteSetup(suite))
46+
suite.beforeAll('codeceptjs.beforeSuite', () => suiteSetup(suite)())
4747
suite.afterAll('codeceptjs.afterSuite', () => suiteTeardown(suite))
4848

4949
const runSteps = async steps => {
@@ -201,7 +201,7 @@ function getTranslation(language) {
201201
// Translations not loaded yet, return null (will use default)
202202
return null
203203
}
204-
204+
205205
const translationKeys = Object.keys(translations)
206206
for (const availableTranslation of translationKeys) {
207207
if (!language) {

lib/mocha/ui.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ export default function (suite) {
104104
suite.beforeEach('codeceptjs.before', () => setup(suite))
105105
afterEachHooks.push(['finalize codeceptjs', () => teardown(suite)])
106106

107-
suite.beforeAll('codeceptjs.beforeSuite', () => suiteSetup(suite))
107+
suite.beforeAll('codeceptjs.beforeSuite', () => suiteSetup(suite)())
108108
afterAllHooks.push(['codeceptjs.afterSuite', () => suiteTeardown(suite)])
109109

110110
return new FeatureConfig(suite)
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"compilerOptions": {
3+
"target": "es2020",
4+
"module": "esnext",
5+
"moduleResolution": "node",
6+
"esModuleInterop": true,
7+
"allowSyntheticDefaultImports": true,
8+
"skipLibCheck": true,
9+
"declaration": false
10+
},
11+
"ts-node": {
12+
"esm": true
13+
}
14+
}

test/runner/gherkin_test.js

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import chai from 'chai';
2-
chai.should();
3-
import assert from 'assert';
4-
import path from 'path';
5-
import fs from 'fs';
6-
import { exec } from 'child_process';
7-
import { fileURLToPath } from 'url';
8-
const __filename = fileURLToPath(import.meta.url);
9-
const __dirname = path.dirname(__filename);
1+
import chai from 'chai'
2+
chai.should()
3+
import assert from 'assert'
4+
import path from 'path'
5+
import fs from 'fs'
6+
import { exec } from 'child_process'
7+
import { fileURLToPath } from 'url'
8+
const __filename = fileURLToPath(import.meta.url)
9+
const __dirname = path.dirname(__filename)
1010

1111
const runner = path.join(__dirname, '/../../bin/codecept.js')
1212
const codecept_dir = path.join(__dirname, '/../data/sandbox/configs/gherkin/')
@@ -58,13 +58,17 @@ describe('gherkin bdd commands', () => {
5858
it(`prepare CodeceptJS to run feature files (codecept.conf.${extension})`, done => {
5959
exec(`${runner} gherkin:init ${codecept_dir_test}`, (err, stdout) => {
6060
let dir = path.join(codecept_dir_test, 'features')
61+
// Convert to relative path for output comparison
62+
let relativeDir = path.relative(process.cwd(), dir)
6163

6264
stdout.should.include('Initializing Gherkin (Cucumber BDD) for CodeceptJS')
63-
stdout.should.include(`Created ${dir}, place your *.feature files in it`)
65+
stdout.should.include(`Created ${relativeDir}, place your *.feature files in it`)
6466
stdout.should.include('Created sample feature file: features/basic.feature')
6567

6668
dir = path.join(codecept_dir_test, 'step_definitions')
67-
stdout.should.include(`Created ${dir}, place step definitions into it`)
69+
// Convert to relative path for output comparison
70+
relativeDir = path.relative(process.cwd(), dir)
71+
stdout.should.include(`Created ${relativeDir}, place step definitions into it`)
6872
stdout.should.include(`Created sample steps file: step_definitions/steps.${extension}`)
6973
assert(!err)
7074

0 commit comments

Comments
 (0)