From f2a5593cedde036da84f9c4fdb82ec5c373ef1c1 Mon Sep 17 00:00:00 2001
From: Anton Usmansky <cody0@yandex-team.ru>
Date: Sun, 12 Feb 2017 00:10:15 +0300
Subject: [PATCH] feat: BEFORE_FILE_READ and AFTER_FILE_READ events

---
 README.md                                     |  4 ++-
 lib/constants/runner-events.js                |  3 +++
 lib/runner/index.js                           |  3 +++
 lib/runner/mocha-runner/index.js              |  4 +--
 lib/runner/mocha-runner/mocha-adapter.js      |  9 +++++++
 test/lib/runner/index.js                      |  5 +++-
 test/lib/runner/mocha-runner/index.js         |  8 ++++++
 test/lib/runner/mocha-runner/mocha-adapter.js | 27 +++++++++++++++++++
 8 files changed, 59 insertions(+), 4 deletions(-)

diff --git a/README.md b/README.md
index 1fad97d2c..2e0a390a2 100644
--- a/README.md
+++ b/README.md
@@ -391,7 +391,7 @@ sets: {
     common: {                 // run tests associated with this path in all browsers
         files: 'tests/common' // which are configured in the `browsers` option
     },
-    desktop: {              
+    desktop: {
         files: [
             'tests/desktop/*.hermione.js',
             'tests/common/*.hermione.js'
@@ -547,6 +547,8 @@ Property name             | Description
 
 Event                     | Description
 ------------------------- | -------------
+`BEFORE_FILE_READ`        | Will be triggered on test files parsing before reading the file. The handler accepts data object with `file`, `browser` (browser id string) and `hermione` (helper which will be available in test file) fields.
+`AFTER_FILE_READ`         | Will be triggered on test files parsing right after reading the file. The handler accepts data object with `file`, `browser` (browser id string) and `hermione` (helper which will be available in test file) fields.
 `RUNNER_START`            | Will be triggered before test execution. If a handler returns a promise, tests will be executed only after the promise is resolved. The handler accepts an instance of a runner as the first argument. You can use this instance to emit and subscribe to any other available events.
 `RUNNER_END`              | Will be triggered after test execution. If a handler returns a promise, tests will be executed only after the promise is resolved.
 `SESSION_START`           | Will be triggered after browser session initialization. If a handler returns a promise, tests will be executed only after the promise is resolved. The handler accepts an instance of webdriverIO as the first argument and an object with a browser identifier as the second.
diff --git a/lib/constants/runner-events.js b/lib/constants/runner-events.js
index 91c38be96..71d24eafb 100644
--- a/lib/constants/runner-events.js
+++ b/lib/constants/runner-events.js
@@ -13,6 +13,9 @@ const getAsyncEvents = () => ({
 });
 
 const getSyncEvents = () => ({
+    BEFORE_FILE_READ: 'beforeFileRead',
+    AFTER_FILE_READ: 'afterFileRead',
+
     SUITE_BEGIN: 'beginSuite',
     SUITE_END: 'endSuite',
 
diff --git a/lib/runner/index.js b/lib/runner/index.js
index 49340ff83..11a189c10 100644
--- a/lib/runner/index.js
+++ b/lib/runner/index.js
@@ -73,6 +73,9 @@ module.exports = class MainRunner extends QEmitter {
         const mochaRunner = MochaRunner.create(this._config, browserAgent, this._testSkipper);
 
         qUtils.passthroughEvent(mochaRunner, this, [
+            RunnerEvents.BEFORE_FILE_READ,
+            RunnerEvents.AFTER_FILE_READ,
+
             RunnerEvents.SUITE_BEGIN,
             RunnerEvents.SUITE_END,
 
diff --git a/lib/runner/mocha-runner/index.js b/lib/runner/mocha-runner/index.js
index 82b167149..73658cafb 100644
--- a/lib/runner/mocha-runner/index.js
+++ b/lib/runner/mocha-runner/index.js
@@ -45,7 +45,7 @@ module.exports = class MochaRunner extends QEmitter {
             .attachTestFilter(filterFn)
             .attachTitleValidator(titles)
             .applySkip(this._testSkipper)
-            .addFiles(suiteFiles)
-            .attachEmitFn(this.emit.bind(this));
+            .attachEmitFn(this.emit.bind(this))
+            .addFiles(suiteFiles);
     }
 };
diff --git a/lib/runner/mocha-runner/mocha-adapter.js b/lib/runner/mocha-runner/mocha-adapter.js
index b85d86767..f4fe5e135 100644
--- a/lib/runner/mocha-runner/mocha-adapter.js
+++ b/lib/runner/mocha-runner/mocha-adapter.js
@@ -5,6 +5,7 @@ const logger = require('../../utils').logger;
 const Skip = require('./skip/');
 const SkipBuilder = require('./skip/skip-builder');
 const OnlyBuilder = require('./skip/only-builder');
+const RunnerEvents = require('../../constants/runner-events');
 const Mocha = require('mocha');
 const path = require('path');
 const clearRequire = require('clear-require');
@@ -94,6 +95,14 @@ module.exports = class MochaAdapter {
         const Reporter = _.partial(ProxyReporter, emit, () => this._getBrowser());
         this._mocha.reporter(Reporter);
 
+        const emit_ = (event, file) => emit(event, {
+            file,
+            hermione: global.hermione,
+            browser: this._browserAgent.browserId
+        });
+        this.suite.on('pre-require', (ctx, file) => emit_(RunnerEvents.BEFORE_FILE_READ, file));
+        this.suite.on('post-require', (ctx, file) => emit_(RunnerEvents.AFTER_FILE_READ, file));
+
         return this;
     }
 
diff --git a/test/lib/runner/index.js b/test/lib/runner/index.js
index c7cee5f35..98b87ed8a 100644
--- a/test/lib/runner/index.js
+++ b/test/lib/runner/index.js
@@ -198,6 +198,9 @@ describe('Runner', () => {
 
             describe('events', () => {
                 const mochaRunnerEvents = [
+                    RunnerEvents.BEFORE_FILE_READ,
+                    RunnerEvents.AFTER_FILE_READ,
+
                     RunnerEvents.SUITE_BEGIN,
                     RunnerEvents.SUITE_END,
 
@@ -243,7 +246,7 @@ describe('Runner', () => {
             });
         });
 
-        describe('passing of events from browser agent', () => {
+        describe('passing events from browser agent', () => {
             beforeEach(() => sandbox.stub(BrowserAgent, 'create').returns(sinon.createStubInstance(BrowserAgent)));
 
             [RunnerEvents.SESSION_START, RunnerEvents.SESSION_END].forEach((event) => {
diff --git a/test/lib/runner/mocha-runner/index.js b/test/lib/runner/mocha-runner/index.js
index 3b3cd61a5..0a6ff4d07 100644
--- a/test/lib/runner/mocha-runner/index.js
+++ b/test/lib/runner/mocha-runner/index.js
@@ -105,6 +105,14 @@ describe('mocha-runner', () => {
                 ));
         });
 
+        it('should attach emit function before file adding', () => {
+            return run_()
+                .then(() => assert.callOrder(
+                    MochaAdapter.prototype.attachEmitFn,
+                    MochaAdapter.prototype.addFiles
+                ));
+        });
+
         it('should call title vaidator for each file', () => {
             return run_(['some/path/file.js', 'other/path/file.js'])
                 .then(() => {
diff --git a/test/lib/runner/mocha-runner/mocha-adapter.js b/test/lib/runner/mocha-runner/mocha-adapter.js
index 41f1bf18a..fb627c2de 100644
--- a/test/lib/runner/mocha-runner/mocha-adapter.js
+++ b/test/lib/runner/mocha-runner/mocha-adapter.js
@@ -7,6 +7,7 @@ const SkipBuilder = require('../../../../lib/runner/mocha-runner/skip/skip-build
 const OnlyBuilder = require('../../../../lib/runner/mocha-runner/skip/only-builder');
 const Skip = require('../../../../lib/runner/mocha-runner/skip/');
 const TestSkipper = require('../../../../lib/runner/test-skipper');
+const RunnerEvents = require('../../../../lib/constants/runner-events');
 const proxyquire = require('proxyquire').noCallThru();
 const inherit = require('inherit');
 const _ = require('lodash');
@@ -550,5 +551,31 @@ describe('mocha-runner/mocha-adapter', () => {
             const getBrowser = ProxyReporter.prototype.__constructor.lastCall.args[1];
             assert.deepEqual(getBrowser(), {id: 'some-browser'});
         });
+
+        describe('file events', () => {
+            beforeEach(() => MochaAdapter.init());
+            afterEach(() => delete global.hermione);
+
+            _.forEach({
+                'pre-require': 'BEFORE_FILE_READ',
+                'post-require': 'AFTER_FILE_READ'
+            }, (hermioneEvent, mochaEvent) => {
+                it(`should emit ${hermioneEvent} on mocha ${mochaEvent}`, () => {
+                    const emit = sinon.spy();
+                    browserAgent.browserId = 'bro';
+
+                    mochaAdapter.attachEmitFn(emit);
+
+                    MochaStub.prototype.suite.emit(mochaEvent, {}, '/some/file.js');
+
+                    assert.calledOnce(emit);
+                    assert.calledWith(emit, RunnerEvents[hermioneEvent], sinon.match({
+                        file: '/some/file.js',
+                        hermione: global.hermione,
+                        browser: 'bro'
+                    }));
+                });
+            });
+        });
     });
 });