Home Manual Reference Source Test

test/src/tasks/watch.spec.js

import Emitter from 'events';
import proxyquire from 'proxyquire';
import { obj as createThroughStream } from 'through2';
import { spy, stub } from 'sinon';
import expect from '../../expect';
import watch, { WatchTask } from '../../../src/tasks/watch';
import NodeId from '../../../src/lib/model/opcua/NodeId';

class TestEmitter extends Emitter {
  constructor(name, payload = true, delay = 1) {
    super();

    setTimeout(() => this.emit(name, payload), delay);
  }
}

class NoopStream {
  constructor(otherStream) {
    return otherStream.pipe(createThroughStream()).on('data', () => {});
  }
}

const stubGulp = {
  src(path, options) {
    const stream = createThroughStream();
    stream.write(Object.assign({}, options, { path, nodeId: 'source node id' }));
    stream.end();

    return stream;
  },
};

const stubModule = proxyquire('../../../src/tasks/watch', {
  sane: () => new TestEmitter('ready', 1),
  'browser-sync': {
    create() {
      return {
        init() {
          this.emitter.emit('service:running', true);
        },
        emitter: new TestEmitter(),
        // eslint-disable-next-line @typescript-eslint/no-empty-function
        reload() {},
      };
    },
  },
  gulp: stubGulp,
  '../util/fs': {
    validateDirectoryExists: () => Promise.resolve(true),
  },
  '../lib/server/Watcher': {
    default: class extends TestEmitter {
      constructor() {
        super('ready', 1);
      }
    },
  },
  '../lib/gulp/PullStream': { default: NoopStream },
  '../lib/gulp/PushStream': { default: NoopStream },
});

const StubWatchTask = stubModule.WatchTask;
const stubWatch = stubModule.default;

/** @test {WatchTask} */
describe('WatchTask', function () {
  /** @test {WatchTask#constructor} */
  describe('#constructor', function () {
    it('should create a new browser-sync instance', function () {
      expect(new WatchTask().browserSyncInstance, 'to be defined');
    });
  });

  /** @test {WatchTask#_waitForWatcher} */
  describe('#_waitForWatcher', function () {
    it('should be rejected on error', function () {
      const task = new WatchTask();
      const fakeWatcher = new TestEmitter('error', new Error('Test error'));

      return expect(task._waitForWatcher(fakeWatcher), 'to be rejected with', 'Test error');
    });

    it('should be fulfilled on ready', function () {
      const task = new WatchTask();
      const fakeWatcher = new TestEmitter('ready');

      return expect(task._waitForWatcher(fakeWatcher), 'to be fulfilled');
    });
  });

  /** @test {WatchTask#startFileWatcher} */
  describe('#startFileWatcher', function () {
    it('should fail if directory does not exist', function () {
      class FailingTask extends WatchTask {
        get directoryToWatch() {
          return './does-not-exist';
        }
      }

      const task = new FailingTask();

      return expect(task.startFileWatcher(), 'to be rejected with', /does not exist/);
    });

    it('should fail if fs#stat fails', function () {
      class FailingTask extends WatchTask {
        get directoryToWatch() {
          return 13;
        }
      }

      const task = new FailingTask();

      return expect(
        task.startFileWatcher(),
        'to be rejected with',
        /"?Path"?.* must be (a|of type) string/i
      );
    });

    it('should call #_waitForWatcher', function () {
      const task = new StubWatchTask();
      spy(task, '_waitForWatcher');

      return expect(task.startFileWatcher(), 'to be fulfilled').then(() =>
        expect(task._waitForWatcher, 'was called once')
      );
    });
  });

  /** @test {WatchTask#startServerWatcher} */
  describe('#startServerWatcher', function () {
    it('should call #_waitForWatcher', function () {
      const task = new StubWatchTask();
      spy(task, '_waitForWatcher');

      return expect(task.startServerWatcher(), 'to be fulfilled').then(() =>
        expect(task._waitForWatcher, 'was called once')
      );
    });
  });

  /** @test {WatchTask#initBrowserSync} */
  describe('#initBrowserSync', function () {
    it('should call BrowserSync#init', function () {
      const task = new WatchTask();
      stub(task.browserSyncInstance, 'init').returns(true);

      task.initBrowserSync();
      expect(task.browserSyncInstance.init, 'was called once');
    });
  });

  /** @test {WatchTask#handleFileChange} */
  describe('#handleFileChange', function () {
    it.skip('should not do anything when lately pulled files change', function () {
      const task = new StubWatchTask();

      return expect(
        task.handleFileChange('./path.file', './src', { mtime: new Date(-10000) }),
        'to be fulfilled with',
        false
      );
    });

    it('should not do anything while pulling', function () {
      const task = new StubWatchTask();
      task._handlingChange = true;

      return expect(
        task.handleFileChange('./path.file', './src', { mtime: new Date(Date.now()) }),
        'to be fulfilled with',
        false
      );
    });

    it.skip('should push changed files', function () {
      const task = new StubWatchTask();

      return expect(
        task.handleFileChange('./path.file', './src', { mtime: new Date(Date.now()) }),
        'to be fulfilled with',
        true
      );
    });

    it.skip('should reload browser', function () {
      const task = new StubWatchTask();
      spy(task.browserSyncInstance, 'reload');

      return expect(
        task.handleFileChange('./path.file', './src', { mtime: new Date(Date.now()) }),
        'to be fulfilled with',
        true
      ).then(() => expect(task.browserSyncInstance.reload, 'was called once'));
    });
  });

  /** @test {WatchTask#handleServerChange} */
  describe('#handleServerChange', function () {
    it('should do nothing while pushing', function () {
      const task = new StubWatchTask();
      task._handlingChange = true;

      return expect(
        task.handleServerChange({
          nodeId: new NodeId('AGENT.OBJECTS.Test'),
        }),
        'to be fulfilled with',
        false
      );
    });

    it.skip('should do nothing when handling node that was just pushed', function () {
      const task = new StubWatchTask();
      task._lastPushed = 'ns=13;s=Test';

      return expect(
        task.handleServerChange({ nodeId: task._lastPushed }),
        'to be fulfilled with',
        false
      );
    });

    it.skip('should pull changed nodes', function () {
      const task = new StubWatchTask();

      return expect(
        task.handleServerChange({ nodeId: 'ns=13;s=Test', mtime: new Date() }),
        'to be fulfilled with',
        true
      );
    });
  });

  /** @test {WatchTask#run} */
  describe('#run', function () {
    it('should fail if file watcher errors', function () {
      const task = new StubWatchTask();
      task.startFileWatcher = () => Promise.reject(new Error('Test'));

      return expect(task.run(), 'to be rejected with', 'Test');
    });

    it('should fail if server watcher errors', function () {
      const task = new StubWatchTask();
      task.startServerWatcher = () => Promise.reject(new Error('Test'));

      return expect(task.run(), 'to be rejected with', 'Test');
    });

    it('should init browser sync', function () {
      const task = new StubWatchTask();
      stub(task.browserSyncInstance, 'init').callsFake(() => {
        task.browserSyncInstance.emitter.emit('service:running', true);
      });

      return expect(task.run(), 'to be fulfilled').then(() =>
        expect(task.browserSyncInstance.init, 'was called once')
      );
    });
  });
});

/** @test {watch} */
describe('watch', function () {
  it('should export a function', function () {
    expect(watch, 'to be a', 'function');
  });

  it('should resolve once watchers are ready', function () {
    return expect(stubWatch(), 'to be fulfilled');
  });

  it('should export a description', function () {
    expect(watch.description, 'to be defined');
  });
});