Repository: Codeception/CodeceptJS
Pull request #1800 (2019-07-24 18:17:44)

feat(ts-typing): add definition for internal objects and inject

codacy-bot: 
![Codacy](https://app.codacy.com/assets/images/favicon.png) Here is an overview of what got changed by this pull request:


```diff

Clones added
============
- test/runner/list_test.js  3
         
```


See the complete overview on [Codacy](https://app.codacy.com/app/DavertMik/CodeceptJS/pullRequest?prid=3906441&bid=13589700)
        
codacy-bot: 
![Codacy](https://app.codacy.com/assets/images/favicon.png) Here is an overview of what got changed by this pull request:


```diff

Clones added
============
- test/runner/list_test.js  2
         
```


See the complete overview on [Codacy](https://app.codacy.com/app/DavertMik/CodeceptJS/pullRequest?prid=3906441&bid=13589700)
        
codacy-bot: 
![Codacy](https://app.codacy.com/assets/images/favicon.png) Here is an overview of what got changed by this pull request:


```diff

Complexity increasing per file
==============================
- test/data/sandbox/support/second_page.js  1
         

Clones added
============
- test/runner/list_test.js  6
         
```


See the complete overview on [Codacy](https://app.codacy.com/app/DavertMik/CodeceptJS/pullRequest?prid=3906441&bid=13589700)
        
codacy-bot: 
![Codacy](https://app.codacy.com/assets/images/favicon.png) Here is an overview of what got changed by this pull request:


```diff

Complexity increasing per file
==============================
- test/data/sandbox/support/second_page.js  1
         

Clones added
============
- test/runner/list_test.js  6
         
```


See the complete overview on [Codacy](https://app.codacy.com/app/DavertMik/CodeceptJS/pullRequest?prid=3906441&bid=13589700)
        
codacy-bot: 
![Codacy](https://app.codacy.com/assets/images/favicon.png) Here is an overview of what got changed by this pull request:


```diff

Complexity increasing per file
==============================
- test/data/sandbox/support/second_page.js  1
         

Clones added
============
- test/runner/list_test.js  7
         
```


See the complete overview on [Codacy](https://app.codacy.com/app/DavertMik/CodeceptJS/pullRequest?prid=3906441&bid=13589700)
        
DavertMik: Thanks!

Patch:
diff --git a/lib/command/definitions.js b/lib/command/definitions.js
index 380d19ed..72db5e58 100644
--- a/lib/command/definitions.js
+++ b/lib/command/definitions.js
@@ -9,7 +9,7 @@ const fs = require('fs');
 const path = require('path');
 
 const template = `
-type ICodeceptCallback = (i: CodeceptJS.{{I}}{{callbackParams}}) => void;
+type ICodeceptCallback = (i?: CodeceptJS.{{I}}, current?:any{{callbackParams}}) => void;
 
 declare class FeatureConfig {
   retry(times: number): FeatureConfig
@@ -41,6 +41,134 @@ interface ILocator {
 
 type LocatorOrString = string | ILocator | Locator;
 
+declare class Container {
+  create(config: Object, opts: Object): void
+  plugins(name?: string): Object
+  support(name?: string): Object
+  helpers(name?: string): Object
+  translation(): Object
+  mocha(): Object
+  append(newContainer: Object): void
+  clear(newHelpers: Object, newSupport: Object, newPlugins: Object): void
+}
+
+declare class RecorderSession {
+  running: boolean
+    start(name: string): void
+    restore(name: string): void
+    catch(fn: CallableFunction): void
+}
+
+declare class Recorder {
+  retries: Object[]
+  start(): void
+  isRunning(): boolean
+  startUnlessRunning(): void
+  errHandler(fn: CallableFunction): void
+  reset(): void
+  session: RecorderSession
+  add(taskName, fn?: CallableFunction, force?: boolean, retry?: boolean): Promise<any>
+  retry(opts: Object): Promise<any>
+  catch(customErrFn: CallableFunction): Promise<any>
+  catchWithoutStop(customErrFn: CallableFunction ): Promise<any>
+  throw(err: Error): Promise<any>
+  saveFirstAsyncError(err: Error): void
+  getAsyncErr(): Promise<Error>
+  cleanAsyncErr(): void
+  stop():void
+  promise(): Promise<any>
+  scheduled(): string[]
+  toString(): string
+  add(hookName: string, fn: CallableFunction, force?: boolean): void
+  catch(customErrFn: CallableFunction)
+}
+
+declare class CodeceptJSEvent {
+  dispatcher: EventEmitter
+  test: {
+    started: string
+    before: string
+    after: string
+    passed: string
+    failed: string
+    finished: string
+  }
+  suite: {
+    before: string,
+    after: string,
+  }
+  hook: {
+    started: string,
+    passed: string,
+  }
+  step: {
+    before: string,
+    after: string,
+    started: string,
+    passed: string,
+    failed: string,
+    finished: string,
+  }
+  all: {
+    before: string,
+    after: string,
+    result: string,
+  }
+  multiple: {
+    before: string,
+    after: string,
+  }
+  emit(event: string, param: string)
+  cleanDispatcher(): void
+}
+
+declare class Output {
+  colors: any
+  styles: {
+    error: any,
+    success: any,
+    scenario: any,
+    basic: any,
+    debug: any,
+    log: any,
+  }
+
+  print(msg: string): void
+  stepShift: number
+  level(level: number): number
+  process(process: string): string
+  debug(msg: string): void
+  log(msg: string): void
+  error(msg: string): void
+  success(msg: string): void
+  plugin(name: string, msg: string): void
+  step(step: any): void
+  suite: {
+    started: Function
+  }
+  test: {
+    started(test: string): void
+    passed(test: string): void
+    failed(test: string): void
+    skipped(test: string): void
+  }
+  scenario: {
+    started(test: string): void
+    passed(test: string): void
+    failed(test: string): void
+  }
+  say(message: string, color?: string): void
+  result(passed: number, failed: number, skipped: number, duration: string): void
+}
+
+declare class Config {
+  create(newConfig: Object): Object
+  load(configFile: string): Config
+  get(key: string, val: any): any
+  append(additionalConfig: Object): Object
+  reset(): Object
+}
+
 declare class Helper {
   /** Abstract method to provide required config options */
   static _config(): any;
@@ -129,7 +257,9 @@ declare function BeforeSuite(callback: ICodeceptCallback): void;
 declare function After(callback: ICodeceptCallback): void;
 declare function AfterSuite(callback: ICodeceptCallback): void;
 
-declare function inject(): { [key: string]: any };
+declare function inject(): {
+{{injectPageObjects}}
+};
 declare function locate(selector: LocatorOrString): Locator;
 declare function within(selector: LocatorOrString, callback: Function): Promise<any>;
 declare function session(selector: LocatorOrString, callback: Function): Promise<any>;
@@ -140,6 +270,12 @@ declare function secret(secret: any): string;
 declare const codeceptjs: any;
 
 declare namespace CodeceptJS {
+  export const container: Container
+  export const recorder: Recorder
+  export const event: CodeceptJSEvent
+  export const output: Output
+  export const config: Config
+
   export interface {{I}} {
 {{methods}}
   }
@@ -151,6 +287,8 @@ declare module "codeceptjs" {
 }
 `;
 
+const injectSupportTemplate = '  {{name}}: CodeceptJS.{{name}}';
+
 const pageObjectTemplate = `
   export interface {{name}} {
 {{methods}}
@@ -181,20 +319,26 @@ module.exports = function (genPath, options) {
 
   const supports = container.support(); // return all support objects
   const exportPageObjects = [];
+  const injectPageObjects = [];
   const callbackParams = [];
   // See #1795 and #1799, there's no obvious way to use for-in with Proxies
   Reflect.ownKeys(supports).forEach((name) => {
-    if (name === 'I') return;
-    callbackParams.push(`${String(name)}:CodeceptJS.${String(name)}`);
+    if (name === 'I') {
+      injectPageObjects.push(injectSupportTemplate.replace(/{{name}}/g, String(name)));
+      return;
+    }
+    callbackParams.push(`${String(name)}?:CodeceptJS.${String(name)}`);
     const pageObject = supports[name];
     const pageMethods = addAllMethodsInObject(pageObject, {}, []);
     let pageObjectExport = pageObjectTemplate.replace('{{methods}}', pageMethods.join(''));
     pageObjectExport = pageObjectExport.replace('{{name}}', String(name));
+    injectPageObjects.push(injectSupportTemplate.replace(/{{name}}/g, String(name)));
     exportPageObjects.push(pageObjectExport);
   });
 
   let definitionsTemplate = template.replace('{{methods}}', methods.join(''));
   definitionsTemplate = definitionsTemplate.replace('{{exportPageObjects}}', exportPageObjects.join('\n'));
+  definitionsTemplate = definitionsTemplate.replace('{{injectPageObjects}}', injectPageObjects.join('\n'));
   definitionsTemplate = definitionsTemplate.replace('{{callbackParams}}', `, ${[...callbackParams, '...args: any'].join(', ')}`);
   if (translations) {
     definitionsTemplate = definitionsTemplate.replace(/\{\{I\}\}/g, translations.I);


Test patch:
diff --git a/test/data/sandbox/codecept.inject.po.json b/test/data/sandbox/codecept.inject.po.json
index a914e7f3..5dde0213 100644
--- a/test/data/sandbox/codecept.inject.po.json
+++ b/test/data/sandbox/codecept.inject.po.json
@@ -7,7 +7,8 @@
   },
   "include": {
     "I": "./support/custom_steps.js",
-    "MyPage": "./support/my_page.js"
+    "MyPage": "./support/my_page.js",
+    "SecondPage": "./support/second_page.js"
   },
   "bootstrap": false,
   "mocha": {},
diff --git a/test/data/sandbox/support/second_page.js b/test/data/sandbox/support/second_page.js
new file mode 100644
index 00000000..adfd44c9
--- /dev/null
+++ b/test/data/sandbox/support/second_page.js
@@ -0,0 +1,5 @@
+module.exports = {
+  secondPageMethod() {
+    console.log('secondPageMethod');
+  },
+};
diff --git a/test/runner/list_test.js b/test/runner/list_test.js
index 90785d37..825c26e0 100644
--- a/test/runner/list_test.js
+++ b/test/runner/list_test.js
@@ -62,4 +62,63 @@ describe('list/def commands', () => {
       done();
     });
   });
+
+  it('def should create definition file with internal object', (done) => {
+    try {
+      fs.unlinkSync(`${codecept_dir}/steps.d.ts`);
+    } catch (e) {
+      // continue regardless of error
+    }
+    exec(`${runner} def --config ${codecept_dir}/codecept.inject.po.json`, () => {
+      const content = fs.readFileSync(`${codecept_dir}/steps.d.ts`).toString();
+      content.should.include('  export const recorder: Recorder');
+      content.should.include('  export const event: CodeceptJSEvent');
+      content.should.include('  export const output: Output');
+      content.should.include('  export const config: Config');
+      content.should.include('  export const container: Container');
+      done();
+    });
+  });
+
+  it('def should create definition file with inject which contains support objects', (done) => {
+    try {
+      fs.unlinkSync(`${codecept_dir}/steps.d.ts`);
+    } catch (e) {
+      // continue regardless of error
+    }
+    exec(`${runner} def --config ${codecept_dir}/codecept.inject.po.json`, () => {
+      const content = fs.readFileSync(`${codecept_dir}/steps.d.ts`).toString();
+      content.should.include('declare function inject(): {');
+      content.should.include(' MyPage: CodeceptJS.MyPage');
+      content.should.include(' SecondPage: CodeceptJS.SecondPage');
+      done();
+    });
+  });
+
+  it('def should create definition file with inject which contains I object', (done) => {
+    try {
+      fs.unlinkSync(`${codecept_dir}/steps.d.ts`);
+    } catch (e) {
+      // continue regardless of error
+    }
+    exec(`${runner} def --config ${codecept_dir}/codecept.inject.po.json`, () => {
+      const content = fs.readFileSync(`${codecept_dir}/steps.d.ts`).toString();
+      content.should.include('declare function inject(): {');
+      content.should.include(' I: CodeceptJS.I');
+      done();
+    });
+  });
+
+  it('def should create definition file with callback params', (done) => {
+    try {
+      fs.unlinkSync(`${codecept_dir}/steps.d.ts`);
+    } catch (e) {
+      // continue regardless of error
+    }
+    exec(`${runner} def --config ${codecept_dir}/codecept.inject.po.json`, () => {
+      const content = fs.readFileSync(`${codecept_dir}/steps.d.ts`).toString();
+      content.should.include('type ICodeceptCallback = (i?: CodeceptJS.I, current?:any, MyPage?:CodeceptJS.MyPage, SecondPage?:CodeceptJS.SecondPage, ...args: any) => void;');
+      done();
+    });
+  });
 });