diff --git a/lib/actor.js b/lib/actor.js index cf87d8b46..af5351be5 100644 --- a/lib/actor.js +++ b/lib/actor.js @@ -105,15 +105,13 @@ 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) @@ -121,8 +119,6 @@ module.exports = function (obj = {}) { actor[key] = ms.run.bind(ms, obj[key]) }) - // store.actor = actor; - return actor } diff --git a/lib/container.js b/lib/container.js index 24d77868d..13df14c51 100644 --- a/lib/container.js +++ b/lib/container.js @@ -15,6 +15,7 @@ let asyncHelperPromise let container = { helpers: {}, support: {}, + proxySupport: {}, plugins: {}, actor: null, /** @@ -41,11 +42,11 @@ 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 || []) @@ -53,7 +54,7 @@ class Container { } static actor() { - return container.actor + return container.support.I } /** @@ -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] } /** @@ -126,7 +127,6 @@ class Container { static append(newContainer) { const deepMerge = require('./utils').deepMerge container = deepMerge(container, newContainer) - container.actor = container.support.I } /** @@ -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) => { @@ -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 = {} @@ -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) @@ -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}`,