alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

What this is

This file is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Other links

The source code

/* @author Edward Hieatt, edward@jsunit.net */

function jsUnitTestManager()
{
  this._windowForAllProblemMessages = null;


  this.container            = top.frames.testContainer
  this.documentLoader       = top.frames.documentLoader;
  this.mainFrame            = top.frames.mainFrame;

  this.containerController = this.container.frames.testContainerController;
  this.containerTestFrame  = this.container.frames.testFrame;

  var mainData             = this.mainFrame.frames.mainData;

  // form elements on mainData frame
  this.testFileName        = mainData.document.testRunnerForm.testFileName;
  this.runButton           = mainData.document.testRunnerForm.runButton;
  this.traceLevel          = mainData.document.testRunnerForm.traceLevel;
  this.closeTraceWindowOnNewRun = mainData.document.testRunnerForm.closeTraceWindowOnNewRun;
  this.timeout             = mainData.document.testRunnerForm.timeout;
  this.setUpPageTimeout      = mainData.document.testRunnerForm.setUpPageTimeout;

  // image output
  this.progressBar         = this.mainFrame.frames.mainProgress.document.progress;

  this.problemsListField           = this.mainFrame.frames.mainErrors.document.testRunnerForm.problemsList;
  this.testCaseResultsField        = this.mainFrame.frames.mainResults.document.resultsForm.testCases;  
  this.resultsTimeField			   = this.mainFrame.frames.mainResults.document.resultsForm.time;  

  // 'layer' output frames
  this.uiFrames                    = new Object();
  this.uiFrames.mainStatus         = this.mainFrame.frames.mainStatus;

  var mainCounts                   = this.mainFrame.frames.mainCounts;

  this.uiFrames.mainCountsErrors   = mainCounts.frames.mainCountsErrors;
  this.uiFrames.mainCountsFailures = mainCounts.frames.mainCountsFailures;
  this.uiFrames.mainCountsRuns     = mainCounts.frames.mainCountsRuns;
  this._baseURL = "";

  this.setup();
}

// seconds to wait for each test page to load
jsUnitTestManager.TESTPAGE_WAIT_SEC  = 20;
jsUnitTestManager.TIMEOUT_LENGTH     = 20;

// seconds to wait for setUpPage to complete
jsUnitTestManager.SETUPPAGE_TIMEOUT    = 60; 

// milliseconds to wait between polls on setUpPages
jsUnitTestManager.SETUPPAGE_INTERVAL   = 100;

jsUnitTestManager.prototype.setup = function ()
{
  this.totalCount    = 0;
  this.errorCount    = 0;
  this.failureCount  = 0;
  this._suiteStack   = Array();


  var initialSuite   = new top.jsUnitTestSuite();
  push(this._suiteStack, initialSuite);
}

jsUnitTestManager.prototype.start = function () 
{
  this._baseURL = this.resolveUserEnteredTestFileName();
  var firstQuery = this._baseURL.indexOf("?");
  if (firstQuery >= 0) {
       this._baseURL = this._baseURL.substring(0, firstQuery);
  }
  var lastSlash = this._baseURL.lastIndexOf("/");
  var lastRevSlash = this._baseURL.lastIndexOf("\\");
  if (lastRevSlash > lastSlash) {
     lastSlash = lastRevSlash;
  }
  if (lastSlash > 0) {
     this._baseURL = this._baseURL.substring(0, lastSlash + 1);
  }

  this._timeRunStarted = new Date();
  this.initialize();
  setTimeout('top.testManager._nextPage();', jsUnitTestManager.TIMEOUT_LENGTH);
}

jsUnitTestManager.prototype.getBaseURL = function () {
  return this._baseURL;
}

jsUnitTestManager.prototype.doneLoadingPage = function (pageName) 
{
  //this.containerTestFrame.setTracer(top.tracer);
  this._testFileName = pageName;
  if (this.isTestPageSuite()) 
    this._handleNewSuite();
  else
  {
    this._testIndex   = 0;
    this._testsInPage = this.getTestFunctionNames();
    this._numberOfTestsInPage = this._testsInPage.length;
    this._runTest();
  }
}

jsUnitTestManager.prototype._handleNewSuite = function () 
{
  var allegedSuite = this.containerTestFrame.suite();
  if (allegedSuite.isjsUnitTestSuite) {
    var newSuite = allegedSuite.clone();
    if (newSuite.containsTestPages())
      push(this._suiteStack, newSuite);
    this._nextPage();
  }
  else {
    alert('Invalid test suite in file ' + this._testFileName);
    this.abort();
  }
}

jsUnitTestManager.prototype._runTest = function () 
{
  if (this._testIndex + 1 > this._numberOfTestsInPage)
  {
    this._nextPage();
    return;
  }

  if (this._testIndex == 0 && typeof(this.containerTestFrame.setUpPage) == 'function')
  {
    // first test for this page and a setUpPage is defined
    if (typeof(this.containerTestFrame.setUpPageStatus) == 'undefined')
    {
      // setUpPage() not called yet, so call it
      this.containerTestFrame.setUpPageStatus = false;
      this.containerTestFrame.startTime = new Date();
      this.containerTestFrame.setUpPage();
      // try test again later
      setTimeout('top.testManager._runTest()', jsUnitTestManager.SETUPPAGE_INTERVAL);
      return;
    }

    if (this.containerTestFrame.setUpPageStatus != 'complete')
    {
      // setUpPage called, but not complete yet
      top.status = 'setUpPage not completed... ' + this.containerTestFrame.setUpPageStatus + ' ' + (new Date());
      if ((new Date() - this.containerTestFrame.startTime) /1000 > this.getsetUpPageTimeout()) {
        alert('setUpPage timed out without completing.');
        if (prompt('Retry or Cancel ?', 'Retry') != 'Retry')
        {
          this.abort();
          return;
        }
        this.containerTestFrame.startTime = (new Date());
      }
      // try test again later
      setTimeout('top.testManager._runTest()', jsUnitTestManager.SETUPPAGE_INTERVAL);
      return;
    }
  }

  top.status = '';
  // either not first test, or no setUpPage defined, or setUpPage completed
  this.executeTestFunction(this._testsInPage[this._testIndex]);
  this.totalCount++;
  this.updateProgressIndicators();
  this._testIndex++;
  setTimeout('top.testManager._runTest()', jsUnitTestManager.TIMEOUT_LENGTH);
}

jsUnitTestManager.prototype._done = function () 
{
  var secondsSinceRunBegan=(new Date() - this._timeRunStarted)/1000;
  this.setStatus('Done (' + secondsSinceRunBegan + ' seconds)');
  this._cleanUp();
  if (top.shouldSubmitResults()) {
    this.resultsTimeField.value = secondsSinceRunBegan;
  	top.submitResults();
  }
}

jsUnitTestManager.prototype._nextPage = function () 
{
  if (this._currentSuite().hasMorePages()) {
    this.loadPage(this._currentSuite().nextPage());
  }
  else {
    pop(this._suiteStack);
    if (this._currentSuite() == null)
      this._done();
    else
      this._nextPage();
  }
}

jsUnitTestManager.prototype._currentSuite = function () 
{
  var suite = null;

  if (this._suiteStack && this._suiteStack.length > 0)
    suite = this._suiteStack[this._suiteStack.length-1];

  return suite;
}

jsUnitTestManager.prototype.calculateProgressBarProportion = function () 
{
  if (this.totalCount == 0) 
    return 0;
  var currentDivisor = 1;
  var result         = 0;
  
  for (var i = 0; i < this._suiteStack.length; i++) {
    var aSuite     = this._suiteStack[i];
    currentDivisor *= aSuite.testPages.length;
    result += (aSuite.pageIndex - 1)/currentDivisor;
  }
  result += (this._testIndex + 1)/(this._numberOfTestsInPage * currentDivisor);
  return result;
}

jsUnitTestManager.prototype._cleanUp = function () 
{
  this.containerController.setTestPage('./app/emptyPage.html');
  this.finalize();
  top.tracer.finalize();
}

jsUnitTestManager.prototype.abort = function () 
{
  this.setStatus('Aborted');
  this._cleanUp();
}

jsUnitTestManager.prototype.getTimeout = function () 
{
  var result = jsUnitTestManager.TESTPAGE_WAIT_SEC;
  try {
    result = eval(this.timeout.value);
  } 
  catch (e) {
  }
  return result;
}

jsUnitTestManager.prototype.getsetUpPageTimeout = function () 
{
  var result = jsUnitTestManager.SETUPPAGE_TIMEOUT;
  try {
    result = eval(this.setUpPageTimeout.value);
  } 
  catch (e) {
  }
  return result;
}

jsUnitTestManager.prototype.isTestPageSuite = function () 
{
  var result = false;
  if (typeof(this.containerTestFrame.suite) == 'function')
  {
    result = true;
  }
  return result;
}

jsUnitTestManager.prototype.getTestFunctionNames = function () 
{
  var testFrame         = this.containerTestFrame;
  var testFunctionNames = new Array();
  var i;
  
  if (testFrame && typeof(testFrame.exposeTestFunctionNames) == 'function')
        return testFrame.exposeTestFunctionNames();
  
  if (testFrame && 
      testFrame.document && 
      typeof(testFrame.document.scripts) != 'undefined') { // IE5 and up
    var scriptsInTestFrame = testFrame.document.scripts;
    
    for (i = 0; i < scriptsInTestFrame.length; i++) {
      var someNames = this._extractTestFunctionNamesFromScript(scriptsInTestFrame[i]);
      if (someNames)
        testFunctionNames=testFunctionNames.concat(someNames);
    }
  } 
  else {
    for (i in testFrame) {
      if (i.substring(0, 4) == 'test' && typeof(testFrame[i]) == 'function')
        push(testFunctionNames, i);
    }
  }
  return testFunctionNames;
}

jsUnitTestManager.prototype._extractTestFunctionNamesFromScript = function (aScript) 
{
  var result;
  var remainingScriptToInspect = aScript.text;
  var currentIndex             = remainingScriptToInspect.indexOf('function test');
  while (currentIndex != -1) {
    if (!result) 
      result=new Array();
      
    var fragment = remainingScriptToInspect.substring(currentIndex, remainingScriptToInspect.length);
    result       = result.concat(fragment.substring('function '.length, fragment.indexOf('(')));
                remainingScriptToInspect=remainingScriptToInspect.substring(currentIndex+12, remainingScriptToInspect.length);
                currentIndex=remainingScriptToInspect.indexOf('function test');
  }
  return result;
}

jsUnitTestManager.prototype.loadPage = function (testFileName) 
{
  this._testFileName         = testFileName;
  this._loadAttemptStartTime = new Date();
  this.setStatus('Opening Test Page "' + this._testFileName + '"');
  this.containerController.setTestPage(this._testFileName);
  this._callBackWhenPageIsLoaded();
}

jsUnitTestManager.prototype._callBackWhenPageIsLoaded = function () 
{
  if ((new Date() - this._loadAttemptStartTime) / 1000 > this.getTimeout()) {
    alert('Reading Test Page ' + this._testFileName + ' timed out.\nMake sure that the file exists and is a Test Page.');
    if (prompt('Retry or Cancel ?', 'Retry') != 'Retry')
    {
      this.abort();
      return;
    }
  }
  if (!this._isTestFrameLoaded()) {
    setTimeout('top.testManager._callBackWhenPageIsLoaded();', jsUnitTestManager.TIMEOUT_LENGTH);
    return;
  }
  this.doneLoadingPage(this._testFileName);
}

jsUnitTestManager.prototype._isTestFrameLoaded = function () 
{
  try {
    return this.containerController.isPageLoaded();
  } 
  catch (e) {
  }
  return false;
}

jsUnitTestManager.prototype.executeTestFunction = function (functionName) 
{
  this._testFunctionName=functionName;
  this.setStatus('Running test "' + this._testFunctionName + '"');
  var excep=null;
  var timeBefore = new Date();  
  try {
    this.containerTestFrame.setUp();
    eval('this.containerTestFrame.' + this._testFunctionName + '();');
  } 
  catch (e1) {
    excep = e1;
  }
  finally {
    try {
      this.containerTestFrame.tearDown();
    } 
    catch (e2) {
      excep = e2;
    }
  }
  var timeTaken = (new Date() - timeBefore) / 1000;
  if (excep != null)
    this._handleTestException(excep);
  var serializedTestCaseString = this._fullyQualifiedCurrentTestFunctionName()+"|"+timeTaken+"|";
  if (excep==null)
  	serializedTestCaseString+="S||";
  else {
  	if (typeof(excep.isJsUnitException) != 'undefined' && excep.isJsUnitException)
  		serializedTestCaseString+="F|";
  	else {
  		serializedTestCaseString+="E|";
  	}
  	serializedTestCaseString+=this._problemDetailMessageFor(excep);
  }  	
  var newOption = new Option(serializedTestCaseString);
  this.testCaseResultsField[this.testCaseResultsField.length]=newOption;  
}

jsUnitTestManager.prototype._fullyQualifiedCurrentTestFunctionName = function() {
    var testURL = this.containerTestFrame.location.href;
    var testQuery = testURL.indexOf("?");
    if (testQuery >= 0) {
        testURL = testURL.substring(0, testQuery);
    }
    if (testURL.substring(0, this._baseURL.length) == this._baseURL) {
          testURL = testURL.substring(this._baseURL.length);
    }
    return testURL + ':' + this._testFunctionName;
}


jsUnitTestManager.prototype._handleTestException = function (excep) 
{
  var problemMessage = this._fullyQualifiedCurrentTestFunctionName() + ' ';
  var errOption;
  if (typeof(excep.isJsUnitException) == 'undefined' || !excep.isJsUnitException) {
    problemMessage += 'had an error';
    this.errorCount++;
  } 
  else {
    problemMessage += 'failed';
    this.failureCount++;
  }
  var listField = this.problemsListField;
  var problemDocument = this.mainFrame.frames.mainErrors.document;
  if (typeof(problemDocument.createElement) != 'undefined') {
    // DOM Level 2 HTML method.
    // this is required for Opera 7 since appending to the end of the 
    // options array does not work, and adding an Option created by new Option()
    // and appended by listField.options.add() fails due to WRONG_DOCUMENT_ERR
    errOption = problemDocument.createElement('option');
    errOption.setAttribute('value', this._problemDetailMessageFor(excep));
    errOption.appendChild(problemDocument.createTextNode(problemMessage));
    listField.appendChild(errOption);
  }
  else {
    // new Option() is DOM 0
    errOption = new Option(problemMessage, this._problemDetailMessageFor(excep));
    if (typeof(listField.add) != 'undefined') {
      // DOM 2 HTML 
      listField.add( errOption , null);
    }
    else if (typeof(listField.options.add) != 'undefined') {
      // DOM 0
      listField.options.add( errOption, null);
    }
    else {
      // DOM 0
      listField.options[listField.length]= errOption;
    }
  }
}

jsUnitTestManager.prototype._problemDetailMessageFor = function (excep) 
{
  var result=null;
  if (typeof(excep.isJsUnitException) != 'undefined' && excep.isJsUnitException) {
    result = '';
    if (excep.comment != null)
      result+=('"'+excep.comment+'"\n');
    
    result += excep.jsUnitMessage;
    
    if (excep.stackTrace)
      result+='\n\nStack trace follows:\n'+excep.stackTrace;
  }
  else {
    result = 'Error message is:\n"';
    result +=
      (typeof(excep.description) == 'undefined') ?
      excep :
      excep.description;
    result += '"';
    if (typeof(excep.stack) != 'undefined') // Mozilla only
      result+='\n\nStack trace follows:\n'+excep.stack;
  }
  return result;
}

jsUnitTestManager.prototype._setTextOnLayer = function (layerName, str)
{
  var html = '';
  html += '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">';
  html += '<html><\/head>';
  html += '<body>
'; html += str; html += '<\/div><\/body>'; html += '<\/html>'; this.uiFrames[layerName].document.write(html); this.uiFrames[layerName].document.close(); } jsUnitTestManager.prototype.setStatus = function (str) { this._setTextOnLayer('mainStatus', '<b>Status:<\/b> '+str); } jsUnitTestManager.prototype._setErrors = function (n) { this._setTextOnLayer('mainCountsErrors', '<b>Errors: <\/b>' + n); } jsUnitTestManager.prototype._setFailures = function (n) { this._setTextOnLayer('mainCountsFailures', '<b>Failures:<\/b> ' + n); } jsUnitTestManager.prototype._setTotal = function (n) { this._setTextOnLayer('mainCountsRuns', '<b>Runs:<\/b> ' + n); } jsUnitTestManager.prototype._setProgressBarImage = function (imgName) { this.progressBar.src=imgName; } jsUnitTestManager.prototype._setProgressBarWidth = function (w) { this.progressBar.width=w; } jsUnitTestManager.prototype.updateProgressIndicators = function () { this._setTotal(this.totalCount); this._setErrors(this.errorCount); this._setFailures(this.failureCount); this._setProgressBarWidth(300 * this.calculateProgressBarProportion()); if (this.errorCount > 0 || this.failureCount > 0) this._setProgressBarImage('../images/red.gif'); else this._setProgressBarImage('../images/green.gif'); } jsUnitTestManager.prototype.showMessageForSelectedProblemTest = function () { var problemTestIndex = this.problemsListField.selectedIndex; if (problemTestIndex != -1) alert(this.problemsListField[problemTestIndex].value); } jsUnitTestManager.prototype.showMessagesForAllProblemTests = function () { if (this.problemsListField.length == 0) return; try { if (this._windowForAllProblemMessages && !this._windowForAllProblemMessages.closed) this._windowForAllProblemMessages.close(); } catch(e) { } this._windowForAllProblemMessages = window.open('','','width=600, height=350,status=no,resizable=yes,scrollbars=yes'); var resDoc = this._windowForAllProblemMessages.document; resDoc.write('<html>Tests with problems - JsUnit<\/title><head><body>'); resDoc.write('<p class="jsUnitSubHeading">Tests with problems (' + this.problemsListField.length + ' total) - JsUnit<\/p>'); resDoc.write('<p class="jsUnitSubSubHeading"><i>Running on '+navigator.userAgent+'</i></p>'); for (var i = 0; i < this.problemsListField.length; i++) { resDoc.write('<p class="jsUnitDefault">'); resDoc.write('<b>' + (i + 1) + '. '); resDoc.write(this.problemsListField[i].text); resDoc.write('<\/b><\/p><p><pre>'); resDoc.write(this.problemsListField[i].value); resDoc.write('<\/pre><\/p>'); } resDoc.write('<\/body><\/html>'); resDoc.close(); } jsUnitTestManager.prototype._clearProblemsList = function () { var listField = this.problemsListField; var initialLength=listField.options.length; for (var i = 0; i < initialLength; i++) listField.remove(0); } jsUnitTestManager.prototype.initialize = function () { this.setStatus('Initializing...'); this._setRunButtonEnabled(false); this._clearProblemsList(); this.updateProgressIndicators(); this.setStatus('Done initializing'); } jsUnitTestManager.prototype.finalize = function () { this._setRunButtonEnabled(true); } jsUnitTestManager.prototype._setRunButtonEnabled = function (b) { this.runButton.disabled = !b; } jsUnitTestManager.prototype.getTestFileName = function () { var rawEnteredFileName = this.testFileName.value; var result = rawEnteredFileName; while (result.indexOf('\\') != -1) result = result.replace('\\', '/'); return result; } jsUnitTestManager.prototype.resolveUserEnteredTestFileName = function (rawText) { var userEnteredTestFileName = top.testManager.getTestFileName(); // only test for file:// since Opera uses a different format if (userEnteredTestFileName.indexOf('http://') == 0 || userEnteredTestFileName.indexOf('https://') == 0 || userEnteredTestFileName.indexOf('file://') == 0) return userEnteredTestFileName; return getTestFileProtocol() + this.getTestFileName(); } function getTestFileProtocol() { return getDocumentProtocol(); } function getDocumentProtocol() { var protocol = top.document.location.protocol; if (protocol == "file:") return "file:///"; if (protocol == "http:") return "http://"; if (protocol == 'https:') return 'https://'; if (protocol == "chrome:") return "chrome://"; return null; } function isBeingRunOverHTTP() { return getDocumentProtocol()=="http://"; } function getWebserver() { if (isBeingRunOverHTTP()) { var myUrl = location.href; var myUrlWithProtocolStripped = myUrl.substring(myUrl.indexOf("/") + 2); return myUrlWithProtocolStripped.substring(0, myUrlWithProtocolStripped.indexOf("/")); } return null; } // the functions push(anArray, anObject) and pop(anArray) // exist because the JavaScript Array.push(anObject) and Array.pop() // functions are not available in IE 5.0 function push(anArray, anObject) { anArray[anArray.length]=anObject; } function pop(anArray) { if (anArray.length>=1) { delete anArray[anArray.length - 1]; anArray.length--; } } if (xbDEBUG.on) { xbDebugTraceObject('window', 'jsUnitTestManager'); xbDebugTraceFunction('window', 'getTestFileProtocol'); xbDebugTraceFunction('window', 'getDocumentProtocol'); } </pre> </td> </tr> </table> </div> <div style="padding-top: 1em; width: 310px; margin-left: auto; margin-right: auto; table {border-collapse: collapse; border: none;}; tr {border-collapse: collapse; border: none; text-align: center;};"> <table width="100%" cellspacing="0" cellpadding="0"> <tr> <td colspan="2" style="border-collapse: collapse; border: none; text-align: center;};"> <em>... this post is sponsored by my books ...</em> </td> </tr> <tr> <td width="150" style="border-collapse: collapse; border: none; text-align: center;};"> <a href="https://kbhr.co/ckbk-v2"><img src="/images/books/scala-cookbook-v2-cover-220h.jpg" title="The Scala Cookbook, by Alvin Alexander" height="220" /> <br /><span style="opacity: 0.4;">#1 New Release!</span></a> </td> <td width="150" style="border-collapse: collapse; border: none; text-align: center; padding-left: 8px;"> <a href="http://kbhr.co/fps-book"><img src="/images/books/functional-programming-simplified-small.jpg" title="Functional Programming, Simplified, by Alvin Alexander" height="220" /> <br /><span style="opacity: 0.4;">FP Best Seller</span></a> </td> </tr> </table> <p> </p> </div> <div id="whats_new"> <h2>new blog posts</h2> <div id="whats_new_list"> <ul> <li><a class="whats_new_link" href="/java/java-current-date-example-now">Java: How to get the current date (and time) in Java 8, 11, 14, 17, etc.</a></li> <li><a class="whats_new_link" href="/photos/coldplays-chris-martin-so-happy-be-alive">Coldplay'''s Chris Martin: '''So happy to be alive'''</a></li> <li><a class="whats_new_link" href="/misc/listen-ears-tolerance-eyes-compassion-love-rumi">Listen with ears of tolerance, see through eyes of compassion</a></li> <br/> <li><a class="whats_new_link" href="/misc/dropping-keys-by-hafez">Dropping Keys, by Hafez</a></li> <li><a class="whats_new_link" href="/personal/mindfulness-purposes-motivations-why-be-mindful">The purposes of mindfulness (or, why bother being mindful, and motivation)</a></li> <li><a class="whats_new_link" href="/java/how-round-double-float-two-decimal-places-format-output">Java: How to round a double (or float) to two decimal places? (formatting output)</a></li> <br/> <li><a class="whats_new_link" href="/misc/rumi-my-religion-is-love-quote-poem">Rumi: My religion is love</a></li> <li><a class="whats_new_link" href="/personal/ram-dass-grist-for-the-mill-book-tv-channels-karma">Ram Dass'''s Grist for the Mill</a></li> <li><a class="whats_new_link" href="/personal/elton-john-tiny-dancer-music-video-story-meaning">Elton John'''s Tiny Dancer music video: Its story and meaning</a></li> <br/> <br/> </div> </ul> </div> <p> </p> <p align="center"><font color="#000000" size="2" face="Verdana,Arial">Copyright 1998-2021 Alvin Alexander, alvinalexander.com<br/> All Rights Reserved.<br/> <br/> A percentage of advertising revenue from<br/> pages under the <a href="/java/jwarehouse">/java/jwarehouse</a> URI on this website is<br/> paid back to open source projects.</p> <script> shuffle(books); var div = document.getElementById("leftcol"); var pre = '<div style="margin: 0; padding-right: 1.6em"><h2 align="center">favorite books</h2>'; var post = '</div>'; if (adblock) { var str = books.slice(0,3).join(" "); div.insertAdjacentHTML('beforeend', pre + str + post); } else { var str = books.slice(0,1).join(" "); div.insertAdjacentHTML('beforeend', pre + str + post); } </script> <p style="padding-bottom: 80px;"> </p> </body>