"use strict";

var sinon = require("sinon");
var events = require("../../").Runner.constants;
var helpers = require("./helpers.cjs");
var reporters = require("../../").reporters;

var Base = reporters.Base;
var NyanCat = reporters.Nyan;
var createMockRunner = helpers.createMockRunner;
var makeRunReporter = helpers.createRunReporterFunction;

var EVENT_RUN_BEGIN = events.EVENT_RUN_BEGIN;
var EVENT_RUN_END = events.EVENT_RUN_END;
var EVENT_TEST_FAIL = events.EVENT_TEST_FAIL;
var EVENT_TEST_PASS = events.EVENT_TEST_PASS;
var EVENT_TEST_PENDING = events.EVENT_TEST_PENDING;

describe("Nyan reporter", function () {
  var noop = function () {};

  afterEach(function () {
    sinon.restore();
  });

  describe("event handlers", function () {
    var runReporter = makeRunReporter(NyanCat);

    describe("on 'start' event", function () {
      it("should call draw", async function () {
        var fakeThis = {
          draw: sinon.stub().callsFake(noop),
          generateColors: noop,
        };

        var runner = createMockRunner("start", EVENT_RUN_BEGIN);
        var options = {};
        await runReporter(fakeThis, runner, options);

        expect(fakeThis.draw.called, "to be true");
      });
    });

    describe("on 'pending' event", function () {
      it("should call draw", async function () {
        var fakeThis = {
          draw: sinon.stub().callsFake(noop),
          generateColors: noop,
        };
        var runner = createMockRunner("pending", EVENT_TEST_PENDING);
        var options = {};
        await runReporter(fakeThis, runner, options);

        expect(fakeThis.draw.called, "to be true");
      });
    });

    describe("on 'pass' event", function () {
      it("should call draw", async function () {
        var test = {
          duration: "",
          slow: noop,
        };
        var fakeThis = {
          draw: sinon.stub().callsFake(noop),
          generateColors: noop,
        };
        var runner = createMockRunner(
          "pass",
          EVENT_TEST_PASS,
          null,
          null,
          test,
        );
        var options = {};
        await runReporter(fakeThis, runner, options);

        expect(fakeThis.draw.called, "to be true");
      });
    });

    describe("on 'fail' event", function () {
      it("should call draw", async function () {
        var test = {
          err: "",
        };
        var fakeThis = {
          draw: sinon.stub().callsFake(noop),
          generateColors: noop,
        };
        var runner = createMockRunner(
          "fail",
          EVENT_TEST_FAIL,
          null,
          null,
          test,
        );
        var options = {};
        await runReporter(fakeThis, runner, options);

        expect(fakeThis.draw.called, "to be true");
      });
    });

    describe("on 'end' event", function () {
      it("should call epilogue", async function () {
        var fakeThis = {
          draw: noop,
          epilogue: sinon.stub().callsFake(noop),
          generateColors: noop,
        };
        var runner = createMockRunner("end", EVENT_RUN_END);
        var options = {};
        await runReporter(fakeThis, runner, options);

        expect(fakeThis.epilogue.called, "to be true");
      });

      it("should write numberOfLines amount of newlines", async function () {
        var expectedNumberOfLines = 4;
        var fakeThis = {
          draw: noop,
          epilogue: noop,
          generateColors: noop,
        };
        var runner = createMockRunner("end", EVENT_RUN_END);
        var options = {};
        var { stdout } = await runReporter(fakeThis, runner, options);

        var isBlankLine = function (value) {
          return value === "\n";
        };

        expect(
          stdout.filter(isBlankLine),
          "to have length",
          expectedNumberOfLines,
        );
      });

      it("should call Base show", async function () {
        var showCursorStub = sinon.stub(Base.cursor, "show");
        var fakeThis = {
          draw: noop,
          epilogue: noop,
          generateColors: noop,
        };
        var runner = createMockRunner("end", EVENT_RUN_END);
        var options = {};
        await runReporter(fakeThis, runner, options);
        sinon.restore();

        expect(showCursorStub.called, "to be true");
      });
    });
  });

  describe("#draw", function () {
    var stdoutWriteStub;
    var stdout;

    beforeEach(function () {
      stdoutWriteStub = sinon.stub(process.stdout, "write");
      stdoutWriteStub.callsFake(function (chunk) {
        stdout.push(chunk);
      });
      stdout = [];
    });

    describe("when 'tick' is false", function () {
      it("should draw face with expected spaces, _ and ^", function () {
        var runner = { on: noop, once: noop };
        var options = {};
        var nyanCat = new NyanCat(runner, options);
        nyanCat.stats = { passes: 2, pending: 1, failures: 0 };
        var fakeThis = {
          tick: false,
          appendRainbow: noop,
          rainbowify: noop,
          drawScoreboard: noop,
          drawRainbow: noop,
          drawNyanCat: NyanCat.prototype.drawNyanCat,
          scoreboardWidth: 0,
          trajectories: [[]],
          face: noop,
          cursorUp: noop,
        };

        try {
          nyanCat.draw.call(fakeThis);
        } finally {
          sinon.restore();
        }

        var expectedArray = [
          "\u001b[0C",
          "_,------,",
          "\n",
          "\u001b[0C",
          "_|   /\\_/\\ ",
          "\n",
          "\u001b[0C",
          "^|__undefined ",
          "\n",
          "\u001b[0C",
          '  ""  "" ',
          "\n",
        ];
        expect(stdout, "to equal", expectedArray);
      });
    });

    describe("when 'tick' is true", function () {
      it("should draw face with expected spaces, _ and ~", function () {
        var runner = { on: noop, once: noop };
        var options = {};
        var nyanCat = new NyanCat(runner, options);
        nyanCat.stats = { passes: 2, pending: 1, failures: 0 };
        var fakeThis = {
          tick: true,
          appendRainbow: noop,
          rainbowify: noop,
          drawScoreboard: noop,
          drawRainbow: noop,
          drawNyanCat: NyanCat.prototype.drawNyanCat,
          scoreboardWidth: 0,
          trajectories: [[]],
          face: noop,
          cursorUp: noop,
        };

        try {
          nyanCat.draw.call(fakeThis);
        } finally {
          sinon.restore();
        }

        var expectedArray = [
          "\u001b[0C",
          "_,------,",
          "\n",
          "\u001b[0C",
          "_|  /\\_/\\ ",
          "\n",
          "\u001b[0C",
          "~|_undefined ",
          "\n",
          "\u001b[0C",
          ' ""  "" ',
          "\n",
        ];
        expect(stdout, "to equal", expectedArray);
      });
    });
  });

  describe("#cursorDown", function () {
    var stdoutWriteStub;
    var stdout;

    beforeEach(function () {
      stdoutWriteStub = sinon.stub(process.stdout, "write");
      stdoutWriteStub.callsFake(function (chunk) {
        stdout.push(chunk);
      });
      stdout = [];
    });

    it("should write cursor down interaction with expected number", function () {
      var runner = { on: noop, once: noop };
      var options = {};
      var nyanCat = new NyanCat(runner, options);
      var expectedNumber = 25;

      try {
        nyanCat.cursorDown(expectedNumber);
      } finally {
        sinon.restore();
      }

      var expectedArray = ["\u001b[" + expectedNumber + "B"];
      expect(stdout, "to equal", expectedArray);
    });
  });

  describe("#cursorUp", function () {
    var stdoutWriteStub;
    var stdout;

    beforeEach(function () {
      stdoutWriteStub = sinon.stub(process.stdout, "write");
      stdoutWriteStub.callsFake(function (chunk) {
        stdout.push(chunk);
      });
      stdout = [];
    });

    it("should write cursor up interaction with expected number", function () {
      var runner = { on: noop, once: noop };
      var options = {};
      var nyanCat = new NyanCat(runner, options);
      var expectedNumber = 25;

      try {
        nyanCat.cursorUp(expectedNumber);
      } finally {
        sinon.restore();
      }

      var expectedArray = ["\u001b[" + expectedNumber + "A"];
      expect(stdout, "to equal", expectedArray);
    });
  });

  describe("#rainbowify", function () {
    var useColorsStub;

    beforeEach(function () {
      useColorsStub = sinon.stub(Base, "useColors");
    });

    afterEach(function () {
      sinon.restore();
    });

    describe("when 'useColors' is false", function () {
      beforeEach(function () {
        useColorsStub.value(false);
      });

      it("should return argument string", function () {
        var runner = { on: noop, once: noop };
        var options = {};
        var nyanCat = new NyanCat(runner, options);

        var inputString = "hello";
        var outputString = nyanCat.rainbowify(inputString);
        sinon.restore();

        var expectedString = inputString;
        expect(outputString, "to be", expectedString);
      });
    });

    describe("when 'useColors' is true", function () {
      beforeEach(function () {
        useColorsStub.value(true);
      });

      it("should return rainbowified string from the given string and predefined codes", function () {
        var runner = { on: noop, once: noop };
        var options = {};
        var nyanCat = new NyanCat(runner, options);

        var inputString = "hello";
        var colorCode = "somecode";
        var fakeThis = {
          rainbowColors: [colorCode],
          colorIndex: 0,
        };
        var outputString = nyanCat.rainbowify.call(fakeThis, inputString);
        sinon.restore();

        var startCode = "\u001b[38;5;";
        var endCode = "\u001b[0m";
        var expectedString =
          startCode + colorCode + "m" + inputString + endCode;
        expect(outputString, "to be", expectedString);
      });
    });
  });

  describe("#appendRainbow", function () {
    describe("when 'tick' is true", function () {
      it("should set an underscore segment", function () {
        var runner = { on: noop, once: noop };
        var options = {};
        var nyanCat = new NyanCat(runner, options);
        var expectedSegment;
        var inputArray = [];
        var trajectories = [inputArray, inputArray, inputArray, inputArray];
        nyanCat.appendRainbow.call({
          tick: true,
          rainbowify: function (segment) {
            expectedSegment = segment;
          },
          numberOfLines: 4,
          trajectoryWidthMax: 0,
          trajectories,
        });

        expect(expectedSegment, "to be", "_");
      });

      it("should shift each trajectory item if its length is greater than or equal to its max width", function () {
        var runner = { on: noop, once: noop };
        var options = {};
        var nyanCat = new NyanCat(runner, options);

        var rainbowifyResult = "rainbowify";
        var inputArray = ["itemToShify"];
        var trajectories = [inputArray, inputArray, inputArray, inputArray];
        var expectedArray = [rainbowifyResult];
        var expectedTrajectories = [
          expectedArray,
          expectedArray,
          expectedArray,
          expectedArray,
        ];
        nyanCat.appendRainbow.call({
          tick: true,
          rainbowify: function () {
            return rainbowifyResult;
          },
          numberOfLines: 4,
          trajectoryWidthMax: 0,
          trajectories,
        });

        expect(trajectories, "to equal", expectedTrajectories);
      });
    });

    describe("when 'tick' is false", function () {
      it("should set a dash segment", function () {
        var runner = { on: noop, once: noop };
        var options = {};
        var nyanCat = new NyanCat(runner, options);
        var expectedSegment;
        var inputArray = [];
        var trajectories = [inputArray, inputArray, inputArray, inputArray];
        nyanCat.appendRainbow.call({
          tick: false,
          rainbowify: function (segment) {
            expectedSegment = segment;
          },
          numberOfLines: 4,
          trajectoryWidthMax: 5,
          trajectories,
        });

        expect(expectedSegment, "to equal", "-");
      });
    });
  });

  describe("#drawScoreboard", function () {
    var stdout;

    beforeEach(function () {
      sinon.stub(Base, "color").callsFake(function (type, n) {
        return type + n;
      });
      var stdoutWriteStub = sinon.stub(process.stdout, "write");
      stdoutWriteStub.callsFake(function (chunk) {
        stdout.push(chunk);
      });
      stdout = [];
    });

    it("should write scoreboard with color set with each stat", function () {
      var runner = { on: noop, once: noop };
      var options = {};
      var nyanCat = new NyanCat(runner, options);

      var passes = 2;
      var pending = 1;
      var failures = 1;

      var fakeThis = {
        cursorUp: noop,
        stats: { passes, pending, failures },
        numberOfLines: 4,
      };

      try {
        nyanCat.drawScoreboard.call(fakeThis);
      } finally {
        sinon.restore();
      }

      var expectedArray = [
        " ",
        "green" + passes,
        "\n",
        " ",
        "fail" + failures,
        "\n",
        " ",
        "pending" + pending,
        "\n",
        "\n",
      ];
      expect(stdout, "to equal", expectedArray);
    });

    it("should call cursorUp with given numberOfLines", function () {
      var expectedNumberOfLines = 1000;

      var runner = { on: noop, once: noop };
      var options = {};
      var nyanCat = new NyanCat(runner, options);
      var fakeThis = {
        cursorUp: sinon.spy(),
        stats: { passes: 0, pending: 0, failures: 0 },
        numberOfLines: expectedNumberOfLines,
      };

      try {
        nyanCat.drawScoreboard.call(fakeThis);
      } finally {
        sinon.restore();
      }

      expect(fakeThis.cursorUp.calledWith(expectedNumberOfLines), "to be true");
    });
  });

  describe("#drawRainbow", function () {
    var stdout;

    beforeEach(function () {
      var stdoutWriteStub = sinon.stub(process.stdout, "write");
      stdoutWriteStub.callsFake(function (chunk) {
        stdout.push(chunk);
      });
      stdout = [];
    });

    it("should write width, contents and newline for each trajectory", function () {
      var expectedWidth = 444;

      var expectedContents = "input";
      var inputArray = [expectedContents];
      var trajectories = [inputArray];
      var runner = { on: noop, once: noop };
      var options = {};
      var nyanCat = new NyanCat(runner, options);
      var fakeThis = {
        cursorUp: noop,
        trajectories,
        scoreboardWidth: expectedWidth,
        numberOfLines: 1,
      };

      try {
        nyanCat.drawRainbow.call(fakeThis);
      } finally {
        sinon.restore();
      }

      var expectedArray = [
        "\u001b[" + expectedWidth + "C",
        expectedContents,
        "\n",
      ];
      expect(stdout, "to equal", expectedArray);
    });

    it("should call cursorUp with given numberOfLines", function () {
      var expectedCursorArgument = null;
      var expectedNumberOfLines = 1000;

      var runner = { on: noop, once: noop };
      var options = {};
      var nyanCat = new NyanCat(runner, options);
      var fakeThis = {
        cursorUp: function (lines) {
          expectedCursorArgument = lines;
        },
        trajectories: [["input"]],
        scoreboardWidth: 1,
        numberOfLines: expectedNumberOfLines,
      };

      try {
        nyanCat.drawRainbow.call(fakeThis);
      } finally {
        sinon.restore();
      }

      expect(expectedCursorArgument, "to be", expectedNumberOfLines);
    });
  });

  describe("#face", function () {
    it('should expect "( x .x)" if any failures', function () {
      var runner = { on: noop, once: noop };
      var options = {};
      var nyanCat = new NyanCat(runner, options);
      nyanCat.stats = { passes: 2, pending: 1, failures: 1 };

      expect(nyanCat.face(), "to be", "( x .x)");
    });

    it('should expect "( o .o)" if any pending but none failing', function () {
      var runner = { on: noop, once: noop };
      var options = {};
      var nyanCat = new NyanCat(runner, options);
      nyanCat.stats = { passes: 2, pending: 1, failures: 0 };

      expect(nyanCat.face(), "to be", "( o .o)");
    });

    it('should expect "( ^ .^)" if all passing', function () {
      var runner = { on: noop, once: noop };
      var options = {};
      var nyanCat = new NyanCat(runner, options);
      nyanCat.stats = { passes: 1, pending: 0, failures: 0 };

      expect(nyanCat.face(), "to be", "( ^ .^)");
    });

    it('should expect "( - .-)" otherwise', function (done) {
      var runner = { on: noop, once: noop };
      var options = {};
      var nyanCat = new NyanCat(runner, options);
      nyanCat.stats = { passes: 0, pending: 0, failures: 0 };

      expect(nyanCat.face(), "to be", "( - .-)");
      done();
    });
  });
});
