Skip to content

Commit

Permalink
added proxies to resolve circular dependencies
Browse files Browse the repository at this point in the history
  • Loading branch information
DavertMik committed Dec 27, 2024
1 parent 64d95ff commit 0a27b34
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 102 deletions.
6 changes: 1 addition & 5 deletions lib/actor.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,24 +105,20 @@ module.exports = function (obj = {}) {
}
})

store.actor = actor
container.append({
support: {
[translation.I]: actor,
I: actor,
},
})
})

// store.actor = actor;
// add custom steps from actor
Object.keys(obj).forEach((key) => {
const ms = new MetaStep('I', key)
ms.setContext(actor)
actor[key] = ms.run.bind(ms, obj[key])
})

// store.actor = actor;

return actor
}

Expand Down
189 changes: 92 additions & 97 deletions lib/container.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ let asyncHelperPromise
let container = {
helpers: {},
support: {},
proxySupport: {},
plugins: {},
actor: null,
/**
Expand All @@ -41,19 +42,19 @@ class Container {

container.helpers = createHelpers(config.helpers || {})
container.translation = loadTranslation(config.translation || null, config.vocabularies || [])
container.support = createSupportObjects(config.include || {})
container.proxySupport = createSupportObjects(config.include || {})
container.plugins = createPlugins(config.plugins || {}, opts)

createActor(config.include?.I)
createMocha(config, opts)
createActor()

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

static actor() {
return container.actor
return container.support.I
}

/**
Expand All @@ -79,9 +80,9 @@ class Container {
*/
static support(name) {
if (!name) {
return container.support
return container.proxySupport
}
return container.support[name]
return container.support[name] || container.proxySupport[name]
}

/**
Expand Down Expand Up @@ -126,7 +127,6 @@ class Container {
static append(newContainer) {
const deepMerge = require('./utils').deepMerge
container = deepMerge(container, newContainer)
container.actor = container.support.I
}

/**
Expand Down Expand Up @@ -260,28 +260,6 @@ function requireHelperFromModule(helperName, config, HelperClass) {
}

function createSupportObjects(config) {
const objects = {}

for (const name in config) {
objects[name] = {} // placeholders
}

container.support = objects

function lazyLoad(name) {
let newObj = getSupportObject(config, name)
try {
if (typeof newObj === 'function') {
newObj = newObj()
} else if (newObj._init) {
newObj._init()
}
} catch (err) {
throw new Error(`Initialization failed for ${name}: ${newObj}\n${err.message}\n${err.stack}`)
}
return newObj
}

const asyncWrapper = function (f) {
return function () {
return f.apply(this, arguments).catch((e) => {
Expand All @@ -291,47 +269,92 @@ function createSupportObjects(config) {
}
}

Object.keys(objects).forEach((object) => {
const currentObject = objects[object]
Object.keys(currentObject).forEach((method) => {
const currentMethod = currentObject[method]
if (currentMethod && currentMethod[Symbol.toStringTag] === 'AsyncFunction') {
objects[object][method] = asyncWrapper(currentMethod)
}
})
})
function lazyLoad(name) {
return new Proxy(
{},
{
get(target, prop) {
// load actual name from vocabulary
if (container.translation.name) {
name = container.translation.name
}

if (name === 'I') {
const actor = createActor(config.I)
methodsOfObject(actor)
return actor[prop]
}

if (!container.support[name]) {
// Load object on first access
const supportObject = loadSupportObject(config[name])
container.support[name] = supportObject
try {
if (container.support[name]._init) {
container.support[name]._init()
}
} catch (err) {
throw new Error(
`Initialization failed for ${name}: ${container.support[name]}\n${err.message}\n${err.stack}`,
)
}
}

const currentObject = container.support[name]
let currentValue = currentObject[prop]

if (isFunction(currentValue) || isAsyncFunction(currentValue)) {
const ms = new MetaStep(name, currentValue)
ms.setContext(currentObject)
if (isAsyncFunction(currentValue)) currentValue = asyncWrapper(currentValue)
currentObject[prop] = ms.run.bind(ms, currentValue)
}

return currentValue
},
has(target, prop) {
container.support[name] = container.support[name] || loadSupportObject(config[name])
return prop in container.support[name]
},
ownKeys() {
container.support[name] = container.support[name] || loadSupportObject(config[name])
return Reflect.ownKeys(container.support[name])
},
},
)
}

return new Proxy(
{},
{
has(target, key) {
return key in config
return key in container.support
},
ownKeys() {
return Reflect.ownKeys(config)
return Reflect.ownKeys(container.support)
},
get(target, key) {
// configured but not in support object, yet: load the module
if (key in objects && !(key in target)) {
// load default I
if (key in objects && !(key in config)) {
return (target[key] = objects[key])
}

// load new object
const object = lazyLoad(key)
// check that object is a real object and not an array
if (Object.prototype.toString.call(object) === '[object Object]') {
return (target[key] = Object.assign(objects[key], object))
}
target[key] = object
}
return target[key]
return lazyLoad(key)
},
},
)
}

function createActor(actorPath) {
if (container.support.I) return container.support.I

if (actorPath) {
container.support.I = loadSupportObject(actorPath)
} else {
const actor = require('./actor')
container.support.I = actor()
}

container.support.I = container.support.I

return container.support.I
}

function createPlugins(config, options = {}) {
const plugins = {}

Expand Down Expand Up @@ -360,22 +383,6 @@ function createPlugins(config, options = {}) {
return plugins
}

function createActor() {
const actor = require('./actor')
container.support.I = container.support.I || actor()

container.actor = container.support.I
if (container.translation.I !== 'I') container.support[container.translation.I] = container.actor
}

function getSupportObject(config, name) {
const module = config[name]
if (typeof module === 'string') {
return loadSupportObject(module, name)
}
return module
}

function loadGherkinSteps(paths) {
global.Before = (fn) => event.dispatcher.on(event.test.started, fn)
global.After = (fn) => event.dispatcher.on(event.test.finished, fn)
Expand Down Expand Up @@ -406,38 +413,26 @@ function loadSupportObject(modulePath, supportObjectName) {
if (modulePath.charAt(0) === '.') {
modulePath = path.join(global.codecept_dir, modulePath)
}

try {
const obj = require(modulePath)

if (typeof obj !== 'function' && Object.getPrototypeOf(obj) !== Object.prototype && !Array.isArray(obj)) {
const methods = methodsOfObject(obj)
Object.keys(methods)
.filter((key) => !key.startsWith('_'))
.forEach((key) => {
const currentMethod = methods[key]
if (isFunction(currentMethod) || isAsyncFunction(currentMethod)) {
const ms = new MetaStep(supportObjectName, key)
ms.setContext(methods)
methods[key] = ms.run.bind(ms, currentMethod)
}
})
return methods
// Handle different types of imports
if (typeof obj === 'function') {
// If it's a class (constructor function)
if (obj.prototype && obj.prototype.constructor === obj) {
return new obj()
}
// If it's a regular function
return obj()
}
if (!Array.isArray(obj)) {
Object.keys(obj)
.filter((key) => !key.startsWith('_'))
.forEach((key) => {
const currentMethod = obj[key]
if (isFunction(currentMethod) || isAsyncFunction(currentMethod)) {
const ms = new MetaStep(supportObjectName, key)
ms.setContext(obj)
obj[key] = ms.run.bind(ms, currentMethod)
}
})
// If it's a plain object
if (obj && typeof obj === 'object' && !Array.isArray(obj)) {
return obj
}

return obj
throw new Error(
`Support object "${supportObjectName}" should be an object, class, or function, but got ${typeof obj}`,
)
} catch (err) {
throw new Error(
`Could not include object ${supportObjectName} from module '${modulePath}'\n${err.message}\n${err.stack}`,
Expand Down

0 comments on commit 0a27b34

Please sign in to comment.