Skip to content

Commit 0a27b34

Browse files
author
DavertMik
committed
added proxies to resolve circular dependencies
1 parent 64d95ff commit 0a27b34

File tree

2 files changed

+93
-102
lines changed

2 files changed

+93
-102
lines changed

lib/actor.js

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -105,24 +105,20 @@ module.exports = function (obj = {}) {
105105
}
106106
})
107107

108-
store.actor = actor
109108
container.append({
110109
support: {
111-
[translation.I]: actor,
112110
I: actor,
113111
},
114112
})
115113
})
116-
114+
// store.actor = actor;
117115
// add custom steps from actor
118116
Object.keys(obj).forEach((key) => {
119117
const ms = new MetaStep('I', key)
120118
ms.setContext(actor)
121119
actor[key] = ms.run.bind(ms, obj[key])
122120
})
123121

124-
// store.actor = actor;
125-
126122
return actor
127123
}
128124

lib/container.js

Lines changed: 92 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ let asyncHelperPromise
1515
let container = {
1616
helpers: {},
1717
support: {},
18+
proxySupport: {},
1819
plugins: {},
1920
actor: null,
2021
/**
@@ -41,19 +42,19 @@ class Container {
4142

4243
container.helpers = createHelpers(config.helpers || {})
4344
container.translation = loadTranslation(config.translation || null, config.vocabularies || [])
44-
container.support = createSupportObjects(config.include || {})
45+
container.proxySupport = createSupportObjects(config.include || {})
4546
container.plugins = createPlugins(config.plugins || {}, opts)
4647

48+
createActor(config.include?.I)
4749
createMocha(config, opts)
48-
createActor()
4950

5051
if (opts && opts.ai) ai.enable(config.ai) // enable AI Assistant
5152
if (config.gherkin) loadGherkinSteps(config.gherkin.steps || [])
5253
if (opts && typeof opts.timeouts === 'boolean') store.timeouts = opts.timeouts
5354
}
5455

5556
static actor() {
56-
return container.actor
57+
return container.support.I
5758
}
5859

5960
/**
@@ -79,9 +80,9 @@ class Container {
7980
*/
8081
static support(name) {
8182
if (!name) {
82-
return container.support
83+
return container.proxySupport
8384
}
84-
return container.support[name]
85+
return container.support[name] || container.proxySupport[name]
8586
}
8687

8788
/**
@@ -126,7 +127,6 @@ class Container {
126127
static append(newContainer) {
127128
const deepMerge = require('./utils').deepMerge
128129
container = deepMerge(container, newContainer)
129-
container.actor = container.support.I
130130
}
131131

132132
/**
@@ -260,28 +260,6 @@ function requireHelperFromModule(helperName, config, HelperClass) {
260260
}
261261

262262
function createSupportObjects(config) {
263-
const objects = {}
264-
265-
for (const name in config) {
266-
objects[name] = {} // placeholders
267-
}
268-
269-
container.support = objects
270-
271-
function lazyLoad(name) {
272-
let newObj = getSupportObject(config, name)
273-
try {
274-
if (typeof newObj === 'function') {
275-
newObj = newObj()
276-
} else if (newObj._init) {
277-
newObj._init()
278-
}
279-
} catch (err) {
280-
throw new Error(`Initialization failed for ${name}: ${newObj}\n${err.message}\n${err.stack}`)
281-
}
282-
return newObj
283-
}
284-
285263
const asyncWrapper = function (f) {
286264
return function () {
287265
return f.apply(this, arguments).catch((e) => {
@@ -291,47 +269,92 @@ function createSupportObjects(config) {
291269
}
292270
}
293271

294-
Object.keys(objects).forEach((object) => {
295-
const currentObject = objects[object]
296-
Object.keys(currentObject).forEach((method) => {
297-
const currentMethod = currentObject[method]
298-
if (currentMethod && currentMethod[Symbol.toStringTag] === 'AsyncFunction') {
299-
objects[object][method] = asyncWrapper(currentMethod)
300-
}
301-
})
302-
})
272+
function lazyLoad(name) {
273+
return new Proxy(
274+
{},
275+
{
276+
get(target, prop) {
277+
// load actual name from vocabulary
278+
if (container.translation.name) {
279+
name = container.translation.name
280+
}
281+
282+
if (name === 'I') {
283+
const actor = createActor(config.I)
284+
methodsOfObject(actor)
285+
return actor[prop]
286+
}
287+
288+
if (!container.support[name]) {
289+
// Load object on first access
290+
const supportObject = loadSupportObject(config[name])
291+
container.support[name] = supportObject
292+
try {
293+
if (container.support[name]._init) {
294+
container.support[name]._init()
295+
}
296+
} catch (err) {
297+
throw new Error(
298+
`Initialization failed for ${name}: ${container.support[name]}\n${err.message}\n${err.stack}`,
299+
)
300+
}
301+
}
302+
303+
const currentObject = container.support[name]
304+
let currentValue = currentObject[prop]
305+
306+
if (isFunction(currentValue) || isAsyncFunction(currentValue)) {
307+
const ms = new MetaStep(name, currentValue)
308+
ms.setContext(currentObject)
309+
if (isAsyncFunction(currentValue)) currentValue = asyncWrapper(currentValue)
310+
currentObject[prop] = ms.run.bind(ms, currentValue)
311+
}
312+
313+
return currentValue
314+
},
315+
has(target, prop) {
316+
container.support[name] = container.support[name] || loadSupportObject(config[name])
317+
return prop in container.support[name]
318+
},
319+
ownKeys() {
320+
container.support[name] = container.support[name] || loadSupportObject(config[name])
321+
return Reflect.ownKeys(container.support[name])
322+
},
323+
},
324+
)
325+
}
303326

304327
return new Proxy(
305328
{},
306329
{
307330
has(target, key) {
308-
return key in config
331+
return key in container.support
309332
},
310333
ownKeys() {
311-
return Reflect.ownKeys(config)
334+
return Reflect.ownKeys(container.support)
312335
},
313336
get(target, key) {
314-
// configured but not in support object, yet: load the module
315-
if (key in objects && !(key in target)) {
316-
// load default I
317-
if (key in objects && !(key in config)) {
318-
return (target[key] = objects[key])
319-
}
320-
321-
// load new object
322-
const object = lazyLoad(key)
323-
// check that object is a real object and not an array
324-
if (Object.prototype.toString.call(object) === '[object Object]') {
325-
return (target[key] = Object.assign(objects[key], object))
326-
}
327-
target[key] = object
328-
}
329-
return target[key]
337+
return lazyLoad(key)
330338
},
331339
},
332340
)
333341
}
334342

343+
function createActor(actorPath) {
344+
if (container.support.I) return container.support.I
345+
346+
if (actorPath) {
347+
container.support.I = loadSupportObject(actorPath)
348+
} else {
349+
const actor = require('./actor')
350+
container.support.I = actor()
351+
}
352+
353+
container.support.I = container.support.I
354+
355+
return container.support.I
356+
}
357+
335358
function createPlugins(config, options = {}) {
336359
const plugins = {}
337360

@@ -360,22 +383,6 @@ function createPlugins(config, options = {}) {
360383
return plugins
361384
}
362385

363-
function createActor() {
364-
const actor = require('./actor')
365-
container.support.I = container.support.I || actor()
366-
367-
container.actor = container.support.I
368-
if (container.translation.I !== 'I') container.support[container.translation.I] = container.actor
369-
}
370-
371-
function getSupportObject(config, name) {
372-
const module = config[name]
373-
if (typeof module === 'string') {
374-
return loadSupportObject(module, name)
375-
}
376-
return module
377-
}
378-
379386
function loadGherkinSteps(paths) {
380387
global.Before = (fn) => event.dispatcher.on(event.test.started, fn)
381388
global.After = (fn) => event.dispatcher.on(event.test.finished, fn)
@@ -406,38 +413,26 @@ function loadSupportObject(modulePath, supportObjectName) {
406413
if (modulePath.charAt(0) === '.') {
407414
modulePath = path.join(global.codecept_dir, modulePath)
408415
}
409-
410416
try {
411417
const obj = require(modulePath)
412418

413-
if (typeof obj !== 'function' && Object.getPrototypeOf(obj) !== Object.prototype && !Array.isArray(obj)) {
414-
const methods = methodsOfObject(obj)
415-
Object.keys(methods)
416-
.filter((key) => !key.startsWith('_'))
417-
.forEach((key) => {
418-
const currentMethod = methods[key]
419-
if (isFunction(currentMethod) || isAsyncFunction(currentMethod)) {
420-
const ms = new MetaStep(supportObjectName, key)
421-
ms.setContext(methods)
422-
methods[key] = ms.run.bind(ms, currentMethod)
423-
}
424-
})
425-
return methods
419+
// Handle different types of imports
420+
if (typeof obj === 'function') {
421+
// If it's a class (constructor function)
422+
if (obj.prototype && obj.prototype.constructor === obj) {
423+
return new obj()
424+
}
425+
// If it's a regular function
426+
return obj()
426427
}
427-
if (!Array.isArray(obj)) {
428-
Object.keys(obj)
429-
.filter((key) => !key.startsWith('_'))
430-
.forEach((key) => {
431-
const currentMethod = obj[key]
432-
if (isFunction(currentMethod) || isAsyncFunction(currentMethod)) {
433-
const ms = new MetaStep(supportObjectName, key)
434-
ms.setContext(obj)
435-
obj[key] = ms.run.bind(ms, currentMethod)
436-
}
437-
})
428+
// If it's a plain object
429+
if (obj && typeof obj === 'object' && !Array.isArray(obj)) {
430+
return obj
438431
}
439432

440-
return obj
433+
throw new Error(
434+
`Support object "${supportObjectName}" should be an object, class, or function, but got ${typeof obj}`,
435+
)
441436
} catch (err) {
442437
throw new Error(
443438
`Could not include object ${supportObjectName} from module '${modulePath}'\n${err.message}\n${err.stack}`,

0 commit comments

Comments
 (0)