"use strict"; /* This Source Code Form is subject to the terms of the Mozilla Public
               * License, v. 2.0. If a copy of the MPL was not distributed with this file,
               * You can obtain one at http://mozilla.org/MPL/2.0/. */

describe("loop.shared.utils", function () {
  "use strict";

  var expect = chai.expect;
  var mozL10n = navigator.mozL10n || document.mozL10n;
  var sandbox;
  var sharedUtils = loop.shared.utils;

  beforeEach(function () {
    sandbox = LoopMochaUtils.createSandbox();});


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


  describe("#getUnsupportedPlatform", function () {
    it("should detect iOS", function () {
      expect(sharedUtils.getUnsupportedPlatform("iPad")).eql("ios");
      expect(sharedUtils.getUnsupportedPlatform("iPod")).eql("ios");
      expect(sharedUtils.getUnsupportedPlatform("iPhone")).eql("ios");
      expect(sharedUtils.getUnsupportedPlatform("iPhone Simulator")).eql("ios");});


    it("should detect Windows Phone", function () {
      expect(sharedUtils.getUnsupportedPlatform("Windows Phone")).
      eql("windows_phone");});


    it("should detect BlackBerry", function () {
      expect(sharedUtils.getUnsupportedPlatform("BlackBerry")).
      eql("blackberry");});


    it("shouldn't detect other platforms", function () {
      expect(sharedUtils.getUnsupportedPlatform("MacIntel")).eql(null);});});



  describe("#isFirefox", function () {
    it("should detect Firefox", function () {
      expect(sharedUtils.isFirefox("Firefox")).eql(true);
      expect(sharedUtils.isFirefox("Gecko/Firefox")).eql(true);
      expect(sharedUtils.isFirefox("Firefox/Gecko")).eql(true);
      expect(sharedUtils.isFirefox("Gecko/Firefox/Chuck Norris")).eql(true);});


    it("shouldn't detect Firefox with other platforms", function () {
      expect(sharedUtils.isFirefox("Opera")).eql(false);});});



  describe("#formatDate", function () {
    beforeEach(function () {
      sandbox.stub(Date.prototype, "toLocaleDateString").returns("fake result");});


    it("should call toLocaleDateString with arguments", function () {
      sharedUtils.formatDate(1000);

      sinon.assert.calledOnce(Date.prototype.toLocaleDateString);
      sinon.assert.calledWithExactly(Date.prototype.toLocaleDateString, 
      navigator.language, 
      { year: "numeric", month: "long", day: "numeric" });});



    it("should return the formatted string", function () {
      expect(sharedUtils.formatDate(1000)).eql("fake result");});});



  describe("#getBoolPreference", function () {
    afterEach(function () {
      localStorage.removeItem("test.true");});


    describe("mozLoop set", function () {
      beforeEach(function () {
        LoopMochaUtils.stubLoopRequest({ 
          GetLoopPref: function GetLoopPref(prefName) {
            return prefName === "test.true";} });});




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


      it("should return the mozLoop preference", function () {
        sharedUtils.getBoolPreference("test.true", function (result) {
          expect(result).eql(true);});});



      it("should not use the localStorage value", function () {
        localStorage.setItem("test.false", true);

        sharedUtils.getBoolPreference("test.false", function (result) {
          expect(result).eql(false);});});});




    describe("mozLoop not set", function () {
      beforeEach(function () {
        sandbox.stub(loop.shared.utils, "isDesktop").returns(false);});


      it("should return the localStorage value", function () {
        localStorage.setItem("test.true", true);

        sharedUtils.getBoolPreference("test.true", function (result) {
          expect(result).eql(true);});});});});





  describe("#hasAudioOrVideoDevices", function () {
    var fakeNavigatorObject, fakeWindowObject;

    beforeEach(function () {
      fakeNavigatorObject = { 
        mediaDevices: { 
          enumerateDevices: sinon.stub() } };



      fakeWindowObject = { 
        MediaStreamTrack: { 
          getSources: sinon.stub() } };



      sharedUtils.setRootObjects(fakeWindowObject, fakeNavigatorObject);});


    afterEach(function () {
      sharedUtils.setRootObjects();});


    it("should return true if no APIs to detect devices exist", function (done) {
      delete fakeNavigatorObject.mediaDevices;
      delete fakeWindowObject.MediaStreamTrack;

      sharedUtils.hasAudioOrVideoDevices(function (result) {
        expect(result).eql(true);
        done();});});



    it("should return true if enumerateDevices doesn't exist in navigator.mediaDevices", function (done) {
      sharedUtils.setRootObjects(fakeWindowObject, { 
        mediaDevices: {} });

      delete fakeWindowObject.MediaStreamTrack;

      sharedUtils.hasAudioOrVideoDevices(function (result) {
        expect(result).eql(true);
        done();});});



    it("should return true if getSources doesn't exist in window.MediaStreamTrack", function (done) {
      sharedUtils.setRootObjects({ 
        MediaStreamTrack: {} }, 
      fakeNavigatorObject);
      delete fakeNavigatorObject.mediaDevices;

      sharedUtils.hasAudioOrVideoDevices(function (result) {
        expect(result).eql(true);
        done();});});



    it("should return false if no audio nor video devices exist according to navigator.mediaDevices", function (done) {
      delete fakeWindowObject.MediaStreamTrack;

      fakeNavigatorObject.mediaDevices.enumerateDevices.returns(Promise.resolve([]));

      sharedUtils.hasAudioOrVideoDevices(function (result) {
        try {
          expect(result).eql(false);
          done();} 
        catch (ex) {
          done(ex);}});});




    it("should return true if audio devices exist according to navigator.mediaDevices", function (done) {
      delete fakeWindowObject.MediaStreamTrack;

      fakeNavigatorObject.mediaDevices.enumerateDevices.returns(
      Promise.resolve([{ 
        deviceId: "54321", 
        groupId: "", 
        kind: "audioinput", 
        label: "" }]));



      sharedUtils.hasAudioOrVideoDevices(function (result) {
        try {
          expect(result).eql(true);
          done();} 
        catch (ex) {
          done(ex);}});});




    it("should return true if video devices exist according to navigator.mediaDevices", function (done) {
      delete fakeWindowObject.MediaStreamTrack;

      fakeNavigatorObject.mediaDevices.enumerateDevices.returns(
      Promise.resolve([{ 
        deviceId: "15234", 
        groupId: "", 
        kind: "videoinput", 
        label: "" }]));



      sharedUtils.hasAudioOrVideoDevices(function (result) {
        try {
          expect(result).eql(true);
          done();} 
        catch (ex) {
          done(ex);}});});




    it("should return false if no audio nor video devices exist according to window.MediaStreamTrack", function (done) {
      delete fakeNavigatorObject.mediaDevices;

      fakeWindowObject.MediaStreamTrack.getSources.callsArgWith(0, []);
      sharedUtils.hasAudioOrVideoDevices(function (result) {
        try {
          expect(result).eql(false);
          done();} 
        catch (ex) {
          done(ex);}});});




    it("should return true if audio devices exist according to window.MediaStreamTrack", function (done) {
      delete fakeNavigatorObject.mediaDevices;

      fakeWindowObject.MediaStreamTrack.getSources.callsArgWith(0, [{ 
        facing: "", 
        id: "54321", 
        kind: "audio", 
        label: "" }]);


      sharedUtils.hasAudioOrVideoDevices(function (result) {
        try {
          expect(result).eql(true);
          done();} 
        catch (ex) {
          done(ex);}});});




    it("should return true if video devices exist according to window.MediaStreamTrack", function (done) {
      delete fakeNavigatorObject.mediaDevices;

      fakeWindowObject.MediaStreamTrack.getSources.callsArgWith(0, [{ 
        facing: "", 
        id: "15234", 
        kind: "video", 
        label: "" }]);


      sharedUtils.hasAudioOrVideoDevices(function (result) {
        try {
          expect(result).eql(true);
          done();} 
        catch (ex) {
          done(ex);}});});});





  describe("#formatURL", function () {
    beforeEach(function () {
      // Stub to prevent console messages.
      sandbox.stub(window.console, "log");});


    it("should decode encoded URIs", function () {
      expect(sharedUtils.formatURL("http://invalid.com/?a=Foo%20Bar")).
      eql({ 
        location: "http://invalid.com/?a=Foo Bar", 
        hostname: "invalid.com", 
        protocol: "http:" });});



    it("should change some idn urls to ascii encoded", function () {
      // Note, this is based on the browser's list of what does/doesn't get
      // altered for punycode, so if the list changes this could change in the
      // future.
      expect(sharedUtils.formatURL("http://ɡoogle.com/")).
      eql({ 
        location: "http://xn--oogle-qmc.com/", 
        hostname: "xn--oogle-qmc.com", 
        protocol: "http:" });});



    it("should return null if suppressConsoleError is true and the url is not valid", function () {
      expect(sharedUtils.formatURL("hinvalid//url", true)).eql(null);});


    it("should return null if suppressConsoleError is true and is a malformed URI sequence", function () {
      expect(sharedUtils.formatURL("http://www.example.com/DNA/pizza/menu/lots-of-different-kinds-of-pizza/%8D%E0%B8%88%E0%B8%A1%E0%B8%A3%E0%8D%E0%B8%88%E0%B8%A1%E0%B8%A3%E0%", true)).eql(null);});


    it("should log a malformed URI sequence error to the console", function () {
      sharedUtils.formatURL("http://www.example.com/DNA/pizza/menu/lots-of-different-kinds-of-pizza/%8D%E0%B8%88%E0%B8%A1%E0%B8%A3%E0%8D%E0%B8%88%E0%B8%A1%E0%B8%A3%E0%");
      sinon.assert.calledOnce(console.log);});});



  describe("#composeCallUrlEmail", function () {
    var requestStubs;

    beforeEach(function () {
      // fake mozL10n
      sandbox.stub(mozL10n, "get", function (id) {
        switch (id) {
          case "share_email_subject7":
            return "subject";
          case "share_email_body7":
            return "body";
          case "share_email_body_context3":
            return "body_context";
          case "share_email_footer2":
            return "footer";
          default:
            return "unknown";}});


      LoopMochaUtils.stubLoopRequest(requestStubs = { 
        GetAllConstants: function GetAllConstants() {
          return { 
            SHARING_ROOM_URL: { 
              EMAIL_FROM_CALLFAILED: 2, 
              EMAIL_FROM_CONVERSATION: 3 } };}, 



        GetLoopPref: sinon.stub(), 
        ComposeEmail: sinon.stub() });});



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


    it("should compose a call url email", function () {
      sharedUtils.composeCallUrlEmail("http://invalid", "fake@invalid.tld");

      sinon.assert.calledOnce(requestStubs.ComposeEmail);
      sinon.assert.calledWith(requestStubs.ComposeEmail, 
      "subject", 
      "body" + 
      "footer", 
      "fake@invalid.tld");});


    it("should compose a different email when context info is provided", function () {
      sharedUtils.composeCallUrlEmail("http://invalid", null, "Hello, is me you're looking for?");

      sinon.assert.calledOnce(requestStubs.ComposeEmail);
      sinon.assert.calledWith(requestStubs.ComposeEmail, 
      "subject", 
      "body_context" + 
      "footer");});});



  describe("#btoa", function () {
    it("should encode a basic base64 string", function () {
      var result = sharedUtils.btoa(sharedUtils.strToUint8Array("crypto is great"));

      expect(result).eql("Y3J5cHRvIGlzIGdyZWF0");});


    it("should pad encoded base64 strings", function () {
      var result = sharedUtils.btoa(sharedUtils.strToUint8Array("crypto is grea"));

      expect(result).eql("Y3J5cHRvIGlzIGdyZWE=");

      result = sharedUtils.btoa(sharedUtils.strToUint8Array("crypto is gre"));

      expect(result).eql("Y3J5cHRvIGlzIGdyZQ==");});


    it("should encode a non-unicode base64 string", function () {
      var result = sharedUtils.btoa(sharedUtils.strToUint8Array("﷽"));
      expect(result).eql("77e9");});});



  describe("#atob", function () {
    it("should decode a basic base64 string", function () {
      var result = sharedUtils.Uint8ArrayToStr(sharedUtils.atob("Y3J5cHRvIGlzIGdyZWF0"));

      expect(result).eql("crypto is great");});


    it("should decode a padded base64 string", function () {
      var result = sharedUtils.Uint8ArrayToStr(sharedUtils.atob("Y3J5cHRvIGlzIGdyZWE="));

      expect(result).eql("crypto is grea");

      result = sharedUtils.Uint8ArrayToStr(sharedUtils.atob("Y3J5cHRvIGlzIGdyZQ=="));

      expect(result).eql("crypto is gre");});


    it("should decode a base64 string that has unicode characters", function () {
      var result = sharedUtils.Uint8ArrayToStr(sharedUtils.atob("77e9"));

      expect(result).eql("﷽");});});



  describe("#getOS", function () {
    it("should recognize the OSX userAgent string", function () {
      var UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:37.0) Gecko/20100101 Firefox/37.0";
      var result = sharedUtils.getOS(UA);

      expect(result).eql("intel mac os x");});


    it("should recognize the OSX userAgent string with version", function () {
      var UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:37.0) Gecko/20100101 Firefox/37.0";
      var result = sharedUtils.getOS(UA, true);

      expect(result).eql("intel mac os x 10.10");});


    it("should recognize the Windows userAgent string with version", function () {
      var UA = "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:10.0) Gecko/20100101 Firefox/10.0";
      var result = sharedUtils.getOS(UA, true);

      expect(result).eql("windows nt 6.1");});


    it("should recognize the Linux userAgent string", function () {
      var UA = "Mozilla/5.0 (X11; Linux i686 on x86_64; rv:10.0) Gecko/20100101 Firefox/10.0";
      var result = sharedUtils.getOS(UA);

      expect(result).eql("linux i686 on x86_64");});


    it("should recognize the OSX oscpu string", function () {
      var oscpu = "Intel Mac OS X 10.10";
      var result = sharedUtils.getOS(oscpu, true);

      expect(result).eql("intel mac os x 10.10");});


    it("should recognize the Windows oscpu string", function () {
      var oscpu = "Windows NT 5.3; Win64; x64";
      var result = sharedUtils.getOS(oscpu, true);

      expect(result).eql("windows nt 5.3");});});



  describe("#getOSVersion", function () {
    it("should fetch the correct version info for OSX", function () {
      var UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:37.0) Gecko/20100101 Firefox/37.0";
      var result = sharedUtils.getOSVersion(UA);

      expect(result).eql({ major: 10, minor: 10 });});


    it("should fetch the correct version info for Windows", function () {
      var UA = "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:10.0) Gecko/20100101 Firefox/10.0";
      var result = sharedUtils.getOSVersion(UA);

      expect(result).eql({ major: 6, minor: 1 });});


    it("should fetch the correct version info for Windows XP", function () {
      var oscpu = "Windows XP";
      var result = sharedUtils.getOSVersion(oscpu);

      expect(result).eql({ major: 5, minor: 2 });});


    it("should fetch the correct version info for Linux", function () {
      var UA = "Mozilla/5.0 (X11; Linux i686 on x86_64; rv:10.0) Gecko/20100101 Firefox/10.0";
      var result = sharedUtils.getOSVersion(UA);

      // Linux version can't be determined correctly.
      expect(result).eql({ major: Infinity, minor: 0 });});});



  describe("#getPlatform", function () {
    it("should recognize the OSX userAgent string", function () {
      var UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:37.0) Gecko/20100101 Firefox/37.0";
      var result = sharedUtils.getPlatform(UA);

      expect(result).eql("mac");});


    it("should recognize the Windows userAgent string", function () {
      var UA = "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:10.0) Gecko/20100101 Firefox/10.0";
      var result = sharedUtils.getPlatform(UA);

      expect(result).eql("win");});


    it("should recognize the Linux userAgent string", function () {
      var UA = "Mozilla/5.0 (X11; Linux i686 on x86_64; rv:10.0) Gecko/20100101 Firefox/10.0";
      var result = sharedUtils.getPlatform(UA);

      expect(result).eql("other");});


    it("should recognize the OSX oscpu string", function () {
      var oscpu = "Intel Mac OS X 10.10";
      var result = sharedUtils.getPlatform(oscpu);

      expect(result).eql("mac");});


    it("should recognize the Windows oscpu string", function () {
      var oscpu = "Windows NT 5.3; Win64; x64";
      var result = sharedUtils.getPlatform(oscpu);

      expect(result).eql("win");});});



  describe("#objectDiff", function () {
    var a, b, diff;

    afterEach(function () {
      a = b = diff = null;});


    it("should find object property additions", function () {
      a = { 
        prop1: null };

      b = { 
        prop1: null, 
        prop2: null };


      diff = sharedUtils.objectDiff(a, b);
      expect(diff.updated).to.eql([]);
      expect(diff.removed).to.eql([]);
      expect(diff.added).to.eql(["prop2"]);});


    it("should find object property value changes", function () {
      a = { 
        prop1: null };

      b = { 
        prop1: "null" };


      diff = sharedUtils.objectDiff(a, b);
      expect(diff.updated).to.eql(["prop1"]);
      expect(diff.removed).to.eql([]);
      expect(diff.added).to.eql([]);});


    it("should find object property removals", function () {
      a = { 
        prop1: null };

      b = {};

      diff = sharedUtils.objectDiff(a, b);
      expect(diff.updated).to.eql([]);
      expect(diff.removed).to.eql(["prop1"]);
      expect(diff.added).to.eql([]);});


    it("should report a mix of removed, added and updated properties", function () {
      a = { 
        prop1: null, 
        prop2: null };

      b = { 
        prop1: "null", 
        prop3: null };


      diff = sharedUtils.objectDiff(a, b);
      expect(diff.updated).to.eql(["prop1"]);
      expect(diff.removed).to.eql(["prop2"]);
      expect(diff.added).to.eql(["prop3"]);});});



  describe("#stripFalsyValues", function () {
    var obj;

    afterEach(function () {
      obj = null;});


    it("should strip falsy object property values", function () {
      obj = { 
        prop1: null, 
        prop2: false, 
        prop3: undefined, 
        prop4: void 0, // eslint-disable-line no-void
        prop5: "", 
        prop6: 0 };


      sharedUtils.stripFalsyValues(obj);
      expect(obj).to.eql({});});


    it("should keep non-falsy values", function () {
      obj = { 
        prop1: "null", 
        prop2: null, 
        prop3: true };


      sharedUtils.stripFalsyValues(obj);
      expect(obj).to.eql({ prop1: "null", prop3: true });});});});
