// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.


/**
 * @fileoverview Unit tests for Closure's base.js that require debug loading.
 */

goog.provide('goog.baseDebugLoaderTest');

goog.setTestOnly('goog.baseDebugLoaderTest');

goog.require('goog.functions');
goog.require('goog.testing.PropertyReplacer');
goog.require('goog.testing.jsunit');
goog.require('goog.testing.recordFunction');

var stubs = new goog.testing.PropertyReplacer();
var originalGoogBind = goog.bind;

function tearDown() {
  goog.setCssNameMapping(undefined);
  stubs.reset();
  goog.bind = originalGoogBind;
}

function testInHtmlDocument() {
  var savedGoogGlobal = goog.global;
  try {
    goog.global = {};
    assertFalse(goog.inHtmlDocument_());
    goog.global.document = {};
    assertFalse(goog.inHtmlDocument_());
    goog.global.document.write = function() {};
    assertTrue(goog.inHtmlDocument_());
  } finally {
    // Restore context to respect other tests.
    goog.global = savedGoogGlobal;
  }
}

/** @suppress {missingRequire|visibility} */
function testLoadInNonHtmlNotThrows() {
  var savedGoogGlobal = goog.global;
  try {
    goog.global = {};
    goog.global.document = {};
    assertFalse(goog.inHtmlDocument_());
    // The goog code which is executed at load.
    goog.findBasePath_();
    goog.debugLoader_.writeScriptTag_(goog.basePath + 'deps.js');
  } finally {
    // Restore context to respect other tests.
    goog.global = savedGoogGlobal;
  }
}

function testLoadBaseWithQueryParamOk() {
  var savedGoogGlobal = goog.global;
  try {
    goog.global = {};
    goog.global.document = {
      write: goog.nullFunction,
      getElementsByTagName:
          goog.functions.constant([{src: '/path/to/base.js?zx=5'}])
    };
    assertTrue(goog.inHtmlDocument_());
    goog.findBasePath_();
    assertEquals('/path/to/', goog.basePath);
  } finally {
    // Restore context to respect other tests.
    goog.global = savedGoogGlobal;
  }
}

function testLoadBaseFromGlobalVariableOk() {
  var savedGoogGlobal = goog.global;
  try {
    goog.global = {};
    goog.global.document = {
      write: goog.nullFunction,
      getElementsByTagName:
          goog.functions.constant([{src: '/path/to/base.js?zx=5'}])
    };
    goog.global.CLOSURE_BASE_PATH = '/from/constant/';
    goog.findBasePath_();
    assertEquals(goog.global.CLOSURE_BASE_PATH, goog.basePath);
  } finally {
    // Restore context to respect other tests.
    goog.global = savedGoogGlobal;
  }
}

function testLoadBaseFromGlobalVariableDOMClobbered() {
  var savedGoogGlobal = goog.global;
  try {
    goog.global = {};
    goog.global.document = {
      write: goog.nullFunction,
      getElementsByTagName:
          goog.functions.constant([{src: '/path/to/base.js?zx=5'}])
    };
    // Make goog.global.CLOSURE_BASE_PATH an object with a toString, like
    // it would be if it were a DOM clobbered HTMLElement.
    goog.global.CLOSURE_BASE_PATH = {};
    goog.global.CLOSURE_BASE_PATH.toString = function() {
      return '/from/constant/';
    };
    goog.findBasePath_();
    assertEquals('/path/to/', goog.basePath);
  } finally {
    // Restore context to respect other tests.
    goog.global = savedGoogGlobal;
  }
}

function testLoadBaseFromCurrentScriptIgnoringOthers() {
  var savedGoogGlobal = goog.global;
  try {
    goog.global = {};
    goog.global.document = {
      write: goog.nullFunction,
      currentScript: {src: '/currentScript/base.js?zx=5'},
      getElementsByTagName:
          goog.functions.constant([{src: '/path/to/base.js?zx=5'}])
    };
    goog.findBasePath_();
    assertEquals('/currentScript/', goog.basePath);
  } finally {
    // Restore context to respect other tests.
    goog.global = savedGoogGlobal;
  }
}


function testAddDependency() {
  /** @suppress {missingRequire} */
  stubs.set(goog.debugLoader_, 'writeScriptTag_', goog.nullFunction);

  goog.addDependency('foo.js', ['testDep.foo'], ['testDep.bar']);

  // alias to avoid the being picked up by the deps scanner.
  var provide = goog.provide;

  provide('testDep.bar');

  // To differentiate this call from the real one.
  var require = goog.require;

  // this used to throw an exception
  require('testDep.foo');

  assertTrue(goog.isObject(testDep.bar));

  // Unset provided namespace so the test can be re-run.
  testDep = undefined;
}


function testAddDependencyModule() {
  var load = goog.testing.recordFunction();
  stubs.set(goog.debugLoader_, 'writeScriptTag_', load);

  goog.addDependency('mod.js', ['testDep.mod'], [], true);
  goog.addDependency('empty.js', ['testDep.empty'], [], {});
  goog.addDependency('mod-goog.js', ['testDep.goog'], [], {'module': 'goog'});

  // To differentiate this call from the real one.
  var require = goog.require;

  var assertModuleLoad = function(module, args) {
    assertEquals(2, args.length);
    assertEquals('', args[0]);
    assertRegExp(
        '^goog\\.debugLoader_\\.retrieveAndExec_\\(".*/' + module +
            '", true, false\\);$',
        args[1]);
  };

  require('testDep.mod');
  assertEquals(1, load.getCallCount());
  assertModuleLoad('mod.js', load.getCalls()[0].getArguments());

  require('testDep.empty');
  assertEquals(2, load.getCallCount());
  assertEquals(2, load.getCalls()[1].getArguments().length);
  assertRegExp('^.*/empty.js$', load.getCalls()[1].getArguments()[0]);
  assertUndefined(load.getCalls()[1].getArguments()[1]);

  require('testDep.goog');
  assertEquals(3, load.getCallCount());
  assertModuleLoad('mod-goog.js', load.getCalls()[2].getArguments());

  // Unset provided namespace so the test can be re-run.
  testDep = undefined;
}

function testAddDependencyEs6() {
  var script = null;
  var requireTranspilation = false;
  stubs.set(goog.transpiler_, 'needsTranspile', function() {
    return requireTranspilation;
  });
  stubs.set(goog.debugLoader_, 'writeScriptTag_', function(src, scriptText) {
    if (script != null) {
      throw new Error('Multiple scripts written');
    }
    script = scriptText;
  });

  goog.addDependency(
      'fancy.js', ['testDep.fancy'], [],
      {'lang': 'es6-impl', 'module': 'goog'});
  goog.addDependency('super.js', ['testDep.superFancy'], [], {'lang': 'es6'});

  // To differentiate this call from the real one.
  var require = goog.require;

  requireTranspilation = false;
  require('testDep.fancy');
  assertRegExp(
      '^goog\\.debugLoader_\\.retrieveAndExec_\\(' +
          '".*\\/fancy\\.js", true, false\\);$',
      script);
  script = null;

  requireTranspilation = true;
  require('testDep.superFancy');
  assertRegExp(
      '^goog\\.debugLoader_\\.retrieveAndExec_\\(' +
          '".*\\/super\\.js", false, true\\);$',
      script);

  // Unset provided namespace so the test can be re-run.
  testDep = undefined;
}

function testLateRequireProtection() {
  if (!document.readyState) return;
  var e = assertThrows(function() {
    // To differentiate this call from the real one.
    var require = goog.require;
    require('goog.ui.Component');
  });

  assertContains('after document load', e.message);
}
