From 766e67b615b16f1f552925038f9052f12ac2a989 Mon Sep 17 00:00:00 2001 From: Vishal Date: Thu, 29 Jun 2017 15:02:18 +0530 Subject: [PATCH 01/41] Image in contact list view --- frappe/contacts/doctype/contact/contact_list.js | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 frappe/contacts/doctype/contact/contact_list.js diff --git a/frappe/contacts/doctype/contact/contact_list.js b/frappe/contacts/doctype/contact/contact_list.js new file mode 100644 index 0000000000..a93b3f0d73 --- /dev/null +++ b/frappe/contacts/doctype/contact/contact_list.js @@ -0,0 +1,3 @@ +frappe.listview_settings['Contact'] = { + add_fields: ["image"], +}; \ No newline at end of file From f409fd735888c8aa85a3dc0dc88aa2017ffdd5cd Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Mon, 3 Jul 2017 11:53:00 +0530 Subject: [PATCH 02/41] [ui-tests] python is back! (#3565) * [ui-tests] python is back! * [minor] remove old test * [test] dont test test_runner * [tests] try firefox * [tests] try chrome * [tests] try chrome * [tests] try chrome * [tests] try chrome 1 * [tests] try chrome 2 * [tests] try chrome 3 * [tests] try phantomJS * [tests] try chrome * [tests] try chrome * [tests] try chrome * [tests] try chrome * [tests] try chrome * [tests] try chrome * [tests] try chrome * [tests] try chrome * [tests] login click button * [tests] login click button * [tests] show log * [test] test with start_maximized * [test] test only login * [travis] test another port for selenium * [travis] try running ui tests after unittests are done * [travis] pring body_div if fails * [tests] complete setup wizard for frappe * [minor] move ui tests to frappe/ui/tests * [tests] ui tests in public and codacy fixes * [fix] tests + eslint * [minor] move tests to tests/ui folder and print console after print * [fix] linting * [tests] added documentation and better integration testing * [promise] form triggering is now promise based * [test] * [test] * [test] * [test] * [test] print output * [minor] default empty in select and print console * [cleanup] more minor fixes * [enhance] first-cut done! * [minor] frappe.run_serially to pass arguments while chaining --- .eslintrc | 3 +- .travis.yml | 28 +- frappe/commands/utils.py | 7 +- frappe/core/doctype/test_runner/__init__.py | 0 .../core/doctype/test_runner/test_runner.js | 63 + .../core/doctype/test_runner/test_runner.json | 122 + .../core/doctype/test_runner/test_runner.py | 27 + .../img/app-development/test-runner.png | Bin 0 -> 264454 bytes .../user/en/guides/automated-testing/index.md | 7 + .../en/guides/automated-testing/index.txt | 3 + .../automated-testing/integration-testing.md | 49 + .../guides/automated-testing/qunit-testing.md | 46 + .../unit-testing.md} | 38 +- frappe/modules/utils.py | 4 +- frappe/nightwatch.global.js | 12 - frappe/nightwatch.js | 96 - frappe/public/build.json | 3 + frappe/public/js/frappe/dom.js | 17 + frappe/public/js/frappe/form/control.js | 79 +- frappe/public/js/frappe/form/grid.js | 721 +- frappe/public/js/frappe/form/grid_row.js | 586 ++ frappe/public/js/frappe/form/grid_row_form.js | 97 + frappe/public/js/frappe/form/quick_entry.js | 136 +- frappe/public/js/frappe/form/save.js | 7 +- .../public/js/frappe/form/script_manager.js | 78 +- frappe/public/js/frappe/model/create_new.js | 36 +- frappe/public/js/frappe/model/meta.js | 3 +- frappe/public/js/frappe/model/model.js | 37 +- frappe/public/js/frappe/provide.js | 2 +- frappe/public/js/frappe/request.js | 29 +- frappe/public/js/frappe/router.js | 41 +- frappe/public/js/frappe/ui/base_list.js | 9 +- frappe/public/js/frappe/ui/field_group.js | 17 +- .../js/frappe/ui/filters/edit_filter.html | 7 +- frappe/public/js/frappe/ui/find.js | 16 + frappe/public/js/frappe/views/test_runner.js | 27 - frappe/public/js/legacy/clientscriptAPI.js | 6 +- frappe/public/js/legacy/form.js | 72 +- frappe/public/js/lib/jquery/qunit.css | 35 +- frappe/public/js/lib/jquery/qunit.js | 9321 +++++++++-------- frappe/test_runner.py | 20 +- frappe/tests/test_client_login.py | 27 - frappe/tests/ui/__init__.py | 0 frappe/tests/ui/login.js | 19 - frappe/tests/ui/test_lib.js | 93 + frappe/tests/ui/test_list.js | 33 + frappe/tests/ui/test_test_runner.py | 17 + frappe/tests/ui/test_todo.py | 50 + frappe/utils/install.py | 16 + frappe/utils/nestedset.py | 2 +- frappe/utils/sel.py | 187 - frappe/utils/selenium_testdriver.py | 260 + 52 files changed, 6885 insertions(+), 5726 deletions(-) create mode 100644 frappe/core/doctype/test_runner/__init__.py create mode 100644 frappe/core/doctype/test_runner/test_runner.js create mode 100644 frappe/core/doctype/test_runner/test_runner.json create mode 100644 frappe/core/doctype/test_runner/test_runner.py create mode 100644 frappe/docs/assets/img/app-development/test-runner.png create mode 100644 frappe/docs/user/en/guides/automated-testing/index.md create mode 100644 frappe/docs/user/en/guides/automated-testing/index.txt create mode 100644 frappe/docs/user/en/guides/automated-testing/integration-testing.md create mode 100644 frappe/docs/user/en/guides/automated-testing/qunit-testing.md rename frappe/docs/user/en/guides/{basics/writing-tests.md => automated-testing/unit-testing.md} (93%) delete mode 100644 frappe/nightwatch.global.js delete mode 100644 frappe/nightwatch.js create mode 100644 frappe/public/js/frappe/form/grid_row.js create mode 100644 frappe/public/js/frappe/form/grid_row_form.js create mode 100644 frappe/public/js/frappe/ui/find.js delete mode 100644 frappe/public/js/frappe/views/test_runner.js delete mode 100644 frappe/tests/test_client_login.py create mode 100644 frappe/tests/ui/__init__.py delete mode 100644 frappe/tests/ui/login.js create mode 100644 frappe/tests/ui/test_lib.js create mode 100644 frappe/tests/ui/test_list.js create mode 100644 frappe/tests/ui/test_test_runner.py create mode 100644 frappe/tests/ui/test_todo.py delete mode 100644 frappe/utils/sel.py create mode 100644 frappe/utils/selenium_testdriver.py diff --git a/.eslintrc b/.eslintrc index c8efd4375e..44af7b458f 100644 --- a/.eslintrc +++ b/.eslintrc @@ -117,6 +117,7 @@ "set_field_options": true, "getCookie": true, "getCookies": true, - "get_url_arg": true + "get_url_arg": true, + "QUnit": true } } diff --git a/.travis.yml b/.travis.yml index 0b9f8293df..90def73b8b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,23 +2,22 @@ language: python dist: trusty group: deprecated-2017Q2 -python: - - "2.7" - addons: apt: sources: - google-chrome packages: - google-chrome-stable + # sauce_connect: + # username: "rmehta1" + # access_key: "a80640ec-24c8-44ad-9398-1b6f123ae4a1" + +python: + - "2.7" services: - mysql -before_install: - - export DISPLAY=:99.0 - - sh -e /etc/init.d/xvfb start - install: - sudo apt-get purge -y mysql-common mysql-server mysql-client - nvm install v7.10.0 @@ -31,10 +30,19 @@ install: - cp -r $TRAVIS_BUILD_DIR/test_sites/test_site ~/frappe-bench/sites/ before_script: + - wget http://chromedriver.storage.googleapis.com/2.27/chromedriver_linux64.zip + - unzip chromedriver_linux64.zip + - sudo apt-get install libnss3 + - sudo apt-get --only-upgrade install google-chrome-stable + - sudo cp chromedriver /usr/local/bin/. + - sudo chmod +x /usr/local/bin/chromedriver + - export DISPLAY=:99.0 + - sh -e /etc/init.d/xvfb start + - sleep 3 - mysql -u root -ptravis -e 'create database test_frappe' - echo "USE mysql;\nCREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe';\nFLUSH PRIVILEGES;\n" | mysql -u root -ptravis - echo "USE mysql;\nGRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost';\n" | mysql -u root -ptravis - + - cd ~/frappe-bench - bench use test_site - bench reinstall --yes @@ -44,5 +52,5 @@ before_script: script: - set -e - bench --verbose run-tests - - bench reinstall --yes - - bench run-ui-tests --ci + - sleep 5 + - bench --verbose run-tests --ui-tests diff --git a/frappe/commands/utils.py b/frappe/commands/utils.py index 550073f663..9cabd36c75 100644 --- a/frappe/commands/utils.py +++ b/frappe/commands/utils.py @@ -298,11 +298,13 @@ def console(context): @click.option('--doctype', help="For DocType") @click.option('--test', multiple=True, help="Specific test") @click.option('--driver', help="For Travis") +@click.option('--ui-tests', is_flag=True, default=False, help="Run UI Tests") @click.option('--module', help="Run tests in a module") @click.option('--profile', is_flag=True, default=False) @click.option('--junit-xml-output', help="Destination file path for junit xml report") @pass_context -def run_tests(context, app=None, module=None, doctype=None, test=(), driver=None, profile=False, junit_xml_output=False): +def run_tests(context, app=None, module=None, doctype=None, test=(), + driver=None, profile=False, junit_xml_output=False, ui_tests = False): "Run tests" import frappe.test_runner tests = test @@ -311,7 +313,8 @@ def run_tests(context, app=None, module=None, doctype=None, test=(), driver=None frappe.init(site=site) ret = frappe.test_runner.main(app, module, doctype, context.verbose, tests=tests, - force=context.force, profile=profile, junit_xml_output=junit_xml_output) + force=context.force, profile=profile, junit_xml_output=junit_xml_output, + ui_tests = ui_tests) if len(ret.failures) == 0 and len(ret.errors) == 0: ret = 0 diff --git a/frappe/core/doctype/test_runner/__init__.py b/frappe/core/doctype/test_runner/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/core/doctype/test_runner/test_runner.js b/frappe/core/doctype/test_runner/test_runner.js new file mode 100644 index 0000000000..477d8903de --- /dev/null +++ b/frappe/core/doctype/test_runner/test_runner.js @@ -0,0 +1,63 @@ +// Copyright (c) 2017, Frappe Technologies and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Test Runner', { + refresh: (frm) => { + frm.disable_save(); + frm.page.set_primary_action(__("Run Tests"), () => { + return new Promise(resolve => { + let wrapper = $(frm.fields_dict.output.wrapper).empty(); + $("

Loading...

").appendTo(wrapper); + + // all tests + frappe.call({ + method: 'frappe.core.doctype.test_runner.test_runner.get_all_tests' + }).always((data) => { + $("
").appendTo(wrapper.empty()); + frm.events.run_tests(frm, data.message); + resolve(); + }); + }); + }); + + }, + run_tests: function(frm, files) { + let require_list = [ + "assets/frappe/js/lib/jquery/qunit.js", + "assets/frappe/js/lib/jquery/qunit.css" + ].concat(); + + frappe.require(require_list, () => { + files.forEach((f) => { + frappe.dom.eval(f.script); + }); + + // if(frm.doc.module_name) { + // QUnit.module.only(frm.doc.module_name); + // } + + QUnit.testDone(function(details) { + var result = { + "Module name": details.module, + "Test name": details.name, + "Assertions": { + "Total": details.total, + "Passed": details.passed, + "Failed": details.failed + }, + "Skipped": details.skipped, + "Todo": details.todo, + "Runtime": details.runtime + }; + + // eslint-disable-next-line + console.log(JSON.stringify(result, null, 2)); + }); + QUnit.load(); + QUnit.done(() => { + frappe.set_route('Form', 'Test Runner', 'Test Runner'); + }); + }); + + } +}); diff --git a/frappe/core/doctype/test_runner/test_runner.json b/frappe/core/doctype/test_runner/test_runner.json new file mode 100644 index 0000000000..0094d6c659 --- /dev/null +++ b/frappe/core/doctype/test_runner/test_runner.json @@ -0,0 +1,122 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 0, + "creation": "2017-06-26 10:57:19.976624", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "module_path", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Module Path", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "output", + "fieldtype": "HTML", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Output", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 1, + "istable": 0, + "max_attachments": 0, + "modified": "2017-06-26 10:57:19.976624", + "modified_by": "Administrator", + "module": "Core", + "name": "Test Runner", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 0, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 0, + "role": "System Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + } + ], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1, + "track_seen": 0 +} \ No newline at end of file diff --git a/frappe/core/doctype/test_runner/test_runner.py b/frappe/core/doctype/test_runner/test_runner.py new file mode 100644 index 0000000000..2d66622955 --- /dev/null +++ b/frappe/core/doctype/test_runner/test_runner.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Frappe Technologies and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe, os +from frappe.model.document import Document + +class TestRunner(Document): + pass + +@frappe.whitelist() +def get_all_tests(): + tests = [] + for app in frappe.get_installed_apps(): + tests_path = frappe.get_app_path(app, 'tests', 'ui') + if os.path.exists(tests_path): + for basepath, folders, files in os.walk(tests_path): # pylint: disable=unused-variable + for fname in files: + if fname.startswith('test') and fname.endswith('.js'): + path = os.path.join(basepath, fname) + with open(path, 'r') as fileobj: + tests.append(dict( + path = os.path.relpath(frappe.get_app_path(app), path), + script = fileobj.read() + )) + return tests diff --git a/frappe/docs/assets/img/app-development/test-runner.png b/frappe/docs/assets/img/app-development/test-runner.png new file mode 100644 index 0000000000000000000000000000000000000000..22708e7dec17619add485b13a524b6f4e241f170 GIT binary patch literal 264454 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�<+NnBCxc$_^Yk# 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 Nbl&hB=IpI|{|_`V`9lBz literal 0 HcmV?d00001 diff --git a/frappe/docs/user/en/guides/automated-testing/index.md b/frappe/docs/user/en/guides/automated-testing/index.md new file mode 100644 index 0000000000..ad1bae8629 --- /dev/null +++ b/frappe/docs/user/en/guides/automated-testing/index.md @@ -0,0 +1,7 @@ +# Automated Testing + +Frappé Provides you a test framework to write and execute tests that can be run directly on a Continuous Integration Tool like Travis + +You can write server-side unit tests or UI tests + +{index} \ No newline at end of file diff --git a/frappe/docs/user/en/guides/automated-testing/index.txt b/frappe/docs/user/en/guides/automated-testing/index.txt new file mode 100644 index 0000000000..7d40d39f8a --- /dev/null +++ b/frappe/docs/user/en/guides/automated-testing/index.txt @@ -0,0 +1,3 @@ +unit-testing +integration-testing +qunit-testing \ No newline at end of file diff --git a/frappe/docs/user/en/guides/automated-testing/integration-testing.md b/frappe/docs/user/en/guides/automated-testing/integration-testing.md new file mode 100644 index 0000000000..fb99949a61 --- /dev/null +++ b/frappe/docs/user/en/guides/automated-testing/integration-testing.md @@ -0,0 +1,49 @@ +# UI Integration Testing + +You can write integration tests using the Selenium Driver. `frappe.utils.selenium_driver` gives you a friendly API to write selenium based tests + +To write integration tests, create a standard test case by creating a python file starting with `test_` + +All integration tests will be run at the end of the unittests. + +### Example + +Here is an example of an integration test to check insertion of a To Do + + from __future__ import print_function + from frappe.utils.selenium_testdriver import TestDriver + import unittest + import time + + class TestToDo(unittest.TestCase): + def setUp(self): + self.driver = TestDriver() + + def test_todo(self): + self.driver.login() + + # list view + self.driver.set_route('List', 'ToDo') + + time.sleep(2) + + # new + self.driver.click_primary_action() + + time.sleep(2) + + # set input + self.driver.set_text_editor('description', 'hello') + + # save + self.driver.click_modal_primary_action() + + time.sleep(2) + + self.assertTrue(self.driver.get_visible_element('.result-list') + .find_element_by_css_selector('.list-item') + .find_element_by_css_selector('.list-id').text=='hello') + + def tearDown(self): + self.driver.close() + diff --git a/frappe/docs/user/en/guides/automated-testing/qunit-testing.md b/frappe/docs/user/en/guides/automated-testing/qunit-testing.md new file mode 100644 index 0000000000..a8e35607d5 --- /dev/null +++ b/frappe/docs/user/en/guides/automated-testing/qunit-testing.md @@ -0,0 +1,46 @@ +# UI Testing with Frappe API + +You can either write integration tests, or directly write tests in Javascript using [QUnit](http://api.qunitjs.com/) + +QUnit helps you write UI tests using the UQuit framework and native frappe API. As you might have guessed, this is a much faster way of writing tests. + +### Test Runner + +To write QUnit based tests, add your tests in the `tests/ui` folder of your application. Your test files must begin with `test_` and end with `.js` extension. + +To run your files, you can use the **Test Runner**. The **Test Runner** gives a user interface to load all your QUnit tests and run them in the browser. + +In the CI, all QUnit tests are run by the **Test Runner** using `frappe/tests/test_test_runner.py` + + + +### Example QUnit Test + +Here is the example of the To Do test in QUnit + + QUnit.test("test quick entry", function(assert) { + assert.expect(2); + let done = assert.async(); + let random = frappe.utils.get_random(10); + + frappe.set_route('List', 'ToDo') + .then(() => { + return frappe.new_doc('ToDo'); + }) + .then(() => { + frappe.quick_entry.dialog.set_value('description', random); + return frappe.quick_entry.insert(); + }) + .then((doc) => { + assert.ok(doc && !doc.__islocal); + return frappe.set_route('Form', 'ToDo', doc.name); + }) + .then(() => { + assert.ok(cur_frm.doc.description.includes(random)); + done(); + }); + }); + +### Writing Test Friendly Code with Promises + +Promises are a great way to write test-friendly code. If your method calls an aysnchronous call (ajax), then you should return an `Promise` object. While writing tests, if you encounter a function that does not return a `Promise` object, you should update the code to return a `Promise` object. diff --git a/frappe/docs/user/en/guides/basics/writing-tests.md b/frappe/docs/user/en/guides/automated-testing/unit-testing.md similarity index 93% rename from frappe/docs/user/en/guides/basics/writing-tests.md rename to frappe/docs/user/en/guides/automated-testing/unit-testing.md index 2cb528f3e2..730ae792de 100755 --- a/frappe/docs/user/en/guides/basics/writing-tests.md +++ b/frappe/docs/user/en/guides/automated-testing/unit-testing.md @@ -1,4 +1,4 @@ -# Writing Tests Guide +# Unit Testing ## 1.Introduction @@ -16,12 +16,12 @@ Frappe provides some basic tooling to quickly write automated tests. There are s This function will build all the test dependencies and run your tests. You should run tests from "frappe_bench" folder. Without options all tests will be run. - bench run-tests + bench run-tests If you need more information about test execution - you can use verbose log level for bench. bench --verbose run-tests - + ### Options: --app @@ -30,9 +30,9 @@ If you need more information about test execution - you can use verbose log leve --module (Run a particular module that has tests) --profile (Runs a Python profiler on the test) --junit-xml-output (The command provides test results in the standard XUnit XML format) - + #### 2.1. Example for app: -All applications are located in folder: "~/frappe-bench/apps". +All applications are located in folder: "~/frappe-bench/apps". We can run tests for each application. - frappe-bench/apps/erpnext/ @@ -50,7 +50,7 @@ We can run tests for each application. . ---------------------------------------------------------------------- Ran 1 test in 0.008s - + OK #### 2.3. Example for test: @@ -60,44 +60,44 @@ Run a specific case in User: . ---------------------------------------------------------------------- Ran 1 test in 0.005s - + OK #### 2.4. Example for module: If we want to run tests in the module: /home/frappe/frappe-bench/apps/erpnext/erpnext/support/doctype/issue/test_issue.py - + We should use module name like this (related to application folder) erpnext.support.doctype.issue.test_issue - + #####EXAMPLE: - + frappe@erpnext:~/frappe-bench$ bench run-tests --module "erpnext.stock.doctype.stock_entry.test_stock_entry" ........................... ---------------------------------------------------------------------- Ran 27 tests in 30.549s - + #### 2.5. Example for profile: frappe@erpnext:~/frappe-bench$ bench run-tests --doctype "Activity Cost" --profile . ---------------------------------------------------------------------- Ran 1 test in 0.010s - + OK 9133 function calls (8912 primitive calls) in 0.011 seconds - + Ordered by: cumulative time - + ncalls tottime percall cumtime percall filename:lineno(function) 2 0.000 0.000 0.008 0.004 /home/frappe/frappe-bench/apps/frappe/frappe/model/document.py:187(insert) 1 0.000 0.000 0.003 0.003 /home/frappe/frappe-bench/apps/frappe/frappe/model/document.py:386(_validate) 13 0.000 0.000 0.002 0.000 /home/frappe/frappe-bench/apps/frappe/frappe/database.py:77(sql) 255 0.000 0.000 0.002 0.000 /home/frappe/frappe-bench/apps/frappe/frappe/model/base_document.py:91(get) - 12 0.000 0.000 0.002 0.000 + 12 0.000 0.000 0.002 0.000 #### 2.6. Example for XUnit XML: @@ -118,7 +118,7 @@ We should use module name like this (related to application folder) It’s designed for the CI Jenkins, but will work for anything else that understands an XUnit-formatted XML representation of test results. #### Jenkins configuration support: -1. You should install xUnit plugin - https://wiki.jenkins-ci.org/display/JENKINS/xUnit+Plugin +1. You should install xUnit plugin - https://wiki.jenkins-ci.org/display/JENKINS/xUnit+Plugin 2. After installation open Jenkins job configuration, click the box named “Publish JUnit test result report” under the "Post-build Actions" and enter path to XML report: (Example: _reports/*.xml_) @@ -197,9 +197,3 @@ It’s designed for the CI Jenkins, but will work for anything else that underst self.assertTrue("_Test Event 3" in subjects) self.assertFalse("_Test Event 2" in subjects) - -## 4. Client Side Testing (Using Selenium) - -This feature is still under development. - -For an example see, [https://github.com/frappe/erpnext/blob/develop/erpnext/tests/sel_tests.py](https://github.com/frappe/erpnext/blob/develop/erpnext/tests/sel_tests.py) \ No newline at end of file diff --git a/frappe/modules/utils.py b/frappe/modules/utils.py index 293c371075..2acb5b5db6 100644 --- a/frappe/modules/utils.py +++ b/frappe/modules/utils.py @@ -180,8 +180,8 @@ def load_doctype_module(doctype, module=None, prefix="", suffix=""): try: if key not in doctype_python_modules: doctype_python_modules[key] = frappe.get_module(module_name) - except ImportError: - raise ImportError('Module import failed for {0} ({1})'.format(doctype, module_name)) + except ImportError, e: + raise ImportError('Module import failed for {0} ({1})'.format(doctype, module_name + ' Error: ' + str(e))) return doctype_python_modules[key] diff --git a/frappe/nightwatch.global.js b/frappe/nightwatch.global.js deleted file mode 100644 index f7530e422f..0000000000 --- a/frappe/nightwatch.global.js +++ /dev/null @@ -1,12 +0,0 @@ -var chromedriver = require('chromedriver'); -module.exports = { - before: function (done) { - chromedriver.start(); - done(); - }, - - after: function (done) { - chromedriver.stop(); - done(); - } -}; \ No newline at end of file diff --git a/frappe/nightwatch.js b/frappe/nightwatch.js deleted file mode 100644 index a54c764b88..0000000000 --- a/frappe/nightwatch.js +++ /dev/null @@ -1,96 +0,0 @@ -const fs = require('fs'); - -const ci_mode = get_cli_arg('env') === 'ci_server'; -const site_name = get_cli_arg('site'); -let app_name = get_cli_arg('app'); - -if(!app_name) { - console.log('Please specify app to run tests'); - return; -} - -if(!ci_mode && !site_name) { - console.log('Please specify site to run tests'); - return; -} - -// site url -let site_url; -if(site_name) { - site_url = 'http://' + site_name + ':' + get_port(); -} - -// multiple apps -if(app_name.includes(',')) { - app_name = app_name.split(','); -} else { - app_name = [app_name]; -} - -let test_folders = []; -let page_objects = []; -for(const app of app_name) { - const test_folder = `apps/${app}/${app}/tests/ui`; - const page_object = test_folder + '/page_objects'; - - if(!fs.existsSync(test_folder)) { - console.log(`No test folder found for "${app}"`); - continue; - } - test_folders.push(test_folder); - - if(fs.existsSync(page_object)) { - page_objects.push(); - } -} - -const config = { - "src_folders": test_folders, - "globals_path" : 'apps/frappe/frappe/nightwatch.global.js', - "page_objects_path": page_objects, - "selenium": { - "start_process": false - }, - "test_settings": { - "default": { - "launch_url": site_url, - "selenium_port": 9515, - "selenium_host": "127.0.0.1", - "default_path_prefix": "", - "silent": true, - // "screenshots": { - // "enabled": true, - // "path": SCREENSHOT_PATH - // }, - "globals": { - "waitForConditionTimeout": 15000 - }, - "desiredCapabilities": { - "browserName": "chrome", - "chromeOptions": { - "args": ["--no-sandbox", "--start-maximized"] - }, - "javascriptEnabled": true, - "acceptSslCerts": true - } - }, - "ci_server": { - "launch_url": 'http://localhost:8000' - } - } -} -module.exports = config; - -function get_cli_arg(key) { - var args = process.argv; - var i = args.indexOf('--' + key); - if(i === -1) { - return null; - } - return args[i + 1]; -} - -function get_port() { - var bench_config = JSON.parse(fs.readFileSync('sites/common_site_config.json')); - return bench_config.webserver_port; -} \ No newline at end of file diff --git a/frappe/public/build.json b/frappe/public/build.json index 13bc48b8b5..75e4e76469 100755 --- a/frappe/public/build.json +++ b/frappe/public/build.json @@ -101,6 +101,7 @@ "public/js/frappe/ui/page.html", "public/js/frappe/ui/page.js", + "public/js/frappe/ui/find.js", "public/js/frappe/ui/iconbar.js", "public/js/frappe/form/layout.js", "public/js/frappe/ui/field_group.js", @@ -194,6 +195,8 @@ "public/js/frappe/form/save.js", "public/js/frappe/form/script_manager.js", "public/js/frappe/form/grid.js", + "public/js/frappe/form/grid_row.js", + "public/js/frappe/form/grid_row_form.js", "public/js/frappe/form/linked_with.js", "public/js/frappe/form/workflow.js", "public/js/frappe/form/print.js", diff --git a/frappe/public/js/frappe/dom.js b/frappe/public/js/frappe/dom.js index 939e5daa80..5f35dc83c9 100644 --- a/frappe/public/js/frappe/dom.js +++ b/frappe/public/js/frappe/dom.js @@ -195,6 +195,23 @@ frappe.ellipsis = function(text, max) { return text; }; +frappe.run_serially = function(tasks) { + var result = Promise.resolve(); + tasks.forEach(task => { + if(task) { + result = result.then ? result.then(task) : Promise.resolve(); + } + }); + return result; +}; + +frappe.timeout = seconds => { + return new Promise((resolve) => { + setTimeout(() => resolve(), seconds * 1000); + }); +}; + + frappe.get_modal = function(title, content) { return $(frappe.render_template("modal", {title:title, content:content})).appendTo(document.body); }; diff --git a/frappe/public/js/frappe/form/control.js b/frappe/public/js/frappe/form/control.js index 38aa00c401..ab85faeb33 100755 --- a/frappe/public/js/frappe/form/control.js +++ b/frappe/public/js/frappe/form/control.js @@ -98,29 +98,44 @@ frappe.ui.form.Control = Class.extend({ } }, set_value: function(value) { - this.parse_validate_and_set_in_model(value); + return this.parse_validate_and_set_in_model(value); }, parse_validate_and_set_in_model: function(value, e) { var me = this; - if(this.inside_change_event) return; - this.inside_change_event = true; - if(this.parse) value = this.parse(value); - - var set = function(value) { - me.set_model_value(value); - me.inside_change_event = false; - me.set_mandatory && me.set_mandatory(value); - - if(me.df.change || me.df.onchange) { - // onchange event specified in df - (me.df.change || me.df.onchange).apply(me, [e]); + return new Promise(resolve => { + if(this.inside_change_event) { + resolve(); + return; + } + this.inside_change_event = true; + if(this.parse) { + value = this.parse(value); + } + + var set = function(value) { + me.inside_change_event = false; + me.set_model_value(value) + .then(() => { + me.set_mandatory && me.set_mandatory(value); + + if(me.df.change || me.df.onchange) { + // onchange event specified in df + let _promise = (me.df.change || me.df.onchange).apply(me, [e]); + if(_promise && _promise.then) { + _promise.then(() => { resolve(); }); + } else { + resolve(); + } + } else { + resolve(); + } + }); } - } - this.validate ? this.validate(value, set) : set(value); + this.validate ? this.validate(value, set) : set(value); + }) }, get_parsed_value: function() { - var me = this; if(this.get_status()==='Write') { return this.get_value ? (this.parse ? this.parse(this.get_value()) : this.get_value()) : @@ -132,17 +147,20 @@ frappe.ui.form.Control = Class.extend({ } }, set_model_value: function(value) { - if(this.doctype && this.docname) { - if(frappe.model.set_value(this.doctype, this.docname, this.df.fieldname, - value, this.df.fieldtype)) { + return new Promise(resolve => { + if(this.doctype && this.docname) { + frappe.model.set_value(this.doctype, this.docname, this.df.fieldname, + value, this.df.fieldtype) + .then(() => resolve()); this.last_value = value; + } else { + if(this.doc) { + this.doc[this.df.fieldname] = value; + } + this.set_input(value); + resolve(); } - } else { - if(this.doc) { - this.doc[this.df.fieldname] = value; - } - this.set_input(value); - } + }); }, set_focus: function() { if(this.$input) { @@ -878,7 +896,7 @@ frappe.ui.form.ControlButton = frappe.ui.form.ControlData.extend({ }, onclick: function() { if(this.frm && this.frm.doc) { - if(this.frm.script_manager.get_handlers(this.df.fieldname, this.doctype, this.docname).length) { + if(this.frm.script_manager.has_handlers(this.df.fieldname, this.doctype)) { this.frm.script_manager.trigger(this.df.fieldname, this.doctype, this.docname); } else { this.frm.runscript(this.df.options, this); @@ -1290,14 +1308,7 @@ frappe.ui.form.ControlLink = frappe.ui.form.ControlData.extend({ frappe._from_link = this; frappe._from_link_scrollY = $(document).scrollTop(); - var trimmed_doctype = doctype.replace(/ /g, ''); - var controller_name = "QuickEntryForm"; - - if(frappe.ui.form[trimmed_doctype + "QuickEntryForm"]){ - controller_name = trimmed_doctype + "QuickEntryForm"; - } - - new frappe.ui.form[controller_name](doctype, function(doc) { + frappe.ui.form.make_quick_entry(doctype, (doc) => { if(me.frm) { me.parse_validate_and_set_in_model(doc.name); } else { diff --git a/frappe/public/js/frappe/form/grid.js b/frappe/public/js/frappe/form/grid.js index 8aa7abfef1..5e0437100a 100644 --- a/frappe/public/js/frappe/form/grid.js +++ b/frappe/public/js/frappe/form/grid.js @@ -108,6 +108,11 @@ frappe.ui.form.Grid = Class.extend({ select_row: function(name) { this.grid_rows_by_docname[name].select(); }, + remove_all: function() { + this.grid_rows.forEach(row => { + row.remove(); + }); + }, refresh_remove_rows_button: function() { this.remove_rows_button.toggleClass('hide', this.wrapper.find('.grid-body .grid-row-check:checked:first').length ? false : true); @@ -257,7 +262,7 @@ frappe.ui.form.Grid = Class.extend({ if (this.frm && this.frm.docname) { // use doc specific docfield object this.df = frappe.meta.get_docfield(this.frm.doctype, this.df.fieldname, - this.frm.docname); + this.frm.docname); } else { // use non-doc specific docfield if(this.df.options) { @@ -360,8 +365,19 @@ frappe.ui.form.Grid = Class.extend({ get_docfield: function(fieldname) { return frappe.meta.get_docfield(this.doctype, fieldname, this.frm ? this.frm.docname : null); }, - get_grid_row: function(docname) { - return this.grid_rows_by_docname[docname]; + get_row: function(key) { + if(typeof key == 'number') { + if(key < 0) { + return this.grid_rows[this.grid_rows.length + key]; + } else { + return this.grid_rows[key]; + } + } else { + return this.grid_rows_by_docname[key]; + } + }, + get_grid_row: function(key) { + return this.get_row(key); }, get_field: function(fieldname) { // Note: workaround for get_query @@ -435,21 +451,21 @@ frappe.ui.form.Grid = Class.extend({ && (this.frm && this.frm.get_perm(df.permlevel, "read") || !this.frm) && !in_list(frappe.model.layout_fields, df.fieldtype)) { - if(df.columns) { - df.colsize=df.columns; - } - else { - var colsize=2; - switch(df.fieldtype){ - case"Text": - case"Small Text": - colsize=3; - break; - case"Check": - colsize=1 - } - df.colsize=colsize + if(df.columns) { + df.colsize=df.columns; + } + else { + var colsize=2; + switch(df.fieldtype) { + case"Text": + case"Small Text": + colsize=3; + break; + case"Check": + colsize=1 } + df.colsize=colsize; + } if(df.columns) { df.colsize=df.columns; @@ -641,673 +657,4 @@ frappe.ui.form.Grid = Class.extend({ // hide all custom buttons this.grid_buttons.find('.btn-custom').addClass('hidden'); } -}); - -frappe.ui.form.GridRow = Class.extend({ - init: function(opts) { - this.on_grid_fields_dict = {}; - this.on_grid_fields = []; - this.row_check_html = ''; - this.columns = {}; - this.columns_list = []; - $.extend(this, opts); - this.make(); - }, - make: function() { - var me = this; - - this.wrapper = $('

').appendTo(this.parent).data("grid_row", this); - this.row = $('
').appendTo(this.wrapper) - .on("click", function(e) { - if($(e.target).hasClass('grid-row-check') || $(e.target).hasClass('row-index') || $(e.target).parent().hasClass('row-index')) { - return; - } - if(me.grid.allow_on_grid_editing() && me.grid.is_editable()) { - // pass - } else { - me.toggle_view(); - return false; - } - }); - - // no checkboxes if too small - // if(this.is_too_small()) { - // this.row_check_html = ''; - // } - - if(this.grid.template && !this.grid.meta.editable_grid) { - this.render_template(); - } else { - this.render_row(); - } - if(this.doc) { - this.set_data(); - } - }, - set_data: function() { - this.wrapper.data({ - "doc": this.doc - }) - }, - set_row_index: function() { - if(this.doc) { - this.wrapper - .attr('data-name', this.doc.name) - .attr("data-idx", this.doc.idx) - .find(".row-index span, .grid-form-row-index").html(this.doc.idx) - - } - }, - select: function(checked) { - this.doc.__checked = checked ? 1 : 0; - }, - refresh_check: function() { - this.wrapper.find('.grid-row-check').prop('checked', this.doc ? !!this.doc.__checked : false); - this.grid.refresh_remove_rows_button(); - }, - remove: function() { - var me = this; - if(this.grid.is_editable()) { - if(this.frm) { - if(this.get_open_form()) { - this.hide_form(); - } - - this.frm.script_manager.trigger("before_" + this.grid.df.fieldname + "_remove", - this.doc.doctype, this.doc.name); - - //this.wrapper.toggle(false); - frappe.model.clear_doc(this.doc.doctype, this.doc.name); - - this.frm.script_manager.trigger(this.grid.df.fieldname + "_remove", - this.doc.doctype, this.doc.name); - this.frm.dirty(); - } else { - this.grid.df.data = this.grid.df.data.filter(function(d) { - return d.name !== me.doc.name; - }) - // remap idxs - this.grid.df.data.forEach(function(d, i) { - d.idx = i+1; - }); - } - this.grid.refresh(); - } - }, - insert: function(show, below) { - var idx = this.doc.idx; - if(below) idx ++; - this.toggle_view(false); - this.grid.add_new_row(idx, null, show); - }, - refresh: function() { - if(this.frm && this.doc) { - this.doc = locals[this.doc.doctype][this.doc.name]; - } - // re write columns - this.visible_columns = null; - - if(this.grid.template && !this.grid.meta.editable_grid) { - this.render_template(); - } else { - this.render_row(true); - } - - // refersh form fields - if(this.grid_form) { - this.grid_form.layout && this.grid_form.layout.refresh(this.doc); - } - }, - render_template: function() { - this.set_row_index(); - - if(this.row_display) { - this.row_display.remove(); - } - var index_html = ''; - - // row index - if(this.doc) { - if(!this.row_index) { - this.row_index = $('
'+this.row_check_html+'
').appendTo(this.row); - } - this.row_index.find('span').html(this.doc.idx); - } - - this.row_display = $('
'+ - +'
').appendTo(this.row) - .html(frappe.render(this.grid.template, { - doc: this.doc ? frappe.get_format_helper(this.doc) : null, - frm: this.frm, - row: this - })); - }, - render_row: function(refresh) { - var me = this; - this.set_row_index(); - - // index (1, 2, 3 etc) - if(!this.row_index) { - var txt = (this.doc ? this.doc.idx : " "); - this.row_index = $( - `
- ${this.row_check_html} - ${txt}
`) - .appendTo(this.row) - .on('click', function(e) { - if(!$(e.target).hasClass('grid-row-check')) { - me.toggle_view(); - } - }); - } else { - this.row_index.find('span').html(txt); - } - - this.setup_columns(); - this.add_open_form_button(); - this.refresh_check(); - - if(this.frm && this.doc) { - $(this.frm.wrapper).trigger("grid-row-render", [this]); - } - }, - - make_editable: function() { - this.row.toggleClass('editable-row', this.grid.is_editable()); - }, - - is_too_small: function() { - return this.row.width() ? this.row.width() < 300 : false; - }, - - add_open_form_button: function() { - var me = this; - if(this.doc && !this.grid.df.in_place_edit) { - // remove row - if(!this.open_form_button) { - this.open_form_button = $('
\ - ') - .appendTo($('
').appendTo(this.row)) - .on('click', function() { me.toggle_view(); return false; }); - - if(this.is_too_small()) { - // narrow - this.open_form_button.css({'margin-right': '-2px'}); - } - } - } - }, - - setup_columns: function() { - var me = this; - this.focus_set = false; - this.grid.setup_visible_columns(); - - for(var ci in this.grid.visible_columns) { - var df = this.grid.visible_columns[ci][0], - colsize = this.grid.visible_columns[ci][1], - txt = this.doc ? - frappe.format(this.doc[df.fieldname], df, null, this.doc) : - __(df.label); - - if(this.doc && df.fieldtype === "Select") { - txt = __(txt); - } - - if(!this.columns[df.fieldname]) { - var column = this.make_column(df, colsize, txt, ci); - } else { - var column = this.columns[df.fieldname]; - this.refresh_field(df.fieldname, txt); - } - - // background color for cellz - if(this.doc) { - if(df.reqd && !txt) { - column.addClass('error'); - } - if (df.reqd || df.bold) { - column.addClass('bold'); - } - } - } - }, - - make_column: function(df, colsize, txt, ci) { - var me = this; - var add_class = ((["Text", "Small Text"].indexOf(df.fieldtype)!==-1) ? - " grid-overflow-no-ellipsis" : ""); - add_class += (["Int", "Currency", "Float", "Percent"].indexOf(df.fieldtype)!==-1) ? - " text-right": ""; - add_class += (["Check"].indexOf(df.fieldtype)!==-1) ? - " text-center": ""; - - var $col = $('
') - .attr("data-fieldname", df.fieldname) - .attr("data-fieldtype", df.fieldtype) - .data("df", df) - .appendTo(this.row) - .on('click', function() { - if(frappe.ui.form.editable_row===me) { - return; - } - var out = me.toggle_editable_row(); - var col = this; - setTimeout(function() { - $(col).find('input[type="Text"]:first').focus(); - }, 500); - return out; - }); - - $col.field_area = $('
').appendTo($col).toggle(false); - $col.static_area = $('
').appendTo($col).html(txt); - $col.df = df; - $col.column_index = ci; - - this.columns[df.fieldname] = $col; - this.columns_list.push($col); - - return $col; - }, - - toggle_editable_row: function(show) { - var me = this; - // show static for field based on - // whether grid is editable - if(this.grid.allow_on_grid_editing() && this.grid.is_editable() && this.doc && show !== false) { - - // disable other editale row - if(frappe.ui.form.editable_row - && frappe.ui.form.editable_row !== this) { - frappe.ui.form.editable_row.toggle_editable_row(false); - } - - this.row.toggleClass('editable-row', true); - - // setup controls - this.columns_list.forEach(function(column) { - me.make_control(column); - column.static_area.toggle(false); - column.field_area.toggle(true); - }); - - frappe.ui.form.editable_row = this; - return false; - } else { - this.row.toggleClass('editable-row', false); - this.columns_list.forEach(function(column) { - column.static_area.toggle(true); - column.field_area && column.field_area.toggle(false); - }); - frappe.ui.form.editable_row = null; - } - }, - - make_control: function(column) { - if(column.field) return; - - var me = this, - parent = column.field_area, - df = column.df; - - // no text editor in grid - if (df.fieldtype=='Text Editor') { - df.fieldtype = 'Text'; - } - - var field = frappe.ui.form.make_control({ - df: df, - parent: parent, - only_input: true, - with_link_btn: true, - doc: this.doc, - doctype: this.doc.doctype, - docname: this.doc.name, - frm: this.grid.frm, - grid: this.grid, - grid_row: this, - value: this.doc[df.fieldname] - }); - - // sync get_query - field.get_query = this.grid.get_field(df.fieldname).get_query; - field.refresh(); - if(field.$input) { - field.$input - .addClass('input-sm') - .attr('data-col-idx', column.column_index) - .attr('placeholder', __(df.label)); - // flag list input - if (this.columns_list && this.columns_list.slice(-1)[0]===column) { - field.$input.attr('data-last-input', 1); - } - } - - this.set_arrow_keys(field); - column.field = field; - this.on_grid_fields_dict[df.fieldname] = field; - this.on_grid_fields.push(field); - - }, - - set_arrow_keys: function(field) { - var me = this; - if(field.$input) { - field.$input.on('keydown', function(e) { - var { TAB, UP_ARROW, DOWN_ARROW } = frappe.ui.keyCode; - if(!in_list([TAB, UP_ARROW, DOWN_ARROW], e.which)) { - return; - } - - var values = me.grid.get_data(); - var fieldname = $(this).attr('data-fieldname'); - var fieldtype = $(this).attr('data-fieldtype'); - - var move_up_down = function(base) { - if(in_list(['Text', 'Small Text'], fieldtype)) { - return; - } - - base.toggle_editable_row(); - setTimeout(function() { - var input = base.columns[fieldname].field.$input; - if(input) { - input.focus(); - } - }, 400) - - } - - // TAB - if(e.which==TAB && !e.shiftKey) { - // last column - if($(this).attr('data-last-input') || - me.grid.wrapper.find('.grid-row :input:enabled:last').get(0)===this) { - setTimeout(function() { - if(me.doc.idx === values.length) { - // last row - me.grid.add_new_row(null, null, true); - me.grid.grid_rows[me.grid.grid_rows.length - 1].toggle_editable_row(); - me.grid.set_focus_on_row(); - } else { - me.grid.grid_rows[me.doc.idx].toggle_editable_row(); - me.grid.set_focus_on_row(me.doc.idx+1); - } - }, 500); - } - } else if(e.which==UP_ARROW) { - if(me.doc.idx > 1) { - var prev = me.grid.grid_rows[me.doc.idx-2]; - move_up_down(prev); - } - } else if(e.which==DOWN_ARROW) { - if(me.doc.idx < values.length) { - var next = me.grid.grid_rows[me.doc.idx]; - move_up_down(next); - } - } - - }); - } - }, - - get_open_form: function() { - return frappe.ui.form.get_open_grid_form(); - }, - - toggle_view: function(show, callback) { - if(!this.doc) { - return this; - } - - if(this.frm) { - // reload doc - this.doc = locals[this.doc.doctype][this.doc.name]; - } - - // hide other - var open_row = this.get_open_form(); - - if (show===undefined) show = !!!open_row; - - // call blur - document.activeElement && document.activeElement.blur(); - - if(show && open_row) { - if(open_row==this) { - // already open, do nothing - callback && callback(); - return; - } else { - // close other views - open_row.toggle_view(false); - } - } - - if(show) { - this.show_form(); - } else { - this.hide_form(); - } - callback && callback(); - - return this; - }, - show_form: function() { - if(!this.grid_form) { - this.grid_form = new frappe.ui.form.GridRowForm({ - row: this - }); - } - this.grid_form.render(); - this.row.toggle(false); - // this.form_panel.toggle(true); - frappe.dom.freeze("", "dark"); - cur_frm.cur_grid = this; - this.wrapper.addClass("grid-row-open"); - if(!frappe.dom.is_element_in_viewport(this.wrapper)) { - frappe.utils.scroll_to(this.wrapper, true, 15); - } - - if(this.frm) { - this.frm.script_manager.trigger(this.doc.parentfield + "_on_form_rendered"); - this.frm.script_manager.trigger("form_render", this.doc.doctype, this.doc.name); - } - }, - hide_form: function() { - frappe.dom.unfreeze(); - this.row.toggle(true); - this.refresh(); - cur_frm.cur_grid = null; - this.wrapper.removeClass("grid-row-open"); - }, - open_prev: function() { - if(this.grid.grid_rows[this.doc.idx-2]) { - this.grid.grid_rows[this.doc.idx-2].toggle_view(true); - } - }, - open_next: function() { - if(this.grid.grid_rows[this.doc.idx]) { - this.grid.grid_rows[this.doc.idx].toggle_view(true); - } else { - this.grid.add_new_row(null, null, true); - } - }, - refresh_field: function(fieldname, txt) { - var df = this.grid.get_docfield(fieldname) || undefined; - - // format values if no frm - if(!df) { - df = this.grid.visible_columns.find((col) => { - return col[0].fieldname === fieldname; - }); - if(df && this.doc) { - var txt = frappe.format(this.doc[fieldname], df[0], - null, this.doc); - } - } - - if(txt===undefined && this.frm) { - var txt = frappe.format(this.doc[fieldname], df, - null, this.frm.doc); - } - - // reset static value - var column = this.columns[fieldname]; - if(column) { - column.static_area.html(txt || ""); - if(df && df.reqd) { - column.toggleClass('error', !!(txt===null || txt==='')); - } - } - - // reset field value - var field = this.on_grid_fields_dict[fieldname]; - if(field) { - field.docname = this.doc.name; - field.refresh(); - } - - // in form - if(this.grid_form) { - this.grid_form.refresh_field(fieldname); - } - }, - get_visible_columns: function(blacklist) { - var me = this; - var visible_columns = $.map(this.docfields, function(df) { - var visible = !df.hidden && df.in_list_view && me.grid.frm.get_perm(df.permlevel, "read") - && !in_list(frappe.model.layout_fields, df.fieldtype) && !in_list(blacklist, df.fieldname); - - return visible ? df : null; - }); - return visible_columns; - }, - set_field_property: function(fieldname, property, value) { - // set a field property for open form / grid form - var me = this; - - var set_property = function(field) { - if(!field) return; - field.df[property] = value; - field.refresh(); - } - - // set property in grid form - if(this.grid_form) { - set_property(this.grid_form.fields_dict[fieldname]); - this.grid_form.layout && this.grid_form.layout.refresh_sections(); - } - - // set property in on grid fields - set_property(this.on_grid_fields_dict[fieldname]); - }, - toggle_reqd: function(fieldname, reqd) { - this.set_field_property(fieldname, 'reqd', reqd ? 1 : 0); - }, - toggle_display: function(fieldname, show) { - this.set_field_property(fieldname, 'hidden', show ? 0 : 1); - }, - toggle_editable: function(fieldname, editable) { - this.set_field_property(fieldname, 'read_only', editable ? 0 : 1); - }, -}); - -frappe.ui.form.GridRowForm = Class.extend({ - init: function(opts) { - $.extend(this, opts); - this.wrapper = $('
') - .appendTo(this.row.wrapper); - - }, - render: function() { - var me = this; - this.make_form(); - this.form_area.empty(); - - this.layout = new frappe.ui.form.Layout({ - fields: this.row.docfields, - body: this.form_area, - no_submit_on_enter: true, - frm: this.row.frm, - }); - this.layout.make(); - - this.fields = this.layout.fields; - this.fields_dict = this.layout.fields_dict; - - this.layout.refresh(this.row.doc); - - // copy get_query to fields - for(var fieldname in (this.row.grid.fieldinfo || {})) { - var fi = this.row.grid.fieldinfo[fieldname]; - $.extend(me.fields_dict[fieldname], fi); - } - - this.toggle_add_delete_button_display(this.wrapper); - - this.row.grid.open_grid_row = this; - - this.set_focus(); - }, - make_form: function() { - if(!this.form_area) { - $(frappe.render_template("grid_form", {grid:this})).appendTo(this.wrapper); - this.form_area = this.wrapper.find(".form-area"); - this.row.set_row_index(); - this.set_form_events(); - } - }, - set_form_events: function() { - var me = this; - this.wrapper.find(".grid-delete-row") - .on('click', function() { - me.row.remove(); return false; - }); - this.wrapper.find(".grid-insert-row") - .on('click', function() { - me.row.insert(true); return false; - }); - this.wrapper.find(".grid-insert-row-below") - .on('click', function() { - me.row.insert(true, true); return false; - }); - this.wrapper.find(".grid-append-row") - .on('click', function() { - me.row.toggle_view(false); - me.row.grid.add_new_row(me.row.doc.idx+1, null, true); - return false; - }); - this.wrapper.find(".grid-form-heading, .grid-footer-toolbar").on("click", function() { - me.row.toggle_view(); - return false; - }); - }, - toggle_add_delete_button_display: function($parent) { - $parent.find(".grid-header-toolbar .btn, .grid-footer-toolbar .btn") - .toggle(this.row.grid.is_editable()); - }, - refresh_field: function(fieldname) { - if(this.fields_dict[fieldname]) { - this.fields_dict[fieldname].refresh(); - this.layout && this.layout.refresh_dependency(); - } - }, - set_focus: function() { - // wait for animation and then focus on the first row - var me = this; - setTimeout(function() { - if(me.row.frm && me.row.frm.doc.docstatus===0 || !me.row.frm) { - var first = me.form_area.find("input:first"); - if(first.length && !in_list(["Date", "Datetime", "Time"], first.attr("data-fieldtype"))) { - try { - first.get(0).focus(); - } catch(e) { - // - } - } - } - }, 500); - }, -}); +}); \ No newline at end of file diff --git a/frappe/public/js/frappe/form/grid_row.js b/frappe/public/js/frappe/form/grid_row.js new file mode 100644 index 0000000000..3ff9f85198 --- /dev/null +++ b/frappe/public/js/frappe/form/grid_row.js @@ -0,0 +1,586 @@ +frappe.ui.form.GridRow = Class.extend({ + init: function(opts) { + this.on_grid_fields_dict = {}; + this.on_grid_fields = []; + this.row_check_html = ''; + this.columns = {}; + this.columns_list = []; + $.extend(this, opts); + this.make(); + }, + make: function() { + var me = this; + + this.wrapper = $('
').appendTo(this.parent).data("grid_row", this); + this.row = $('
').appendTo(this.wrapper) + .on("click", function(e) { + if($(e.target).hasClass('grid-row-check') || $(e.target).hasClass('row-index') || $(e.target).parent().hasClass('row-index')) { + return; + } + if(me.grid.allow_on_grid_editing() && me.grid.is_editable()) { + // pass + } else { + me.toggle_view(); + return false; + } + }); + + // no checkboxes if too small + // if(this.is_too_small()) { + // this.row_check_html = ''; + // } + + if(this.grid.template && !this.grid.meta.editable_grid) { + this.render_template(); + } else { + this.render_row(); + } + if(this.doc) { + this.set_data(); + } + }, + set_data: function() { + this.wrapper.data({ + "doc": this.doc + }) + }, + set_row_index: function() { + if(this.doc) { + this.wrapper + .attr('data-name', this.doc.name) + .attr("data-idx", this.doc.idx) + .find(".row-index span, .grid-form-row-index").html(this.doc.idx) + + } + }, + select: function(checked) { + this.doc.__checked = checked ? 1 : 0; + }, + refresh_check: function() { + this.wrapper.find('.grid-row-check').prop('checked', this.doc ? !!this.doc.__checked : false); + this.grid.refresh_remove_rows_button(); + }, + remove: function() { + var me = this; + if(this.grid.is_editable()) { + if(this.frm) { + if(this.get_open_form()) { + this.hide_form(); + } + + this.frm.script_manager.trigger("before_" + this.grid.df.fieldname + "_remove", + this.doc.doctype, this.doc.name); + + //this.wrapper.toggle(false); + frappe.model.clear_doc(this.doc.doctype, this.doc.name); + + this.frm.script_manager.trigger(this.grid.df.fieldname + "_remove", + this.doc.doctype, this.doc.name); + this.frm.dirty(); + } else { + this.grid.df.data = this.grid.df.data.filter(function(d) { + return d.name !== me.doc.name; + }) + // remap idxs + this.grid.df.data.forEach(function(d, i) { + d.idx = i+1; + }); + } + this.grid.refresh(); + } + }, + insert: function(show, below) { + var idx = this.doc.idx; + if(below) idx ++; + this.toggle_view(false); + this.grid.add_new_row(idx, null, show); + }, + refresh: function() { + if(this.frm && this.doc) { + this.doc = locals[this.doc.doctype][this.doc.name]; + } + // re write columns + this.visible_columns = null; + + if(this.grid.template && !this.grid.meta.editable_grid) { + this.render_template(); + } else { + this.render_row(true); + } + + // refersh form fields + if(this.grid_form) { + this.grid_form.layout && this.grid_form.layout.refresh(this.doc); + } + }, + render_template: function() { + this.set_row_index(); + + if(this.row_display) { + this.row_display.remove(); + } + var index_html = ''; + + // row index + if(this.doc) { + if(!this.row_index) { + this.row_index = $('
'+this.row_check_html+'
').appendTo(this.row); + } + this.row_index.find('span').html(this.doc.idx); + } + + this.row_display = $('
'+ + +'
').appendTo(this.row) + .html(frappe.render(this.grid.template, { + doc: this.doc ? frappe.get_format_helper(this.doc) : null, + frm: this.frm, + row: this + })); + }, + render_row: function(refresh) { + var me = this; + this.set_row_index(); + + // index (1, 2, 3 etc) + if(!this.row_index) { + var txt = (this.doc ? this.doc.idx : " "); + this.row_index = $( + `
+ ${this.row_check_html} + ${txt}
`) + .appendTo(this.row) + .on('click', function(e) { + if(!$(e.target).hasClass('grid-row-check')) { + me.toggle_view(); + } + }); + } else { + this.row_index.find('span').html(txt); + } + + this.setup_columns(); + this.add_open_form_button(); + this.refresh_check(); + + if(this.frm && this.doc) { + $(this.frm.wrapper).trigger("grid-row-render", [this]); + } + }, + + make_editable: function() { + this.row.toggleClass('editable-row', this.grid.is_editable()); + }, + + is_too_small: function() { + return this.row.width() ? this.row.width() < 300 : false; + }, + + add_open_form_button: function() { + var me = this; + if(this.doc && !this.grid.df.in_place_edit) { + // remove row + if(!this.open_form_button) { + this.open_form_button = $('\ + ') + .appendTo($('
').appendTo(this.row)) + .on('click', function() { me.toggle_view(); return false; }); + + if(this.is_too_small()) { + // narrow + this.open_form_button.css({'margin-right': '-2px'}); + } + } + } + }, + + setup_columns: function() { + var me = this; + this.focus_set = false; + this.grid.setup_visible_columns(); + + for(var ci in this.grid.visible_columns) { + var df = this.grid.visible_columns[ci][0], + colsize = this.grid.visible_columns[ci][1], + txt = this.doc ? + frappe.format(this.doc[df.fieldname], df, null, this.doc) : + __(df.label); + + if(this.doc && df.fieldtype === "Select") { + txt = __(txt); + } + + if(!this.columns[df.fieldname]) { + var column = this.make_column(df, colsize, txt, ci); + } else { + var column = this.columns[df.fieldname]; + this.refresh_field(df.fieldname, txt); + } + + // background color for cellz + if(this.doc) { + if(df.reqd && !txt) { + column.addClass('error'); + } + if (df.reqd || df.bold) { + column.addClass('bold'); + } + } + } + }, + + make_column: function(df, colsize, txt, ci) { + var me = this; + var add_class = ((["Text", "Small Text"].indexOf(df.fieldtype)!==-1) ? + " grid-overflow-no-ellipsis" : ""); + add_class += (["Int", "Currency", "Float", "Percent"].indexOf(df.fieldtype)!==-1) ? + " text-right": ""; + add_class += (["Check"].indexOf(df.fieldtype)!==-1) ? + " text-center": ""; + + var $col = $('
') + .attr("data-fieldname", df.fieldname) + .attr("data-fieldtype", df.fieldtype) + .data("df", df) + .appendTo(this.row) + .on('click', function() { + if(frappe.ui.form.editable_row===me) { + return; + } + var out = me.toggle_editable_row(); + var col = this; + setTimeout(function() { + $(col).find('input[type="Text"]:first').focus(); + }, 500); + return out; + }); + + $col.field_area = $('
').appendTo($col).toggle(false); + $col.static_area = $('
').appendTo($col).html(txt); + $col.df = df; + $col.column_index = ci; + + this.columns[df.fieldname] = $col; + this.columns_list.push($col); + + return $col; + }, + + activate: function() { + this.toggle_editable_row(true); + return this; + }, + + toggle_editable_row: function(show) { + var me = this; + // show static for field based on + // whether grid is editable + if(this.grid.allow_on_grid_editing() && this.grid.is_editable() && this.doc && show !== false) { + + // disable other editale row + if(frappe.ui.form.editable_row + && frappe.ui.form.editable_row !== this) { + frappe.ui.form.editable_row.toggle_editable_row(false); + } + + this.row.toggleClass('editable-row', true); + + // setup controls + this.columns_list.forEach(function(column) { + me.make_control(column); + column.static_area.toggle(false); + column.field_area.toggle(true); + }); + + frappe.ui.form.editable_row = this; + return false; + } else { + this.row.toggleClass('editable-row', false); + this.columns_list.forEach(function(column) { + column.static_area.toggle(true); + column.field_area && column.field_area.toggle(false); + }); + frappe.ui.form.editable_row = null; + } + }, + + make_control: function(column) { + if(column.field) return; + + var me = this, + parent = column.field_area, + df = column.df; + + // no text editor in grid + if (df.fieldtype=='Text Editor') { + df.fieldtype = 'Text'; + } + + var field = frappe.ui.form.make_control({ + df: df, + parent: parent, + only_input: true, + with_link_btn: true, + doc: this.doc, + doctype: this.doc.doctype, + docname: this.doc.name, + frm: this.grid.frm, + grid: this.grid, + grid_row: this, + value: this.doc[df.fieldname] + }); + + // sync get_query + field.get_query = this.grid.get_field(df.fieldname).get_query; + field.refresh(); + if(field.$input) { + field.$input + .addClass('input-sm') + .attr('data-col-idx', column.column_index) + .attr('placeholder', __(df.label)); + // flag list input + if (this.columns_list && this.columns_list.slice(-1)[0]===column) { + field.$input.attr('data-last-input', 1); + } + } + + this.set_arrow_keys(field); + column.field = field; + this.on_grid_fields_dict[df.fieldname] = field; + this.on_grid_fields.push(field); + + }, + + set_arrow_keys: function(field) { + var me = this; + if(field.$input) { + field.$input.on('keydown', function(e) { + var { TAB, UP_ARROW, DOWN_ARROW } = frappe.ui.keyCode; + if(!in_list([TAB, UP_ARROW, DOWN_ARROW], e.which)) { + return; + } + + var values = me.grid.get_data(); + var fieldname = $(this).attr('data-fieldname'); + var fieldtype = $(this).attr('data-fieldtype'); + + var move_up_down = function(base) { + if(in_list(['Text', 'Small Text'], fieldtype)) { + return; + } + + base.toggle_editable_row(); + setTimeout(function() { + var input = base.columns[fieldname].field.$input; + if(input) { + input.focus(); + } + }, 400) + + } + + // TAB + if(e.which==TAB && !e.shiftKey) { + // last column + if($(this).attr('data-last-input') || + me.grid.wrapper.find('.grid-row :input:enabled:last').get(0)===this) { + setTimeout(function() { + if(me.doc.idx === values.length) { + // last row + me.grid.add_new_row(null, null, true); + me.grid.grid_rows[me.grid.grid_rows.length - 1].toggle_editable_row(); + me.grid.set_focus_on_row(); + } else { + me.grid.grid_rows[me.doc.idx].toggle_editable_row(); + me.grid.set_focus_on_row(me.doc.idx+1); + } + }, 500); + } + } else if(e.which==UP_ARROW) { + if(me.doc.idx > 1) { + var prev = me.grid.grid_rows[me.doc.idx-2]; + move_up_down(prev); + } + } else if(e.which==DOWN_ARROW) { + if(me.doc.idx < values.length) { + var next = me.grid.grid_rows[me.doc.idx]; + move_up_down(next); + } + } + + }); + } + }, + + get_open_form: function() { + return frappe.ui.form.get_open_grid_form(); + }, + + toggle_view: function(show, callback) { + if(!this.doc) { + return this; + } + + if(this.frm) { + // reload doc + this.doc = locals[this.doc.doctype][this.doc.name]; + } + + // hide other + var open_row = this.get_open_form(); + + if (show===undefined) show = !!!open_row; + + // call blur + document.activeElement && document.activeElement.blur(); + + if(show && open_row) { + if(open_row==this) { + // already open, do nothing + callback && callback(); + return; + } else { + // close other views + open_row.toggle_view(false); + } + } + + if(show) { + this.show_form(); + } else { + this.hide_form(); + } + callback && callback(); + + return this; + }, + show_form: function() { + if(!this.grid_form) { + this.grid_form = new frappe.ui.form.GridRowForm({ + row: this + }); + } + this.grid_form.render(); + this.row.toggle(false); + // this.form_panel.toggle(true); + frappe.dom.freeze("", "dark"); + cur_frm.cur_grid = this; + this.wrapper.addClass("grid-row-open"); + if(!frappe.dom.is_element_in_viewport(this.wrapper)) { + frappe.utils.scroll_to(this.wrapper, true, 15); + } + + if(this.frm) { + this.frm.script_manager.trigger(this.doc.parentfield + "_on_form_rendered"); + this.frm.script_manager.trigger("form_render", this.doc.doctype, this.doc.name); + } + }, + hide_form: function() { + frappe.dom.unfreeze(); + this.row.toggle(true); + this.refresh(); + cur_frm.cur_grid = null; + this.wrapper.removeClass("grid-row-open"); + }, + open_prev: function() { + if(this.grid.grid_rows[this.doc.idx-2]) { + this.grid.grid_rows[this.doc.idx-2].toggle_view(true); + } + }, + open_next: function() { + if(this.grid.grid_rows[this.doc.idx]) { + this.grid.grid_rows[this.doc.idx].toggle_view(true); + } else { + this.grid.add_new_row(null, null, true); + } + }, + refresh_field: function(fieldname, txt) { + var df = this.grid.get_docfield(fieldname) || undefined; + + // format values if no frm + if(!df) { + df = this.grid.visible_columns.find((col) => { + return col[0].fieldname === fieldname; + }); + if(df && this.doc) { + var txt = frappe.format(this.doc[fieldname], df[0], + null, this.doc); + } + } + + if(txt===undefined && this.frm) { + var txt = frappe.format(this.doc[fieldname], df, + null, this.frm.doc); + } + + // reset static value + var column = this.columns[fieldname]; + if(column) { + column.static_area.html(txt || ""); + if(df && df.reqd) { + column.toggleClass('error', !!(txt===null || txt==='')); + } + } + + // reset field value + var field = this.on_grid_fields_dict[fieldname]; + if(field) { + field.docname = this.doc.name; + field.refresh(); + } + + // in form + if(this.grid_form) { + this.grid_form.refresh_field(fieldname); + } + }, + get_field: function(fieldname) { + let field = this.on_grid_fields_dict[fieldname]; + if (field) { + return field; + } else if(this.grid_form) { + return this.grid_form.fields_dict[fieldname]; + } else { + throw `fieldname ${fieldname} not found`; + } + }, + + get_visible_columns: function(blacklist) { + var me = this; + var visible_columns = $.map(this.docfields, function(df) { + var visible = !df.hidden && df.in_list_view && me.grid.frm.get_perm(df.permlevel, "read") + && !in_list(frappe.model.layout_fields, df.fieldtype) && !in_list(blacklist, df.fieldname); + + return visible ? df : null; + }); + return visible_columns; + }, + set_field_property: function(fieldname, property, value) { + // set a field property for open form / grid form + var me = this; + + var set_property = function(field) { + if(!field) return; + field.df[property] = value; + field.refresh(); + } + + // set property in grid form + if(this.grid_form) { + set_property(this.grid_form.fields_dict[fieldname]); + this.grid_form.layout && this.grid_form.layout.refresh_sections(); + } + + // set property in on grid fields + set_property(this.on_grid_fields_dict[fieldname]); + }, + toggle_reqd: function(fieldname, reqd) { + this.set_field_property(fieldname, 'reqd', reqd ? 1 : 0); + }, + toggle_display: function(fieldname, show) { + this.set_field_property(fieldname, 'hidden', show ? 0 : 1); + }, + toggle_editable: function(fieldname, editable) { + this.set_field_property(fieldname, 'read_only', editable ? 0 : 1); + }, +}); \ No newline at end of file diff --git a/frappe/public/js/frappe/form/grid_row_form.js b/frappe/public/js/frappe/form/grid_row_form.js new file mode 100644 index 0000000000..d1bd3c6baa --- /dev/null +++ b/frappe/public/js/frappe/form/grid_row_form.js @@ -0,0 +1,97 @@ +frappe.ui.form.GridRowForm = Class.extend({ + init: function(opts) { + $.extend(this, opts); + this.wrapper = $('
') + .appendTo(this.row.wrapper); + + }, + render: function() { + var me = this; + this.make_form(); + this.form_area.empty(); + + this.layout = new frappe.ui.form.Layout({ + fields: this.row.docfields, + body: this.form_area, + no_submit_on_enter: true, + frm: this.row.frm, + }); + this.layout.make(); + + this.fields = this.layout.fields; + this.fields_dict = this.layout.fields_dict; + + this.layout.refresh(this.row.doc); + + // copy get_query to fields + for(var fieldname in (this.row.grid.fieldinfo || {})) { + var fi = this.row.grid.fieldinfo[fieldname]; + $.extend(me.fields_dict[fieldname], fi); + } + + this.toggle_add_delete_button_display(this.wrapper); + + this.row.grid.open_grid_row = this; + + this.set_focus(); + }, + make_form: function() { + if(!this.form_area) { + $(frappe.render_template("grid_form", {grid:this})).appendTo(this.wrapper); + this.form_area = this.wrapper.find(".form-area"); + this.row.set_row_index(); + this.set_form_events(); + } + }, + set_form_events: function() { + var me = this; + this.wrapper.find(".grid-delete-row") + .on('click', function() { + me.row.remove(); return false; + }); + this.wrapper.find(".grid-insert-row") + .on('click', function() { + me.row.insert(true); return false; + }); + this.wrapper.find(".grid-insert-row-below") + .on('click', function() { + me.row.insert(true, true); return false; + }); + this.wrapper.find(".grid-append-row") + .on('click', function() { + me.row.toggle_view(false); + me.row.grid.add_new_row(me.row.doc.idx+1, null, true); + return false; + }); + this.wrapper.find(".grid-form-heading, .grid-footer-toolbar").on("click", function() { + me.row.toggle_view(); + return false; + }); + }, + toggle_add_delete_button_display: function($parent) { + $parent.find(".grid-header-toolbar .btn, .grid-footer-toolbar .btn") + .toggle(this.row.grid.is_editable()); + }, + refresh_field: function(fieldname) { + if(this.fields_dict[fieldname]) { + this.fields_dict[fieldname].refresh(); + this.layout && this.layout.refresh_dependency(); + } + }, + set_focus: function() { + // wait for animation and then focus on the first row + var me = this; + setTimeout(function() { + if(me.row.frm && me.row.frm.doc.docstatus===0 || !me.row.frm) { + var first = me.form_area.find("input:first"); + if(first.length && !in_list(["Date", "Datetime", "Time"], first.attr("data-fieldtype"))) { + try { + first.get(0).focus(); + } catch(e) { + // + } + } + } + }, 500); + }, +}); diff --git a/frappe/public/js/frappe/form/quick_entry.js b/frappe/public/js/frappe/form/quick_entry.js index ec48b3b1da..45cf759d7c 100644 --- a/frappe/public/js/frappe/form/quick_entry.js +++ b/frappe/public/js/frappe/form/quick_entry.js @@ -1,20 +1,37 @@ frappe.provide('frappe.ui.form'); +frappe.ui.form.make_quick_entry = (doctype, after_insert) => { + var trimmed_doctype = doctype.replace(/ /g, ''); + var controller_name = "QuickEntryForm"; + + if(frappe.ui.form[trimmed_doctype + "QuickEntryForm"]){ + controller_name = trimmed_doctype + "QuickEntryForm"; + } + + frappe.quick_entry = new frappe.ui.form[controller_name](doctype, after_insert); + return frappe.quick_entry.setup(); +}; + frappe.ui.form.QuickEntryForm = Class.extend({ - init: function(doctype, success_function){ + init: function(doctype, after_insert){ this.doctype = doctype; - this.success_function = success_function; - this.setup(); + this.after_insert = after_insert; }, - setup: function(){ - var me = this; - frappe.model.with_doctype(this.doctype, function() { - me.set_meta_and_mandatory_fields(); - var validate_flag = me.validate_quick_entry(); - if(!validate_flag){ - me.render_dialog(); - } + setup: function() { + let me = this; + return new Promise(resolve => { + frappe.model.with_doctype(this.doctype, function() { + me.set_meta_and_mandatory_fields(); + if(me.is_quick_entry()) { + me.render_dialog(); + resolve(me); + } else { + frappe.quick_entry = null; + frappe.set_route('Form', me.doctype, me.doc.name) + .then(() => resolve(me)); + } + }); }); }, @@ -25,34 +42,34 @@ frappe.ui.form.QuickEntryForm = Class.extend({ this.doc = frappe.model.get_new_doc(this.doctype, null, null, true); }, - validate_quick_entry: function(){ + is_quick_entry: function(){ if(this.meta.quick_entry != 1) { - frappe.set_route('Form', this.doctype, this.doc.name); - return true; + return false; } - var mandatory_flag = this.validate_mandatory_length(); - var child_table_flag = this.validate_for_child_table(); - if (mandatory_flag || child_table_flag){ - return true; + if (this.too_many_mandatory_fields() || this.has_child_table()) { + return false; } + this.validate_for_prompt_autoname(); + return true; }, - validate_mandatory_length: function(){ + too_many_mandatory_fields: function(){ if(this.mandatory.length > 7) { // too many fields, show form - frappe.set_route('Form', this.doctype, this.doc.name); return true; } + return false; }, - validate_for_child_table: function(){ - if($.map(this.mandatory, function(d) { return d.fieldtype==='Table' ? d : null; }).length) { + has_child_table: function(){ + if($.map(this.mandatory, function(d) { + return d.fieldtype==='Table' ? d : null; }).length) { // has mandatory table, quit! - frappe.set_route('Form', this.doctype, this.doc.name); return true; } + return false; }, validate_for_prompt_autoname: function(){ @@ -86,6 +103,7 @@ frappe.ui.form.QuickEntryForm = Class.extend({ } }); + this.dialog.onhide = () => frappe.quick_entry = null; this.dialog.show(); this.set_defaults(); }, @@ -93,44 +111,62 @@ frappe.ui.form.QuickEntryForm = Class.extend({ register_primary_action: function(){ var me = this; this.dialog.set_primary_action(__('Save'), function() { - if(me.dialog.working) return; + if(me.dialog.working) { + return; + } var data = me.dialog.get_values(); if(data) { me.dialog.working = true; - var values = me.update_doc(); - me.insert_document(values); + me.insert(); } }); }, - insert_document: function(values){ - var me = this; - frappe.call({ - method: "frappe.client.insert", - args: { - doc: values - }, - callback: function(r) { - me.dialog.hide(); - // delete the old doc - frappe.model.clear_doc(me.dialog.doc.doctype, me.dialog.doc.name); - var doc = r.message; - if(me.success_function) { - me.success_function(doc); - } - frappe.ui.form.update_calling_link(doc.name); - }, - error: function() { - me.open_doc(); - }, - always: function() { - me.dialog.working = false; - }, - freeze: true + insert: function() { + let me = this; + return new Promise(resolve => { + me.update_doc(); + frappe.call({ + method: "frappe.client.insert", + args: { + doc: me.dialog.doc + }, + callback: function(r) { + me.dialog.hide(); + // delete the old doc + frappe.model.clear_doc(me.dialog.doc.doctype, me.dialog.doc.name); + me.dialog.doc = r.message; + if(frappe._from_link) { + frappe.ui.form.update_calling_link(me.dialog.doc.name); + } else { + if(me.after_insert) { + me.after_insert(me.dialig.doc); + } else { + me.open_from_if_not_list(); + } + } + }, + error: function() { + me.open_doc(); + }, + always: function() { + me.dialog.working = false; + resolve(me.dialog.doc); + }, + freeze: true + }); }); }, + open_from_if_not_list: function() { + let route = frappe.get_route(); + let doc = this.dialog.doc; + if(route && !(route[0]==='List' && route[1]===doc.doctype)) { + frappe.set_route('Form', doc.doctype, doc.name); + } + }, + update_doc: function(){ var me = this; var data = this.dialog.get_values(true); diff --git a/frappe/public/js/frappe/form/save.js b/frappe/public/js/frappe/form/save.js index 0c00ab3876..ae71144e0b 100644 --- a/frappe/public/js/frappe/form/save.js +++ b/frappe/public/js/frappe/form/save.js @@ -226,8 +226,11 @@ frappe.ui.form.update_calling_link = function (newdoc) { // if from form, switch if (frappe._from_link.frm) { - frappe.set_route("Form", frappe._from_link.frm.doctype, frappe._from_link.frm.docname); - setTimeout(function () { frappe.utils.scroll_to(frappe._from_link_scrollY); }, 100); + frappe.set_route("Form", + frappe._from_link.frm.doctype, frappe._from_link.frm.docname) + .then(() => { + frappe.utils.scroll_to(frappe._from_link_scrollY); + }); } frappe._from_link = null; diff --git a/frappe/public/js/frappe/form/script_manager.js b/frappe/public/js/frappe/form/script_manager.js index be79a38c88..f86ab2520e 100644 --- a/frappe/public/js/frappe/form/script_manager.js +++ b/frappe/public/js/frappe/form/script_manager.js @@ -55,8 +55,8 @@ frappe.ui.form.off = function(doctype, fieldname, handler) { } -frappe.ui.form.trigger = function(doctype, fieldname, callback) { - cur_frm.script_manager.trigger(fieldname, doctype, null, callback); +frappe.ui.form.trigger = function(doctype, fieldname) { + cur_frm.script_manager.trigger(fieldname, doctype); } frappe.ui.form.ScriptManager = Class.extend({ @@ -64,32 +64,76 @@ frappe.ui.form.ScriptManager = Class.extend({ $.extend(this, opts); }, make: function(ControllerClass) { - this.frm.cscript = $.extend(this.frm.cscript, new ControllerClass({frm: this.frm})); + this.frm.cscript = $.extend(this.frm.cscript, + new ControllerClass({frm: this.frm})); }, - trigger: function(event_name, doctype, name, callback) { - var me = this; - doctype = doctype || this.frm.doctype; - name = name || this.frm.docname; - var handlers = this.get_handlers(event_name, doctype, name, callback); - if(callback) handlers.push(callback); + trigger: function(event_name, doctype, name) { + // trigger all the form level events that + // are bound to this event_name + let me = this; + return new Promise(resolve => { + doctype = doctype || this.frm.doctype; + name = name || this.frm.docname; + + let tasks = []; + let handlers = this.get_handlers(event_name, doctype); + + // helper for child table + this.frm.selected_doc = frappe.get_doc(doctype, name); + + let runner = (_function, is_old_style) => { + let _promise = null; + if(is_old_style) { + // old style arguments (doc, cdt, cdn) + _promise = me.frm.cscript[_function](me.frm.doc, doctype, name); + } else { + // new style (frm, doctype, name) + _promise = _function(me.frm, doctype, name); + } + + // if the trigger returns a promise, return it, + // or use the default promise frappe.after_ajax + if (_promise && _promise.then) { + return _promise; + } else { + return frappe.after_server_call(); + } + }; + + // make list of functions to be run serially + handlers.new_style.forEach((_function) => { + tasks.push(() => runner(_function, false)); + }); - this.frm.selected_doc = frappe.get_doc(doctype, name); + handlers.old_style.forEach((_function) => { + tasks.push(() => runner(_function, true)); + }); - return $.when.apply($, $.map(handlers, function(fn) { return fn(); })); + // run them serially + frappe.run_serially(tasks).then(resolve()); + }); }, - get_handlers: function(event_name, doctype, name, callback) { - var handlers = []; - var me = this; + has_handlers: function(event_name, doctype) { + let handlers = this.get_handlers(event_name, doctype); + return handlers && (handlers.old_style.length || handlers.new_style.length); + }, + get_handlers: function(event_name, doctype) { + // returns list of all functions to be called (old style and new style) + let me = this; + let handlers = { + old_style: [], + new_style: [] + }; if(frappe.ui.form.handlers[doctype] && frappe.ui.form.handlers[doctype][event_name]) { $.each(frappe.ui.form.handlers[doctype][event_name], function(i, fn) { - handlers.push(function() { return fn(me.frm, doctype, name) }); + handlers.new_style.push(fn); }); } if(this.frm.cscript[event_name]) { - handlers.push(function() { return me.frm.cscript[event_name](me.frm.doc, doctype, name); }); + handlers.old_style.push(event_name); } if(this.frm.cscript["custom_" + event_name]) { - handlers.push(function() { return me.frm.cscript["custom_" + event_name](me.frm.doc, doctype, name); }); + handlers.old_style.push("custom_" + event_name); } return handlers; }, diff --git a/frappe/public/js/frappe/model/create_new.js b/frappe/public/js/frappe/model/create_new.js index 79d9ab44ef..04496c6238 100644 --- a/frappe/public/js/frappe/model/create_new.js +++ b/frappe/public/js/frappe/model/create_new.js @@ -313,32 +313,20 @@ $.extend(frappe.model, { frappe.create_routes = {}; frappe.new_doc = function (doctype, opts) { - if(opts && $.isPlainObject(opts)) { frappe.route_options = opts; } - frappe.model.with_doctype(doctype, function() { - if(frappe.create_routes[doctype]) { - frappe.set_route(frappe.create_routes[doctype]); - } else { - var trimmed_doctype = doctype.replace(/ /g, ''); - var controller_name = "QuickEntryForm"; - - if(frappe.ui.form[trimmed_doctype + "QuickEntryForm"]){ - controller_name = trimmed_doctype + "QuickEntryForm"; + return new Promise(resolve => { + if(opts && $.isPlainObject(opts)) { + frappe.route_options = opts; + } + frappe.model.with_doctype(doctype, function() { + if(frappe.create_routes[doctype]) { + frappe.set_route(frappe.create_routes[doctype]) + .then(() => resolve()); + } else { + frappe.ui.form.make_quick_entry(doctype) + .then(() => resolve()); } + }); - new frappe.ui.form[controller_name](doctype, function(doc) { - //frappe.set_route('List', doctype); - var title = doc.name; - var title_field = frappe.get_meta(doc.doctype).title_field; - if (title_field) { - title = doc[title_field]; - } - - var route = frappe.get_route(); - if(route && !(route[0]==='List' && route[1]===doc.doctype)) { - frappe.set_route('Form', doc.doctype, doc.name); - } - }); - } }); } diff --git a/frappe/public/js/frappe/model/meta.js b/frappe/public/js/frappe/model/meta.js index 04b0594685..3b5fd9e857 100644 --- a/frappe/public/js/frappe/model/meta.js +++ b/frappe/public/js/frappe/model/meta.js @@ -164,7 +164,8 @@ $.extend(frappe.meta, { }); if(!out) { - frappe.msgprint(__('Warning: Unable to find {0} in any table related to {1}', [ + // eslint-disable-next-line + console.log(__('Warning: Unable to find {0} in any table related to {1}', [ key, __(doctype)])); } } diff --git a/frappe/public/js/frappe/model/model.js b/frappe/public/js/frappe/model/model.js index 9a1dd1d30d..23f5e6f0d5 100644 --- a/frappe/public/js/frappe/model/model.js +++ b/frappe/public/js/frappe/model/model.js @@ -327,9 +327,11 @@ $.extend(frappe.model, { set_value: function(doctype, docname, fieldname, value, fieldtype) { /* help: Set a value locally (if changed) and execute triggers */ + var doc = locals[doctype] && locals[doctype][docname]; var to_update = fieldname; + let tasks = []; if(!$.isPlainObject(to_update)) { to_update = {}; to_update[fieldname] = value; @@ -343,14 +345,16 @@ $.extend(frappe.model, { } doc[key] = value; - frappe.model.trigger(key, value, doc); + tasks.push(() => frappe.model.trigger(key, value, doc)); } else { // execute link triggers (want to reselect to execute triggers) if(fieldtype=="Link" && doc) { - frappe.model.trigger(key, value, doc); + tasks.push(() => frappe.model.trigger(key, value, doc)); } } }); + + return frappe.run_serially(tasks); }, on: function(doctype, fieldname, fn) { @@ -371,21 +375,34 @@ $.extend(frappe.model, { }, trigger: function(fieldname, value, doc) { - - var run = function(events, event_doc) { + let tasks = []; + var runner = function(events, event_doc) { $.each(events || [], function(i, fn) { - fn && fn(fieldname, value, event_doc || doc); + if(fn) { + let _promise = fn(fieldname, value, event_doc || doc); + + // if the trigger returns a promise, return it, + // or use the default promise frappe.after_ajax + if (_promise && _promise.then) { + return _promise; + } else { + return frappe.after_server_call(); + } + } }); }; if(frappe.model.events[doc.doctype]) { + tasks.push(() => { + return runner(frappe.model.events[doc.doctype][fieldname]); + }); - // field-level - run(frappe.model.events[doc.doctype][fieldname]); - - // doctype-level - run(frappe.model.events[doc.doctype]['*']); + tasks.push(() => { + runner(frappe.model.events[doc.doctype]['*']); + }); } + + frappe.run_serially(tasks); }, get_doc: function(doctype, name) { diff --git a/frappe/public/js/frappe/provide.js b/frappe/public/js/frappe/provide.js index 145cf9ede6..40610548ec 100644 --- a/frappe/public/js/frappe/provide.js +++ b/frappe/public/js/frappe/provide.js @@ -26,4 +26,4 @@ frappe.provide("frappe.utils"); frappe.provide("frappe.ui"); frappe.provide("frappe.modules"); frappe.provide("frappe.templates"); - +frappe.provide("frappe.test_data"); diff --git a/frappe/public/js/frappe/request.js b/frappe/public/js/frappe/request.js index babcae673e..e2a894af7b 100644 --- a/frappe/public/js/frappe/request.js +++ b/frappe/public/js/frappe/request.js @@ -10,8 +10,9 @@ frappe.request.waiting_for_ajax = []; // generic server call (call page, object) frappe.call = function(opts) { - if(opts.quiet) + if(opts.quiet) { opts.no_spinner = true; + } var args = $.extend({}, opts.args); // cmd @@ -302,13 +303,31 @@ frappe.request.cleanup = function(opts, r) { } } -frappe.after_ajax = function(fn) { +frappe.after_server_call = () => { if(frappe.request.ajax_count) { - frappe.request.waiting_for_ajax.push(fn); + return new Promise(resolve => { + frappe.request.waiting_for_ajax.push(() => { + resolve(); + }); + }); } else { - fn(); + return null; } -} +}; + +frappe.after_ajax = function(fn) { + return new Promise(resolve => { + if(frappe.request.ajax_count) { + frappe.request.waiting_for_ajax.push(() => { + if(fn) fn(); + resolve(); + }); + } else { + if(fn) fn(); + resolve(); + } + }); +}; frappe.request.report_error = function(xhr, request_opts) { var data = JSON.parse(xhr.responseText); diff --git a/frappe/public/js/frappe/router.js b/frappe/public/js/frappe/router.js index c3cdc971aa..438d3ee3d2 100644 --- a/frappe/public/js/frappe/router.js +++ b/frappe/public/js/frappe/router.js @@ -123,24 +123,31 @@ frappe.get_route_str = function(route) { } frappe.set_route = function() { - var params = arguments; - if(params.length===1 && $.isArray(params[0])) { - params = params[0]; - } - var route = $.map(params, function(a) { - if($.isPlainObject(a)) { - frappe.route_options = a; - return null; - } else { - return a; - // return a ? encodeURIComponent(a) : null; + return new Promise(resolve => { + var params = arguments; + if(params.length===1 && $.isArray(params[0])) { + params = params[0]; } - }).join('/'); - - window.location.hash = route; - - // Set favicon (app.js) - frappe.app.set_favicon && frappe.app.set_favicon(); + var route = $.map(params, function(a) { + if($.isPlainObject(a)) { + frappe.route_options = a; + return null; + } else { + return a; + // return a ? encodeURIComponent(a) : null; + } + }).join('/'); + + window.location.hash = route; + + // Set favicon (app.js) + frappe.app.set_favicon && frappe.app.set_favicon(); + setTimeout(() => { + frappe.after_ajax(() => { + resolve(); + }); + }, 100); + }); } frappe.set_re_route = function() { diff --git a/frappe/public/js/frappe/ui/base_list.js b/frappe/public/js/frappe/ui/base_list.js index 8f04efe6ba..cbb57aa341 100644 --- a/frappe/public/js/frappe/ui/base_list.js +++ b/frappe/public/js/frappe/ui/base_list.js @@ -192,7 +192,6 @@ frappe.ui.BaseList = Class.extend({ onchange: () => { me.refresh(true); } }); - var has_standard_filters = false; this.meta.fields.forEach(function(df) { if(df.in_standard_filter) { if(df.fieldtype == "Select" && df.options) { @@ -205,17 +204,13 @@ frappe.ui.BaseList = Class.extend({ me.page.add_field({ fieldtype: df.fieldtype, label: __(df.label), - options: df.options, + options: options, fieldname: df.fieldname, - onchange: () => {me.refresh(true);} + onchange: () => { me.refresh(true); } }); } }); - this.page.page_form.on('change', ':input', function() { - me.refresh(true); - }); - this.standard_filters_added = true; }, diff --git a/frappe/public/js/frappe/ui/field_group.js b/frappe/public/js/frappe/ui/field_group.js index 896dca5901..3084340cd4 100644 --- a/frappe/public/js/frappe/ui/field_group.js +++ b/frappe/public/js/frappe/ui/field_group.js @@ -89,11 +89,18 @@ frappe.ui.FieldGroup = frappe.ui.form.Layout.extend({ return f && (f.get_parsed_value ? f.get_parsed_value() : null); }, set_value: function(key, val){ - var f = this.fields_dict[key]; - if(f) { - f.set_input(val); - this.refresh_dependency(); - } + return new Promise(resolve => { + var f = this.fields_dict[key]; + if(f) { + f.set_value(val).then(() => { + f.set_input(val); + this.refresh_dependency(); + resolve(); + }); + } else { + resolve(); + } + }); }, set_input: function(key, val) { return this.set_value(key, val); diff --git a/frappe/public/js/frappe/ui/filters/edit_filter.html b/frappe/public/js/frappe/ui/filters/edit_filter.html index 6233e65b1b..49944a459e 100644 --- a/frappe/public/js/frappe/ui/filters/edit_filter.html +++ b/frappe/public/js/frappe/ui/filters/edit_filter.html @@ -19,8 +19,11 @@
diff --git a/frappe/public/js/frappe/ui/find.js b/frappe/public/js/frappe/ui/find.js new file mode 100644 index 0000000000..6b2756ab73 --- /dev/null +++ b/frappe/public/js/frappe/ui/find.js @@ -0,0 +1,16 @@ +frappe.find = { + page_primary_action: () => { + return $('.page-actions:visible .btn-primary'); + }, + field: (fieldname, value) => { + return new Promise(resolve => { + let input = $(`[data-fieldname="${fieldname}"] :input`); + if(value) { + input.val(value).trigger('change'); + frappe.after_ajax(() => { resolve(input); }); + } else { + resolve(input); + } + }); + } +}; \ No newline at end of file diff --git a/frappe/public/js/frappe/views/test_runner.js b/frappe/public/js/frappe/views/test_runner.js deleted file mode 100644 index 2e8638d9bc..0000000000 --- a/frappe/public/js/frappe/views/test_runner.js +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -// MIT License. See license.txt - -frappe.standard_pages["test-runner"] = function() { - var wrapper = frappe.container.add_page('test-runner'); - - frappe.ui.make_app_page({ - parent: wrapper, - single_column: true, - title: __("Test Runner") - }); - - $("
").appendTo($(wrapper).find(".layout-main")); - - var route = frappe.get_route(); - if(route.length < 2) { - frappe.msgprint(__("To run a test add the module name in the route after '{0}'. For example, {1}", ['test-runner/', '#test-runner/lib/js/frappe/test_app.js'])); - return; - } - - var requires = ["assets/frappe/js/lib/jquery/qunit.js", - "assets/frappe/js/lib/jquery/qunit.css"].concat(route.splice(1).join("/")); - - frappe.require(requires, function() { - QUnit.load(); - }); -} diff --git a/frappe/public/js/legacy/clientscriptAPI.js b/frappe/public/js/legacy/clientscriptAPI.js index a57362bd63..78e5c6b517 100644 --- a/frappe/public/js/legacy/clientscriptAPI.js +++ b/frappe/public/js/legacy/clientscriptAPI.js @@ -368,13 +368,13 @@ _f.Frm.prototype.set_read_only = function() { } _f.Frm.prototype.trigger = function(event) { - this.script_manager.trigger(event); + return this.script_manager.trigger(event); }; _f.Frm.prototype.get_formatted = function(fieldname) { return frappe.format(this.doc[fieldname], - frappe.meta.get_docfield(this.doctype, fieldname, this.docname), - {no_icon:true}, this.doc); + frappe.meta.get_docfield(this.doctype, fieldname, this.docname), + {no_icon:true}, this.doc); } _f.Frm.prototype.open_grid_row = function() { diff --git a/frappe/public/js/legacy/form.js b/frappe/public/js/legacy/form.js index 24e73d3bd8..8c33c7f55a 100644 --- a/frappe/public/js/legacy/form.js +++ b/frappe/public/js/legacy/form.js @@ -606,18 +606,19 @@ _f.Frm.prototype.setnewdoc = function() { var me = this; // hide any open grid - this.script_manager.trigger("before_load", this.doctype, this.docname, function() { - me.script_manager.trigger("onload"); - me.opendocs[me.docname] = true; - me.render_form(); + this.script_manager.trigger("before_load", this.doctype, this.docname) + .then(() => { + me.script_manager.trigger("onload"); + me.opendocs[me.docname] = true; + me.render_form(); + + frappe.after_ajax(function() { + me.trigger_link_fields(); + }); - frappe.after_ajax(function() { - me.trigger_link_fields(); + frappe.breadcrumbs.add(me.meta.module, me.doctype) }); - frappe.breadcrumbs.add(me.meta.module, me.doctype) - }); - // update seen if(this.meta.track_seen) { $('.list-id[data-name="'+ me.docname +'"]').addClass('seen'); @@ -705,17 +706,21 @@ Object.defineProperty(window, 'validated', { }); _f.Frm.prototype.save = function(save_action, callback, btn, on_error) { - btn && $(btn).prop("disabled", true); - $(document.activeElement).blur(); + let me = this; + return new Promise(resolve => { + btn && $(btn).prop("disabled", true); + $(document.activeElement).blur(); - frappe.ui.form.close_grid_form(); + frappe.ui.form.close_grid_form(); - // let any pending js process finish - var me = this; - setTimeout(function() { me._save(save_action, callback, btn, on_error) }, 100); + // let any pending js process finish + setTimeout(function() { + me._save(save_action, callback, btn, on_error, resolve); + }, 100); + }); } -_f.Frm.prototype._save = function(save_action, callback, btn, on_error) { +_f.Frm.prototype._save = function(save_action, callback, btn, on_error, resolve) { var me = this; if(!save_action) save_action = "Save"; this.validate_form_action(save_action); @@ -736,26 +741,29 @@ _f.Frm.prototype._save = function(save_action, callback, btn, on_error) { on_error(); } callback && callback(r); + resolve(); } if(save_action != "Update") { // validate frappe.validated = true; - $.when(this.script_manager.trigger("validate"), this.script_manager.trigger("before_save")) - .done(function() { - // done is called after all ajaxes in validate & before_save are completed :) - - if(!frappe.validated) { - btn && $(btn).prop("disabled", false); - if(on_error) { - on_error(); - } - return; - } + Promise.all([ + this.script_manager.trigger("validate"), + this.script_manager.trigger("before_save") + ]).then(() => { + // done is called after all ajaxes in validate & before_save are completed :) - frappe.ui.form.save(me, save_action, after_save, btn); - }); + if(!frappe.validated) { + btn && $(btn).prop("disabled", false); + if(on_error) { + on_error(); + } + resolve(); + return; + } + frappe.ui.form.save(me, save_action, after_save, btn); + }); } else { frappe.ui.form.save(me, save_action, after_save, btn); } @@ -767,7 +775,7 @@ _f.Frm.prototype.savesubmit = function(btn, callback, on_error) { this.validate_form_action("Submit"); frappe.confirm(__("Permanently Submit {0}?", [this.docname]), function() { frappe.validated = true; - me.script_manager.trigger("before_submit").done(function() { + me.script_manager.trigger("before_submit").then(function() { if(!frappe.validated) { if(on_error) on_error(); @@ -964,10 +972,6 @@ _f.Frm.prototype.validate_form_action = function(action) { } }; -_f.Frm.prototype.get_handlers = function(fieldname, doctype, docname) { - return this.script_manager.get_handlers(fieldname, doctype || this.doctype, docname || this.docname) -} - _f.Frm.prototype.has_perm = function(ptype) { return frappe.perm.has_perm(this.doctype, 0, ptype, this.doc); } diff --git a/frappe/public/js/lib/jquery/qunit.css b/frappe/public/js/lib/jquery/qunit.css index 3850bf18b9..7a46935334 100644 --- a/frappe/public/js/lib/jquery/qunit.css +++ b/frappe/public/js/lib/jquery/qunit.css @@ -1,12 +1,12 @@ /*! - * QUnit 2.0.0 + * QUnit 2.3.3 * https://qunitjs.com/ * * Copyright jQuery Foundation and other contributors * Released under the MIT license * https://jquery.org/license * - * Date: 2016-06-16T17:09Z + * Date: 2017-06-02T14:07Z */ /** Font Family and Sizes */ @@ -226,7 +226,8 @@ #qunit-tests li.running, #qunit-tests li.pass, #qunit-tests li.fail, -#qunit-tests li.skipped { +#qunit-tests li.skipped, +#qunit-tests li.aborted { display: list-item; } @@ -235,7 +236,7 @@ } #qunit-tests.hidepass li.running, -#qunit-tests.hidepass li.pass { +#qunit-tests.hidepass li.pass:not(.todo) { visibility: hidden; position: absolute; width: 0; @@ -374,12 +375,16 @@ #qunit-banner.qunit-fail { background-color: #EE5757; } + +/*** Aborted tests */ +#qunit-tests .aborted { color: #000; background-color: orange; } /*** Skipped tests */ #qunit-tests .skipped { background-color: #EBECE9; } +#qunit-tests .qunit-todo-label, #qunit-tests .qunit-skipped-label { background-color: #F4FF77; display: inline-block; @@ -390,19 +395,35 @@ margin: -0.4em 0.4em -0.4em 0; } +#qunit-tests .qunit-todo-label { + background-color: #EEE; +} + /** Result */ #qunit-testresult { - padding: 0.5em 1em 0.5em 1em; - color: #2B81AF; background-color: #D2E0E6; border-bottom: 1px solid #FFF; } +#qunit-testresult .clearfix { + height: 0; + clear: both; +} #qunit-testresult .module-name { font-weight: 700; } +#qunit-testresult-display { + padding: 0.5em 1em 0.5em 1em; + width: 85%; + float:left; +} +#qunit-testresult-controls { + padding: 0.5em 1em 0.5em 1em; + width: 10%; + float:left; +} /** Fixture */ @@ -412,4 +433,4 @@ left: -10000px; width: 1000px; height: 1000px; -} +} \ No newline at end of file diff --git a/frappe/public/js/lib/jquery/qunit.js b/frappe/public/js/lib/jquery/qunit.js index d3eae929fb..790f8c5652 100644 --- a/frappe/public/js/lib/jquery/qunit.js +++ b/frappe/public/js/lib/jquery/qunit.js @@ -1,4419 +1,4926 @@ /*! - * QUnit 2.0.0 + * QUnit 2.3.3 * https://qunitjs.com/ * * Copyright jQuery Foundation and other contributors * Released under the MIT license * https://jquery.org/license * - * Date: 2016-06-16T17:09Z + * Date: 2017-06-02T14:07Z */ +(function (global$1) { + 'use strict'; -( function( global ) { - -var QUnit = {}; - -var Date = global.Date; -var now = Date.now || function() { - return new Date().getTime(); -}; - -var setTimeout = global.setTimeout; -var clearTimeout = global.clearTimeout; - -// Store a local window from the global to allow direct references. -var window = global.window; - -var defined = { - document: window && window.document !== undefined, - setTimeout: setTimeout !== undefined, - sessionStorage: ( function() { - var x = "qunit-test-string"; - try { - sessionStorage.setItem( x, x ); - sessionStorage.removeItem( x ); - return true; - } catch ( e ) { - return false; - } - }() ) -}; - -var fileName = ( sourceFromStacktrace( 0 ) || "" ).replace( /(:\d+)+\)?/, "" ).replace( /.+\//, "" ); -var globalStartCalled = false; -var runStarted = false; - -var autorun = false; - -var toString = Object.prototype.toString, - hasOwn = Object.prototype.hasOwnProperty; - -// Returns a new Array with the elements that are in a but not in b -function diff( a, b ) { - var i, j, - result = a.slice(); - - for ( i = 0; i < result.length; i++ ) { - for ( j = 0; j < b.length; j++ ) { - if ( result[ i ] === b[ j ] ) { - result.splice( i, 1 ); - i--; - break; - } - } - } - return result; -} - -// From jquery.js -function inArray( elem, array ) { - if ( array.indexOf ) { - return array.indexOf( elem ); - } - - for ( var i = 0, length = array.length; i < length; i++ ) { - if ( array[ i ] === elem ) { - return i; - } - } - - return -1; -} - -/** - * Makes a clone of an object using only Array or Object as base, - * and copies over the own enumerable properties. - * - * @param {Object} obj - * @return {Object} New object with only the own properties (recursively). - */ -function objectValues ( obj ) { - var key, val, - vals = QUnit.is( "array", obj ) ? [] : {}; - for ( key in obj ) { - if ( hasOwn.call( obj, key ) ) { - val = obj[ key ]; - vals[ key ] = val === Object( val ) ? objectValues( val ) : val; - } - } - return vals; -} - -function extend( a, b, undefOnly ) { - for ( var prop in b ) { - if ( hasOwn.call( b, prop ) ) { - if ( b[ prop ] === undefined ) { - delete a[ prop ]; - } else if ( !( undefOnly && typeof a[ prop ] !== "undefined" ) ) { - a[ prop ] = b[ prop ]; - } - } - } - - return a; -} - -function objectType( obj ) { - if ( typeof obj === "undefined" ) { - return "undefined"; - } - - // Consider: typeof null === object - if ( obj === null ) { - return "null"; - } - - var match = toString.call( obj ).match( /^\[object\s(.*)\]$/ ), - type = match && match[ 1 ]; - - switch ( type ) { - case "Number": - if ( isNaN( obj ) ) { - return "nan"; - } - return "number"; - case "String": - case "Boolean": - case "Array": - case "Set": - case "Map": - case "Date": - case "RegExp": - case "Function": - case "Symbol": - return type.toLowerCase(); - } - if ( typeof obj === "object" ) { - return "object"; - } -} - -// Safe object type checking -function is( type, obj ) { - return QUnit.objectType( obj ) === type; -} - -// Doesn't support IE9, it will return undefined on these browsers -// See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack -function extractStacktrace( e, offset ) { - offset = offset === undefined ? 4 : offset; - - var stack, include, i; - - if ( e.stack ) { - stack = e.stack.split( "\n" ); - if ( /^error$/i.test( stack[ 0 ] ) ) { - stack.shift(); - } - if ( fileName ) { - include = []; - for ( i = offset; i < stack.length; i++ ) { - if ( stack[ i ].indexOf( fileName ) !== -1 ) { - break; - } - include.push( stack[ i ] ); - } - if ( include.length ) { - return include.join( "\n" ); - } - } - return stack[ offset ]; - } -} - -function sourceFromStacktrace( offset ) { - var error = new Error(); - - // Support: Safari <=7 only, IE <=10 - 11 only - // Not all browsers generate the `stack` property for `new Error()`, see also #636 - if ( !error.stack ) { - try { - throw error; - } catch ( err ) { - error = err; - } - } - - return extractStacktrace( error, offset ); -} - -/** - * Config object: Maintain internal state - * Later exposed as QUnit.config - * `config` initialized at top of scope - */ -var config = { + global$1 = 'default' in global$1 ? global$1['default'] : global$1; - // The queue of tests to run - queue: [], + var window = global$1.window; + var self$1 = global$1.self; + var console = global$1.console; + var setTimeout = global$1.setTimeout; + var clearTimeout = global$1.clearTimeout; - // Block until document ready - blocking: true, + var document = window && window.document; + var navigator = window && window.navigator; - // By default, run previously failed tests first - // very useful in combination with "Hide passed tests" checked - reorder: true, - - // By default, modify document.title when suite is done - altertitle: true, - - // HTML Reporter: collapse every test except the first failing test - // If false, all failing tests will be expanded - collapse: true, - - // By default, scroll to top of the page when suite is done - scrolltop: true, - - // Depth up-to which object will be dumped - maxDepth: 5, - - // When enabled, all tests must call expect() - requireExpects: false, - - // Placeholder for user-configurable form-exposed URL parameters - urlConfig: [], - - // Set of all modules. - modules: [], - - // Stack of nested modules - moduleStack: [], - - // The first unnamed module - currentModule: { - name: "", - tests: [] - }, - - callbacks: {} -}; - -// Push a loose unnamed module to the modules collection -config.modules.push( config.currentModule ); - -// Register logging callbacks -function registerLoggingCallbacks( obj ) { - var i, l, key, - callbackNames = [ "begin", "done", "log", "testStart", "testDone", - "moduleStart", "moduleDone" ]; - - function registerLoggingCallback( key ) { - var loggingCallback = function( callback ) { - if ( objectType( callback ) !== "function" ) { - throw new Error( - "QUnit logging methods require a callback function as their first parameters." - ); - } - - config.callbacks[ key ].push( callback ); - }; - - return loggingCallback; - } - - for ( i = 0, l = callbackNames.length; i < l; i++ ) { - key = callbackNames[ i ]; - - // Initialize key collection of logging callback - if ( objectType( config.callbacks[ key ] ) === "undefined" ) { - config.callbacks[ key ] = []; - } - - obj[ key ] = registerLoggingCallback( key ); - } -} - -function runLoggingCallbacks( key, args ) { - var i, l, callbacks; - - callbacks = config.callbacks[ key ]; - for ( i = 0, l = callbacks.length; i < l; i++ ) { - callbacks[ i ]( args ); - } -} - -( function() { - if ( !defined.document ) { - return; - } - - // `onErrorFnPrev` initialized at top of scope - // Preserve other handlers - var onErrorFnPrev = window.onerror; - - // Cover uncaught exceptions - // Returning true will suppress the default browser handler, - // returning false will let it run. - window.onerror = function( error, filePath, linerNr ) { - var ret = false; - if ( onErrorFnPrev ) { - ret = onErrorFnPrev( error, filePath, linerNr ); - } - - // Treat return value as window.onerror itself does, - // Only do our handling if not suppressed. - if ( ret !== true ) { - if ( QUnit.config.current ) { - if ( QUnit.config.current.ignoreGlobalErrors ) { - return true; - } - QUnit.pushFailure( error, filePath + ":" + linerNr ); - } else { - QUnit.test( "global failure", extend( function() { - QUnit.pushFailure( error, filePath + ":" + linerNr ); - }, { validTest: true } ) ); - } - return false; - } - - return ret; - }; -}() ); - -// Figure out if we're running the tests from a server or not -QUnit.isLocal = !( defined.document && window.location.protocol !== "file:" ); - -// Expose the current QUnit version -QUnit.version = "2.0.0"; - -extend( QUnit, { - - // Call on start of module test to prepend name to all tests - module: function( name, testEnvironment, executeNow ) { - var module, moduleFns; - var currentModule = config.currentModule; - - if ( arguments.length === 2 ) { - if ( objectType( testEnvironment ) === "function" ) { - executeNow = testEnvironment; - testEnvironment = undefined; - } - } - - module = createModule(); - - if ( testEnvironment && ( testEnvironment.setup || testEnvironment.teardown ) ) { - console.warn( - "Module's `setup` and `teardown` are not hooks anymore on QUnit 2.0, use " + - "`beforeEach` and `afterEach` instead\n" + - "Details in our upgrade guide at https://qunitjs.com/upgrade-guide-2.x/" - ); - } - - moduleFns = { - before: setHook( module, "before" ), - beforeEach: setHook( module, "beforeEach" ), - afterEach: setHook( module, "afterEach" ), - after: setHook( module, "after" ) - }; - - if ( objectType( executeNow ) === "function" ) { - config.moduleStack.push( module ); - setCurrentModule( module ); - executeNow.call( module.testEnvironment, moduleFns ); - config.moduleStack.pop(); - module = module.parentModule || currentModule; - } - - setCurrentModule( module ); - - function createModule() { - var parentModule = config.moduleStack.length ? - config.moduleStack.slice( -1 )[ 0 ] : null; - var moduleName = parentModule !== null ? - [ parentModule.name, name ].join( " > " ) : name; - var module = { - name: moduleName, - parentModule: parentModule, - tests: [], - moduleId: generateHash( moduleName ), - testsRun: 0 - }; - - var env = {}; - if ( parentModule ) { - parentModule.childModule = module; - extend( env, parentModule.testEnvironment ); - delete env.beforeEach; - delete env.afterEach; - } - extend( env, testEnvironment ); - module.testEnvironment = env; - - config.modules.push( module ); - return module; - } - - function setCurrentModule( module ) { - config.currentModule = module; - } - - }, - - test: test, - - skip: skip, - - only: only, - - start: function( count ) { - var globalStartAlreadyCalled = globalStartCalled; - - if ( !config.current ) { - globalStartCalled = true; - - if ( runStarted ) { - throw new Error( "Called start() while test already started running" ); - } else if ( globalStartAlreadyCalled || count > 1 ) { - throw new Error( "Called start() outside of a test context too many times" ); - } else if ( config.autostart ) { - throw new Error( "Called start() outside of a test context when " + - "QUnit.config.autostart was true" ); - } else if ( !config.pageLoaded ) { - - // The page isn't completely loaded yet, so bail out and let `QUnit.load` handle it - config.autostart = true; - return; - } - } else { - throw new Error( - "QUnit.start cannot be called inside a test context. This feature is removed in " + - "QUnit 2.0. For async tests, use QUnit.test() with assert.async() instead.\n" + - "Details in our upgrade guide at https://qunitjs.com/upgrade-guide-2.x/" - ); - } - - resumeProcessing(); - }, - - config: config, - - is: is, - - objectType: objectType, - - extend: extend, - - load: function() { - config.pageLoaded = true; - - // Initialize the configuration options - extend( config, { - stats: { all: 0, bad: 0 }, - moduleStats: { all: 0, bad: 0 }, - started: 0, - updateRate: 1000, - autostart: true, - filter: "" - }, true ); - - config.blocking = false; - - if ( config.autostart ) { - resumeProcessing(); - } - }, - - stack: function( offset ) { - offset = ( offset || 0 ) + 2; - return sourceFromStacktrace( offset ); - } -} ); - -registerLoggingCallbacks( QUnit ); - -function begin() { - var i, l, - modulesLog = []; - - // If the test run hasn't officially begun yet - if ( !config.started ) { - - // Record the time of the test run's beginning - config.started = now(); - - // Delete the loose unnamed module if unused. - if ( config.modules[ 0 ].name === "" && config.modules[ 0 ].tests.length === 0 ) { - config.modules.shift(); - } - - // Avoid unnecessary information by not logging modules' test environments - for ( i = 0, l = config.modules.length; i < l; i++ ) { - modulesLog.push( { - name: config.modules[ i ].name, - tests: config.modules[ i ].tests - } ); - } - - // The test run is officially beginning now - runLoggingCallbacks( "begin", { - totalTests: Test.count, - modules: modulesLog - } ); - } - - config.blocking = false; - process( true ); -} - -function process( last ) { - function next() { - process( last ); - } - var start = now(); - config.depth = ( config.depth || 0 ) + 1; - - while ( config.queue.length && !config.blocking ) { - if ( !defined.setTimeout || config.updateRate <= 0 || - ( ( now() - start ) < config.updateRate ) ) { - if ( config.current ) { - - // Reset async tracking for each phase of the Test lifecycle - config.current.usedAsync = false; - } - config.queue.shift()(); - } else { - setTimeout( next, 13 ); - break; - } - } - config.depth--; - if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) { - done(); - } -} - -function pauseProcessing( test ) { - config.blocking = true; - - if ( config.testTimeout && defined.setTimeout ) { - clearTimeout( config.timeout ); - config.timeout = setTimeout( function() { - test.semaphore = 0; - QUnit.pushFailure( "Test timed out", sourceFromStacktrace( 2 ) ); - resumeProcessing( test ); - }, config.testTimeout ); - } -} - -function resumeProcessing( test ) { - runStarted = true; - - // A slight delay to allow this iteration of the event loop to finish (more assertions, etc.) - if ( defined.setTimeout ) { - setTimeout( function() { - var current = test || config.current; - if ( current && ( current.semaphore > 0 || current.resumed ) ) { - return; - } - - if ( config.timeout ) { - clearTimeout( config.timeout ); - } - - if ( current ) { - current.resumed = true; - } - - begin(); - }, 13 ); - } else { - begin(); - } -} - -function done() { - var runtime, passed; - - autorun = true; - - // Log the last module results - if ( config.previousModule ) { - runLoggingCallbacks( "moduleDone", { - name: config.previousModule.name, - tests: config.previousModule.tests, - failed: config.moduleStats.bad, - passed: config.moduleStats.all - config.moduleStats.bad, - total: config.moduleStats.all, - runtime: now() - config.moduleStats.started - } ); - } - delete config.previousModule; - - runtime = now() - config.started; - passed = config.stats.all - config.stats.bad; - - runLoggingCallbacks( "done", { - failed: config.stats.bad, - passed: passed, - total: config.stats.all, - runtime: runtime - } ); -} - -function setHook( module, hookName ) { - if ( module.testEnvironment === undefined ) { - module.testEnvironment = {}; - } - - return function( callback ) { - module.testEnvironment[ hookName ] = callback; - }; -} - -var unitSampler, - focused = false, - priorityCount = 0; - -function Test( settings ) { - var i, l; - - ++Test.count; - - this.expected = null; - extend( this, settings ); - this.assertions = []; - this.semaphore = 0; - this.usedAsync = false; - this.module = config.currentModule; - this.stack = sourceFromStacktrace( 3 ); - - // Register unique strings - for ( i = 0, l = this.module.tests; i < l.length; i++ ) { - if ( this.module.tests[ i ].name === this.testName ) { - this.testName += " "; - } - } - - this.testId = generateHash( this.module.name, this.testName ); - - this.module.tests.push( { - name: this.testName, - testId: this.testId - } ); - - if ( settings.skip ) { - - // Skipped tests will fully ignore any sent callback - this.callback = function() {}; - this.async = false; - this.expected = 0; - } else { - this.assert = new Assert( this ); - } -} - -Test.count = 0; - -Test.prototype = { - before: function() { - if ( - - // Emit moduleStart when we're switching from one module to another - this.module !== config.previousModule || - - // They could be equal (both undefined) but if the previousModule property doesn't - // yet exist it means this is the first test in a suite that isn't wrapped in a - // module, in which case we'll just emit a moduleStart event for 'undefined'. - // Without this, reporters can get testStart before moduleStart which is a problem. - !hasOwn.call( config, "previousModule" ) - ) { - if ( hasOwn.call( config, "previousModule" ) ) { - runLoggingCallbacks( "moduleDone", { - name: config.previousModule.name, - tests: config.previousModule.tests, - failed: config.moduleStats.bad, - passed: config.moduleStats.all - config.moduleStats.bad, - total: config.moduleStats.all, - runtime: now() - config.moduleStats.started - } ); - } - config.previousModule = this.module; - config.moduleStats = { all: 0, bad: 0, started: now() }; - runLoggingCallbacks( "moduleStart", { - name: this.module.name, - tests: this.module.tests - } ); - } - - config.current = this; - - if ( this.module.testEnvironment ) { - delete this.module.testEnvironment.before; - delete this.module.testEnvironment.beforeEach; - delete this.module.testEnvironment.afterEach; - delete this.module.testEnvironment.after; - } - this.testEnvironment = extend( {}, this.module.testEnvironment ); - - this.started = now(); - runLoggingCallbacks( "testStart", { - name: this.testName, - module: this.module.name, - testId: this.testId - } ); - - if ( !config.pollution ) { - saveGlobal(); - } - }, - - run: function() { - var promise; - - config.current = this; - - if ( this.async ) { - internalStop( this ); - } - - this.callbackStarted = now(); - - if ( config.notrycatch ) { - runTest( this ); - return; - } - - try { - runTest( this ); - } catch ( e ) { - this.pushFailure( "Died on test #" + ( this.assertions.length + 1 ) + " " + - this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) ); - - // Else next test will carry the responsibility - saveGlobal(); - - // Restart the tests if they're blocking - if ( config.blocking ) { - internalStart( this ); - } - } - - function runTest( test ) { - promise = test.callback.call( test.testEnvironment, test.assert ); - test.resolvePromise( promise ); - } - }, - - after: function() { - checkPollution(); - }, - - queueHook: function( hook, hookName, hookOwner ) { - var promise, - test = this; - return function runHook() { - if ( hookName === "before" ) { - if ( hookOwner.testsRun !== 0 ) { - return; - } - - test.preserveEnvironment = true; - } - - if ( hookName === "after" && hookOwner.testsRun !== numberOfTests( hookOwner ) - 1 ) { - return; - } - - config.current = test; - if ( config.notrycatch ) { - callHook(); - return; - } - try { - callHook(); - } catch ( error ) { - test.pushFailure( hookName + " failed on " + test.testName + ": " + - ( error.message || error ), extractStacktrace( error, 0 ) ); - } - - function callHook() { - promise = hook.call( test.testEnvironment, test.assert ); - test.resolvePromise( promise, hookName ); - } - }; - }, - - // Currently only used for module level hooks, can be used to add global level ones - hooks: function( handler ) { - var hooks = []; - - function processHooks( test, module ) { - if ( module.parentModule ) { - processHooks( test, module.parentModule ); - } - if ( module.testEnvironment && - QUnit.objectType( module.testEnvironment[ handler ] ) === "function" ) { - hooks.push( test.queueHook( module.testEnvironment[ handler ], handler, module ) ); - } - } - - // Hooks are ignored on skipped tests - if ( !this.skip ) { - processHooks( this, this.module ); - } - return hooks; - }, - - finish: function() { - config.current = this; - if ( config.requireExpects && this.expected === null ) { - this.pushFailure( "Expected number of assertions to be defined, but expect() was " + - "not called.", this.stack ); - } else if ( this.expected !== null && this.expected !== this.assertions.length ) { - this.pushFailure( "Expected " + this.expected + " assertions, but " + - this.assertions.length + " were run", this.stack ); - } else if ( this.expected === null && !this.assertions.length ) { - this.pushFailure( "Expected at least one assertion, but none were run - call " + - "expect(0) to accept zero assertions.", this.stack ); - } - - var i, - bad = 0; - - this.runtime = now() - this.started; - config.stats.all += this.assertions.length; - config.moduleStats.all += this.assertions.length; - - for ( i = 0; i < this.assertions.length; i++ ) { - if ( !this.assertions[ i ].result ) { - bad++; - config.stats.bad++; - config.moduleStats.bad++; - } - } - - notifyTestsRan( this.module ); - runLoggingCallbacks( "testDone", { - name: this.testName, - module: this.module.name, - skipped: !!this.skip, - failed: bad, - passed: this.assertions.length - bad, - total: this.assertions.length, - runtime: this.runtime, - - // HTML Reporter use - assertions: this.assertions, - testId: this.testId, - - // Source of Test - source: this.stack - } ); - - config.current = undefined; - }, - - preserveTestEnvironment: function() { - if ( this.preserveEnvironment ) { - this.module.testEnvironment = this.testEnvironment; - this.testEnvironment = extend( {}, this.module.testEnvironment ); - } - }, - - queue: function() { - var priority, - test = this; - - if ( !this.valid() ) { - return; - } - - function run() { - - // Each of these can by async - synchronize( [ - function() { - test.before(); - }, - - test.hooks( "before" ), - - function() { - test.preserveTestEnvironment(); - }, - - test.hooks( "beforeEach" ), - - function() { - test.run(); - }, - - test.hooks( "afterEach" ).reverse(), - test.hooks( "after" ).reverse(), - - function() { - test.after(); - }, - - function() { - test.finish(); - } - ] ); - } - - // Prioritize previously failed tests, detected from sessionStorage - priority = QUnit.config.reorder && defined.sessionStorage && - +sessionStorage.getItem( "qunit-test-" + this.module.name + "-" + this.testName ); - - return synchronize( run, priority, config.seed ); - }, - - pushResult: function( resultInfo ) { - - // Destructure of resultInfo = { result, actual, expected, message, negative } - var source, - details = { - module: this.module.name, - name: this.testName, - result: resultInfo.result, - message: resultInfo.message, - actual: resultInfo.actual, - expected: resultInfo.expected, - testId: this.testId, - negative: resultInfo.negative || false, - runtime: now() - this.started - }; - - if ( !resultInfo.result ) { - source = sourceFromStacktrace(); - - if ( source ) { - details.source = source; - } - } - - runLoggingCallbacks( "log", details ); - - this.assertions.push( { - result: !!resultInfo.result, - message: resultInfo.message - } ); - }, - - pushFailure: function( message, source, actual ) { - if ( !( this instanceof Test ) ) { - throw new Error( "pushFailure() assertion outside test context, was " + - sourceFromStacktrace( 2 ) ); - } - - var details = { - module: this.module.name, - name: this.testName, - result: false, - message: message || "error", - actual: actual || null, - testId: this.testId, - runtime: now() - this.started - }; - - if ( source ) { - details.source = source; - } - - runLoggingCallbacks( "log", details ); - - this.assertions.push( { - result: false, - message: message - } ); - }, - - resolvePromise: function( promise, phase ) { - var then, message, - test = this; - if ( promise != null ) { - then = promise.then; - if ( QUnit.objectType( then ) === "function" ) { - internalStop( test ); - then.call( - promise, - function() { internalStart( test ); }, - function( error ) { - message = "Promise rejected " + - ( !phase ? "during" : phase.replace( /Each$/, "" ) ) + - " " + test.testName + ": " + ( error.message || error ); - test.pushFailure( message, extractStacktrace( error, 0 ) ); - - // Else next test will carry the responsibility - saveGlobal(); - - // Unblock - internalStart( test ); - } - ); - } - } - }, - - valid: function() { - var filter = config.filter, - regexFilter = /^(!?)\/([\w\W]*)\/(i?$)/.exec( filter ), - module = config.module && config.module.toLowerCase(), - fullName = ( this.module.name + ": " + this.testName ); - - function moduleChainNameMatch( testModule ) { - var testModuleName = testModule.name ? testModule.name.toLowerCase() : null; - if ( testModuleName === module ) { - return true; - } else if ( testModule.parentModule ) { - return moduleChainNameMatch( testModule.parentModule ); - } else { - return false; - } - } - - function moduleChainIdMatch( testModule ) { - return inArray( testModule.moduleId, config.moduleId ) > -1 || - testModule.parentModule && moduleChainIdMatch( testModule.parentModule ); - } - - // Internally-generated tests are always valid - if ( this.callback && this.callback.validTest ) { - return true; - } - - if ( config.moduleId && config.moduleId.length > 0 && - !moduleChainIdMatch( this.module ) ) { - - return false; - } - - if ( config.testId && config.testId.length > 0 && - inArray( this.testId, config.testId ) < 0 ) { - - return false; - } - - if ( module && !moduleChainNameMatch( this.module ) ) { - return false; - } - - if ( !filter ) { - return true; - } - - return regexFilter ? - this.regexFilter( !!regexFilter[ 1 ], regexFilter[ 2 ], regexFilter[ 3 ], fullName ) : - this.stringFilter( filter, fullName ); - }, - - regexFilter: function( exclude, pattern, flags, fullName ) { - var regex = new RegExp( pattern, flags ); - var match = regex.test( fullName ); - - return match !== exclude; - }, - - stringFilter: function( filter, fullName ) { - filter = filter.toLowerCase(); - fullName = fullName.toLowerCase(); - - var include = filter.charAt( 0 ) !== "!"; - if ( !include ) { - filter = filter.slice( 1 ); - } - - // If the filter matches, we need to honour include - if ( fullName.indexOf( filter ) !== -1 ) { - return include; - } - - // Otherwise, do the opposite - return !include; - } -}; - -QUnit.pushFailure = function() { - if ( !QUnit.config.current ) { - throw new Error( "pushFailure() assertion outside test context, in " + - sourceFromStacktrace( 2 ) ); - } - - // Gets current test obj - var currentTest = QUnit.config.current; - - return currentTest.pushFailure.apply( currentTest, arguments ); -}; - -// Based on Java's String.hashCode, a simple but not -// rigorously collision resistant hashing function -function generateHash( module, testName ) { - var hex, - i = 0, - hash = 0, - str = module + "\x1C" + testName, - len = str.length; - - for ( ; i < len; i++ ) { - hash = ( ( hash << 5 ) - hash ) + str.charCodeAt( i ); - hash |= 0; - } - - // Convert the possibly negative integer hash code into an 8 character hex string, which isn't - // strictly necessary but increases user understanding that the id is a SHA-like hash - hex = ( 0x100000000 + hash ).toString( 16 ); - if ( hex.length < 8 ) { - hex = "0000000" + hex; - } - - return hex.slice( -8 ); -} - -function synchronize( callback, priority, seed ) { - var last = !priority, - index; - - if ( QUnit.objectType( callback ) === "array" ) { - while ( callback.length ) { - synchronize( callback.shift() ); - } - return; - } - - if ( priority ) { - config.queue.splice( priorityCount++, 0, callback ); - } else if ( seed ) { - if ( !unitSampler ) { - unitSampler = unitSamplerGenerator( seed ); - } - - // Insert into a random position after all priority items - index = Math.floor( unitSampler() * ( config.queue.length - priorityCount + 1 ) ); - config.queue.splice( priorityCount + index, 0, callback ); - } else { - config.queue.push( callback ); - } - - if ( autorun && !config.blocking ) { - process( last ); - } -} - -function unitSamplerGenerator( seed ) { - - // 32-bit xorshift, requires only a nonzero seed - // http://excamera.com/sphinx/article-xorshift.html - var sample = parseInt( generateHash( seed ), 16 ) || -1; - return function() { - sample ^= sample << 13; - sample ^= sample >>> 17; - sample ^= sample << 5; - - // ECMAScript has no unsigned number type - if ( sample < 0 ) { - sample += 0x100000000; - } - - return sample / 0x100000000; - }; -} - -function saveGlobal() { - config.pollution = []; - - if ( config.noglobals ) { - for ( var key in global ) { - if ( hasOwn.call( global, key ) ) { - - // In Opera sometimes DOM element ids show up here, ignore them - if ( /^qunit-test-output/.test( key ) ) { - continue; - } - config.pollution.push( key ); - } - } - } -} - -function checkPollution() { - var newGlobals, - deletedGlobals, - old = config.pollution; - - saveGlobal(); - - newGlobals = diff( config.pollution, old ); - if ( newGlobals.length > 0 ) { - QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join( ", " ) ); - } - - deletedGlobals = diff( old, config.pollution ); - if ( deletedGlobals.length > 0 ) { - QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join( ", " ) ); - } -} - -// Will be exposed as QUnit.test -function test( testName, callback ) { - if ( focused ) { return; } - - var newTest; - - newTest = new Test( { - testName: testName, - callback: callback - } ); - - newTest.queue(); -} - -// Will be exposed as QUnit.skip -function skip( testName ) { - if ( focused ) { return; } - - var test = new Test( { - testName: testName, - skip: true - } ); - - test.queue(); -} - -// Will be exposed as QUnit.only -function only( testName, callback ) { - var newTest; - - if ( focused ) { return; } - - QUnit.config.queue.length = 0; - focused = true; - - newTest = new Test( { - testName: testName, - callback: callback - } ); - - newTest.queue(); -} - -function internalStop( test ) { - - // If a test is running, adjust its semaphore - test.semaphore += 1; - - pauseProcessing( test ); -} - -function internalStart( test ) { - - // If a test is running, adjust its semaphore - test.semaphore -= 1; - - // If semaphore is non-numeric, throw error - if ( isNaN( test.semaphore ) ) { - test.semaphore = 0; - - QUnit.pushFailure( - "Invalid value on test.semaphore", - sourceFromStacktrace( 2 ) - ); - return; - } - - // Don't start until equal number of stop-calls - if ( test.semaphore > 0 ) { - return; - } - - // Throw an Error if start is called more often than stop - if ( test.semaphore < 0 ) { - test.semaphore = 0; - - QUnit.pushFailure( - "Tried to restart test while already started (test's semaphore was 0 already)", - sourceFromStacktrace( 2 ) - ); - return; - } - - resumeProcessing( test ); -} - -function numberOfTests( module ) { - var count = module.tests.length; - while ( module = module.childModule ) { - count += module.tests.length; - } - return count; -} - -function notifyTestsRan( module ) { - module.testsRun++; - while ( module = module.parentModule ) { - module.testsRun++; - } -} - -function Assert( testContext ) { - this.test = testContext; -} - -// Assert helpers -QUnit.assert = Assert.prototype = { - - // Specify the number of expected assertions to guarantee that failed test - // (no assertions are run at all) don't slip through. - expect: function( asserts ) { - if ( arguments.length === 1 ) { - this.test.expected = asserts; - } else { - return this.test.expected; - } - }, - - // Increment this Test's semaphore counter, then return a function that - // decrements that counter a maximum of once. - async: function( count ) { - var test = this.test, - popped = false, - acceptCallCount = count; - - if ( typeof acceptCallCount === "undefined" ) { - acceptCallCount = 1; - } - - test.semaphore += 1; - test.usedAsync = true; - pauseProcessing( test ); - - return function done() { - - if ( popped ) { - test.pushFailure( "Too many calls to the `assert.async` callback", - sourceFromStacktrace( 2 ) ); - return; - } - acceptCallCount -= 1; - if ( acceptCallCount > 0 ) { - return; - } - - test.semaphore -= 1; - popped = true; - resumeProcessing( test ); - }; - }, - - // Exports test.push() to the user API - // Alias of pushResult. - push: function( result, actual, expected, message, negative ) { - var currentAssert = this instanceof Assert ? this : QUnit.config.current.assert; - return currentAssert.pushResult( { - result: result, - actual: actual, - expected: expected, - message: message, - negative: negative - } ); - }, - - pushResult: function( resultInfo ) { - - // Destructure of resultInfo = { result, actual, expected, message, negative } - var assert = this, - currentTest = ( assert instanceof Assert && assert.test ) || QUnit.config.current; - - // Backwards compatibility fix. - // Allows the direct use of global exported assertions and QUnit.assert.* - // Although, it's use is not recommended as it can leak assertions - // to other tests from async tests, because we only get a reference to the current test, - // not exactly the test where assertion were intended to be called. - if ( !currentTest ) { - throw new Error( "assertion outside test context, in " + sourceFromStacktrace( 2 ) ); - } - - if ( currentTest.usedAsync === true && currentTest.semaphore === 0 ) { - currentTest.pushFailure( "Assertion after the final `assert.async` was resolved", - sourceFromStacktrace( 2 ) ); - - // Allow this assertion to continue running anyway... - } - - if ( !( assert instanceof Assert ) ) { - assert = currentTest.assert; - } - - return assert.test.pushResult( resultInfo ); - }, - - ok: function( result, message ) { - message = message || ( result ? "okay" : "failed, expected argument to be truthy, was: " + - QUnit.dump.parse( result ) ); - this.pushResult( { - result: !!result, - actual: result, - expected: true, - message: message - } ); - }, - - notOk: function( result, message ) { - message = message || ( !result ? "okay" : "failed, expected argument to be falsy, was: " + - QUnit.dump.parse( result ) ); - this.pushResult( { - result: !result, - actual: result, - expected: false, - message: message - } ); - }, - - equal: function( actual, expected, message ) { - /*jshint eqeqeq:false */ - this.pushResult( { - result: expected == actual, - actual: actual, - expected: expected, - message: message - } ); - }, - - notEqual: function( actual, expected, message ) { - /*jshint eqeqeq:false */ - this.pushResult( { - result: expected != actual, - actual: actual, - expected: expected, - message: message, - negative: true - } ); - }, - - propEqual: function( actual, expected, message ) { - actual = objectValues( actual ); - expected = objectValues( expected ); - this.pushResult( { - result: QUnit.equiv( actual, expected ), - actual: actual, - expected: expected, - message: message - } ); - }, - - notPropEqual: function( actual, expected, message ) { - actual = objectValues( actual ); - expected = objectValues( expected ); - this.pushResult( { - result: !QUnit.equiv( actual, expected ), - actual: actual, - expected: expected, - message: message, - negative: true - } ); - }, - - deepEqual: function( actual, expected, message ) { - this.pushResult( { - result: QUnit.equiv( actual, expected ), - actual: actual, - expected: expected, - message: message - } ); - }, - - notDeepEqual: function( actual, expected, message ) { - this.pushResult( { - result: !QUnit.equiv( actual, expected ), - actual: actual, - expected: expected, - message: message, - negative: true - } ); - }, - - strictEqual: function( actual, expected, message ) { - this.pushResult( { - result: expected === actual, - actual: actual, - expected: expected, - message: message - } ); - }, - - notStrictEqual: function( actual, expected, message ) { - this.pushResult( { - result: expected !== actual, - actual: actual, - expected: expected, - message: message, - negative: true - } ); - }, - - "throws": function( block, expected, message ) { - var actual, expectedType, - expectedOutput = expected, - ok = false, - currentTest = ( this instanceof Assert && this.test ) || QUnit.config.current; - - // 'expected' is optional unless doing string comparison - if ( QUnit.objectType( expected ) === "string" ) { - if ( message == null ) { - message = expected; - expected = null; - } else { - throw new Error( - "throws/raises does not accept a string value for the expected argument.\n" + - "Use a non-string object value (e.g. regExp) instead if it's necessary." + - "Details in our upgrade guide at https://qunitjs.com/upgrade-guide-2.x/" - ); - } - } - - currentTest.ignoreGlobalErrors = true; - try { - block.call( currentTest.testEnvironment ); - } catch ( e ) { - actual = e; - } - currentTest.ignoreGlobalErrors = false; - - if ( actual ) { - expectedType = QUnit.objectType( expected ); - - // We don't want to validate thrown error - if ( !expected ) { - ok = true; - expectedOutput = null; - - // Expected is a regexp - } else if ( expectedType === "regexp" ) { - ok = expected.test( errorString( actual ) ); - - // Expected is a constructor, maybe an Error constructor - } else if ( expectedType === "function" && actual instanceof expected ) { - ok = true; - - // Expected is an Error object - } else if ( expectedType === "object" ) { - ok = actual instanceof expected.constructor && - actual.name === expected.name && - actual.message === expected.message; - - // Expected is a validation function which returns true if validation passed - } else if ( expectedType === "function" && expected.call( {}, actual ) === true ) { - expectedOutput = null; - ok = true; - } - } - - currentTest.assert.pushResult( { - result: ok, - actual: actual, - expected: expectedOutput, - message: message - } ); - } -}; - -// Provide an alternative to assert.throws(), for environments that consider throws a reserved word -// Known to us are: Closure Compiler, Narwhal -( function() { - /*jshint sub:true */ - Assert.prototype.raises = Assert.prototype [ "throws" ]; //jscs:ignore requireDotNotation -}() ); - -function errorString( error ) { - var name, message, - resultErrorString = error.toString(); - if ( resultErrorString.substring( 0, 7 ) === "[object" ) { - name = error.name ? error.name.toString() : "Error"; - message = error.message ? error.message.toString() : ""; - if ( name && message ) { - return name + ": " + message; - } else if ( name ) { - return name; - } else if ( message ) { - return message; - } else { - return "Error"; - } - } else { - return resultErrorString; - } -} - -// Test for equality any JavaScript type. -// Author: Philippe Rathé -QUnit.equiv = ( function() { - - // Stack to decide between skip/abort functions - var callers = []; - - // Stack to avoiding loops from circular referencing - var parents = []; - var parentsB = []; - - var getProto = Object.getPrototypeOf || function( obj ) { - - /*jshint proto: true */ - return obj.__proto__; - }; - - function useStrictEquality( b, a ) { - - // To catch short annotation VS 'new' annotation of a declaration. e.g.: - // `var i = 1;` - // `var j = new Number(1);` - if ( typeof a === "object" ) { - a = a.valueOf(); - } - if ( typeof b === "object" ) { - b = b.valueOf(); - } - - return a === b; - } - - function compareConstructors( a, b ) { - var protoA = getProto( a ); - var protoB = getProto( b ); - - // Comparing constructors is more strict than using `instanceof` - if ( a.constructor === b.constructor ) { - return true; - } - - // Ref #851 - // If the obj prototype descends from a null constructor, treat it - // as a null prototype. - if ( protoA && protoA.constructor === null ) { - protoA = null; - } - if ( protoB && protoB.constructor === null ) { - protoB = null; - } - - // Allow objects with no prototype to be equivalent to - // objects with Object as their constructor. - if ( ( protoA === null && protoB === Object.prototype ) || - ( protoB === null && protoA === Object.prototype ) ) { - return true; - } - - return false; - } - - function getRegExpFlags( regexp ) { - return "flags" in regexp ? regexp.flags : regexp.toString().match( /[gimuy]*$/ )[ 0 ]; - } - - var callbacks = { - "string": useStrictEquality, - "boolean": useStrictEquality, - "number": useStrictEquality, - "null": useStrictEquality, - "undefined": useStrictEquality, - "symbol": useStrictEquality, - "date": useStrictEquality, - - "nan": function() { - return true; - }, - - "regexp": function( b, a ) { - return a.source === b.source && - - // Include flags in the comparison - getRegExpFlags( a ) === getRegExpFlags( b ); - }, - - // - skip when the property is a method of an instance (OOP) - // - abort otherwise, - // initial === would have catch identical references anyway - "function": function() { - var caller = callers[ callers.length - 1 ]; - return caller !== Object && typeof caller !== "undefined"; - }, - - "array": function( b, a ) { - var i, j, len, loop, aCircular, bCircular; - - len = a.length; - if ( len !== b.length ) { - - // Safe and faster - return false; - } - - // Track reference to avoid circular references - parents.push( a ); - parentsB.push( b ); - for ( i = 0; i < len; i++ ) { - loop = false; - for ( j = 0; j < parents.length; j++ ) { - aCircular = parents[ j ] === a[ i ]; - bCircular = parentsB[ j ] === b[ i ]; - if ( aCircular || bCircular ) { - if ( a[ i ] === b[ i ] || aCircular && bCircular ) { - loop = true; - } else { - parents.pop(); - parentsB.pop(); - return false; - } - } - } - if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) { - parents.pop(); - parentsB.pop(); - return false; - } - } - parents.pop(); - parentsB.pop(); - return true; - }, - - "set": function( b, a ) { - var innerEq, - outerEq = true; - - if ( a.size !== b.size ) { - return false; - } - - a.forEach( function( aVal ) { - innerEq = false; - - b.forEach( function( bVal ) { - if ( innerEquiv( bVal, aVal ) ) { - innerEq = true; - } - } ); - - if ( !innerEq ) { - outerEq = false; - } - } ); - - return outerEq; - }, - - "map": function( b, a ) { - var innerEq, - outerEq = true; - - if ( a.size !== b.size ) { - return false; - } - - a.forEach( function( aVal, aKey ) { - innerEq = false; - - b.forEach( function( bVal, bKey ) { - if ( innerEquiv( [ bVal, bKey ], [ aVal, aKey ] ) ) { - innerEq = true; - } - } ); - - if ( !innerEq ) { - outerEq = false; - } - } ); - - return outerEq; - }, - - "object": function( b, a ) { - var i, j, loop, aCircular, bCircular; - - // Default to true - var eq = true; - var aProperties = []; - var bProperties = []; - - if ( compareConstructors( a, b ) === false ) { - return false; - } - - // Stack constructor before traversing properties - callers.push( a.constructor ); - - // Track reference to avoid circular references - parents.push( a ); - parentsB.push( b ); - - // Be strict: don't ensure hasOwnProperty and go deep - for ( i in a ) { - loop = false; - for ( j = 0; j < parents.length; j++ ) { - aCircular = parents[ j ] === a[ i ]; - bCircular = parentsB[ j ] === b[ i ]; - if ( aCircular || bCircular ) { - if ( a[ i ] === b[ i ] || aCircular && bCircular ) { - loop = true; - } else { - eq = false; - break; - } - } - } - aProperties.push( i ); - if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) { - eq = false; - break; - } - } - - parents.pop(); - parentsB.pop(); - - // Unstack, we are done - callers.pop(); - - for ( i in b ) { - - // Collect b's properties - bProperties.push( i ); - } - - // Ensures identical properties name - return eq && innerEquiv( aProperties.sort(), bProperties.sort() ); - } - }; - - function typeEquiv( a, b ) { - var type = QUnit.objectType( a ); - return QUnit.objectType( b ) === type && callbacks[ type ]( b, a ); - } - - // The real equiv function - function innerEquiv( a, b ) { - - // We're done when there's nothing more to compare - if ( arguments.length < 2 ) { - return true; - } - - // Require type-specific equality - return ( a === b || typeEquiv( a, b ) ) && - - // ...across all consecutive argument pairs - ( arguments.length === 2 || innerEquiv.apply( this, [].slice.call( arguments, 1 ) ) ); - } - - return innerEquiv; -}() ); - -// Based on jsDump by Ariel Flesler -// http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html -QUnit.dump = ( function() { - function quote( str ) { - return "\"" + str.toString().replace( /\\/g, "\\\\" ).replace( /"/g, "\\\"" ) + "\""; - } - function literal( o ) { - return o + ""; - } - function join( pre, arr, post ) { - var s = dump.separator(), - base = dump.indent(), - inner = dump.indent( 1 ); - if ( arr.join ) { - arr = arr.join( "," + s + inner ); - } - if ( !arr ) { - return pre + post; - } - return [ pre, inner + arr, base + post ].join( s ); - } - function array( arr, stack ) { - var i = arr.length, - ret = new Array( i ); - - if ( dump.maxDepth && dump.depth > dump.maxDepth ) { - return "[object Array]"; - } - - this.up(); - while ( i-- ) { - ret[ i ] = this.parse( arr[ i ], undefined, stack ); - } - this.down(); - return join( "[", ret, "]" ); - } - - function isArray( obj ) { - return ( - - //Native Arrays - toString.call( obj ) === "[object Array]" || - - // NodeList objects - ( typeof obj.length === "number" && obj.item !== undefined ) && - ( obj.length ? - obj.item( 0 ) === obj[ 0 ] : - ( obj.item( 0 ) === null && obj[ 0 ] === undefined ) - ) - ); - } - - var reName = /^function (\w+)/, - dump = { - - // The objType is used mostly internally, you can fix a (custom) type in advance - parse: function( obj, objType, stack ) { - stack = stack || []; - var res, parser, parserType, - inStack = inArray( obj, stack ); - - if ( inStack !== -1 ) { - return "recursion(" + ( inStack - stack.length ) + ")"; - } - - objType = objType || this.typeOf( obj ); - parser = this.parsers[ objType ]; - parserType = typeof parser; - - if ( parserType === "function" ) { - stack.push( obj ); - res = parser.call( this, obj, stack ); - stack.pop(); - return res; - } - return ( parserType === "string" ) ? parser : this.parsers.error; - }, - typeOf: function( obj ) { - var type; - - if ( obj === null ) { - type = "null"; - } else if ( typeof obj === "undefined" ) { - type = "undefined"; - } else if ( QUnit.is( "regexp", obj ) ) { - type = "regexp"; - } else if ( QUnit.is( "date", obj ) ) { - type = "date"; - } else if ( QUnit.is( "function", obj ) ) { - type = "function"; - } else if ( obj.setInterval !== undefined && - obj.document !== undefined && - obj.nodeType === undefined ) { - type = "window"; - } else if ( obj.nodeType === 9 ) { - type = "document"; - } else if ( obj.nodeType ) { - type = "node"; - } else if ( isArray( obj ) ) { - type = "array"; - } else if ( obj.constructor === Error.prototype.constructor ) { - type = "error"; - } else { - type = typeof obj; - } - return type; - }, - - separator: function() { - return this.multiline ? this.HTML ? "
" : "\n" : this.HTML ? " " : " "; - }, - - // Extra can be a number, shortcut for increasing-calling-decreasing - indent: function( extra ) { - if ( !this.multiline ) { - return ""; - } - var chr = this.indentChar; - if ( this.HTML ) { - chr = chr.replace( /\t/g, " " ).replace( / /g, " " ); - } - return new Array( this.depth + ( extra || 0 ) ).join( chr ); - }, - up: function( a ) { - this.depth += a || 1; - }, - down: function( a ) { - this.depth -= a || 1; - }, - setParser: function( name, parser ) { - this.parsers[ name ] = parser; - }, - - // The next 3 are exposed so you can use them - quote: quote, - literal: literal, - join: join, - depth: 1, - maxDepth: QUnit.config.maxDepth, - - // This is the list of parsers, to modify them, use dump.setParser - parsers: { - window: "[Window]", - document: "[Document]", - error: function( error ) { - return "Error(\"" + error.message + "\")"; - }, - unknown: "[Unknown]", - "null": "null", - "undefined": "undefined", - "function": function( fn ) { - var ret = "function", - - // Functions never have name in IE - name = "name" in fn ? fn.name : ( reName.exec( fn ) || [] )[ 1 ]; - - if ( name ) { - ret += " " + name; - } - ret += "("; - - ret = [ ret, dump.parse( fn, "functionArgs" ), "){" ].join( "" ); - return join( ret, dump.parse( fn, "functionCode" ), "}" ); - }, - array: array, - nodelist: array, - "arguments": array, - object: function( map, stack ) { - var keys, key, val, i, nonEnumerableProperties, - ret = []; - - if ( dump.maxDepth && dump.depth > dump.maxDepth ) { - return "[object Object]"; - } - - dump.up(); - keys = []; - for ( key in map ) { - keys.push( key ); - } - - // Some properties are not always enumerable on Error objects. - nonEnumerableProperties = [ "message", "name" ]; - for ( i in nonEnumerableProperties ) { - key = nonEnumerableProperties[ i ]; - if ( key in map && inArray( key, keys ) < 0 ) { - keys.push( key ); - } - } - keys.sort(); - for ( i = 0; i < keys.length; i++ ) { - key = keys[ i ]; - val = map[ key ]; - ret.push( dump.parse( key, "key" ) + ": " + - dump.parse( val, undefined, stack ) ); - } - dump.down(); - return join( "{", ret, "}" ); - }, - node: function( node ) { - var len, i, val, - open = dump.HTML ? "<" : "<", - close = dump.HTML ? ">" : ">", - tag = node.nodeName.toLowerCase(), - ret = open + tag, - attrs = node.attributes; - - if ( attrs ) { - for ( i = 0, len = attrs.length; i < len; i++ ) { - val = attrs[ i ].nodeValue; - - // IE6 includes all attributes in .attributes, even ones not explicitly - // set. Those have values like undefined, null, 0, false, "" or - // "inherit". - if ( val && val !== "inherit" ) { - ret += " " + attrs[ i ].nodeName + "=" + - dump.parse( val, "attribute" ); - } - } - } - ret += close; - - // Show content of TextNode or CDATASection - if ( node.nodeType === 3 || node.nodeType === 4 ) { - ret += node.nodeValue; - } - - return ret + open + "/" + tag + close; - }, - - // Function calls it internally, it's the arguments part of the function - functionArgs: function( fn ) { - var args, - l = fn.length; - - if ( !l ) { - return ""; - } - - args = new Array( l ); - while ( l-- ) { - - // 97 is 'a' - args[ l ] = String.fromCharCode( 97 + l ); - } - return " " + args.join( ", " ) + " "; - }, - - // Object calls it internally, the key part of an item in a map - key: quote, - - // Function calls it internally, it's the content of the function - functionCode: "[code]", - - // Node calls it internally, it's a html attribute value - attribute: quote, - string: quote, - date: quote, - regexp: literal, - number: literal, - "boolean": literal - }, - - // If true, entities are escaped ( <, >, \t, space and \n ) - HTML: false, - - // Indentation unit - indentChar: " ", - - // If true, items in a collection, are separated by a \n, else just a space. - multiline: true - }; - - return dump; -}() ); - -// Back compat -QUnit.jsDump = QUnit.dump; - -function applyDeprecated( name ) { - return function() { - throw new Error( - name + " is removed in QUnit 2.0.\n" + - "Details in our upgrade guide at https://qunitjs.com/upgrade-guide-2.x/" - ); - }; -} - -Object.keys( Assert.prototype ).forEach( function( key ) { - QUnit[ key ] = applyDeprecated( "`QUnit." + key + "`" ); -} ); - -QUnit.asyncTest = function() { - throw new Error( - "asyncTest is removed in QUnit 2.0, use QUnit.test() with assert.async() instead.\n" + - "Details in our upgrade guide at https://qunitjs.com/upgrade-guide-2.x/" - ); -}; - -QUnit.stop = function() { - throw new Error( - "QUnit.stop is removed in QUnit 2.0, use QUnit.test() with assert.async() instead.\n" + - "Details in our upgrade guide at https://qunitjs.com/upgrade-guide-2.x/" - ); -}; - -function resetThrower() { - throw new Error( - "QUnit.reset is removed in QUnit 2.0 without replacement.\n" + - "Details in our upgrade guide at https://qunitjs.com/upgrade-guide-2.x/" - ); -} - -Object.defineProperty( QUnit, "reset", { - get: function() { - return resetThrower; - }, - set: resetThrower -} ); - -if ( defined.document ) { - if ( window.QUnit ) { - throw new Error( "QUnit has already been defined." ); - } - - [ - "test", - "module", - "expect", - "start", - "ok", - "notOk", - "equal", - "notEqual", - "propEqual", - "notPropEqual", - "deepEqual", - "notDeepEqual", - "strictEqual", - "notStrictEqual", - "throws", - "raises" - ].forEach( function( key ) { - window[ key ] = applyDeprecated( "The global `" + key + "`" ); - } ); - - window.QUnit = QUnit; -} - -// For nodejs -if ( typeof module !== "undefined" && module && module.exports ) { - module.exports = QUnit; - - // For consistency with CommonJS environments' exports - module.exports.QUnit = QUnit; -} - -// For CommonJS with exports, but without module.exports, like Rhino -if ( typeof exports !== "undefined" && exports ) { - exports.QUnit = QUnit; -} - -if ( typeof define === "function" && define.amd ) { - define( function() { - return QUnit; - } ); - QUnit.config.autostart = false; -} - -// Get a reference to the global object, like window in browsers -}( ( function() { - return this; -}() ) ) ); - -( function() { - -if ( typeof window === "undefined" || !window.document ) { - return; -} - -var config = QUnit.config, - hasOwn = Object.prototype.hasOwnProperty; - -// Stores fixture HTML for resetting later -function storeFixture() { - - // Avoid overwriting user-defined values - if ( hasOwn.call( config, "fixture" ) ) { - return; - } - - var fixture = document.getElementById( "qunit-fixture" ); - if ( fixture ) { - config.fixture = fixture.innerHTML; - } -} - -QUnit.begin( storeFixture ); - -// Resets the fixture DOM element if available. -function resetFixture() { - if ( config.fixture == null ) { - return; - } - - var fixture = document.getElementById( "qunit-fixture" ); - if ( fixture ) { - fixture.innerHTML = config.fixture; - } -} - -QUnit.testStart( resetFixture ); - -}() ); - -( function() { - -// Only interact with URLs via window.location -var location = typeof window !== "undefined" && window.location; -if ( !location ) { - return; -} - -var urlParams = getUrlParams(); - -QUnit.urlParams = urlParams; - -// Match module/test by inclusion in an array -QUnit.config.moduleId = [].concat( urlParams.moduleId || [] ); -QUnit.config.testId = [].concat( urlParams.testId || [] ); - -// Exact case-insensitive match of the module name -QUnit.config.module = urlParams.module; - -// Regular expression or case-insenstive substring match against "moduleName: testName" -QUnit.config.filter = urlParams.filter; - -// Test order randomization -if ( urlParams.seed === true ) { - - // Generate a random seed if the option is specified without a value - QUnit.config.seed = Math.random().toString( 36 ).slice( 2 ); -} else if ( urlParams.seed ) { - QUnit.config.seed = urlParams.seed; -} - -// Add URL-parameter-mapped config values with UI form rendering data -QUnit.config.urlConfig.push( - { - id: "hidepassed", - label: "Hide passed tests", - tooltip: "Only show tests and assertions that fail. Stored as query-strings." - }, - { - id: "noglobals", - label: "Check for Globals", - tooltip: "Enabling this will test if any test introduces new properties on the " + - "global object (`window` in Browsers). Stored as query-strings." - }, - { - id: "notrycatch", - label: "No try-catch", - tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging " + - "exceptions in IE reasonable. Stored as query-strings." - } -); - -QUnit.begin( function() { - var i, option, - urlConfig = QUnit.config.urlConfig; - - for ( i = 0; i < urlConfig.length; i++ ) { - - // Options can be either strings or objects with nonempty "id" properties - option = QUnit.config.urlConfig[ i ]; - if ( typeof option !== "string" ) { - option = option.id; - } - - if ( QUnit.config[ option ] === undefined ) { - QUnit.config[ option ] = urlParams[ option ]; - } - } -} ); - -function getUrlParams() { - var i, param, name, value; - var urlParams = {}; - var params = location.search.slice( 1 ).split( "&" ); - var length = params.length; - - for ( i = 0; i < length; i++ ) { - if ( params[ i ] ) { - param = params[ i ].split( "=" ); - name = decodeQueryParam( param[ 0 ] ); - - // Allow just a key to turn on a flag, e.g., test.html?noglobals - value = param.length === 1 || - decodeQueryParam( param.slice( 1 ).join( "=" ) ) ; - if ( urlParams[ name ] ) { - urlParams[ name ] = [].concat( urlParams[ name ], value ); - } else { - urlParams[ name ] = value; - } - } - } - - return urlParams; -} - -function decodeQueryParam( param ) { - return decodeURIComponent( param.replace( /\+/g, "%20" ) ); -} - -// Don't load the HTML Reporter on non-browser environments -if ( typeof window === "undefined" || !window.document ) { - return; -} - -QUnit.init = function() { - throw new Error( - "QUnit.init is removed in QUnit 2.0, use QUnit.test() with assert.async() instead.\n" + - "Details in our upgrade guide at https://qunitjs.com/upgrade-guide-2.x/" - ); -}; - -var config = QUnit.config, - document = window.document, - collapseNext = false, - hasOwn = Object.prototype.hasOwnProperty, - unfilteredUrl = setUrl( { filter: undefined, module: undefined, - moduleId: undefined, testId: undefined } ), - defined = { - sessionStorage: ( function() { - var x = "qunit-test-string"; - try { - sessionStorage.setItem( x, x ); - sessionStorage.removeItem( x ); - return true; - } catch ( e ) { - return false; - } - }() ) - }, - modulesList = []; - -// Escape text for attribute or text content. -function escapeText( s ) { - if ( !s ) { - return ""; - } - s = s + ""; - - // Both single quotes and double quotes (for attributes) - return s.replace( /['"<>&]/g, function( s ) { - switch ( s ) { - case "'": - return "'"; - case "\"": - return """; - case "<": - return "<"; - case ">": - return ">"; - case "&": - return "&"; - } - } ); -} - -function addEvent( elem, type, fn ) { - elem.addEventListener( type, fn, false ); -} - -function removeEvent( elem, type, fn ) { - elem.removeEventListener( type, fn, false ); -} - -function addEvents( elems, type, fn ) { - var i = elems.length; - while ( i-- ) { - addEvent( elems[ i ], type, fn ); - } -} - -function hasClass( elem, name ) { - return ( " " + elem.className + " " ).indexOf( " " + name + " " ) >= 0; -} - -function addClass( elem, name ) { - if ( !hasClass( elem, name ) ) { - elem.className += ( elem.className ? " " : "" ) + name; - } -} - -function toggleClass( elem, name, force ) { - if ( force || typeof force === "undefined" && !hasClass( elem, name ) ) { - addClass( elem, name ); - } else { - removeClass( elem, name ); - } -} - -function removeClass( elem, name ) { - var set = " " + elem.className + " "; - - // Class name may appear multiple times - while ( set.indexOf( " " + name + " " ) >= 0 ) { - set = set.replace( " " + name + " ", " " ); - } - - // Trim for prettiness - elem.className = typeof set.trim === "function" ? set.trim() : set.replace( /^\s+|\s+$/g, "" ); -} - -function id( name ) { - return document.getElementById && document.getElementById( name ); -} - -function interceptNavigation( ev ) { - applyUrlParams(); - - if ( ev && ev.preventDefault ) { - ev.preventDefault(); - } - - return false; -} - -function getUrlConfigHtml() { - var i, j, val, - escaped, escapedTooltip, - selection = false, - urlConfig = config.urlConfig, - urlConfigHtml = ""; - - for ( i = 0; i < urlConfig.length; i++ ) { - - // Options can be either strings or objects with nonempty "id" properties - val = config.urlConfig[ i ]; - if ( typeof val === "string" ) { - val = { - id: val, - label: val - }; - } - - escaped = escapeText( val.id ); - escapedTooltip = escapeText( val.tooltip ); - - if ( !val.value || typeof val.value === "string" ) { - urlConfigHtml += ""; - } else { - urlConfigHtml += ""; - } - } - - return urlConfigHtml; -} - -// Handle "click" events on toolbar checkboxes and "change" for select menus. -// Updates the URL with the new state of `config.urlConfig` values. -function toolbarChanged() { - var updatedUrl, value, tests, - field = this, - params = {}; - - // Detect if field is a select menu or a checkbox - if ( "selectedIndex" in field ) { - value = field.options[ field.selectedIndex ].value || undefined; - } else { - value = field.checked ? ( field.defaultValue || true ) : undefined; - } - - params[ field.name ] = value; - updatedUrl = setUrl( params ); - - // Check if we can apply the change without a page refresh - if ( "hidepassed" === field.name && "replaceState" in window.history ) { - QUnit.urlParams[ field.name ] = value; - config[ field.name ] = value || false; - tests = id( "qunit-tests" ); - if ( tests ) { - toggleClass( tests, "hidepass", value || false ); - } - window.history.replaceState( null, "", updatedUrl ); - } else { - window.location = updatedUrl; - } -} - -function setUrl( params ) { - var key, arrValue, i, - querystring = "?", - location = window.location; - - params = QUnit.extend( QUnit.extend( {}, QUnit.urlParams ), params ); - - for ( key in params ) { - - // Skip inherited or undefined properties - if ( hasOwn.call( params, key ) && params[ key ] !== undefined ) { - - // Output a parameter for each value of this key (but usually just one) - arrValue = [].concat( params[ key ] ); - for ( i = 0; i < arrValue.length; i++ ) { - querystring += encodeURIComponent( key ); - if ( arrValue[ i ] !== true ) { - querystring += "=" + encodeURIComponent( arrValue[ i ] ); - } - querystring += "&"; - } - } - } - return location.protocol + "//" + location.host + - location.pathname + querystring.slice( 0, -1 ); -} - -function applyUrlParams() { - var i, - selectedModules = [], - modulesList = id( "qunit-modulefilter-dropdown-list" ).getElementsByTagName( "input" ), - filter = id( "qunit-filter-input" ).value; - - for ( i = 0; i < modulesList.length; i++ ) { - if ( modulesList[ i ].checked ) { - selectedModules.push( modulesList[ i ].value ); - } - } - - window.location = setUrl( { - filter: ( filter === "" ) ? undefined : filter, - moduleId: ( selectedModules.length === 0 ) ? undefined : selectedModules, - - // Remove module and testId filter - module: undefined, - testId: undefined - } ); -} - -function toolbarUrlConfigContainer() { - var urlConfigContainer = document.createElement( "span" ); - - urlConfigContainer.innerHTML = getUrlConfigHtml(); - addClass( urlConfigContainer, "qunit-url-config" ); - - addEvents( urlConfigContainer.getElementsByTagName( "input" ), "change", toolbarChanged ); - addEvents( urlConfigContainer.getElementsByTagName( "select" ), "change", toolbarChanged ); - - return urlConfigContainer; -} - -function toolbarLooseFilter() { - var filter = document.createElement( "form" ), - label = document.createElement( "label" ), - input = document.createElement( "input" ), - button = document.createElement( "button" ); - - addClass( filter, "qunit-filter" ); - - label.innerHTML = "Filter: "; - - input.type = "text"; - input.value = config.filter || ""; - input.name = "filter"; - input.id = "qunit-filter-input"; - - button.innerHTML = "Go"; - - label.appendChild( input ); - - filter.appendChild( label ); - filter.appendChild( document.createTextNode( " " ) ); - filter.appendChild( button ); - addEvent( filter, "submit", interceptNavigation ); - - return filter; -} - -function moduleListHtml () { - var i, checked, - html = ""; - - for ( i = 0; i < config.modules.length; i++ ) { - if ( config.modules[ i ].name !== "" ) { - checked = config.moduleId.indexOf( config.modules[ i ].moduleId ) > -1; - html += "
  • "; - } - } - - return html; -} - -function toolbarModuleFilter () { - var allCheckbox, commit, reset, - moduleFilter = document.createElement( "form" ), - label = document.createElement( "label" ), - moduleSearch = document.createElement( "input" ), - dropDown = document.createElement( "div" ), - actions = document.createElement( "span" ), - dropDownList = document.createElement( "ul" ), - dirty = false; - - moduleSearch.id = "qunit-modulefilter-search"; - addEvent( moduleSearch, "input", searchInput ); - addEvent( moduleSearch, "input", searchFocus ); - addEvent( moduleSearch, "focus", searchFocus ); - addEvent( moduleSearch, "click", searchFocus ); - - label.id = "qunit-modulefilter-search-container"; - label.innerHTML = "Module: "; - label.appendChild( moduleSearch ); - - actions.id = "qunit-modulefilter-actions"; - actions.innerHTML = - "" + - "" + - ""; - allCheckbox = actions.lastChild.firstChild; - commit = actions.firstChild; - reset = commit.nextSibling; - addEvent( commit, "click", applyUrlParams ); - - dropDownList.id = "qunit-modulefilter-dropdown-list"; - dropDownList.innerHTML = moduleListHtml(); - - dropDown.id = "qunit-modulefilter-dropdown"; - dropDown.style.display = "none"; - dropDown.appendChild( actions ); - dropDown.appendChild( dropDownList ); - addEvent( dropDown, "change", selectionChange ); - selectionChange(); - - moduleFilter.id = "qunit-modulefilter"; - moduleFilter.appendChild( label ); - moduleFilter.appendChild( dropDown ) ; - addEvent( moduleFilter, "submit", interceptNavigation ); - addEvent( moduleFilter, "reset", function() { - - // Let the reset happen, then update styles - window.setTimeout( selectionChange ); - } ); - - // Enables show/hide for the dropdown - function searchFocus() { - if ( dropDown.style.display !== "none" ) { - return; - } - - dropDown.style.display = "block"; - addEvent( document, "click", hideHandler ); - addEvent( document, "keydown", hideHandler ); - - // Hide on Escape keydown or outside-container click - function hideHandler( e ) { - var inContainer = moduleFilter.contains( e.target ); - - if ( e.keyCode === 27 || !inContainer ) { - if ( e.keyCode === 27 && inContainer ) { - moduleSearch.focus(); - } - dropDown.style.display = "none"; - removeEvent( document, "click", hideHandler ); - removeEvent( document, "keydown", hideHandler ); - moduleSearch.value = ""; - searchInput(); - } - } - } - - // Processes module search box input - function searchInput() { - var i, item, - searchText = moduleSearch.value.toLowerCase(), - listItems = dropDownList.children; - - for ( i = 0; i < listItems.length; i++ ) { - item = listItems[ i ]; - if ( !searchText || item.textContent.toLowerCase().indexOf( searchText ) > -1 ) { - item.style.display = ""; - } else { - item.style.display = "none"; - } - } - } - - // Processes selection changes - function selectionChange( evt ) { - var i, - checkbox = evt && evt.target || allCheckbox, - modulesList = dropDownList.getElementsByTagName( "input" ), - selectedNames = []; - - toggleClass( checkbox.parentNode, "checked", checkbox.checked ); - - dirty = false; - if ( checkbox.checked && checkbox !== allCheckbox ) { - allCheckbox.checked = false; - removeClass( allCheckbox.parentNode, "checked" ); - } - for ( i = 0; i < modulesList.length; i++ ) { - if ( !evt ) { - toggleClass( modulesList[ i ].parentNode, "checked", modulesList[ i ].checked ); - } else if ( checkbox === allCheckbox && checkbox.checked ) { - modulesList[ i ].checked = false; - removeClass( modulesList[ i ].parentNode, "checked" ); - } - dirty = dirty || ( checkbox.checked !== checkbox.defaultChecked ); - if ( modulesList[ i ].checked ) { - selectedNames.push( modulesList[ i ].parentNode.textContent ); - } - } - - commit.style.display = reset.style.display = dirty ? "" : "none"; - moduleSearch.placeholder = selectedNames.join( ", " ) || allCheckbox.parentNode.textContent; - moduleSearch.title = "Type to filter list. Current selection:\n" + - ( selectedNames.join( "\n" ) || allCheckbox.parentNode.textContent ); - } - - return moduleFilter; -} - -function appendToolbar() { - var toolbar = id( "qunit-testrunner-toolbar" ); - - if ( toolbar ) { - toolbar.appendChild( toolbarUrlConfigContainer() ); - toolbar.appendChild( toolbarModuleFilter() ); - toolbar.appendChild( toolbarLooseFilter() ); - toolbar.appendChild( document.createElement( "div" ) ).className = "clearfix"; - } -} - -function appendHeader() { - var header = id( "qunit-header" ); - - if ( header ) { - header.innerHTML = "" + header.innerHTML + - " "; - } -} - -function appendBanner() { - var banner = id( "qunit-banner" ); - - if ( banner ) { - banner.className = ""; - } -} - -function appendTestResults() { - var tests = id( "qunit-tests" ), - result = id( "qunit-testresult" ); - - if ( result ) { - result.parentNode.removeChild( result ); - } - - if ( tests ) { - tests.innerHTML = ""; - result = document.createElement( "p" ); - result.id = "qunit-testresult"; - result.className = "result"; - tests.parentNode.insertBefore( result, tests ); - result.innerHTML = "Running...
     "; - } -} - -function appendFilteredTest() { - var testId = QUnit.config.testId; - if ( !testId || testId.length <= 0 ) { - return ""; - } - return "
    Rerunning selected tests: " + - escapeText( testId.join( ", " ) ) + - " Run all tests
    "; -} - -function appendUserAgent() { - var userAgent = id( "qunit-userAgent" ); - - if ( userAgent ) { - userAgent.innerHTML = ""; - userAgent.appendChild( - document.createTextNode( - "QUnit " + QUnit.version + "; " + navigator.userAgent - ) - ); - } -} - -function appendInterface() { - var qunit = id( "qunit" ); - - if ( qunit ) { - qunit.innerHTML = - "

    " + escapeText( document.title ) + "

    " + - "

    " + - "
    " + - appendFilteredTest() + - "

    " + - "
      "; - } - - appendHeader(); - appendBanner(); - appendTestResults(); - appendUserAgent(); - appendToolbar(); -} - -function appendTestsList( modules ) { - var i, l, x, z, test, moduleObj; - - for ( i = 0, l = modules.length; i < l; i++ ) { - moduleObj = modules[ i ]; - - for ( x = 0, z = moduleObj.tests.length; x < z; x++ ) { - test = moduleObj.tests[ x ]; - - appendTest( test.name, test.testId, moduleObj.name ); - } - } -} - -function appendTest( name, testId, moduleName ) { - var title, rerunTrigger, testBlock, assertList, - tests = id( "qunit-tests" ); - - if ( !tests ) { - return; - } - - title = document.createElement( "strong" ); - title.innerHTML = getNameHtml( name, moduleName ); - - rerunTrigger = document.createElement( "a" ); - rerunTrigger.innerHTML = "Rerun"; - rerunTrigger.href = setUrl( { testId: testId } ); - - testBlock = document.createElement( "li" ); - testBlock.appendChild( title ); - testBlock.appendChild( rerunTrigger ); - testBlock.id = "qunit-test-output-" + testId; - - assertList = document.createElement( "ol" ); - assertList.className = "qunit-assert-list"; - - testBlock.appendChild( assertList ); - - tests.appendChild( testBlock ); -} - -// HTML Reporter initialization and load -QUnit.begin( function( details ) { - var i, moduleObj, tests; - - // Sort modules by name for the picker - for ( i = 0; i < details.modules.length; i++ ) { - moduleObj = details.modules[ i ]; - if ( moduleObj.name ) { - modulesList.push( moduleObj.name ); - } - } - modulesList.sort( function( a, b ) { - return a.localeCompare( b ); - } ); - - // Initialize QUnit elements - appendInterface(); - appendTestsList( details.modules ); - tests = id( "qunit-tests" ); - if ( tests && config.hidepassed ) { - addClass( tests, "hidepass" ); - } -} ); - -QUnit.done( function( details ) { - var i, key, - banner = id( "qunit-banner" ), - tests = id( "qunit-tests" ), - html = [ - "Tests completed in ", - details.runtime, - " milliseconds.
      ", - "", - details.passed, - " assertions of ", - details.total, - " passed, ", - details.failed, - " failed." - ].join( "" ); - - if ( banner ) { - banner.className = details.failed ? "qunit-fail" : "qunit-pass"; - } - - if ( tests ) { - id( "qunit-testresult" ).innerHTML = html; - } - - if ( config.altertitle && document.title ) { - - // Show ✖ for good, ✔ for bad suite result in title - // use escape sequences in case file gets loaded with non-utf-8-charset - document.title = [ - ( details.failed ? "\u2716" : "\u2714" ), - document.title.replace( /^[\u2714\u2716] /i, "" ) - ].join( " " ); - } - - // Clear own sessionStorage items if all tests passed - if ( config.reorder && defined.sessionStorage && details.failed === 0 ) { - for ( i = 0; i < sessionStorage.length; i++ ) { - key = sessionStorage.key( i++ ); - if ( key.indexOf( "qunit-test-" ) === 0 ) { - sessionStorage.removeItem( key ); - } - } - } - - // Scroll back to top to show results - if ( config.scrolltop && window.scrollTo ) { - window.scrollTo( 0, 0 ); - } -} ); - -function getNameHtml( name, module ) { - var nameHtml = ""; - - if ( module ) { - nameHtml = "" + escapeText( module ) + ": "; - } - - nameHtml += "" + escapeText( name ) + ""; - - return nameHtml; -} - -QUnit.testStart( function( details ) { - var running, testBlock, bad; - - testBlock = id( "qunit-test-output-" + details.testId ); - if ( testBlock ) { - testBlock.className = "running"; - } else { - - // Report later registered tests - appendTest( details.name, details.testId, details.module ); - } - - running = id( "qunit-testresult" ); - if ( running ) { - bad = QUnit.config.reorder && defined.sessionStorage && - +sessionStorage.getItem( "qunit-test-" + details.module + "-" + details.name ); - - running.innerHTML = ( bad ? - "Rerunning previously failed test:
      " : - "Running:
      " ) + - getNameHtml( details.name, details.module ); - } - -} ); - -function stripHtml( string ) { - - // Strip tags, html entity and whitespaces - return string.replace( /<\/?[^>]+(>|$)/g, "" ).replace( /\"/g, "" ).replace( /\s+/g, "" ); -} - -QUnit.log( function( details ) { - var assertList, assertLi, - message, expected, actual, diff, - showDiff = false, - testItem = id( "qunit-test-output-" + details.testId ); - - if ( !testItem ) { - return; - } - - message = escapeText( details.message ) || ( details.result ? "okay" : "failed" ); - message = "" + message + ""; - message += "@ " + details.runtime + " ms"; - - // The pushFailure doesn't provide details.expected - // when it calls, it's implicit to also not show expected and diff stuff - // Also, we need to check details.expected existence, as it can exist and be undefined - if ( !details.result && hasOwn.call( details, "expected" ) ) { - if ( details.negative ) { - expected = "NOT " + QUnit.dump.parse( details.expected ); - } else { - expected = QUnit.dump.parse( details.expected ); - } - - actual = QUnit.dump.parse( details.actual ); - message += ""; - - if ( actual !== expected ) { - - message += ""; - - // Don't show diff if actual or expected are booleans - if ( !( /^(true|false)$/.test( actual ) ) && - !( /^(true|false)$/.test( expected ) ) ) { - diff = QUnit.diff( expected, actual ); - showDiff = stripHtml( diff ).length !== - stripHtml( expected ).length + - stripHtml( actual ).length; - } - - // Don't show diff if expected and actual are totally different - if ( showDiff ) { - message += ""; - } - } else if ( expected.indexOf( "[object Array]" ) !== -1 || - expected.indexOf( "[object Object]" ) !== -1 ) { - message += ""; - } else { - message += ""; - } - - if ( details.source ) { - message += ""; - } - - message += "
      Expected:
      " +
      -			escapeText( expected ) +
      -			"
      Result:
      " +
      -				escapeText( actual ) + "
      Diff:
      " +
      -					diff + "
      Message: " + - "Diff suppressed as the depth of object is more than current max depth (" + - QUnit.config.maxDepth + ").

      Hint: Use QUnit.dump.maxDepth to " + - " run with a higher max depth or " + - "Rerun without max depth.

      Message: " + - "Diff suppressed as the expected and actual results have an equivalent" + - " serialization
      Source:
      " +
      -				escapeText( details.source ) + "
      "; - - // This occurs when pushFailure is set and we have an extracted stack trace - } else if ( !details.result && details.source ) { - message += "" + - "" + - "
      Source:
      " +
      -			escapeText( details.source ) + "
      "; - } - - assertList = testItem.getElementsByTagName( "ol" )[ 0 ]; - - assertLi = document.createElement( "li" ); - assertLi.className = details.result ? "pass" : "fail"; - assertLi.innerHTML = message; - assertList.appendChild( assertLi ); -} ); - -QUnit.testDone( function( details ) { - var testTitle, time, testItem, assertList, - good, bad, testCounts, skipped, sourceName, - tests = id( "qunit-tests" ); - - if ( !tests ) { - return; - } - - testItem = id( "qunit-test-output-" + details.testId ); - - assertList = testItem.getElementsByTagName( "ol" )[ 0 ]; - - good = details.passed; - bad = details.failed; - - // Store result when possible - if ( config.reorder && defined.sessionStorage ) { - if ( bad ) { - sessionStorage.setItem( "qunit-test-" + details.module + "-" + details.name, bad ); - } else { - sessionStorage.removeItem( "qunit-test-" + details.module + "-" + details.name ); - } - } - - if ( bad === 0 ) { - - // Collapse the passing tests - addClass( assertList, "qunit-collapsed" ); - } else if ( bad && config.collapse && !collapseNext ) { - - // Skip collapsing the first failing test - collapseNext = true; - } else { - - // Collapse remaining tests - addClass( assertList, "qunit-collapsed" ); - } - - // The testItem.firstChild is the test name - testTitle = testItem.firstChild; - - testCounts = bad ? - "" + bad + ", " + "" + good + ", " : - ""; - - testTitle.innerHTML += " (" + testCounts + - details.assertions.length + ")"; - - if ( details.skipped ) { - testItem.className = "skipped"; - skipped = document.createElement( "em" ); - skipped.className = "qunit-skipped-label"; - skipped.innerHTML = "skipped"; - testItem.insertBefore( skipped, testTitle ); - } else { - addEvent( testTitle, "click", function() { - toggleClass( assertList, "qunit-collapsed" ); - } ); - - testItem.className = bad ? "fail" : "pass"; - - time = document.createElement( "span" ); - time.className = "runtime"; - time.innerHTML = details.runtime + " ms"; - testItem.insertBefore( time, assertList ); - } - - // Show the source of the test when showing assertions - if ( details.source ) { - sourceName = document.createElement( "p" ); - sourceName.innerHTML = "Source: " + details.source; - addClass( sourceName, "qunit-source" ); - if ( bad === 0 ) { - addClass( sourceName, "qunit-collapsed" ); - } - addEvent( testTitle, "click", function() { - toggleClass( sourceName, "qunit-collapsed" ); - } ); - testItem.appendChild( sourceName ); - } -} ); - -// Avoid readyState issue with phantomjs -// Ref: #818 -var notPhantom = ( function( p ) { - return !( p && p.version && p.version.major > 0 ); -} )( window.phantom ); - -if ( notPhantom && document.readyState === "complete" ) { - QUnit.load(); -} else { - addEvent( window, "load", QUnit.load ); -} - -/* - * This file is a modified version of google-diff-match-patch's JavaScript implementation - * (https://code.google.com/p/google-diff-match-patch/source/browse/trunk/javascript/diff_match_patch_uncompressed.js), - * modifications are licensed as more fully set forth in LICENSE.txt. - * - * The original source of google-diff-match-patch is attributable and licensed as follows: - * - * Copyright 2006 Google Inc. - * https://code.google.com/p/google-diff-match-patch/ - * - * 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 - * - * https://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. - * - * More Info: - * https://code.google.com/p/google-diff-match-patch/ - * - * Usage: QUnit.diff(expected, actual) - * - */ -QUnit.diff = ( function() { - function DiffMatchPatch() { - } - - // DIFF FUNCTIONS - - /** - * The data structure representing a diff is an array of tuples: - * [[DIFF_DELETE, 'Hello'], [DIFF_INSERT, 'Goodbye'], [DIFF_EQUAL, ' world.']] - * which means: delete 'Hello', add 'Goodbye' and keep ' world.' - */ - var DIFF_DELETE = -1, - DIFF_INSERT = 1, - DIFF_EQUAL = 0; - - /** - * Find the differences between two texts. Simplifies the problem by stripping - * any common prefix or suffix off the texts before diffing. - * @param {string} text1 Old string to be diffed. - * @param {string} text2 New string to be diffed. - * @param {boolean=} optChecklines Optional speedup flag. If present and false, - * then don't run a line-level diff first to identify the changed areas. - * Defaults to true, which does a faster, slightly less optimal diff. - * @return {!Array.} Array of diff tuples. - */ - DiffMatchPatch.prototype.DiffMain = function( text1, text2, optChecklines ) { - var deadline, checklines, commonlength, - commonprefix, commonsuffix, diffs; - - // The diff must be complete in up to 1 second. - deadline = ( new Date() ).getTime() + 1000; - - // Check for null inputs. - if ( text1 === null || text2 === null ) { - throw new Error( "Null input. (DiffMain)" ); - } - - // Check for equality (speedup). - if ( text1 === text2 ) { - if ( text1 ) { - return [ - [ DIFF_EQUAL, text1 ] - ]; - } - return []; - } - - if ( typeof optChecklines === "undefined" ) { - optChecklines = true; - } - - checklines = optChecklines; - - // Trim off common prefix (speedup). - commonlength = this.diffCommonPrefix( text1, text2 ); - commonprefix = text1.substring( 0, commonlength ); - text1 = text1.substring( commonlength ); - text2 = text2.substring( commonlength ); - - // Trim off common suffix (speedup). - commonlength = this.diffCommonSuffix( text1, text2 ); - commonsuffix = text1.substring( text1.length - commonlength ); - text1 = text1.substring( 0, text1.length - commonlength ); - text2 = text2.substring( 0, text2.length - commonlength ); - - // Compute the diff on the middle block. - diffs = this.diffCompute( text1, text2, checklines, deadline ); - - // Restore the prefix and suffix. - if ( commonprefix ) { - diffs.unshift( [ DIFF_EQUAL, commonprefix ] ); - } - if ( commonsuffix ) { - diffs.push( [ DIFF_EQUAL, commonsuffix ] ); - } - this.diffCleanupMerge( diffs ); - return diffs; - }; - - /** - * Reduce the number of edits by eliminating operationally trivial equalities. - * @param {!Array.} diffs Array of diff tuples. - */ - DiffMatchPatch.prototype.diffCleanupEfficiency = function( diffs ) { - var changes, equalities, equalitiesLength, lastequality, - pointer, preIns, preDel, postIns, postDel; - changes = false; - equalities = []; // Stack of indices where equalities are found. - equalitiesLength = 0; // Keeping our own length var is faster in JS. - /** @type {?string} */ - lastequality = null; - - // Always equal to diffs[equalities[equalitiesLength - 1]][1] - pointer = 0; // Index of current position. - - // Is there an insertion operation before the last equality. - preIns = false; - - // Is there a deletion operation before the last equality. - preDel = false; - - // Is there an insertion operation after the last equality. - postIns = false; - - // Is there a deletion operation after the last equality. - postDel = false; - while ( pointer < diffs.length ) { - - // Equality found. - if ( diffs[ pointer ][ 0 ] === DIFF_EQUAL ) { - if ( diffs[ pointer ][ 1 ].length < 4 && ( postIns || postDel ) ) { - - // Candidate found. - equalities[ equalitiesLength++ ] = pointer; - preIns = postIns; - preDel = postDel; - lastequality = diffs[ pointer ][ 1 ]; - } else { - - // Not a candidate, and can never become one. - equalitiesLength = 0; - lastequality = null; - } - postIns = postDel = false; - - // An insertion or deletion. - } else { - - if ( diffs[ pointer ][ 0 ] === DIFF_DELETE ) { - postDel = true; - } else { - postIns = true; - } - - /* - * Five types to be split: - * ABXYCD - * AXCD - * ABXC - * AXCD - * ABXC - */ - if ( lastequality && ( ( preIns && preDel && postIns && postDel ) || - ( ( lastequality.length < 2 ) && - ( preIns + preDel + postIns + postDel ) === 3 ) ) ) { - - // Duplicate record. - diffs.splice( - equalities[ equalitiesLength - 1 ], - 0, - [ DIFF_DELETE, lastequality ] - ); - - // Change second copy to insert. - diffs[ equalities[ equalitiesLength - 1 ] + 1 ][ 0 ] = DIFF_INSERT; - equalitiesLength--; // Throw away the equality we just deleted; - lastequality = null; - if ( preIns && preDel ) { - - // No changes made which could affect previous entry, keep going. - postIns = postDel = true; - equalitiesLength = 0; - } else { - equalitiesLength--; // Throw away the previous equality. - pointer = equalitiesLength > 0 ? equalities[ equalitiesLength - 1 ] : -1; - postIns = postDel = false; - } - changes = true; - } - } - pointer++; - } - - if ( changes ) { - this.diffCleanupMerge( diffs ); - } - }; - - /** - * Convert a diff array into a pretty HTML report. - * @param {!Array.} diffs Array of diff tuples. - * @param {integer} string to be beautified. - * @return {string} HTML representation. - */ - DiffMatchPatch.prototype.diffPrettyHtml = function( diffs ) { - var op, data, x, - html = []; - for ( x = 0; x < diffs.length; x++ ) { - op = diffs[ x ][ 0 ]; // Operation (insert, delete, equal) - data = diffs[ x ][ 1 ]; // Text of change. - switch ( op ) { - case DIFF_INSERT: - html[ x ] = "" + escapeText( data ) + ""; - break; - case DIFF_DELETE: - html[ x ] = "" + escapeText( data ) + ""; - break; - case DIFF_EQUAL: - html[ x ] = "" + escapeText( data ) + ""; - break; - } - } - return html.join( "" ); - }; - - /** - * Determine the common prefix of two strings. - * @param {string} text1 First string. - * @param {string} text2 Second string. - * @return {number} The number of characters common to the start of each - * string. - */ - DiffMatchPatch.prototype.diffCommonPrefix = function( text1, text2 ) { - var pointermid, pointermax, pointermin, pointerstart; - - // Quick check for common null cases. - if ( !text1 || !text2 || text1.charAt( 0 ) !== text2.charAt( 0 ) ) { - return 0; - } - - // Binary search. - // Performance analysis: https://neil.fraser.name/news/2007/10/09/ - pointermin = 0; - pointermax = Math.min( text1.length, text2.length ); - pointermid = pointermax; - pointerstart = 0; - while ( pointermin < pointermid ) { - if ( text1.substring( pointerstart, pointermid ) === - text2.substring( pointerstart, pointermid ) ) { - pointermin = pointermid; - pointerstart = pointermin; - } else { - pointermax = pointermid; - } - pointermid = Math.floor( ( pointermax - pointermin ) / 2 + pointermin ); - } - return pointermid; - }; - - /** - * Determine the common suffix of two strings. - * @param {string} text1 First string. - * @param {string} text2 Second string. - * @return {number} The number of characters common to the end of each string. - */ - DiffMatchPatch.prototype.diffCommonSuffix = function( text1, text2 ) { - var pointermid, pointermax, pointermin, pointerend; - - // Quick check for common null cases. - if ( !text1 || - !text2 || - text1.charAt( text1.length - 1 ) !== text2.charAt( text2.length - 1 ) ) { - return 0; - } - - // Binary search. - // Performance analysis: https://neil.fraser.name/news/2007/10/09/ - pointermin = 0; - pointermax = Math.min( text1.length, text2.length ); - pointermid = pointermax; - pointerend = 0; - while ( pointermin < pointermid ) { - if ( text1.substring( text1.length - pointermid, text1.length - pointerend ) === - text2.substring( text2.length - pointermid, text2.length - pointerend ) ) { - pointermin = pointermid; - pointerend = pointermin; - } else { - pointermax = pointermid; - } - pointermid = Math.floor( ( pointermax - pointermin ) / 2 + pointermin ); - } - return pointermid; - }; - - /** - * Find the differences between two texts. Assumes that the texts do not - * have any common prefix or suffix. - * @param {string} text1 Old string to be diffed. - * @param {string} text2 New string to be diffed. - * @param {boolean} checklines Speedup flag. If false, then don't run a - * line-level diff first to identify the changed areas. - * If true, then run a faster, slightly less optimal diff. - * @param {number} deadline Time when the diff should be complete by. - * @return {!Array.} Array of diff tuples. - * @private - */ - DiffMatchPatch.prototype.diffCompute = function( text1, text2, checklines, deadline ) { - var diffs, longtext, shorttext, i, hm, - text1A, text2A, text1B, text2B, - midCommon, diffsA, diffsB; - - if ( !text1 ) { - - // Just add some text (speedup). - return [ - [ DIFF_INSERT, text2 ] - ]; - } - - if ( !text2 ) { - - // Just delete some text (speedup). - return [ - [ DIFF_DELETE, text1 ] - ]; - } - - longtext = text1.length > text2.length ? text1 : text2; - shorttext = text1.length > text2.length ? text2 : text1; - i = longtext.indexOf( shorttext ); - if ( i !== -1 ) { - - // Shorter text is inside the longer text (speedup). - diffs = [ - [ DIFF_INSERT, longtext.substring( 0, i ) ], - [ DIFF_EQUAL, shorttext ], - [ DIFF_INSERT, longtext.substring( i + shorttext.length ) ] - ]; - - // Swap insertions for deletions if diff is reversed. - if ( text1.length > text2.length ) { - diffs[ 0 ][ 0 ] = diffs[ 2 ][ 0 ] = DIFF_DELETE; - } - return diffs; - } - - if ( shorttext.length === 1 ) { - - // Single character string. - // After the previous speedup, the character can't be an equality. - return [ - [ DIFF_DELETE, text1 ], - [ DIFF_INSERT, text2 ] - ]; - } - - // Check to see if the problem can be split in two. - hm = this.diffHalfMatch( text1, text2 ); - if ( hm ) { - - // A half-match was found, sort out the return data. - text1A = hm[ 0 ]; - text1B = hm[ 1 ]; - text2A = hm[ 2 ]; - text2B = hm[ 3 ]; - midCommon = hm[ 4 ]; - - // Send both pairs off for separate processing. - diffsA = this.DiffMain( text1A, text2A, checklines, deadline ); - diffsB = this.DiffMain( text1B, text2B, checklines, deadline ); - - // Merge the results. - return diffsA.concat( [ - [ DIFF_EQUAL, midCommon ] - ], diffsB ); - } - - if ( checklines && text1.length > 100 && text2.length > 100 ) { - return this.diffLineMode( text1, text2, deadline ); - } - - return this.diffBisect( text1, text2, deadline ); - }; - - /** - * Do the two texts share a substring which is at least half the length of the - * longer text? - * This speedup can produce non-minimal diffs. - * @param {string} text1 First string. - * @param {string} text2 Second string. - * @return {Array.} Five element Array, containing the prefix of - * text1, the suffix of text1, the prefix of text2, the suffix of - * text2 and the common middle. Or null if there was no match. - * @private - */ - DiffMatchPatch.prototype.diffHalfMatch = function( text1, text2 ) { - var longtext, shorttext, dmp, - text1A, text2B, text2A, text1B, midCommon, - hm1, hm2, hm; - - longtext = text1.length > text2.length ? text1 : text2; - shorttext = text1.length > text2.length ? text2 : text1; - if ( longtext.length < 4 || shorttext.length * 2 < longtext.length ) { - return null; // Pointless. - } - dmp = this; // 'this' becomes 'window' in a closure. - - /** - * Does a substring of shorttext exist within longtext such that the substring - * is at least half the length of longtext? - * Closure, but does not reference any external variables. - * @param {string} longtext Longer string. - * @param {string} shorttext Shorter string. - * @param {number} i Start index of quarter length substring within longtext. - * @return {Array.} Five element Array, containing the prefix of - * longtext, the suffix of longtext, the prefix of shorttext, the suffix - * of shorttext and the common middle. Or null if there was no match. - * @private - */ - function diffHalfMatchI( longtext, shorttext, i ) { - var seed, j, bestCommon, prefixLength, suffixLength, - bestLongtextA, bestLongtextB, bestShorttextA, bestShorttextB; - - // Start with a 1/4 length substring at position i as a seed. - seed = longtext.substring( i, i + Math.floor( longtext.length / 4 ) ); - j = -1; - bestCommon = ""; - while ( ( j = shorttext.indexOf( seed, j + 1 ) ) !== -1 ) { - prefixLength = dmp.diffCommonPrefix( longtext.substring( i ), - shorttext.substring( j ) ); - suffixLength = dmp.diffCommonSuffix( longtext.substring( 0, i ), - shorttext.substring( 0, j ) ); - if ( bestCommon.length < suffixLength + prefixLength ) { - bestCommon = shorttext.substring( j - suffixLength, j ) + - shorttext.substring( j, j + prefixLength ); - bestLongtextA = longtext.substring( 0, i - suffixLength ); - bestLongtextB = longtext.substring( i + prefixLength ); - bestShorttextA = shorttext.substring( 0, j - suffixLength ); - bestShorttextB = shorttext.substring( j + prefixLength ); - } - } - if ( bestCommon.length * 2 >= longtext.length ) { - return [ bestLongtextA, bestLongtextB, - bestShorttextA, bestShorttextB, bestCommon - ]; - } else { - return null; - } - } - - // First check if the second quarter is the seed for a half-match. - hm1 = diffHalfMatchI( longtext, shorttext, - Math.ceil( longtext.length / 4 ) ); - - // Check again based on the third quarter. - hm2 = diffHalfMatchI( longtext, shorttext, - Math.ceil( longtext.length / 2 ) ); - if ( !hm1 && !hm2 ) { - return null; - } else if ( !hm2 ) { - hm = hm1; - } else if ( !hm1 ) { - hm = hm2; - } else { - - // Both matched. Select the longest. - hm = hm1[ 4 ].length > hm2[ 4 ].length ? hm1 : hm2; - } - - // A half-match was found, sort out the return data. - text1A, text1B, text2A, text2B; - if ( text1.length > text2.length ) { - text1A = hm[ 0 ]; - text1B = hm[ 1 ]; - text2A = hm[ 2 ]; - text2B = hm[ 3 ]; - } else { - text2A = hm[ 0 ]; - text2B = hm[ 1 ]; - text1A = hm[ 2 ]; - text1B = hm[ 3 ]; - } - midCommon = hm[ 4 ]; - return [ text1A, text1B, text2A, text2B, midCommon ]; - }; - - /** - * Do a quick line-level diff on both strings, then rediff the parts for - * greater accuracy. - * This speedup can produce non-minimal diffs. - * @param {string} text1 Old string to be diffed. - * @param {string} text2 New string to be diffed. - * @param {number} deadline Time when the diff should be complete by. - * @return {!Array.} Array of diff tuples. - * @private - */ - DiffMatchPatch.prototype.diffLineMode = function( text1, text2, deadline ) { - var a, diffs, linearray, pointer, countInsert, - countDelete, textInsert, textDelete, j; - - // Scan the text on a line-by-line basis first. - a = this.diffLinesToChars( text1, text2 ); - text1 = a.chars1; - text2 = a.chars2; - linearray = a.lineArray; - - diffs = this.DiffMain( text1, text2, false, deadline ); - - // Convert the diff back to original text. - this.diffCharsToLines( diffs, linearray ); - - // Eliminate freak matches (e.g. blank lines) - this.diffCleanupSemantic( diffs ); - - // Rediff any replacement blocks, this time character-by-character. - // Add a dummy entry at the end. - diffs.push( [ DIFF_EQUAL, "" ] ); - pointer = 0; - countDelete = 0; - countInsert = 0; - textDelete = ""; - textInsert = ""; - while ( pointer < diffs.length ) { - switch ( diffs[ pointer ][ 0 ] ) { - case DIFF_INSERT: - countInsert++; - textInsert += diffs[ pointer ][ 1 ]; - break; - case DIFF_DELETE: - countDelete++; - textDelete += diffs[ pointer ][ 1 ]; - break; - case DIFF_EQUAL: - - // Upon reaching an equality, check for prior redundancies. - if ( countDelete >= 1 && countInsert >= 1 ) { - - // Delete the offending records and add the merged ones. - diffs.splice( pointer - countDelete - countInsert, - countDelete + countInsert ); - pointer = pointer - countDelete - countInsert; - a = this.DiffMain( textDelete, textInsert, false, deadline ); - for ( j = a.length - 1; j >= 0; j-- ) { - diffs.splice( pointer, 0, a[ j ] ); - } - pointer = pointer + a.length; - } - countInsert = 0; - countDelete = 0; - textDelete = ""; - textInsert = ""; - break; - } - pointer++; - } - diffs.pop(); // Remove the dummy entry at the end. - - return diffs; - }; - - /** - * Find the 'middle snake' of a diff, split the problem in two - * and return the recursively constructed diff. - * See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations. - * @param {string} text1 Old string to be diffed. - * @param {string} text2 New string to be diffed. - * @param {number} deadline Time at which to bail if not yet complete. - * @return {!Array.} Array of diff tuples. - * @private - */ - DiffMatchPatch.prototype.diffBisect = function( text1, text2, deadline ) { - var text1Length, text2Length, maxD, vOffset, vLength, - v1, v2, x, delta, front, k1start, k1end, k2start, - k2end, k2Offset, k1Offset, x1, x2, y1, y2, d, k1, k2; - - // Cache the text lengths to prevent multiple calls. - text1Length = text1.length; - text2Length = text2.length; - maxD = Math.ceil( ( text1Length + text2Length ) / 2 ); - vOffset = maxD; - vLength = 2 * maxD; - v1 = new Array( vLength ); - v2 = new Array( vLength ); - - // Setting all elements to -1 is faster in Chrome & Firefox than mixing - // integers and undefined. - for ( x = 0; x < vLength; x++ ) { - v1[ x ] = -1; - v2[ x ] = -1; - } - v1[ vOffset + 1 ] = 0; - v2[ vOffset + 1 ] = 0; - delta = text1Length - text2Length; - - // If the total number of characters is odd, then the front path will collide - // with the reverse path. - front = ( delta % 2 !== 0 ); - - // Offsets for start and end of k loop. - // Prevents mapping of space beyond the grid. - k1start = 0; - k1end = 0; - k2start = 0; - k2end = 0; - for ( d = 0; d < maxD; d++ ) { - - // Bail out if deadline is reached. - if ( ( new Date() ).getTime() > deadline ) { - break; - } - - // Walk the front path one step. - for ( k1 = -d + k1start; k1 <= d - k1end; k1 += 2 ) { - k1Offset = vOffset + k1; - if ( k1 === -d || ( k1 !== d && v1[ k1Offset - 1 ] < v1[ k1Offset + 1 ] ) ) { - x1 = v1[ k1Offset + 1 ]; - } else { - x1 = v1[ k1Offset - 1 ] + 1; - } - y1 = x1 - k1; - while ( x1 < text1Length && y1 < text2Length && - text1.charAt( x1 ) === text2.charAt( y1 ) ) { - x1++; - y1++; - } - v1[ k1Offset ] = x1; - if ( x1 > text1Length ) { - - // Ran off the right of the graph. - k1end += 2; - } else if ( y1 > text2Length ) { - - // Ran off the bottom of the graph. - k1start += 2; - } else if ( front ) { - k2Offset = vOffset + delta - k1; - if ( k2Offset >= 0 && k2Offset < vLength && v2[ k2Offset ] !== -1 ) { - - // Mirror x2 onto top-left coordinate system. - x2 = text1Length - v2[ k2Offset ]; - if ( x1 >= x2 ) { - - // Overlap detected. - return this.diffBisectSplit( text1, text2, x1, y1, deadline ); - } - } - } - } - - // Walk the reverse path one step. - for ( k2 = -d + k2start; k2 <= d - k2end; k2 += 2 ) { - k2Offset = vOffset + k2; - if ( k2 === -d || ( k2 !== d && v2[ k2Offset - 1 ] < v2[ k2Offset + 1 ] ) ) { - x2 = v2[ k2Offset + 1 ]; - } else { - x2 = v2[ k2Offset - 1 ] + 1; - } - y2 = x2 - k2; - while ( x2 < text1Length && y2 < text2Length && - text1.charAt( text1Length - x2 - 1 ) === - text2.charAt( text2Length - y2 - 1 ) ) { - x2++; - y2++; - } - v2[ k2Offset ] = x2; - if ( x2 > text1Length ) { - - // Ran off the left of the graph. - k2end += 2; - } else if ( y2 > text2Length ) { - - // Ran off the top of the graph. - k2start += 2; - } else if ( !front ) { - k1Offset = vOffset + delta - k2; - if ( k1Offset >= 0 && k1Offset < vLength && v1[ k1Offset ] !== -1 ) { - x1 = v1[ k1Offset ]; - y1 = vOffset + x1 - k1Offset; - - // Mirror x2 onto top-left coordinate system. - x2 = text1Length - x2; - if ( x1 >= x2 ) { - - // Overlap detected. - return this.diffBisectSplit( text1, text2, x1, y1, deadline ); - } - } - } - } - } - - // Diff took too long and hit the deadline or - // number of diffs equals number of characters, no commonality at all. - return [ - [ DIFF_DELETE, text1 ], - [ DIFF_INSERT, text2 ] - ]; - }; - - /** - * Given the location of the 'middle snake', split the diff in two parts - * and recurse. - * @param {string} text1 Old string to be diffed. - * @param {string} text2 New string to be diffed. - * @param {number} x Index of split point in text1. - * @param {number} y Index of split point in text2. - * @param {number} deadline Time at which to bail if not yet complete. - * @return {!Array.} Array of diff tuples. - * @private - */ - DiffMatchPatch.prototype.diffBisectSplit = function( text1, text2, x, y, deadline ) { - var text1a, text1b, text2a, text2b, diffs, diffsb; - text1a = text1.substring( 0, x ); - text2a = text2.substring( 0, y ); - text1b = text1.substring( x ); - text2b = text2.substring( y ); - - // Compute both diffs serially. - diffs = this.DiffMain( text1a, text2a, false, deadline ); - diffsb = this.DiffMain( text1b, text2b, false, deadline ); - - return diffs.concat( diffsb ); - }; - - /** - * Reduce the number of edits by eliminating semantically trivial equalities. - * @param {!Array.} diffs Array of diff tuples. - */ - DiffMatchPatch.prototype.diffCleanupSemantic = function( diffs ) { - var changes, equalities, equalitiesLength, lastequality, - pointer, lengthInsertions2, lengthDeletions2, lengthInsertions1, - lengthDeletions1, deletion, insertion, overlapLength1, overlapLength2; - changes = false; - equalities = []; // Stack of indices where equalities are found. - equalitiesLength = 0; // Keeping our own length var is faster in JS. - /** @type {?string} */ - lastequality = null; - - // Always equal to diffs[equalities[equalitiesLength - 1]][1] - pointer = 0; // Index of current position. - - // Number of characters that changed prior to the equality. - lengthInsertions1 = 0; - lengthDeletions1 = 0; - - // Number of characters that changed after the equality. - lengthInsertions2 = 0; - lengthDeletions2 = 0; - while ( pointer < diffs.length ) { - if ( diffs[ pointer ][ 0 ] === DIFF_EQUAL ) { // Equality found. - equalities[ equalitiesLength++ ] = pointer; - lengthInsertions1 = lengthInsertions2; - lengthDeletions1 = lengthDeletions2; - lengthInsertions2 = 0; - lengthDeletions2 = 0; - lastequality = diffs[ pointer ][ 1 ]; - } else { // An insertion or deletion. - if ( diffs[ pointer ][ 0 ] === DIFF_INSERT ) { - lengthInsertions2 += diffs[ pointer ][ 1 ].length; - } else { - lengthDeletions2 += diffs[ pointer ][ 1 ].length; - } - - // Eliminate an equality that is smaller or equal to the edits on both - // sides of it. - if ( lastequality && ( lastequality.length <= - Math.max( lengthInsertions1, lengthDeletions1 ) ) && - ( lastequality.length <= Math.max( lengthInsertions2, - lengthDeletions2 ) ) ) { - - // Duplicate record. - diffs.splice( - equalities[ equalitiesLength - 1 ], - 0, - [ DIFF_DELETE, lastequality ] - ); - - // Change second copy to insert. - diffs[ equalities[ equalitiesLength - 1 ] + 1 ][ 0 ] = DIFF_INSERT; - - // Throw away the equality we just deleted. - equalitiesLength--; - - // Throw away the previous equality (it needs to be reevaluated). - equalitiesLength--; - pointer = equalitiesLength > 0 ? equalities[ equalitiesLength - 1 ] : -1; - - // Reset the counters. - lengthInsertions1 = 0; - lengthDeletions1 = 0; - lengthInsertions2 = 0; - lengthDeletions2 = 0; - lastequality = null; - changes = true; - } - } - pointer++; - } - - // Normalize the diff. - if ( changes ) { - this.diffCleanupMerge( diffs ); - } - - // Find any overlaps between deletions and insertions. - // e.g: abcxxxxxxdef - // -> abcxxxdef - // e.g: xxxabcdefxxx - // -> defxxxabc - // Only extract an overlap if it is as big as the edit ahead or behind it. - pointer = 1; - while ( pointer < diffs.length ) { - if ( diffs[ pointer - 1 ][ 0 ] === DIFF_DELETE && - diffs[ pointer ][ 0 ] === DIFF_INSERT ) { - deletion = diffs[ pointer - 1 ][ 1 ]; - insertion = diffs[ pointer ][ 1 ]; - overlapLength1 = this.diffCommonOverlap( deletion, insertion ); - overlapLength2 = this.diffCommonOverlap( insertion, deletion ); - if ( overlapLength1 >= overlapLength2 ) { - if ( overlapLength1 >= deletion.length / 2 || - overlapLength1 >= insertion.length / 2 ) { - - // Overlap found. Insert an equality and trim the surrounding edits. - diffs.splice( - pointer, - 0, - [ DIFF_EQUAL, insertion.substring( 0, overlapLength1 ) ] - ); - diffs[ pointer - 1 ][ 1 ] = - deletion.substring( 0, deletion.length - overlapLength1 ); - diffs[ pointer + 1 ][ 1 ] = insertion.substring( overlapLength1 ); - pointer++; - } - } else { - if ( overlapLength2 >= deletion.length / 2 || - overlapLength2 >= insertion.length / 2 ) { - - // Reverse overlap found. - // Insert an equality and swap and trim the surrounding edits. - diffs.splice( - pointer, - 0, - [ DIFF_EQUAL, deletion.substring( 0, overlapLength2 ) ] - ); - - diffs[ pointer - 1 ][ 0 ] = DIFF_INSERT; - diffs[ pointer - 1 ][ 1 ] = - insertion.substring( 0, insertion.length - overlapLength2 ); - diffs[ pointer + 1 ][ 0 ] = DIFF_DELETE; - diffs[ pointer + 1 ][ 1 ] = - deletion.substring( overlapLength2 ); - pointer++; - } - } - pointer++; - } - pointer++; - } - }; - - /** - * Determine if the suffix of one string is the prefix of another. - * @param {string} text1 First string. - * @param {string} text2 Second string. - * @return {number} The number of characters common to the end of the first - * string and the start of the second string. - * @private - */ - DiffMatchPatch.prototype.diffCommonOverlap = function( text1, text2 ) { - var text1Length, text2Length, textLength, - best, length, pattern, found; - - // Cache the text lengths to prevent multiple calls. - text1Length = text1.length; - text2Length = text2.length; - - // Eliminate the null case. - if ( text1Length === 0 || text2Length === 0 ) { - return 0; - } - - // Truncate the longer string. - if ( text1Length > text2Length ) { - text1 = text1.substring( text1Length - text2Length ); - } else if ( text1Length < text2Length ) { - text2 = text2.substring( 0, text1Length ); - } - textLength = Math.min( text1Length, text2Length ); - - // Quick check for the worst case. - if ( text1 === text2 ) { - return textLength; - } - - // Start by looking for a single character match - // and increase length until no match is found. - // Performance analysis: https://neil.fraser.name/news/2010/11/04/ - best = 0; - length = 1; - while ( true ) { - pattern = text1.substring( textLength - length ); - found = text2.indexOf( pattern ); - if ( found === -1 ) { - return best; - } - length += found; - if ( found === 0 || text1.substring( textLength - length ) === - text2.substring( 0, length ) ) { - best = length; - length++; - } - } - }; - - /** - * Split two texts into an array of strings. Reduce the texts to a string of - * hashes where each Unicode character represents one line. - * @param {string} text1 First string. - * @param {string} text2 Second string. - * @return {{chars1: string, chars2: string, lineArray: !Array.}} - * An object containing the encoded text1, the encoded text2 and - * the array of unique strings. - * The zeroth element of the array of unique strings is intentionally blank. - * @private - */ - DiffMatchPatch.prototype.diffLinesToChars = function( text1, text2 ) { - var lineArray, lineHash, chars1, chars2; - lineArray = []; // E.g. lineArray[4] === 'Hello\n' - lineHash = {}; // E.g. lineHash['Hello\n'] === 4 - - // '\x00' is a valid character, but various debuggers don't like it. - // So we'll insert a junk entry to avoid generating a null character. - lineArray[ 0 ] = ""; - - /** - * Split a text into an array of strings. Reduce the texts to a string of - * hashes where each Unicode character represents one line. - * Modifies linearray and linehash through being a closure. - * @param {string} text String to encode. - * @return {string} Encoded string. - * @private - */ - function diffLinesToCharsMunge( text ) { - var chars, lineStart, lineEnd, lineArrayLength, line; - chars = ""; - - // Walk the text, pulling out a substring for each line. - // text.split('\n') would would temporarily double our memory footprint. - // Modifying text would create many large strings to garbage collect. - lineStart = 0; - lineEnd = -1; - - // Keeping our own length variable is faster than looking it up. - lineArrayLength = lineArray.length; - while ( lineEnd < text.length - 1 ) { - lineEnd = text.indexOf( "\n", lineStart ); - if ( lineEnd === -1 ) { - lineEnd = text.length - 1; - } - line = text.substring( lineStart, lineEnd + 1 ); - lineStart = lineEnd + 1; - - if ( lineHash.hasOwnProperty ? lineHash.hasOwnProperty( line ) : - ( lineHash[ line ] !== undefined ) ) { - chars += String.fromCharCode( lineHash[ line ] ); - } else { - chars += String.fromCharCode( lineArrayLength ); - lineHash[ line ] = lineArrayLength; - lineArray[ lineArrayLength++ ] = line; - } - } - return chars; - } - - chars1 = diffLinesToCharsMunge( text1 ); - chars2 = diffLinesToCharsMunge( text2 ); - return { - chars1: chars1, - chars2: chars2, - lineArray: lineArray - }; - }; - - /** - * Rehydrate the text in a diff from a string of line hashes to real lines of - * text. - * @param {!Array.} diffs Array of diff tuples. - * @param {!Array.} lineArray Array of unique strings. - * @private - */ - DiffMatchPatch.prototype.diffCharsToLines = function( diffs, lineArray ) { - var x, chars, text, y; - for ( x = 0; x < diffs.length; x++ ) { - chars = diffs[ x ][ 1 ]; - text = []; - for ( y = 0; y < chars.length; y++ ) { - text[ y ] = lineArray[ chars.charCodeAt( y ) ]; - } - diffs[ x ][ 1 ] = text.join( "" ); - } - }; - - /** - * Reorder and merge like edit sections. Merge equalities. - * Any edit section can move as long as it doesn't cross an equality. - * @param {!Array.} diffs Array of diff tuples. - */ - DiffMatchPatch.prototype.diffCleanupMerge = function( diffs ) { - var pointer, countDelete, countInsert, textInsert, textDelete, - commonlength, changes, diffPointer, position; - diffs.push( [ DIFF_EQUAL, "" ] ); // Add a dummy entry at the end. - pointer = 0; - countDelete = 0; - countInsert = 0; - textDelete = ""; - textInsert = ""; - commonlength; - while ( pointer < diffs.length ) { - switch ( diffs[ pointer ][ 0 ] ) { - case DIFF_INSERT: - countInsert++; - textInsert += diffs[ pointer ][ 1 ]; - pointer++; - break; - case DIFF_DELETE: - countDelete++; - textDelete += diffs[ pointer ][ 1 ]; - pointer++; - break; - case DIFF_EQUAL: - - // Upon reaching an equality, check for prior redundancies. - if ( countDelete + countInsert > 1 ) { - if ( countDelete !== 0 && countInsert !== 0 ) { - - // Factor out any common prefixes. - commonlength = this.diffCommonPrefix( textInsert, textDelete ); - if ( commonlength !== 0 ) { - if ( ( pointer - countDelete - countInsert ) > 0 && - diffs[ pointer - countDelete - countInsert - 1 ][ 0 ] === - DIFF_EQUAL ) { - diffs[ pointer - countDelete - countInsert - 1 ][ 1 ] += - textInsert.substring( 0, commonlength ); - } else { - diffs.splice( 0, 0, [ DIFF_EQUAL, - textInsert.substring( 0, commonlength ) - ] ); - pointer++; - } - textInsert = textInsert.substring( commonlength ); - textDelete = textDelete.substring( commonlength ); - } - - // Factor out any common suffixies. - commonlength = this.diffCommonSuffix( textInsert, textDelete ); - if ( commonlength !== 0 ) { - diffs[ pointer ][ 1 ] = textInsert.substring( textInsert.length - - commonlength ) + diffs[ pointer ][ 1 ]; - textInsert = textInsert.substring( 0, textInsert.length - - commonlength ); - textDelete = textDelete.substring( 0, textDelete.length - - commonlength ); - } - } - - // Delete the offending records and add the merged ones. - if ( countDelete === 0 ) { - diffs.splice( pointer - countInsert, - countDelete + countInsert, [ DIFF_INSERT, textInsert ] ); - } else if ( countInsert === 0 ) { - diffs.splice( pointer - countDelete, - countDelete + countInsert, [ DIFF_DELETE, textDelete ] ); - } else { - diffs.splice( - pointer - countDelete - countInsert, - countDelete + countInsert, - [ DIFF_DELETE, textDelete ], [ DIFF_INSERT, textInsert ] - ); - } - pointer = pointer - countDelete - countInsert + - ( countDelete ? 1 : 0 ) + ( countInsert ? 1 : 0 ) + 1; - } else if ( pointer !== 0 && diffs[ pointer - 1 ][ 0 ] === DIFF_EQUAL ) { - - // Merge this equality with the previous one. - diffs[ pointer - 1 ][ 1 ] += diffs[ pointer ][ 1 ]; - diffs.splice( pointer, 1 ); - } else { - pointer++; - } - countInsert = 0; - countDelete = 0; - textDelete = ""; - textInsert = ""; - break; - } - } - if ( diffs[ diffs.length - 1 ][ 1 ] === "" ) { - diffs.pop(); // Remove the dummy entry at the end. - } - - // Second pass: look for single edits surrounded on both sides by equalities - // which can be shifted sideways to eliminate an equality. - // e.g: ABAC -> ABAC - changes = false; - pointer = 1; - - // Intentionally ignore the first and last element (don't need checking). - while ( pointer < diffs.length - 1 ) { - if ( diffs[ pointer - 1 ][ 0 ] === DIFF_EQUAL && - diffs[ pointer + 1 ][ 0 ] === DIFF_EQUAL ) { - - diffPointer = diffs[ pointer ][ 1 ]; - position = diffPointer.substring( - diffPointer.length - diffs[ pointer - 1 ][ 1 ].length - ); - - // This is a single edit surrounded by equalities. - if ( position === diffs[ pointer - 1 ][ 1 ] ) { - - // Shift the edit over the previous equality. - diffs[ pointer ][ 1 ] = diffs[ pointer - 1 ][ 1 ] + - diffs[ pointer ][ 1 ].substring( 0, diffs[ pointer ][ 1 ].length - - diffs[ pointer - 1 ][ 1 ].length ); - diffs[ pointer + 1 ][ 1 ] = - diffs[ pointer - 1 ][ 1 ] + diffs[ pointer + 1 ][ 1 ]; - diffs.splice( pointer - 1, 1 ); - changes = true; - } else if ( diffPointer.substring( 0, diffs[ pointer + 1 ][ 1 ].length ) === - diffs[ pointer + 1 ][ 1 ] ) { - - // Shift the edit over the next equality. - diffs[ pointer - 1 ][ 1 ] += diffs[ pointer + 1 ][ 1 ]; - diffs[ pointer ][ 1 ] = - diffs[ pointer ][ 1 ].substring( diffs[ pointer + 1 ][ 1 ].length ) + - diffs[ pointer + 1 ][ 1 ]; - diffs.splice( pointer + 1, 1 ); - changes = true; - } - } - pointer++; - } - - // If shifts were made, the diff needs reordering and another shift sweep. - if ( changes ) { - this.diffCleanupMerge( diffs ); - } - }; - - return function( o, n ) { - var diff, output, text; - diff = new DiffMatchPatch(); - output = diff.DiffMain( o, n ); - diff.diffCleanupEfficiency( output ); - text = diff.diffPrettyHtml( output ); - - return text; - }; -}() ); - -}() ); + var localSessionStorage = function () { + var x = "qunit-test-string"; + try { + global$1.sessionStorage.setItem(x, x); + global$1.sessionStorage.removeItem(x); + return global$1.sessionStorage; + } catch (e) { + return undefined; + } + }(); + + var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { + return typeof obj; + } : function (obj) { + return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; + }; + + + + + + + + + + + + var classCallCheck = function (instance, Constructor) { + if (!(instance instanceof Constructor)) { + throw new TypeError("Cannot call a class as a function"); + } + }; + + var createClass = function () { + function defineProperties(target, props) { + for (var i = 0; i < props.length; i++) { + var descriptor = props[i]; + descriptor.enumerable = descriptor.enumerable || false; + descriptor.configurable = true; + if ("value" in descriptor) descriptor.writable = true; + Object.defineProperty(target, descriptor.key, descriptor); + } + } + + return function (Constructor, protoProps, staticProps) { + if (protoProps) defineProperties(Constructor.prototype, protoProps); + if (staticProps) defineProperties(Constructor, staticProps); + return Constructor; + }; + }(); + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + var toConsumableArray = function (arr) { + if (Array.isArray(arr)) { + for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) arr2[i] = arr[i]; + + return arr2; + } else { + return Array.from(arr); + } + }; + + var toString = Object.prototype.toString; + var hasOwn = Object.prototype.hasOwnProperty; + var now = Date.now || function () { + return new Date().getTime(); + }; + + var defined = { + document: window && window.document !== undefined, + setTimeout: setTimeout !== undefined + }; + + // Returns a new Array with the elements that are in a but not in b + function diff(a, b) { + var i, + j, + result = a.slice(); + + for (i = 0; i < result.length; i++) { + for (j = 0; j < b.length; j++) { + if (result[i] === b[j]) { + result.splice(i, 1); + i--; + break; + } + } + } + return result; + } + + /** + * Determines whether an element exists in a given array or not. + * + * @method inArray + * @param {Any} elem + * @param {Array} array + * @return {Boolean} + */ + function inArray(elem, array) { + return array.indexOf(elem) !== -1; + } + + /** + * Makes a clone of an object using only Array or Object as base, + * and copies over the own enumerable properties. + * + * @param {Object} obj + * @return {Object} New object with only the own properties (recursively). + */ + function objectValues(obj) { + var key, + val, + vals = is("array", obj) ? [] : {}; + for (key in obj) { + if (hasOwn.call(obj, key)) { + val = obj[key]; + vals[key] = val === Object(val) ? objectValues(val) : val; + } + } + return vals; + } + + function extend(a, b, undefOnly) { + for (var prop in b) { + if (hasOwn.call(b, prop)) { + if (b[prop] === undefined) { + delete a[prop]; + } else if (!(undefOnly && typeof a[prop] !== "undefined")) { + a[prop] = b[prop]; + } + } + } + + return a; + } + + function objectType(obj) { + if (typeof obj === "undefined") { + return "undefined"; + } + + // Consider: typeof null === object + if (obj === null) { + return "null"; + } + + var match = toString.call(obj).match(/^\[object\s(.*)\]$/), + type = match && match[1]; + + switch (type) { + case "Number": + if (isNaN(obj)) { + return "nan"; + } + return "number"; + case "String": + case "Boolean": + case "Array": + case "Set": + case "Map": + case "Date": + case "RegExp": + case "Function": + case "Symbol": + return type.toLowerCase(); + } + + if ((typeof obj === "undefined" ? "undefined" : _typeof(obj)) === "object") { + return "object"; + } + } + + // Safe object type checking + function is(type, obj) { + return objectType(obj) === type; + } + + // Based on Java's String.hashCode, a simple but not + // rigorously collision resistant hashing function + function generateHash(module, testName) { + var str = module + "\x1C" + testName; + var hash = 0; + + for (var i = 0; i < str.length; i++) { + hash = (hash << 5) - hash + str.charCodeAt(i); + hash |= 0; + } + + // Convert the possibly negative integer hash code into an 8 character hex string, which isn't + // strictly necessary but increases user understanding that the id is a SHA-like hash + var hex = (0x100000000 + hash).toString(16); + if (hex.length < 8) { + hex = "0000000" + hex; + } + + return hex.slice(-8); + } + + // Test for equality any JavaScript type. + // Authors: Philippe Rathé , David Chan + var equiv = (function () { + + // Value pairs queued for comparison. Used for breadth-first processing order, recursion + // detection and avoiding repeated comparison (see below for details). + // Elements are { a: val, b: val }. + var pairs = []; + + var getProto = Object.getPrototypeOf || function (obj) { + return obj.__proto__; + }; + + function useStrictEquality(a, b) { + + // This only gets called if a and b are not strict equal, and is used to compare on + // the primitive values inside object wrappers. For example: + // `var i = 1;` + // `var j = new Number(1);` + // Neither a nor b can be null, as a !== b and they have the same type. + if ((typeof a === "undefined" ? "undefined" : _typeof(a)) === "object") { + a = a.valueOf(); + } + if ((typeof b === "undefined" ? "undefined" : _typeof(b)) === "object") { + b = b.valueOf(); + } + + return a === b; + } + + function compareConstructors(a, b) { + var protoA = getProto(a); + var protoB = getProto(b); + + // Comparing constructors is more strict than using `instanceof` + if (a.constructor === b.constructor) { + return true; + } + + // Ref #851 + // If the obj prototype descends from a null constructor, treat it + // as a null prototype. + if (protoA && protoA.constructor === null) { + protoA = null; + } + if (protoB && protoB.constructor === null) { + protoB = null; + } + + // Allow objects with no prototype to be equivalent to + // objects with Object as their constructor. + if (protoA === null && protoB === Object.prototype || protoB === null && protoA === Object.prototype) { + return true; + } + + return false; + } + + function getRegExpFlags(regexp) { + return "flags" in regexp ? regexp.flags : regexp.toString().match(/[gimuy]*$/)[0]; + } + + function isContainer(val) { + return ["object", "array", "map", "set"].indexOf(objectType(val)) !== -1; + } + + function breadthFirstCompareChild(a, b) { + + // If a is a container not reference-equal to b, postpone the comparison to the + // end of the pairs queue -- unless (a, b) has been seen before, in which case skip + // over the pair. + if (a === b) { + return true; + } + if (!isContainer(a)) { + return typeEquiv(a, b); + } + if (pairs.every(function (pair) { + return pair.a !== a || pair.b !== b; + })) { + + // Not yet started comparing this pair + pairs.push({ a: a, b: b }); + } + return true; + } + + var callbacks = { + "string": useStrictEquality, + "boolean": useStrictEquality, + "number": useStrictEquality, + "null": useStrictEquality, + "undefined": useStrictEquality, + "symbol": useStrictEquality, + "date": useStrictEquality, + + "nan": function nan() { + return true; + }, + + "regexp": function regexp(a, b) { + return a.source === b.source && + + // Include flags in the comparison + getRegExpFlags(a) === getRegExpFlags(b); + }, + + // abort (identical references / instance methods were skipped earlier) + "function": function _function() { + return false; + }, + + "array": function array(a, b) { + var i, len; + + len = a.length; + if (len !== b.length) { + + // Safe and faster + return false; + } + + for (i = 0; i < len; i++) { + + // Compare non-containers; queue non-reference-equal containers + if (!breadthFirstCompareChild(a[i], b[i])) { + return false; + } + } + return true; + }, + + // Define sets a and b to be equivalent if for each element aVal in a, there + // is some element bVal in b such that aVal and bVal are equivalent. Element + // repetitions are not counted, so these are equivalent: + // a = new Set( [ {}, [], [] ] ); + // b = new Set( [ {}, {}, [] ] ); + "set": function set$$1(a, b) { + var innerEq, + outerEq = true; + + if (a.size !== b.size) { + + // This optimization has certain quirks because of the lack of + // repetition counting. For instance, adding the same + // (reference-identical) element to two equivalent sets can + // make them non-equivalent. + return false; + } + + a.forEach(function (aVal) { + + // Short-circuit if the result is already known. (Using for...of + // with a break clause would be cleaner here, but it would cause + // a syntax error on older Javascript implementations even if + // Set is unused) + if (!outerEq) { + return; + } + + innerEq = false; + + b.forEach(function (bVal) { + var parentPairs; + + // Likewise, short-circuit if the result is already known + if (innerEq) { + return; + } + + // Swap out the global pairs list, as the nested call to + // innerEquiv will clobber its contents + parentPairs = pairs; + if (innerEquiv(bVal, aVal)) { + innerEq = true; + } + + // Replace the global pairs list + pairs = parentPairs; + }); + + if (!innerEq) { + outerEq = false; + } + }); + + return outerEq; + }, + + // Define maps a and b to be equivalent if for each key-value pair (aKey, aVal) + // in a, there is some key-value pair (bKey, bVal) in b such that + // [ aKey, aVal ] and [ bKey, bVal ] are equivalent. Key repetitions are not + // counted, so these are equivalent: + // a = new Map( [ [ {}, 1 ], [ {}, 1 ], [ [], 1 ] ] ); + // b = new Map( [ [ {}, 1 ], [ [], 1 ], [ [], 1 ] ] ); + "map": function map(a, b) { + var innerEq, + outerEq = true; + + if (a.size !== b.size) { + + // This optimization has certain quirks because of the lack of + // repetition counting. For instance, adding the same + // (reference-identical) key-value pair to two equivalent maps + // can make them non-equivalent. + return false; + } + + a.forEach(function (aVal, aKey) { + + // Short-circuit if the result is already known. (Using for...of + // with a break clause would be cleaner here, but it would cause + // a syntax error on older Javascript implementations even if + // Map is unused) + if (!outerEq) { + return; + } + + innerEq = false; + + b.forEach(function (bVal, bKey) { + var parentPairs; + + // Likewise, short-circuit if the result is already known + if (innerEq) { + return; + } + + // Swap out the global pairs list, as the nested call to + // innerEquiv will clobber its contents + parentPairs = pairs; + if (innerEquiv([bVal, bKey], [aVal, aKey])) { + innerEq = true; + } + + // Replace the global pairs list + pairs = parentPairs; + }); + + if (!innerEq) { + outerEq = false; + } + }); + + return outerEq; + }, + + "object": function object(a, b) { + var i, + aProperties = [], + bProperties = []; + + if (compareConstructors(a, b) === false) { + return false; + } + + // Be strict: don't ensure hasOwnProperty and go deep + for (i in a) { + + // Collect a's properties + aProperties.push(i); + + // Skip OOP methods that look the same + if (a.constructor !== Object && typeof a.constructor !== "undefined" && typeof a[i] === "function" && typeof b[i] === "function" && a[i].toString() === b[i].toString()) { + continue; + } + + // Compare non-containers; queue non-reference-equal containers + if (!breadthFirstCompareChild(a[i], b[i])) { + return false; + } + } + + for (i in b) { + + // Collect b's properties + bProperties.push(i); + } + + // Ensures identical properties name + return typeEquiv(aProperties.sort(), bProperties.sort()); + } + }; + + function typeEquiv(a, b) { + var type = objectType(a); + + // Callbacks for containers will append to the pairs queue to achieve breadth-first + // search order. The pairs queue is also used to avoid reprocessing any pair of + // containers that are reference-equal to a previously visited pair (a special case + // this being recursion detection). + // + // Because of this approach, once typeEquiv returns a false value, it should not be + // called again without clearing the pair queue else it may wrongly report a visited + // pair as being equivalent. + return objectType(b) === type && callbacks[type](a, b); + } + + function innerEquiv(a, b) { + var i, pair; + + // We're done when there's nothing more to compare + if (arguments.length < 2) { + return true; + } + + // Clear the global pair queue and add the top-level values being compared + pairs = [{ a: a, b: b }]; + + for (i = 0; i < pairs.length; i++) { + pair = pairs[i]; + + // Perform type-specific comparison on any pairs that are not strictly + // equal. For container types, that comparison will postpone comparison + // of any sub-container pair to the end of the pair queue. This gives + // breadth-first search order. It also avoids the reprocessing of + // reference-equal siblings, cousins etc, which can have a significant speed + // impact when comparing a container of small objects each of which has a + // reference to the same (singleton) large object. + if (pair.a !== pair.b && !typeEquiv(pair.a, pair.b)) { + return false; + } + } + + // ...across all consecutive argument pairs + return arguments.length === 2 || innerEquiv.apply(this, [].slice.call(arguments, 1)); + } + + return innerEquiv; + })(); + + /** + * Config object: Maintain internal state + * Later exposed as QUnit.config + * `config` initialized at top of scope + */ + var config = { + + // The queue of tests to run + queue: [], + + // Block until document ready + blocking: true, + + // By default, run previously failed tests first + // very useful in combination with "Hide passed tests" checked + reorder: true, + + // By default, modify document.title when suite is done + altertitle: true, + + // HTML Reporter: collapse every test except the first failing test + // If false, all failing tests will be expanded + collapse: true, + + // By default, scroll to top of the page when suite is done + scrolltop: true, + + // Depth up-to which object will be dumped + maxDepth: 5, + + // When enabled, all tests must call expect() + requireExpects: false, + + // Placeholder for user-configurable form-exposed URL parameters + urlConfig: [], + + // Set of all modules. + modules: [], + + // The first unnamed module + currentModule: { + name: "", + tests: [], + childModules: [], + testsRun: 0, + unskippedTestsRun: 0 + }, + + callbacks: {}, + + // The storage module to use for reordering tests + storage: localSessionStorage + }; + + // take a predefined QUnit.config and extend the defaults + var globalConfig = window && window.QUnit && window.QUnit.config; + + // only extend the global config if there is no QUnit overload + if (window && window.QUnit && !window.QUnit.version) { + extend(config, globalConfig); + } + + // Push a loose unnamed module to the modules collection + config.modules.push(config.currentModule); + + // Based on jsDump by Ariel Flesler + // http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html + var dump = (function () { + function quote(str) { + return "\"" + str.toString().replace(/\\/g, "\\\\").replace(/"/g, "\\\"") + "\""; + } + function literal(o) { + return o + ""; + } + function join(pre, arr, post) { + var s = dump.separator(), + base = dump.indent(), + inner = dump.indent(1); + if (arr.join) { + arr = arr.join("," + s + inner); + } + if (!arr) { + return pre + post; + } + return [pre, inner + arr, base + post].join(s); + } + function array(arr, stack) { + var i = arr.length, + ret = new Array(i); + + if (dump.maxDepth && dump.depth > dump.maxDepth) { + return "[object Array]"; + } + + this.up(); + while (i--) { + ret[i] = this.parse(arr[i], undefined, stack); + } + this.down(); + return join("[", ret, "]"); + } + + function isArray(obj) { + return ( + + //Native Arrays + toString.call(obj) === "[object Array]" || + + // NodeList objects + typeof obj.length === "number" && obj.item !== undefined && (obj.length ? obj.item(0) === obj[0] : obj.item(0) === null && obj[0] === undefined) + ); + } + + var reName = /^function (\w+)/, + dump = { + + // The objType is used mostly internally, you can fix a (custom) type in advance + parse: function parse(obj, objType, stack) { + stack = stack || []; + var res, + parser, + parserType, + objIndex = stack.indexOf(obj); + + if (objIndex !== -1) { + return "recursion(" + (objIndex - stack.length) + ")"; + } + + objType = objType || this.typeOf(obj); + parser = this.parsers[objType]; + parserType = typeof parser === "undefined" ? "undefined" : _typeof(parser); + + if (parserType === "function") { + stack.push(obj); + res = parser.call(this, obj, stack); + stack.pop(); + return res; + } + return parserType === "string" ? parser : this.parsers.error; + }, + typeOf: function typeOf(obj) { + var type; + + if (obj === null) { + type = "null"; + } else if (typeof obj === "undefined") { + type = "undefined"; + } else if (is("regexp", obj)) { + type = "regexp"; + } else if (is("date", obj)) { + type = "date"; + } else if (is("function", obj)) { + type = "function"; + } else if (obj.setInterval !== undefined && obj.document !== undefined && obj.nodeType === undefined) { + type = "window"; + } else if (obj.nodeType === 9) { + type = "document"; + } else if (obj.nodeType) { + type = "node"; + } else if (isArray(obj)) { + type = "array"; + } else if (obj.constructor === Error.prototype.constructor) { + type = "error"; + } else { + type = typeof obj === "undefined" ? "undefined" : _typeof(obj); + } + return type; + }, + + separator: function separator() { + if (this.multiline) { + return this.HTML ? "
      " : "\n"; + } else { + return this.HTML ? " " : " "; + } + }, + + // Extra can be a number, shortcut for increasing-calling-decreasing + indent: function indent(extra) { + if (!this.multiline) { + return ""; + } + var chr = this.indentChar; + if (this.HTML) { + chr = chr.replace(/\t/g, " ").replace(/ /g, " "); + } + return new Array(this.depth + (extra || 0)).join(chr); + }, + up: function up(a) { + this.depth += a || 1; + }, + down: function down(a) { + this.depth -= a || 1; + }, + setParser: function setParser(name, parser) { + this.parsers[name] = parser; + }, + + // The next 3 are exposed so you can use them + quote: quote, + literal: literal, + join: join, + depth: 1, + maxDepth: config.maxDepth, + + // This is the list of parsers, to modify them, use dump.setParser + parsers: { + window: "[Window]", + document: "[Document]", + error: function error(_error) { + return "Error(\"" + _error.message + "\")"; + }, + unknown: "[Unknown]", + "null": "null", + "undefined": "undefined", + "function": function _function(fn) { + var ret = "function", + + + // Functions never have name in IE + name = "name" in fn ? fn.name : (reName.exec(fn) || [])[1]; + + if (name) { + ret += " " + name; + } + ret += "("; + + ret = [ret, dump.parse(fn, "functionArgs"), "){"].join(""); + return join(ret, dump.parse(fn, "functionCode"), "}"); + }, + array: array, + nodelist: array, + "arguments": array, + object: function object(map, stack) { + var keys, + key, + val, + i, + nonEnumerableProperties, + ret = []; + + if (dump.maxDepth && dump.depth > dump.maxDepth) { + return "[object Object]"; + } + + dump.up(); + keys = []; + for (key in map) { + keys.push(key); + } + + // Some properties are not always enumerable on Error objects. + nonEnumerableProperties = ["message", "name"]; + for (i in nonEnumerableProperties) { + key = nonEnumerableProperties[i]; + if (key in map && !inArray(key, keys)) { + keys.push(key); + } + } + keys.sort(); + for (i = 0; i < keys.length; i++) { + key = keys[i]; + val = map[key]; + ret.push(dump.parse(key, "key") + ": " + dump.parse(val, undefined, stack)); + } + dump.down(); + return join("{", ret, "}"); + }, + node: function node(_node) { + var len, + i, + val, + open = dump.HTML ? "<" : "<", + close = dump.HTML ? ">" : ">", + tag = _node.nodeName.toLowerCase(), + ret = open + tag, + attrs = _node.attributes; + + if (attrs) { + for (i = 0, len = attrs.length; i < len; i++) { + val = attrs[i].nodeValue; + + // IE6 includes all attributes in .attributes, even ones not explicitly + // set. Those have values like undefined, null, 0, false, "" or + // "inherit". + if (val && val !== "inherit") { + ret += " " + attrs[i].nodeName + "=" + dump.parse(val, "attribute"); + } + } + } + ret += close; + + // Show content of TextNode or CDATASection + if (_node.nodeType === 3 || _node.nodeType === 4) { + ret += _node.nodeValue; + } + + return ret + open + "/" + tag + close; + }, + + // Function calls it internally, it's the arguments part of the function + functionArgs: function functionArgs(fn) { + var args, + l = fn.length; + + if (!l) { + return ""; + } + + args = new Array(l); + while (l--) { + + // 97 is 'a' + args[l] = String.fromCharCode(97 + l); + } + return " " + args.join(", ") + " "; + }, + + // Object calls it internally, the key part of an item in a map + key: quote, + + // Function calls it internally, it's the content of the function + functionCode: "[code]", + + // Node calls it internally, it's a html attribute value + attribute: quote, + string: quote, + date: quote, + regexp: literal, + number: literal, + "boolean": literal, + symbol: function symbol(sym) { + return sym.toString(); + } + }, + + // If true, entities are escaped ( <, >, \t, space and \n ) + HTML: false, + + // Indentation unit + indentChar: " ", + + // If true, items in a collection, are separated by a \n, else just a space. + multiline: true + }; + + return dump; + })(); + + var LISTENERS = Object.create(null); + var SUPPORTED_EVENTS = ["runStart", "suiteStart", "testStart", "assertion", "testEnd", "suiteEnd", "runEnd"]; + + /** + * Emits an event with the specified data to all currently registered listeners. + * Callbacks will fire in the order in which they are registered (FIFO). This + * function is not exposed publicly; it is used by QUnit internals to emit + * logging events. + * + * @private + * @method emit + * @param {String} eventName + * @param {Object} data + * @return {Void} + */ + function emit(eventName, data) { + if (objectType(eventName) !== "string") { + throw new TypeError("eventName must be a string when emitting an event"); + } + + // Clone the callbacks in case one of them registers a new callback + var originalCallbacks = LISTENERS[eventName]; + var callbacks = originalCallbacks ? [].concat(toConsumableArray(originalCallbacks)) : []; + + for (var i = 0; i < callbacks.length; i++) { + callbacks[i](data); + } + } + + /** + * Registers a callback as a listener to the specified event. + * + * @public + * @method on + * @param {String} eventName + * @param {Function} callback + * @return {Void} + */ + function on(eventName, callback) { + if (objectType(eventName) !== "string") { + throw new TypeError("eventName must be a string when registering a listener"); + } else if (!inArray(eventName, SUPPORTED_EVENTS)) { + var events = SUPPORTED_EVENTS.join(", "); + throw new Error("\"" + eventName + "\" is not a valid event; must be one of: " + events + "."); + } else if (objectType(callback) !== "function") { + throw new TypeError("callback must be a function when registering a listener"); + } + + if (!LISTENERS[eventName]) { + LISTENERS[eventName] = []; + } + + // Don't register the same callback more than once + if (!inArray(callback, LISTENERS[eventName])) { + LISTENERS[eventName].push(callback); + } + } + + // Register logging callbacks + function registerLoggingCallbacks(obj) { + var i, + l, + key, + callbackNames = ["begin", "done", "log", "testStart", "testDone", "moduleStart", "moduleDone"]; + + function registerLoggingCallback(key) { + var loggingCallback = function loggingCallback(callback) { + if (objectType(callback) !== "function") { + throw new Error("QUnit logging methods require a callback function as their first parameters."); + } + + config.callbacks[key].push(callback); + }; + + return loggingCallback; + } + + for (i = 0, l = callbackNames.length; i < l; i++) { + key = callbackNames[i]; + + // Initialize key collection of logging callback + if (objectType(config.callbacks[key]) === "undefined") { + config.callbacks[key] = []; + } + + obj[key] = registerLoggingCallback(key); + } + } + + function runLoggingCallbacks(key, args) { + var i, l, callbacks; + + callbacks = config.callbacks[key]; + for (i = 0, l = callbacks.length; i < l; i++) { + callbacks[i](args); + } + } + + // Doesn't support IE9, it will return undefined on these browsers + // See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack + var fileName = (sourceFromStacktrace(0) || "").replace(/(:\d+)+\)?/, "").replace(/.+\//, ""); + + function extractStacktrace(e, offset) { + offset = offset === undefined ? 4 : offset; + + var stack, include, i; + + if (e && e.stack) { + stack = e.stack.split("\n"); + if (/^error$/i.test(stack[0])) { + stack.shift(); + } + if (fileName) { + include = []; + for (i = offset; i < stack.length; i++) { + if (stack[i].indexOf(fileName) !== -1) { + break; + } + include.push(stack[i]); + } + if (include.length) { + return include.join("\n"); + } + } + return stack[offset]; + } + } + + function sourceFromStacktrace(offset) { + var error = new Error(); + + // Support: Safari <=7 only, IE <=10 - 11 only + // Not all browsers generate the `stack` property for `new Error()`, see also #636 + if (!error.stack) { + try { + throw error; + } catch (err) { + error = err; + } + } + + return extractStacktrace(error, offset); + } + + var priorityCount = 0; + var unitSampler = void 0; + + /** + * Advances the ProcessingQueue to the next item if it is ready. + * @param {Boolean} last + */ + function advance() { + var start = now(); + config.depth = (config.depth || 0) + 1; + + while (config.queue.length && !config.blocking) { + var elapsedTime = now() - start; + + if (!defined.setTimeout || config.updateRate <= 0 || elapsedTime < config.updateRate) { + if (priorityCount > 0) { + priorityCount--; + } + + config.queue.shift()(); + } else { + setTimeout(advance, 13); + break; + } + } + + config.depth--; + + if (!config.blocking && !config.queue.length && config.depth === 0) { + done(); + } + } + + function addToQueueImmediate(callback) { + if (objectType(callback) === "array") { + while (callback.length) { + addToQueueImmediate(callback.pop()); + } + + return; + } + + config.queue.unshift(callback); + priorityCount++; + } + + /** + * Adds a function to the ProcessingQueue for execution. + * @param {Function|Array} callback + * @param {Boolean} priority + * @param {String} seed + */ + function addToQueue(callback, prioritize, seed) { + if (prioritize) { + config.queue.splice(priorityCount++, 0, callback); + } else if (seed) { + if (!unitSampler) { + unitSampler = unitSamplerGenerator(seed); + } + + // Insert into a random position after all prioritized items + var index = Math.floor(unitSampler() * (config.queue.length - priorityCount + 1)); + config.queue.splice(priorityCount + index, 0, callback); + } else { + config.queue.push(callback); + } + } + + /** + * Creates a seeded "sample" generator which is used for randomizing tests. + */ + function unitSamplerGenerator(seed) { + + // 32-bit xorshift, requires only a nonzero seed + // http://excamera.com/sphinx/article-xorshift.html + var sample = parseInt(generateHash(seed), 16) || -1; + return function () { + sample ^= sample << 13; + sample ^= sample >>> 17; + sample ^= sample << 5; + + // ECMAScript has no unsigned number type + if (sample < 0) { + sample += 0x100000000; + } + + return sample / 0x100000000; + }; + } + + /** + * This function is called when the ProcessingQueue is done processing all + * items. It handles emitting the final run events. + */ + function done() { + var storage = config.storage; + + ProcessingQueue.finished = true; + + var runtime = now() - config.started; + var passed = config.stats.all - config.stats.bad; + + emit("runEnd", globalSuite.end(true)); + runLoggingCallbacks("done", { + passed: passed, + failed: config.stats.bad, + total: config.stats.all, + runtime: runtime + }); + + // Clear own storage items if all tests passed + if (storage && config.stats.bad === 0) { + for (var i = storage.length - 1; i >= 0; i--) { + var key = storage.key(i); + + if (key.indexOf("qunit-test-") === 0) { + storage.removeItem(key); + } + } + } + } + + var ProcessingQueue = { + finished: false, + add: addToQueue, + addImmediate: addToQueueImmediate, + advance: advance + }; + + var TestReport = function () { + function TestReport(name, suite, options) { + classCallCheck(this, TestReport); + + this.name = name; + this.suiteName = suite.name; + this.fullName = suite.fullName.concat(name); + this.runtime = 0; + this.assertions = []; + + this.skipped = !!options.skip; + this.todo = !!options.todo; + + this.valid = options.valid; + + this._startTime = 0; + this._endTime = 0; + + suite.pushTest(this); + } + + createClass(TestReport, [{ + key: "start", + value: function start(recordTime) { + if (recordTime) { + this._startTime = Date.now(); + } + + return { + name: this.name, + suiteName: this.suiteName, + fullName: this.fullName.slice() + }; + } + }, { + key: "end", + value: function end(recordTime) { + if (recordTime) { + this._endTime = Date.now(); + } + + return extend(this.start(), { + runtime: this.getRuntime(), + status: this.getStatus(), + errors: this.getFailedAssertions(), + assertions: this.getAssertions() + }); + } + }, { + key: "pushAssertion", + value: function pushAssertion(assertion) { + this.assertions.push(assertion); + } + }, { + key: "getRuntime", + value: function getRuntime() { + return this._endTime - this._startTime; + } + }, { + key: "getStatus", + value: function getStatus() { + if (this.skipped) { + return "skipped"; + } + + var testPassed = this.getFailedAssertions().length > 0 ? this.todo : !this.todo; + + if (!testPassed) { + return "failed"; + } else if (this.todo) { + return "todo"; + } else { + return "passed"; + } + } + }, { + key: "getFailedAssertions", + value: function getFailedAssertions() { + return this.assertions.filter(function (assertion) { + return !assertion.passed; + }); + } + }, { + key: "getAssertions", + value: function getAssertions() { + return this.assertions.slice(); + } + + // Remove actual and expected values from assertions. This is to prevent + // leaking memory throughout a test suite. + + }, { + key: "slimAssertions", + value: function slimAssertions() { + this.assertions = this.assertions.map(function (assertion) { + delete assertion.actual; + delete assertion.expected; + return assertion; + }); + } + }]); + return TestReport; + }(); + + var focused = false; + + function Test(settings) { + var i, l; + + ++Test.count; + + this.expected = null; + extend(this, settings); + this.assertions = []; + this.semaphore = 0; + this.module = config.currentModule; + this.stack = sourceFromStacktrace(3); + this.steps = []; + + this.testReport = new TestReport(settings.testName, this.module.suiteReport, { + todo: settings.todo, + skip: settings.skip, + valid: this.valid() + }); + + // Register unique strings + for (i = 0, l = this.module.tests; i < l.length; i++) { + if (this.module.tests[i].name === this.testName) { + this.testName += " "; + } + } + + this.testId = generateHash(this.module.name, this.testName); + + this.module.tests.push({ + name: this.testName, + testId: this.testId, + skip: !!settings.skip + }); + + if (settings.skip) { + + // Skipped tests will fully ignore any sent callback + this.callback = function () {}; + this.async = false; + this.expected = 0; + } else { + this.assert = new Assert(this); + } + } + + Test.count = 0; + + function getNotStartedModules(startModule) { + var module = startModule, + modules = []; + + while (module && module.testsRun === 0) { + modules.push(module); + module = module.parentModule; + } + + return modules; + } + + Test.prototype = { + before: function before() { + var i, + startModule, + module = this.module, + notStartedModules = getNotStartedModules(module); + + for (i = notStartedModules.length - 1; i >= 0; i--) { + startModule = notStartedModules[i]; + startModule.stats = { all: 0, bad: 0, started: now() }; + emit("suiteStart", startModule.suiteReport.start(true)); + runLoggingCallbacks("moduleStart", { + name: startModule.name, + tests: startModule.tests + }); + } + + config.current = this; + + this.testEnvironment = extend({}, module.testEnvironment); + + this.started = now(); + emit("testStart", this.testReport.start(true)); + runLoggingCallbacks("testStart", { + name: this.testName, + module: module.name, + testId: this.testId, + previousFailure: this.previousFailure + }); + + if (!config.pollution) { + saveGlobal(); + } + }, + + run: function run() { + var promise; + + config.current = this; + + this.callbackStarted = now(); + + if (config.notrycatch) { + runTest(this); + return; + } + + try { + runTest(this); + } catch (e) { + this.pushFailure("Died on test #" + (this.assertions.length + 1) + " " + this.stack + ": " + (e.message || e), extractStacktrace(e, 0)); + + // Else next test will carry the responsibility + saveGlobal(); + + // Restart the tests if they're blocking + if (config.blocking) { + internalRecover(this); + } + } + + function runTest(test) { + promise = test.callback.call(test.testEnvironment, test.assert); + test.resolvePromise(promise); + } + }, + + after: function after() { + checkPollution(); + }, + + queueHook: function queueHook(hook, hookName, hookOwner) { + var promise, + test = this; + return function runHook() { + if (hookName === "before") { + if (hookOwner.unskippedTestsRun !== 0) { + return; + } + + test.preserveEnvironment = true; + } + + if (hookName === "after" && hookOwner.unskippedTestsRun !== numberOfUnskippedTests(hookOwner) - 1 && config.queue.length > 2) { + return; + } + + config.current = test; + if (config.notrycatch) { + callHook(); + return; + } + try { + callHook(); + } catch (error) { + test.pushFailure(hookName + " failed on " + test.testName + ": " + (error.message || error), extractStacktrace(error, 0)); + } + + function callHook() { + promise = hook.call(test.testEnvironment, test.assert); + test.resolvePromise(promise, hookName); + } + }; + }, + + // Currently only used for module level hooks, can be used to add global level ones + hooks: function hooks(handler) { + var hooks = []; + + function processHooks(test, module) { + if (module.parentModule) { + processHooks(test, module.parentModule); + } + if (module.hooks && objectType(module.hooks[handler]) === "function") { + hooks.push(test.queueHook(module.hooks[handler], handler, module)); + } + } + + // Hooks are ignored on skipped tests + if (!this.skip) { + processHooks(this, this.module); + } + return hooks; + }, + + finish: function finish() { + config.current = this; + if (config.requireExpects && this.expected === null) { + this.pushFailure("Expected number of assertions to be defined, but expect() was " + "not called.", this.stack); + } else if (this.expected !== null && this.expected !== this.assertions.length) { + this.pushFailure("Expected " + this.expected + " assertions, but " + this.assertions.length + " were run", this.stack); + } else if (this.expected === null && !this.assertions.length) { + this.pushFailure("Expected at least one assertion, but none were run - call " + "expect(0) to accept zero assertions.", this.stack); + } + + var i, + module = this.module, + moduleName = module.name, + testName = this.testName, + skipped = !!this.skip, + todo = !!this.todo, + bad = 0, + storage = config.storage; + + this.runtime = now() - this.started; + + config.stats.all += this.assertions.length; + module.stats.all += this.assertions.length; + + for (i = 0; i < this.assertions.length; i++) { + if (!this.assertions[i].result) { + bad++; + config.stats.bad++; + module.stats.bad++; + } + } + + notifyTestsRan(module, skipped); + + // Store result when possible + if (storage) { + if (bad) { + storage.setItem("qunit-test-" + moduleName + "-" + testName, bad); + } else { + storage.removeItem("qunit-test-" + moduleName + "-" + testName); + } + } + + // After emitting the js-reporters event we cleanup the assertion data to + // avoid leaking it. It is not used by the legacy testDone callbacks. + emit("testEnd", this.testReport.end(true)); + this.testReport.slimAssertions(); + + runLoggingCallbacks("testDone", { + name: testName, + module: moduleName, + skipped: skipped, + todo: todo, + failed: bad, + passed: this.assertions.length - bad, + total: this.assertions.length, + runtime: skipped ? 0 : this.runtime, + + // HTML Reporter use + assertions: this.assertions, + testId: this.testId, + + // Source of Test + source: this.stack + }); + + if (module.testsRun === numberOfTests(module)) { + logSuiteEnd(module); + + // Check if the parent modules, iteratively, are done. If that the case, + // we emit the `suiteEnd` event and trigger `moduleDone` callback. + var parent = module.parentModule; + while (parent && parent.testsRun === numberOfTests(parent)) { + logSuiteEnd(parent); + parent = parent.parentModule; + } + } + + config.current = undefined; + + function logSuiteEnd(module) { + emit("suiteEnd", module.suiteReport.end(true)); + runLoggingCallbacks("moduleDone", { + name: module.name, + tests: module.tests, + failed: module.stats.bad, + passed: module.stats.all - module.stats.bad, + total: module.stats.all, + runtime: now() - module.stats.started + }); + } + }, + + preserveTestEnvironment: function preserveTestEnvironment() { + if (this.preserveEnvironment) { + this.module.testEnvironment = this.testEnvironment; + this.testEnvironment = extend({}, this.module.testEnvironment); + } + }, + + queue: function queue() { + var test = this; + + if (!this.valid()) { + return; + } + + function runTest() { + + // Each of these can by async + ProcessingQueue.addImmediate([function () { + test.before(); + }, test.hooks("before"), function () { + test.preserveTestEnvironment(); + }, test.hooks("beforeEach"), function () { + test.run(); + }, test.hooks("afterEach").reverse(), test.hooks("after").reverse(), function () { + test.after(); + }, function () { + test.finish(); + }]); + } + + var previousFailCount = config.storage && +config.storage.getItem("qunit-test-" + this.module.name + "-" + this.testName); + + // Prioritize previously failed tests, detected from storage + var prioritize = config.reorder && !!previousFailCount; + + this.previousFailure = !!previousFailCount; + + ProcessingQueue.add(runTest, prioritize, config.seed); + + // If the queue has already finished, we manually process the new test + if (ProcessingQueue.finished) { + ProcessingQueue.advance(); + } + }, + + + pushResult: function pushResult(resultInfo) { + if (this !== config.current) { + throw new Error("Assertion occured after test had finished."); + } + + // Destructure of resultInfo = { result, actual, expected, message, negative } + var source, + details = { + module: this.module.name, + name: this.testName, + result: resultInfo.result, + message: resultInfo.message, + actual: resultInfo.actual, + expected: resultInfo.expected, + testId: this.testId, + negative: resultInfo.negative || false, + runtime: now() - this.started, + todo: !!this.todo + }; + + if (!resultInfo.result) { + source = resultInfo.source || sourceFromStacktrace(); + + if (source) { + details.source = source; + } + } + + this.logAssertion(details); + + this.assertions.push({ + result: !!resultInfo.result, + message: resultInfo.message + }); + }, + + pushFailure: function pushFailure(message, source, actual) { + if (!(this instanceof Test)) { + throw new Error("pushFailure() assertion outside test context, was " + sourceFromStacktrace(2)); + } + + this.pushResult({ + result: false, + message: message || "error", + actual: actual || null, + expected: null, + source: source + }); + }, + + /** + * Log assertion details using both the old QUnit.log interface and + * QUnit.on( "assertion" ) interface. + * + * @private + */ + logAssertion: function logAssertion(details) { + runLoggingCallbacks("log", details); + + var assertion = { + passed: details.result, + actual: details.actual, + expected: details.expected, + message: details.message, + stack: details.source, + todo: details.todo + }; + this.testReport.pushAssertion(assertion); + emit("assertion", assertion); + }, + + + resolvePromise: function resolvePromise(promise, phase) { + var then, + resume, + message, + test = this; + if (promise != null) { + then = promise.then; + if (objectType(then) === "function") { + resume = internalStop(test); + then.call(promise, function () { + resume(); + }, function (error) { + message = "Promise rejected " + (!phase ? "during" : phase.replace(/Each$/, "")) + " \"" + test.testName + "\": " + (error && error.message || error); + test.pushFailure(message, extractStacktrace(error, 0)); + + // Else next test will carry the responsibility + saveGlobal(); + + // Unblock + resume(); + }); + } + } + }, + + valid: function valid() { + var filter = config.filter, + regexFilter = /^(!?)\/([\w\W]*)\/(i?$)/.exec(filter), + module = config.module && config.module.toLowerCase(), + fullName = this.module.name + ": " + this.testName; + + function moduleChainNameMatch(testModule) { + var testModuleName = testModule.name ? testModule.name.toLowerCase() : null; + if (testModuleName === module) { + return true; + } else if (testModule.parentModule) { + return moduleChainNameMatch(testModule.parentModule); + } else { + return false; + } + } + + function moduleChainIdMatch(testModule) { + return inArray(testModule.moduleId, config.moduleId) || testModule.parentModule && moduleChainIdMatch(testModule.parentModule); + } + + // Internally-generated tests are always valid + if (this.callback && this.callback.validTest) { + return true; + } + + if (config.moduleId && config.moduleId.length > 0 && !moduleChainIdMatch(this.module)) { + + return false; + } + + if (config.testId && config.testId.length > 0 && !inArray(this.testId, config.testId)) { + + return false; + } + + if (module && !moduleChainNameMatch(this.module)) { + return false; + } + + if (!filter) { + return true; + } + + return regexFilter ? this.regexFilter(!!regexFilter[1], regexFilter[2], regexFilter[3], fullName) : this.stringFilter(filter, fullName); + }, + + regexFilter: function regexFilter(exclude, pattern, flags, fullName) { + var regex = new RegExp(pattern, flags); + var match = regex.test(fullName); + + return match !== exclude; + }, + + stringFilter: function stringFilter(filter, fullName) { + filter = filter.toLowerCase(); + fullName = fullName.toLowerCase(); + + var include = filter.charAt(0) !== "!"; + if (!include) { + filter = filter.slice(1); + } + + // If the filter matches, we need to honour include + if (fullName.indexOf(filter) !== -1) { + return include; + } + + // Otherwise, do the opposite + return !include; + } + }; + + function pushFailure() { + if (!config.current) { + throw new Error("pushFailure() assertion outside test context, in " + sourceFromStacktrace(2)); + } + + // Gets current test obj + var currentTest = config.current; + + return currentTest.pushFailure.apply(currentTest, arguments); + } + + function saveGlobal() { + config.pollution = []; + + if (config.noglobals) { + for (var key in global$1) { + if (hasOwn.call(global$1, key)) { + + // In Opera sometimes DOM element ids show up here, ignore them + if (/^qunit-test-output/.test(key)) { + continue; + } + config.pollution.push(key); + } + } + } + } + + function checkPollution() { + var newGlobals, + deletedGlobals, + old = config.pollution; + + saveGlobal(); + + newGlobals = diff(config.pollution, old); + if (newGlobals.length > 0) { + pushFailure("Introduced global variable(s): " + newGlobals.join(", ")); + } + + deletedGlobals = diff(old, config.pollution); + if (deletedGlobals.length > 0) { + pushFailure("Deleted global variable(s): " + deletedGlobals.join(", ")); + } + } + + // Will be exposed as QUnit.test + function test(testName, callback) { + if (focused) { + return; + } + + var newTest = new Test({ + testName: testName, + callback: callback + }); + + newTest.queue(); + } + + function todo(testName, callback) { + if (focused) { + return; + } + + var newTest = new Test({ + testName: testName, + callback: callback, + todo: true + }); + + newTest.queue(); + } + + // Will be exposed as QUnit.skip + function skip(testName) { + if (focused) { + return; + } + + var test = new Test({ + testName: testName, + skip: true + }); + + test.queue(); + } + + // Will be exposed as QUnit.only + function only(testName, callback) { + if (focused) { + return; + } + + config.queue.length = 0; + focused = true; + + var newTest = new Test({ + testName: testName, + callback: callback + }); + + newTest.queue(); + } + + // Put a hold on processing and return a function that will release it. + function internalStop(test) { + var released = false; + + test.semaphore += 1; + config.blocking = true; + + // Set a recovery timeout, if so configured. + if (config.testTimeout && defined.setTimeout) { + clearTimeout(config.timeout); + config.timeout = setTimeout(function () { + pushFailure("Test timed out", sourceFromStacktrace(2)); + internalRecover(test); + }, config.testTimeout); + } + + return function resume() { + if (released) { + return; + } + + released = true; + test.semaphore -= 1; + internalStart(test); + }; + } + + // Forcefully release all processing holds. + function internalRecover(test) { + test.semaphore = 0; + internalStart(test); + } + + // Release a processing hold, scheduling a resumption attempt if no holds remain. + function internalStart(test) { + + // If semaphore is non-numeric, throw error + if (isNaN(test.semaphore)) { + test.semaphore = 0; + + pushFailure("Invalid value on test.semaphore", sourceFromStacktrace(2)); + return; + } + + // Don't start until equal number of stop-calls + if (test.semaphore > 0) { + return; + } + + // Throw an Error if start is called more often than stop + if (test.semaphore < 0) { + test.semaphore = 0; + + pushFailure("Tried to restart test while already started (test's semaphore was 0 already)", sourceFromStacktrace(2)); + return; + } + + // Add a slight delay to allow more assertions etc. + if (defined.setTimeout) { + if (config.timeout) { + clearTimeout(config.timeout); + } + config.timeout = setTimeout(function () { + if (test.semaphore > 0) { + return; + } + + if (config.timeout) { + clearTimeout(config.timeout); + } + + begin(); + }, 13); + } else { + begin(); + } + } + + function collectTests(module) { + var tests = [].concat(module.tests); + var modules = [].concat(toConsumableArray(module.childModules)); + + // Do a breadth-first traversal of the child modules + while (modules.length) { + var nextModule = modules.shift(); + tests.push.apply(tests, nextModule.tests); + modules.push.apply(modules, toConsumableArray(nextModule.childModules)); + } + + return tests; + } + + function numberOfTests(module) { + return collectTests(module).length; + } + + function numberOfUnskippedTests(module) { + return collectTests(module).filter(function (test) { + return !test.skip; + }).length; + } + + function notifyTestsRan(module, skipped) { + module.testsRun++; + if (!skipped) { + module.unskippedTestsRun++; + } + while (module = module.parentModule) { + module.testsRun++; + if (!skipped) { + module.unskippedTestsRun++; + } + } + } + + /** + * Returns a function that proxies to the given method name on the globals + * console object. The proxy will also detect if the console doesn't exist and + * will appropriately no-op. This allows support for IE9, which doesn't have a + * console if the developer tools are not open. + */ + function consoleProxy(method) { + return function () { + if (console) { + console[method].apply(console, arguments); + } + }; + } + + var Logger = { + warn: consoleProxy("warn") + }; + + var Assert = function () { + function Assert(testContext) { + classCallCheck(this, Assert); + + this.test = testContext; + } + + // Assert helpers + + // Documents a "step", which is a string value, in a test as a passing assertion + + + createClass(Assert, [{ + key: "step", + value: function step(message) { + var result = !!message; + + this.test.steps.push(message); + + return this.pushResult({ + result: result, + message: message || "You must provide a message to assert.step" + }); + } + + // Verifies the steps in a test match a given array of string values + + }, { + key: "verifySteps", + value: function verifySteps(steps, message) { + this.deepEqual(this.test.steps, steps, message); + } + + // Specify the number of expected assertions to guarantee that failed test + // (no assertions are run at all) don't slip through. + + }, { + key: "expect", + value: function expect(asserts) { + if (arguments.length === 1) { + this.test.expected = asserts; + } else { + return this.test.expected; + } + } + + // Put a hold on processing and return a function that will release it a maximum of once. + + }, { + key: "async", + value: function async(count) { + var test$$1 = this.test; + + var popped = false, + acceptCallCount = count; + + if (typeof acceptCallCount === "undefined") { + acceptCallCount = 1; + } + + var resume = internalStop(test$$1); + + return function done() { + if (config.current !== test$$1) { + throw Error("assert.async callback called after test finished."); + } + + if (popped) { + test$$1.pushFailure("Too many calls to the `assert.async` callback", sourceFromStacktrace(2)); + return; + } + + acceptCallCount -= 1; + if (acceptCallCount > 0) { + return; + } + + popped = true; + resume(); + }; + } + + // Exports test.push() to the user API + // Alias of pushResult. + + }, { + key: "push", + value: function push(result, actual, expected, message, negative) { + Logger.warn("assert.push is deprecated and will be removed in QUnit 3.0." + " Please use assert.pushResult instead (http://api.qunitjs.com/pushResult/)."); + + var currentAssert = this instanceof Assert ? this : config.current.assert; + return currentAssert.pushResult({ + result: result, + actual: actual, + expected: expected, + message: message, + negative: negative + }); + } + }, { + key: "pushResult", + value: function pushResult(resultInfo) { + + // Destructure of resultInfo = { result, actual, expected, message, negative } + var assert = this; + var currentTest = assert instanceof Assert && assert.test || config.current; + + // Backwards compatibility fix. + // Allows the direct use of global exported assertions and QUnit.assert.* + // Although, it's use is not recommended as it can leak assertions + // to other tests from async tests, because we only get a reference to the current test, + // not exactly the test where assertion were intended to be called. + if (!currentTest) { + throw new Error("assertion outside test context, in " + sourceFromStacktrace(2)); + } + + if (!(assert instanceof Assert)) { + assert = currentTest.assert; + } + + return assert.test.pushResult(resultInfo); + } + }, { + key: "ok", + value: function ok(result, message) { + if (!message) { + message = result ? "okay" : "failed, expected argument to be truthy, was: " + dump.parse(result); + } + + this.pushResult({ + result: !!result, + actual: result, + expected: true, + message: message + }); + } + }, { + key: "notOk", + value: function notOk(result, message) { + if (!message) { + message = !result ? "okay" : "failed, expected argument to be falsy, was: " + dump.parse(result); + } + + this.pushResult({ + result: !result, + actual: result, + expected: false, + message: message + }); + } + }, { + key: "equal", + value: function equal(actual, expected, message) { + + // eslint-disable-next-line eqeqeq + var result = expected == actual; + + this.pushResult({ + result: result, + actual: actual, + expected: expected, + message: message + }); + } + }, { + key: "notEqual", + value: function notEqual(actual, expected, message) { + + // eslint-disable-next-line eqeqeq + var result = expected != actual; + + this.pushResult({ + result: result, + actual: actual, + expected: expected, + message: message, + negative: true + }); + } + }, { + key: "propEqual", + value: function propEqual(actual, expected, message) { + actual = objectValues(actual); + expected = objectValues(expected); + + this.pushResult({ + result: equiv(actual, expected), + actual: actual, + expected: expected, + message: message + }); + } + }, { + key: "notPropEqual", + value: function notPropEqual(actual, expected, message) { + actual = objectValues(actual); + expected = objectValues(expected); + + this.pushResult({ + result: !equiv(actual, expected), + actual: actual, + expected: expected, + message: message, + negative: true + }); + } + }, { + key: "deepEqual", + value: function deepEqual(actual, expected, message) { + this.pushResult({ + result: equiv(actual, expected), + actual: actual, + expected: expected, + message: message + }); + } + }, { + key: "notDeepEqual", + value: function notDeepEqual(actual, expected, message) { + this.pushResult({ + result: !equiv(actual, expected), + actual: actual, + expected: expected, + message: message, + negative: true + }); + } + }, { + key: "strictEqual", + value: function strictEqual(actual, expected, message) { + this.pushResult({ + result: expected === actual, + actual: actual, + expected: expected, + message: message + }); + } + }, { + key: "notStrictEqual", + value: function notStrictEqual(actual, expected, message) { + this.pushResult({ + result: expected !== actual, + actual: actual, + expected: expected, + message: message, + negative: true + }); + } + }, { + key: "throws", + value: function throws(block, expected, message) { + var actual = void 0, + result = false; + + var currentTest = this instanceof Assert && this.test || config.current; + + // 'expected' is optional unless doing string comparison + if (objectType(expected) === "string") { + if (message == null) { + message = expected; + expected = null; + } else { + throw new Error("throws/raises does not accept a string value for the expected argument.\n" + "Use a non-string object value (e.g. regExp) instead if it's necessary."); + } + } + + currentTest.ignoreGlobalErrors = true; + try { + block.call(currentTest.testEnvironment); + } catch (e) { + actual = e; + } + currentTest.ignoreGlobalErrors = false; + + if (actual) { + var expectedType = objectType(expected); + + // We don't want to validate thrown error + if (!expected) { + result = true; + expected = null; + + // Expected is a regexp + } else if (expectedType === "regexp") { + result = expected.test(errorString(actual)); + + // Expected is a constructor, maybe an Error constructor + } else if (expectedType === "function" && actual instanceof expected) { + result = true; + + // Expected is an Error object + } else if (expectedType === "object") { + result = actual instanceof expected.constructor && actual.name === expected.name && actual.message === expected.message; + + // Expected is a validation function which returns true if validation passed + } else if (expectedType === "function" && expected.call({}, actual) === true) { + expected = null; + result = true; + } + } + + currentTest.assert.pushResult({ + result: result, + actual: actual, + expected: expected, + message: message + }); + } + }]); + return Assert; + }(); + + // Provide an alternative to assert.throws(), for environments that consider throws a reserved word + // Known to us are: Closure Compiler, Narwhal + // eslint-disable-next-line dot-notation + + + Assert.prototype.raises = Assert.prototype["throws"]; + + /** + * Converts an error into a simple string for comparisons. + * + * @param {Error} error + * @return {String} + */ + function errorString(error) { + var resultErrorString = error.toString(); + + if (resultErrorString.substring(0, 7) === "[object") { + var name = error.name ? error.name.toString() : "Error"; + var message = error.message ? error.message.toString() : ""; + + if (name && message) { + return name + ": " + message; + } else if (name) { + return name; + } else if (message) { + return message; + } else { + return "Error"; + } + } else { + return resultErrorString; + } + } + + /* global module, exports, define */ + function exportQUnit(QUnit) { + + if (defined.document) { + + // QUnit may be defined when it is preconfigured but then only QUnit and QUnit.config may be defined. + if (window.QUnit && window.QUnit.version) { + throw new Error("QUnit has already been defined."); + } + + window.QUnit = QUnit; + } + + // For nodejs + if (typeof module !== "undefined" && module && module.exports) { + module.exports = QUnit; + + // For consistency with CommonJS environments' exports + module.exports.QUnit = QUnit; + } + + // For CommonJS with exports, but without module.exports, like Rhino + if (typeof exports !== "undefined" && exports) { + exports.QUnit = QUnit; + } + + if (typeof define === "function" && define.amd) { + define(function () { + return QUnit; + }); + QUnit.config.autostart = false; + } + + // For Web/Service Workers + if (self$1 && self$1.WorkerGlobalScope && self$1 instanceof self$1.WorkerGlobalScope) { + self$1.QUnit = QUnit; + } + } + + var SuiteReport = function () { + function SuiteReport(name, parentSuite) { + classCallCheck(this, SuiteReport); + + this.name = name; + this.fullName = parentSuite ? parentSuite.fullName.concat(name) : []; + + this.tests = []; + this.childSuites = []; + + if (parentSuite) { + parentSuite.pushChildSuite(this); + } + } + + createClass(SuiteReport, [{ + key: "start", + value: function start(recordTime) { + if (recordTime) { + this._startTime = Date.now(); + } + + return { + name: this.name, + fullName: this.fullName.slice(), + tests: this.tests.map(function (test) { + return test.start(); + }), + childSuites: this.childSuites.map(function (suite) { + return suite.start(); + }), + testCounts: { + total: this.getTestCounts().total + } + }; + } + }, { + key: "end", + value: function end(recordTime) { + if (recordTime) { + this._endTime = Date.now(); + } + + return { + name: this.name, + fullName: this.fullName.slice(), + tests: this.tests.map(function (test) { + return test.end(); + }), + childSuites: this.childSuites.map(function (suite) { + return suite.end(); + }), + testCounts: this.getTestCounts(), + runtime: this.getRuntime(), + status: this.getStatus() + }; + } + }, { + key: "pushChildSuite", + value: function pushChildSuite(suite) { + this.childSuites.push(suite); + } + }, { + key: "pushTest", + value: function pushTest(test) { + this.tests.push(test); + } + }, { + key: "getRuntime", + value: function getRuntime() { + return this._endTime - this._startTime; + } + }, { + key: "getTestCounts", + value: function getTestCounts() { + var counts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : { passed: 0, failed: 0, skipped: 0, todo: 0, total: 0 }; + + counts = this.tests.reduce(function (counts, test) { + if (test.valid) { + counts[test.getStatus()]++; + counts.total++; + } + + return counts; + }, counts); + + return this.childSuites.reduce(function (counts, suite) { + return suite.getTestCounts(counts); + }, counts); + } + }, { + key: "getStatus", + value: function getStatus() { + var _getTestCounts = this.getTestCounts(), + total = _getTestCounts.total, + failed = _getTestCounts.failed, + skipped = _getTestCounts.skipped, + todo = _getTestCounts.todo; + + if (failed) { + return "failed"; + } else { + if (skipped === total) { + return "skipped"; + } else if (todo === total) { + return "todo"; + } else { + return "passed"; + } + } + } + }]); + return SuiteReport; + }(); + + // Handle an unhandled exception. By convention, returns true if further + // error handling should be suppressed and false otherwise. + // In this case, we will only suppress further error handling if the + // "ignoreGlobalErrors" configuration option is enabled. + function onError(error) { + for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { + args[_key - 1] = arguments[_key]; + } + + if (config.current) { + if (config.current.ignoreGlobalErrors) { + return true; + } + pushFailure.apply(undefined, [error.message, error.fileName + ":" + error.lineNumber].concat(args)); + } else { + test("global failure", extend(function () { + pushFailure.apply(undefined, [error.message, error.fileName + ":" + error.lineNumber].concat(args)); + }, { validTest: true })); + } + + return false; + } + + var QUnit = {}; + var globalSuite = new SuiteReport(); + + // The initial "currentModule" represents the global (or top-level) module that + // is not explicitly defined by the user, therefore we add the "globalSuite" to + // it since each module has a suiteReport associated with it. + config.currentModule.suiteReport = globalSuite; + + var moduleStack = []; + var globalStartCalled = false; + var runStarted = false; + + // Figure out if we're running the tests from a server or not + QUnit.isLocal = !(defined.document && window.location.protocol !== "file:"); + + // Expose the current QUnit version + QUnit.version = "2.3.3"; + + function createModule(name, testEnvironment) { + var parentModule = moduleStack.length ? moduleStack.slice(-1)[0] : null; + var moduleName = parentModule !== null ? [parentModule.name, name].join(" > ") : name; + var parentSuite = parentModule ? parentModule.suiteReport : globalSuite; + + var module = { + name: moduleName, + parentModule: parentModule, + tests: [], + moduleId: generateHash(moduleName), + testsRun: 0, + unskippedTestsRun: 0, + childModules: [], + suiteReport: new SuiteReport(name, parentSuite) + }; + + var env = {}; + if (parentModule) { + parentModule.childModules.push(module); + extend(env, parentModule.testEnvironment); + } + extend(env, testEnvironment); + module.testEnvironment = env; + + config.modules.push(module); + return module; + } + + extend(QUnit, { + on: on, + + // Call on start of module test to prepend name to all tests + module: function module(name, testEnvironment, executeNow) { + if (arguments.length === 2) { + if (objectType(testEnvironment) === "function") { + executeNow = testEnvironment; + testEnvironment = undefined; + } + } + + var module = createModule(name, testEnvironment); + + // Move any hooks to a 'hooks' object + if (module.testEnvironment) { + module.hooks = { + before: module.testEnvironment.before, + beforeEach: module.testEnvironment.beforeEach, + afterEach: module.testEnvironment.afterEach, + after: module.testEnvironment.after + }; + + delete module.testEnvironment.before; + delete module.testEnvironment.beforeEach; + delete module.testEnvironment.afterEach; + delete module.testEnvironment.after; + } + + var moduleFns = { + before: setHook(module, "before"), + beforeEach: setHook(module, "beforeEach"), + afterEach: setHook(module, "afterEach"), + after: setHook(module, "after") + }; + + var currentModule = config.currentModule; + if (objectType(executeNow) === "function") { + moduleStack.push(module); + config.currentModule = module; + executeNow.call(module.testEnvironment, moduleFns); + moduleStack.pop(); + module = module.parentModule || currentModule; + } + + config.currentModule = module; + }, + + test: test, + + todo: todo, + + skip: skip, + + only: only, + + start: function start(count) { + var globalStartAlreadyCalled = globalStartCalled; + + if (!config.current) { + globalStartCalled = true; + + if (runStarted) { + throw new Error("Called start() while test already started running"); + } else if (globalStartAlreadyCalled || count > 1) { + throw new Error("Called start() outside of a test context too many times"); + } else if (config.autostart) { + throw new Error("Called start() outside of a test context when " + "QUnit.config.autostart was true"); + } else if (!config.pageLoaded) { + + // The page isn't completely loaded yet, so we set autostart and then + // load if we're in Node or wait for the browser's load event. + config.autostart = true; + + // Starts from Node even if .load was not previously called. We still return + // early otherwise we'll wind up "beginning" twice. + if (!defined.document) { + QUnit.load(); + } + + return; + } + } else { + throw new Error("QUnit.start cannot be called inside a test context."); + } + + scheduleBegin(); + }, + + config: config, + + is: is, + + objectType: objectType, + + extend: extend, + + load: function load() { + config.pageLoaded = true; + + // Initialize the configuration options + extend(config, { + stats: { all: 0, bad: 0 }, + started: 0, + updateRate: 1000, + autostart: true, + filter: "" + }, true); + + if (!runStarted) { + config.blocking = false; + + if (config.autostart) { + scheduleBegin(); + } + } + }, + + stack: function stack(offset) { + offset = (offset || 0) + 2; + return sourceFromStacktrace(offset); + }, + + onError: onError + }); + + QUnit.pushFailure = pushFailure; + QUnit.assert = Assert.prototype; + QUnit.equiv = equiv; + QUnit.dump = dump; + + registerLoggingCallbacks(QUnit); + + function scheduleBegin() { + + runStarted = true; + + // Add a slight delay to allow definition of more modules and tests. + if (defined.setTimeout) { + setTimeout(function () { + begin(); + }, 13); + } else { + begin(); + } + } + + function begin() { + var i, + l, + modulesLog = []; + + // If the test run hasn't officially begun yet + if (!config.started) { + + // Record the time of the test run's beginning + config.started = now(); + + // Delete the loose unnamed module if unused. + if (config.modules[0].name === "" && config.modules[0].tests.length === 0) { + config.modules.shift(); + } + + // Avoid unnecessary information by not logging modules' test environments + for (i = 0, l = config.modules.length; i < l; i++) { + modulesLog.push({ + name: config.modules[i].name, + tests: config.modules[i].tests + }); + } + + // The test run is officially beginning now + emit("runStart", globalSuite.start(true)); + runLoggingCallbacks("begin", { + totalTests: Test.count, + modules: modulesLog + }); + } + + config.blocking = false; + ProcessingQueue.advance(); + } + + function setHook(module, hookName) { + if (!module.hooks) { + module.hooks = {}; + } + + return function (callback) { + module.hooks[hookName] = callback; + }; + } + + exportQUnit(QUnit); + + (function () { + + if (typeof window === "undefined" || typeof document === "undefined") { + return; + } + + var config = QUnit.config, + hasOwn = Object.prototype.hasOwnProperty; + + // Stores fixture HTML for resetting later + function storeFixture() { + + // Avoid overwriting user-defined values + if (hasOwn.call(config, "fixture")) { + return; + } + + var fixture = document.getElementById("qunit-fixture"); + if (fixture) { + config.fixture = fixture.innerHTML; + } + } + + QUnit.begin(storeFixture); + + // Resets the fixture DOM element if available. + function resetFixture() { + if (config.fixture == null) { + return; + } + + var fixture = document.getElementById("qunit-fixture"); + if (fixture) { + fixture.innerHTML = config.fixture; + } + } + + QUnit.testStart(resetFixture); + })(); + + (function () { + + // Only interact with URLs via window.location + var location = typeof window !== "undefined" && window.location; + if (!location) { + return; + } + + var urlParams = getUrlParams(); + + QUnit.urlParams = urlParams; + + // Match module/test by inclusion in an array + QUnit.config.moduleId = [].concat(urlParams.moduleId || []); + QUnit.config.testId = [].concat(urlParams.testId || []); + + // Exact case-insensitive match of the module name + QUnit.config.module = urlParams.module; + + // Regular expression or case-insenstive substring match against "moduleName: testName" + QUnit.config.filter = urlParams.filter; + + // Test order randomization + if (urlParams.seed === true) { + + // Generate a random seed if the option is specified without a value + QUnit.config.seed = Math.random().toString(36).slice(2); + } else if (urlParams.seed) { + QUnit.config.seed = urlParams.seed; + } + + // Add URL-parameter-mapped config values with UI form rendering data + QUnit.config.urlConfig.push({ + id: "hidepassed", + label: "Hide passed tests", + tooltip: "Only show tests and assertions that fail. Stored as query-strings." + }, { + id: "noglobals", + label: "Check for Globals", + tooltip: "Enabling this will test if any test introduces new properties on the " + "global object (`window` in Browsers). Stored as query-strings." + }, { + id: "notrycatch", + label: "No try-catch", + tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging " + "exceptions in IE reasonable. Stored as query-strings." + }); + + QUnit.begin(function () { + var i, + option, + urlConfig = QUnit.config.urlConfig; + + for (i = 0; i < urlConfig.length; i++) { + + // Options can be either strings or objects with nonempty "id" properties + option = QUnit.config.urlConfig[i]; + if (typeof option !== "string") { + option = option.id; + } + + if (QUnit.config[option] === undefined) { + QUnit.config[option] = urlParams[option]; + } + } + }); + + function getUrlParams() { + var i, param, name, value; + var urlParams = Object.create(null); + var params = location.search.slice(1).split("&"); + var length = params.length; + + for (i = 0; i < length; i++) { + if (params[i]) { + param = params[i].split("="); + name = decodeQueryParam(param[0]); + + // Allow just a key to turn on a flag, e.g., test.html?noglobals + value = param.length === 1 || decodeQueryParam(param.slice(1).join("=")); + if (name in urlParams) { + urlParams[name] = [].concat(urlParams[name], value); + } else { + urlParams[name] = value; + } + } + } + + return urlParams; + } + + function decodeQueryParam(param) { + return decodeURIComponent(param.replace(/\+/g, "%20")); + } + })(); + + var stats = { + passedTests: 0, + failedTests: 0, + skippedTests: 0, + todoTests: 0 + }; + + // Escape text for attribute or text content. + function escapeText(s) { + if (!s) { + return ""; + } + s = s + ""; + + // Both single quotes and double quotes (for attributes) + return s.replace(/['"<>&]/g, function (s) { + switch (s) { + case "'": + return "'"; + case "\"": + return """; + case "<": + return "<"; + case ">": + return ">"; + case "&": + return "&"; + } + }); + } + + (function () { + + // Don't load the HTML Reporter on non-browser environments + if (typeof window === "undefined" || !window.document) { + return; + } + + var config = QUnit.config, + document$$1 = window.document, + collapseNext = false, + hasOwn = Object.prototype.hasOwnProperty, + unfilteredUrl = setUrl({ filter: undefined, module: undefined, + moduleId: undefined, testId: undefined }), + modulesList = []; + + function addEvent(elem, type, fn) { + elem.addEventListener(type, fn, false); + } + + function removeEvent(elem, type, fn) { + elem.removeEventListener(type, fn, false); + } + + function addEvents(elems, type, fn) { + var i = elems.length; + while (i--) { + addEvent(elems[i], type, fn); + } + } + + function hasClass(elem, name) { + return (" " + elem.className + " ").indexOf(" " + name + " ") >= 0; + } + + function addClass(elem, name) { + if (!hasClass(elem, name)) { + elem.className += (elem.className ? " " : "") + name; + } + } + + function toggleClass(elem, name, force) { + if (force || typeof force === "undefined" && !hasClass(elem, name)) { + addClass(elem, name); + } else { + removeClass(elem, name); + } + } + + function removeClass(elem, name) { + var set = " " + elem.className + " "; + + // Class name may appear multiple times + while (set.indexOf(" " + name + " ") >= 0) { + set = set.replace(" " + name + " ", " "); + } + + // Trim for prettiness + elem.className = typeof set.trim === "function" ? set.trim() : set.replace(/^\s+|\s+$/g, ""); + } + + function id(name) { + return document$$1.getElementById && document$$1.getElementById(name); + } + + function abortTests() { + var abortButton = id("qunit-abort-tests-button"); + if (abortButton) { + abortButton.disabled = true; + abortButton.innerHTML = "Aborting..."; + } + QUnit.config.queue.length = 0; + return false; + } + + function interceptNavigation(ev) { + applyUrlParams(); + + if (ev && ev.preventDefault) { + ev.preventDefault(); + } + + return false; + } + + function getUrlConfigHtml() { + var i, + j, + val, + escaped, + escapedTooltip, + selection = false, + urlConfig = config.urlConfig, + urlConfigHtml = ""; + + for (i = 0; i < urlConfig.length; i++) { + + // Options can be either strings or objects with nonempty "id" properties + val = config.urlConfig[i]; + if (typeof val === "string") { + val = { + id: val, + label: val + }; + } + + escaped = escapeText(val.id); + escapedTooltip = escapeText(val.tooltip); + + if (!val.value || typeof val.value === "string") { + urlConfigHtml += ""; + } else { + urlConfigHtml += ""; + } + } + + return urlConfigHtml; + } + + // Handle "click" events on toolbar checkboxes and "change" for select menus. + // Updates the URL with the new state of `config.urlConfig` values. + function toolbarChanged() { + var updatedUrl, + value, + tests, + field = this, + params = {}; + + // Detect if field is a select menu or a checkbox + if ("selectedIndex" in field) { + value = field.options[field.selectedIndex].value || undefined; + } else { + value = field.checked ? field.defaultValue || true : undefined; + } + + params[field.name] = value; + updatedUrl = setUrl(params); + + // Check if we can apply the change without a page refresh + if ("hidepassed" === field.name && "replaceState" in window.history) { + QUnit.urlParams[field.name] = value; + config[field.name] = value || false; + tests = id("qunit-tests"); + if (tests) { + toggleClass(tests, "hidepass", value || false); + } + window.history.replaceState(null, "", updatedUrl); + } else { + window.location = updatedUrl; + } + } + + function setUrl(params) { + var key, + arrValue, + i, + querystring = "?", + location = window.location; + + params = QUnit.extend(QUnit.extend({}, QUnit.urlParams), params); + + for (key in params) { + + // Skip inherited or undefined properties + if (hasOwn.call(params, key) && params[key] !== undefined) { + + // Output a parameter for each value of this key (but usually just one) + arrValue = [].concat(params[key]); + for (i = 0; i < arrValue.length; i++) { + querystring += encodeURIComponent(key); + if (arrValue[i] !== true) { + querystring += "=" + encodeURIComponent(arrValue[i]); + } + querystring += "&"; + } + } + } + return location.protocol + "//" + location.host + location.pathname + querystring.slice(0, -1); + } + + function applyUrlParams() { + var i, + selectedModules = [], + modulesList = id("qunit-modulefilter-dropdown-list").getElementsByTagName("input"), + filter = id("qunit-filter-input").value; + + for (i = 0; i < modulesList.length; i++) { + if (modulesList[i].checked) { + selectedModules.push(modulesList[i].value); + } + } + + window.location = setUrl({ + filter: filter === "" ? undefined : filter, + moduleId: selectedModules.length === 0 ? undefined : selectedModules, + + // Remove module and testId filter + module: undefined, + testId: undefined + }); + } + + function toolbarUrlConfigContainer() { + var urlConfigContainer = document$$1.createElement("span"); + + urlConfigContainer.innerHTML = getUrlConfigHtml(); + addClass(urlConfigContainer, "qunit-url-config"); + + addEvents(urlConfigContainer.getElementsByTagName("input"), "change", toolbarChanged); + addEvents(urlConfigContainer.getElementsByTagName("select"), "change", toolbarChanged); + + return urlConfigContainer; + } + + function abortTestsButton() { + var button = document$$1.createElement("button"); + button.id = "qunit-abort-tests-button"; + button.innerHTML = "Abort"; + addEvent(button, "click", abortTests); + return button; + } + + function toolbarLooseFilter() { + var filter = document$$1.createElement("form"), + label = document$$1.createElement("label"), + input = document$$1.createElement("input"), + button = document$$1.createElement("button"); + + addClass(filter, "qunit-filter"); + + label.innerHTML = "Filter: "; + + input.type = "text"; + input.value = config.filter || ""; + input.name = "filter"; + input.id = "qunit-filter-input"; + + button.innerHTML = "Go"; + + label.appendChild(input); + + filter.appendChild(label); + filter.appendChild(document$$1.createTextNode(" ")); + filter.appendChild(button); + addEvent(filter, "submit", interceptNavigation); + + return filter; + } + + function moduleListHtml() { + var i, + checked, + html = ""; + + for (i = 0; i < config.modules.length; i++) { + if (config.modules[i].name !== "") { + checked = config.moduleId.indexOf(config.modules[i].moduleId) > -1; + html += "
    1. "; + } + } + + return html; + } + + function toolbarModuleFilter() { + var allCheckbox, + commit, + reset, + moduleFilter = document$$1.createElement("form"), + label = document$$1.createElement("label"), + moduleSearch = document$$1.createElement("input"), + dropDown = document$$1.createElement("div"), + actions = document$$1.createElement("span"), + dropDownList = document$$1.createElement("ul"), + dirty = false; + + moduleSearch.id = "qunit-modulefilter-search"; + addEvent(moduleSearch, "input", searchInput); + addEvent(moduleSearch, "input", searchFocus); + addEvent(moduleSearch, "focus", searchFocus); + addEvent(moduleSearch, "click", searchFocus); + + label.id = "qunit-modulefilter-search-container"; + label.innerHTML = "Module: "; + label.appendChild(moduleSearch); + + actions.id = "qunit-modulefilter-actions"; + actions.innerHTML = "" + "" + ""; + allCheckbox = actions.lastChild.firstChild; + commit = actions.firstChild; + reset = commit.nextSibling; + addEvent(commit, "click", applyUrlParams); + + dropDownList.id = "qunit-modulefilter-dropdown-list"; + dropDownList.innerHTML = moduleListHtml(); + + dropDown.id = "qunit-modulefilter-dropdown"; + dropDown.style.display = "none"; + dropDown.appendChild(actions); + dropDown.appendChild(dropDownList); + addEvent(dropDown, "change", selectionChange); + selectionChange(); + + moduleFilter.id = "qunit-modulefilter"; + moduleFilter.appendChild(label); + moduleFilter.appendChild(dropDown); + addEvent(moduleFilter, "submit", interceptNavigation); + addEvent(moduleFilter, "reset", function () { + + // Let the reset happen, then update styles + window.setTimeout(selectionChange); + }); + + // Enables show/hide for the dropdown + function searchFocus() { + if (dropDown.style.display !== "none") { + return; + } + + dropDown.style.display = "block"; + addEvent(document$$1, "click", hideHandler); + addEvent(document$$1, "keydown", hideHandler); + + // Hide on Escape keydown or outside-container click + function hideHandler(e) { + var inContainer = moduleFilter.contains(e.target); + + if (e.keyCode === 27 || !inContainer) { + if (e.keyCode === 27 && inContainer) { + moduleSearch.focus(); + } + dropDown.style.display = "none"; + removeEvent(document$$1, "click", hideHandler); + removeEvent(document$$1, "keydown", hideHandler); + moduleSearch.value = ""; + searchInput(); + } + } + } + + // Processes module search box input + function searchInput() { + var i, + item, + searchText = moduleSearch.value.toLowerCase(), + listItems = dropDownList.children; + + for (i = 0; i < listItems.length; i++) { + item = listItems[i]; + if (!searchText || item.textContent.toLowerCase().indexOf(searchText) > -1) { + item.style.display = ""; + } else { + item.style.display = "none"; + } + } + } + + // Processes selection changes + function selectionChange(evt) { + var i, + item, + checkbox = evt && evt.target || allCheckbox, + modulesList = dropDownList.getElementsByTagName("input"), + selectedNames = []; + + toggleClass(checkbox.parentNode, "checked", checkbox.checked); + + dirty = false; + if (checkbox.checked && checkbox !== allCheckbox) { + allCheckbox.checked = false; + removeClass(allCheckbox.parentNode, "checked"); + } + for (i = 0; i < modulesList.length; i++) { + item = modulesList[i]; + if (!evt) { + toggleClass(item.parentNode, "checked", item.checked); + } else if (checkbox === allCheckbox && checkbox.checked) { + item.checked = false; + removeClass(item.parentNode, "checked"); + } + dirty = dirty || item.checked !== item.defaultChecked; + if (item.checked) { + selectedNames.push(item.parentNode.textContent); + } + } + + commit.style.display = reset.style.display = dirty ? "" : "none"; + moduleSearch.placeholder = selectedNames.join(", ") || allCheckbox.parentNode.textContent; + moduleSearch.title = "Type to filter list. Current selection:\n" + (selectedNames.join("\n") || allCheckbox.parentNode.textContent); + } + + return moduleFilter; + } + + function appendToolbar() { + var toolbar = id("qunit-testrunner-toolbar"); + + if (toolbar) { + toolbar.appendChild(toolbarUrlConfigContainer()); + toolbar.appendChild(toolbarModuleFilter()); + toolbar.appendChild(toolbarLooseFilter()); + toolbar.appendChild(document$$1.createElement("div")).className = "clearfix"; + } + } + + function appendHeader() { + var header = id("qunit-header"); + + if (header) { + header.innerHTML = "" + header.innerHTML + " "; + } + } + + function appendBanner() { + var banner = id("qunit-banner"); + + if (banner) { + banner.className = ""; + } + } + + function appendTestResults() { + var tests = id("qunit-tests"), + result = id("qunit-testresult"), + controls; + + if (result) { + result.parentNode.removeChild(result); + } + + if (tests) { + tests.innerHTML = ""; + result = document$$1.createElement("p"); + result.id = "qunit-testresult"; + result.className = "result"; + tests.parentNode.insertBefore(result, tests); + result.innerHTML = "
      Running...
       
      " + "
      " + "
      "; + controls = id("qunit-testresult-controls"); + } + + if (controls) { + controls.appendChild(abortTestsButton()); + } + } + + function appendFilteredTest() { + var testId = QUnit.config.testId; + if (!testId || testId.length <= 0) { + return ""; + } + return "
      Rerunning selected tests: " + escapeText(testId.join(", ")) + " Run all tests
      "; + } + + function appendUserAgent() { + var userAgent = id("qunit-userAgent"); + + if (userAgent) { + userAgent.innerHTML = ""; + userAgent.appendChild(document$$1.createTextNode("QUnit " + QUnit.version + "; " + navigator.userAgent)); + } + } + + function appendInterface() { + var qunit = id("qunit"); + + if (qunit) { + qunit.innerHTML = "

      " + escapeText(document$$1.title) + "

      " + "

      " + "
      " + appendFilteredTest() + "

      " + "
        "; + } + + appendHeader(); + appendBanner(); + appendTestResults(); + appendUserAgent(); + appendToolbar(); + } + + function appendTestsList(modules) { + var i, l, x, z, test, moduleObj; + + for (i = 0, l = modules.length; i < l; i++) { + moduleObj = modules[i]; + + for (x = 0, z = moduleObj.tests.length; x < z; x++) { + test = moduleObj.tests[x]; + + appendTest(test.name, test.testId, moduleObj.name); + } + } + } + + function appendTest(name, testId, moduleName) { + var title, + rerunTrigger, + testBlock, + assertList, + tests = id("qunit-tests"); + + if (!tests) { + return; + } + + title = document$$1.createElement("strong"); + title.innerHTML = getNameHtml(name, moduleName); + + rerunTrigger = document$$1.createElement("a"); + rerunTrigger.innerHTML = "Rerun"; + rerunTrigger.href = setUrl({ testId: testId }); + + testBlock = document$$1.createElement("li"); + testBlock.appendChild(title); + testBlock.appendChild(rerunTrigger); + testBlock.id = "qunit-test-output-" + testId; + + assertList = document$$1.createElement("ol"); + assertList.className = "qunit-assert-list"; + + testBlock.appendChild(assertList); + + tests.appendChild(testBlock); + } + + // HTML Reporter initialization and load + QUnit.begin(function (details) { + var i, moduleObj, tests; + + // Sort modules by name for the picker + for (i = 0; i < details.modules.length; i++) { + moduleObj = details.modules[i]; + if (moduleObj.name) { + modulesList.push(moduleObj.name); + } + } + modulesList.sort(function (a, b) { + return a.localeCompare(b); + }); + + // Initialize QUnit elements + appendInterface(); + appendTestsList(details.modules); + tests = id("qunit-tests"); + if (tests && config.hidepassed) { + addClass(tests, "hidepass"); + } + }); + + QUnit.done(function (details) { + var banner = id("qunit-banner"), + tests = id("qunit-tests"), + abortButton = id("qunit-abort-tests-button"), + totalTests = stats.passedTests + stats.skippedTests + stats.todoTests + stats.failedTests, + html = [totalTests, " tests completed in ", details.runtime, " milliseconds, with ", stats.failedTests, " failed, ", stats.skippedTests, " skipped, and ", stats.todoTests, " todo.
        ", "", details.passed, " assertions of ", details.total, " passed, ", details.failed, " failed."].join(""), + test, + assertLi, + assertList; + + // Update remaing tests to aborted + if (abortButton && abortButton.disabled) { + html = "Tests aborted after " + details.runtime + " milliseconds."; + + for (var i = 0; i < tests.children.length; i++) { + test = tests.children[i]; + if (test.className === "" || test.className === "running") { + test.className = "aborted"; + assertList = test.getElementsByTagName("ol")[0]; + assertLi = document$$1.createElement("li"); + assertLi.className = "fail"; + assertLi.innerHTML = "Test aborted."; + assertList.appendChild(assertLi); + } + } + } + + if (banner && (!abortButton || abortButton.disabled === false)) { + banner.className = stats.failedTests ? "qunit-fail" : "qunit-pass"; + } + + if (abortButton) { + abortButton.parentNode.removeChild(abortButton); + } + + if (tests) { + id("qunit-testresult-display").innerHTML = html; + } + + if (config.altertitle && document$$1.title) { + + // Show ✖ for good, ✔ for bad suite result in title + // use escape sequences in case file gets loaded with non-utf-8-charset + document$$1.title = [stats.failedTests ? "\u2716" : "\u2714", document$$1.title.replace(/^[\u2714\u2716] /i, "")].join(" "); + } + + // Scroll back to top to show results + if (config.scrolltop && window.scrollTo) { + window.scrollTo(0, 0); + } + }); + + function getNameHtml(name, module) { + var nameHtml = ""; + + if (module) { + nameHtml = "" + escapeText(module) + ": "; + } + + nameHtml += "" + escapeText(name) + ""; + + return nameHtml; + } + + QUnit.testStart(function (details) { + var running, testBlock, bad; + + testBlock = id("qunit-test-output-" + details.testId); + if (testBlock) { + testBlock.className = "running"; + } else { + + // Report later registered tests + appendTest(details.name, details.testId, details.module); + } + + running = id("qunit-testresult-display"); + if (running) { + bad = QUnit.config.reorder && details.previousFailure; + + running.innerHTML = (bad ? "Rerunning previously failed test:
        " : "Running:
        ") + getNameHtml(details.name, details.module); + } + }); + + function stripHtml(string) { + + // Strip tags, html entity and whitespaces + return string.replace(/<\/?[^>]+(>|$)/g, "").replace(/\"/g, "").replace(/\s+/g, ""); + } + + QUnit.log(function (details) { + var assertList, + assertLi, + message, + expected, + actual, + diff, + showDiff = false, + testItem = id("qunit-test-output-" + details.testId); + + if (!testItem) { + return; + } + + message = escapeText(details.message) || (details.result ? "okay" : "failed"); + message = "" + message + ""; + message += "@ " + details.runtime + " ms"; + + // The pushFailure doesn't provide details.expected + // when it calls, it's implicit to also not show expected and diff stuff + // Also, we need to check details.expected existence, as it can exist and be undefined + if (!details.result && hasOwn.call(details, "expected")) { + if (details.negative) { + expected = "NOT " + QUnit.dump.parse(details.expected); + } else { + expected = QUnit.dump.parse(details.expected); + } + + actual = QUnit.dump.parse(details.actual); + message += ""; + + if (actual !== expected) { + + message += ""; + + if (typeof details.actual === "number" && typeof details.expected === "number") { + if (!isNaN(details.actual) && !isNaN(details.expected)) { + showDiff = true; + diff = details.actual - details.expected; + diff = (diff > 0 ? "+" : "") + diff; + } + } else if (typeof details.actual !== "boolean" && typeof details.expected !== "boolean") { + diff = QUnit.diff(expected, actual); + + // don't show diff if there is zero overlap + showDiff = stripHtml(diff).length !== stripHtml(expected).length + stripHtml(actual).length; + } + + if (showDiff) { + message += ""; + } + } else if (expected.indexOf("[object Array]") !== -1 || expected.indexOf("[object Object]") !== -1) { + message += ""; + } else { + message += ""; + } + + if (details.source) { + message += ""; + } + + message += "
        Expected:
        " + escapeText(expected) + "
        Result:
        " + escapeText(actual) + "
        Diff:
        " + diff + "
        Message: " + "Diff suppressed as the depth of object is more than current max depth (" + QUnit.config.maxDepth + ").

        Hint: Use QUnit.dump.maxDepth to " + " run with a higher max depth or " + "Rerun without max depth.

        Message: " + "Diff suppressed as the expected and actual results have an equivalent" + " serialization
        Source:
        " + escapeText(details.source) + "
        "; + + // This occurs when pushFailure is set and we have an extracted stack trace + } else if (!details.result && details.source) { + message += "" + "" + "
        Source:
        " + escapeText(details.source) + "
        "; + } + + assertList = testItem.getElementsByTagName("ol")[0]; + + assertLi = document$$1.createElement("li"); + assertLi.className = details.result ? "pass" : "fail"; + assertLi.innerHTML = message; + assertList.appendChild(assertLi); + }); + + QUnit.testDone(function (details) { + var testTitle, + time, + testItem, + assertList, + good, + bad, + testCounts, + skipped, + sourceName, + tests = id("qunit-tests"); + + if (!tests) { + return; + } + + testItem = id("qunit-test-output-" + details.testId); + + assertList = testItem.getElementsByTagName("ol")[0]; + + good = details.passed; + bad = details.failed; + + // This test passed if it has no unexpected failed assertions + var testPassed = details.failed > 0 ? details.todo : !details.todo; + + if (testPassed) { + + // Collapse the passing tests + addClass(assertList, "qunit-collapsed"); + } else if (config.collapse) { + if (!collapseNext) { + + // Skip collapsing the first failing test + collapseNext = true; + } else { + + // Collapse remaining tests + addClass(assertList, "qunit-collapsed"); + } + } + + // The testItem.firstChild is the test name + testTitle = testItem.firstChild; + + testCounts = bad ? "" + bad + ", " + "" + good + ", " : ""; + + testTitle.innerHTML += " (" + testCounts + details.assertions.length + ")"; + + if (details.skipped) { + stats.skippedTests++; + + testItem.className = "skipped"; + skipped = document$$1.createElement("em"); + skipped.className = "qunit-skipped-label"; + skipped.innerHTML = "skipped"; + testItem.insertBefore(skipped, testTitle); + } else { + addEvent(testTitle, "click", function () { + toggleClass(assertList, "qunit-collapsed"); + }); + + testItem.className = testPassed ? "pass" : "fail"; + + if (details.todo) { + var todoLabel = document$$1.createElement("em"); + todoLabel.className = "qunit-todo-label"; + todoLabel.innerHTML = "todo"; + testItem.className += " todo"; + testItem.insertBefore(todoLabel, testTitle); + } + + time = document$$1.createElement("span"); + time.className = "runtime"; + time.innerHTML = details.runtime + " ms"; + testItem.insertBefore(time, assertList); + + if (!testPassed) { + stats.failedTests++; + } else if (details.todo) { + stats.todoTests++; + } else { + stats.passedTests++; + } + } + + // Show the source of the test when showing assertions + if (details.source) { + sourceName = document$$1.createElement("p"); + sourceName.innerHTML = "Source: " + details.source; + addClass(sourceName, "qunit-source"); + if (testPassed) { + addClass(sourceName, "qunit-collapsed"); + } + addEvent(testTitle, "click", function () { + toggleClass(sourceName, "qunit-collapsed"); + }); + testItem.appendChild(sourceName); + } + }); + + // Avoid readyState issue with phantomjs + // Ref: #818 + var notPhantom = function (p) { + return !(p && p.version && p.version.major > 0); + }(window.phantom); + + if (notPhantom && document$$1.readyState === "complete") { + QUnit.load(); + } else { + addEvent(window, "load", QUnit.load); + } + + // Wrap window.onerror. We will call the original window.onerror to see if + // the existing handler fully handles the error; if not, we will call the + // QUnit.onError function. + var originalWindowOnError = window.onerror; + + // Cover uncaught exceptions + // Returning true will suppress the default browser handler, + // returning false will let it run. + window.onerror = function (message, fileName, lineNumber) { + var ret = false; + if (originalWindowOnError) { + for (var _len = arguments.length, args = Array(_len > 3 ? _len - 3 : 0), _key = 3; _key < _len; _key++) { + args[_key - 3] = arguments[_key]; + } + + ret = originalWindowOnError.call.apply(originalWindowOnError, [this, message, fileName, lineNumber].concat(args)); + } + + // Treat return value as window.onerror itself does, + // Only do our handling if not suppressed. + if (ret !== true) { + var error = { + message: message, + fileName: fileName, + lineNumber: lineNumber + }; + + ret = QUnit.onError(error); + } + + return ret; + }; + })(); + + /* + * This file is a modified version of google-diff-match-patch's JavaScript implementation + * (https://code.google.com/p/google-diff-match-patch/source/browse/trunk/javascript/diff_match_patch_uncompressed.js), + * modifications are licensed as more fully set forth in LICENSE.txt. + * + * The original source of google-diff-match-patch is attributable and licensed as follows: + * + * Copyright 2006 Google Inc. + * https://code.google.com/p/google-diff-match-patch/ + * + * 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 + * + * https://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. + * + * More Info: + * https://code.google.com/p/google-diff-match-patch/ + * + * Usage: QUnit.diff(expected, actual) + * + */ + QUnit.diff = function () { + function DiffMatchPatch() {} + + // DIFF FUNCTIONS + + /** + * The data structure representing a diff is an array of tuples: + * [[DIFF_DELETE, 'Hello'], [DIFF_INSERT, 'Goodbye'], [DIFF_EQUAL, ' world.']] + * which means: delete 'Hello', add 'Goodbye' and keep ' world.' + */ + var DIFF_DELETE = -1, + DIFF_INSERT = 1, + DIFF_EQUAL = 0; + + /** + * Find the differences between two texts. Simplifies the problem by stripping + * any common prefix or suffix off the texts before diffing. + * @param {string} text1 Old string to be diffed. + * @param {string} text2 New string to be diffed. + * @param {boolean=} optChecklines Optional speedup flag. If present and false, + * then don't run a line-level diff first to identify the changed areas. + * Defaults to true, which does a faster, slightly less optimal diff. + * @return {!Array.} Array of diff tuples. + */ + DiffMatchPatch.prototype.DiffMain = function (text1, text2, optChecklines) { + var deadline, checklines, commonlength, commonprefix, commonsuffix, diffs; + + // The diff must be complete in up to 1 second. + deadline = new Date().getTime() + 1000; + + // Check for null inputs. + if (text1 === null || text2 === null) { + throw new Error("Null input. (DiffMain)"); + } + + // Check for equality (speedup). + if (text1 === text2) { + if (text1) { + return [[DIFF_EQUAL, text1]]; + } + return []; + } + + if (typeof optChecklines === "undefined") { + optChecklines = true; + } + + checklines = optChecklines; + + // Trim off common prefix (speedup). + commonlength = this.diffCommonPrefix(text1, text2); + commonprefix = text1.substring(0, commonlength); + text1 = text1.substring(commonlength); + text2 = text2.substring(commonlength); + + // Trim off common suffix (speedup). + commonlength = this.diffCommonSuffix(text1, text2); + commonsuffix = text1.substring(text1.length - commonlength); + text1 = text1.substring(0, text1.length - commonlength); + text2 = text2.substring(0, text2.length - commonlength); + + // Compute the diff on the middle block. + diffs = this.diffCompute(text1, text2, checklines, deadline); + + // Restore the prefix and suffix. + if (commonprefix) { + diffs.unshift([DIFF_EQUAL, commonprefix]); + } + if (commonsuffix) { + diffs.push([DIFF_EQUAL, commonsuffix]); + } + this.diffCleanupMerge(diffs); + return diffs; + }; + + /** + * Reduce the number of edits by eliminating operationally trivial equalities. + * @param {!Array.} diffs Array of diff tuples. + */ + DiffMatchPatch.prototype.diffCleanupEfficiency = function (diffs) { + var changes, equalities, equalitiesLength, lastequality, pointer, preIns, preDel, postIns, postDel; + changes = false; + equalities = []; // Stack of indices where equalities are found. + equalitiesLength = 0; // Keeping our own length var is faster in JS. + /** @type {?string} */ + lastequality = null; + + // Always equal to diffs[equalities[equalitiesLength - 1]][1] + pointer = 0; // Index of current position. + + // Is there an insertion operation before the last equality. + preIns = false; + + // Is there a deletion operation before the last equality. + preDel = false; + + // Is there an insertion operation after the last equality. + postIns = false; + + // Is there a deletion operation after the last equality. + postDel = false; + while (pointer < diffs.length) { + + // Equality found. + if (diffs[pointer][0] === DIFF_EQUAL) { + if (diffs[pointer][1].length < 4 && (postIns || postDel)) { + + // Candidate found. + equalities[equalitiesLength++] = pointer; + preIns = postIns; + preDel = postDel; + lastequality = diffs[pointer][1]; + } else { + + // Not a candidate, and can never become one. + equalitiesLength = 0; + lastequality = null; + } + postIns = postDel = false; + + // An insertion or deletion. + } else { + + if (diffs[pointer][0] === DIFF_DELETE) { + postDel = true; + } else { + postIns = true; + } + + /* + * Five types to be split: + * ABXYCD + * AXCD + * ABXC + * AXCD + * ABXC + */ + if (lastequality && (preIns && preDel && postIns && postDel || lastequality.length < 2 && preIns + preDel + postIns + postDel === 3)) { + + // Duplicate record. + diffs.splice(equalities[equalitiesLength - 1], 0, [DIFF_DELETE, lastequality]); + + // Change second copy to insert. + diffs[equalities[equalitiesLength - 1] + 1][0] = DIFF_INSERT; + equalitiesLength--; // Throw away the equality we just deleted; + lastequality = null; + if (preIns && preDel) { + + // No changes made which could affect previous entry, keep going. + postIns = postDel = true; + equalitiesLength = 0; + } else { + equalitiesLength--; // Throw away the previous equality. + pointer = equalitiesLength > 0 ? equalities[equalitiesLength - 1] : -1; + postIns = postDel = false; + } + changes = true; + } + } + pointer++; + } + + if (changes) { + this.diffCleanupMerge(diffs); + } + }; + + /** + * Convert a diff array into a pretty HTML report. + * @param {!Array.} diffs Array of diff tuples. + * @param {integer} string to be beautified. + * @return {string} HTML representation. + */ + DiffMatchPatch.prototype.diffPrettyHtml = function (diffs) { + var op, + data, + x, + html = []; + for (x = 0; x < diffs.length; x++) { + op = diffs[x][0]; // Operation (insert, delete, equal) + data = diffs[x][1]; // Text of change. + switch (op) { + case DIFF_INSERT: + html[x] = "" + escapeText(data) + ""; + break; + case DIFF_DELETE: + html[x] = "" + escapeText(data) + ""; + break; + case DIFF_EQUAL: + html[x] = "" + escapeText(data) + ""; + break; + } + } + return html.join(""); + }; + + /** + * Determine the common prefix of two strings. + * @param {string} text1 First string. + * @param {string} text2 Second string. + * @return {number} The number of characters common to the start of each + * string. + */ + DiffMatchPatch.prototype.diffCommonPrefix = function (text1, text2) { + var pointermid, pointermax, pointermin, pointerstart; + + // Quick check for common null cases. + if (!text1 || !text2 || text1.charAt(0) !== text2.charAt(0)) { + return 0; + } + + // Binary search. + // Performance analysis: https://neil.fraser.name/news/2007/10/09/ + pointermin = 0; + pointermax = Math.min(text1.length, text2.length); + pointermid = pointermax; + pointerstart = 0; + while (pointermin < pointermid) { + if (text1.substring(pointerstart, pointermid) === text2.substring(pointerstart, pointermid)) { + pointermin = pointermid; + pointerstart = pointermin; + } else { + pointermax = pointermid; + } + pointermid = Math.floor((pointermax - pointermin) / 2 + pointermin); + } + return pointermid; + }; + + /** + * Determine the common suffix of two strings. + * @param {string} text1 First string. + * @param {string} text2 Second string. + * @return {number} The number of characters common to the end of each string. + */ + DiffMatchPatch.prototype.diffCommonSuffix = function (text1, text2) { + var pointermid, pointermax, pointermin, pointerend; + + // Quick check for common null cases. + if (!text1 || !text2 || text1.charAt(text1.length - 1) !== text2.charAt(text2.length - 1)) { + return 0; + } + + // Binary search. + // Performance analysis: https://neil.fraser.name/news/2007/10/09/ + pointermin = 0; + pointermax = Math.min(text1.length, text2.length); + pointermid = pointermax; + pointerend = 0; + while (pointermin < pointermid) { + if (text1.substring(text1.length - pointermid, text1.length - pointerend) === text2.substring(text2.length - pointermid, text2.length - pointerend)) { + pointermin = pointermid; + pointerend = pointermin; + } else { + pointermax = pointermid; + } + pointermid = Math.floor((pointermax - pointermin) / 2 + pointermin); + } + return pointermid; + }; + + /** + * Find the differences between two texts. Assumes that the texts do not + * have any common prefix or suffix. + * @param {string} text1 Old string to be diffed. + * @param {string} text2 New string to be diffed. + * @param {boolean} checklines Speedup flag. If false, then don't run a + * line-level diff first to identify the changed areas. + * If true, then run a faster, slightly less optimal diff. + * @param {number} deadline Time when the diff should be complete by. + * @return {!Array.} Array of diff tuples. + * @private + */ + DiffMatchPatch.prototype.diffCompute = function (text1, text2, checklines, deadline) { + var diffs, longtext, shorttext, i, hm, text1A, text2A, text1B, text2B, midCommon, diffsA, diffsB; + + if (!text1) { + + // Just add some text (speedup). + return [[DIFF_INSERT, text2]]; + } + + if (!text2) { + + // Just delete some text (speedup). + return [[DIFF_DELETE, text1]]; + } + + longtext = text1.length > text2.length ? text1 : text2; + shorttext = text1.length > text2.length ? text2 : text1; + i = longtext.indexOf(shorttext); + if (i !== -1) { + + // Shorter text is inside the longer text (speedup). + diffs = [[DIFF_INSERT, longtext.substring(0, i)], [DIFF_EQUAL, shorttext], [DIFF_INSERT, longtext.substring(i + shorttext.length)]]; + + // Swap insertions for deletions if diff is reversed. + if (text1.length > text2.length) { + diffs[0][0] = diffs[2][0] = DIFF_DELETE; + } + return diffs; + } + + if (shorttext.length === 1) { + + // Single character string. + // After the previous speedup, the character can't be an equality. + return [[DIFF_DELETE, text1], [DIFF_INSERT, text2]]; + } + + // Check to see if the problem can be split in two. + hm = this.diffHalfMatch(text1, text2); + if (hm) { + + // A half-match was found, sort out the return data. + text1A = hm[0]; + text1B = hm[1]; + text2A = hm[2]; + text2B = hm[3]; + midCommon = hm[4]; + + // Send both pairs off for separate processing. + diffsA = this.DiffMain(text1A, text2A, checklines, deadline); + diffsB = this.DiffMain(text1B, text2B, checklines, deadline); + + // Merge the results. + return diffsA.concat([[DIFF_EQUAL, midCommon]], diffsB); + } + + if (checklines && text1.length > 100 && text2.length > 100) { + return this.diffLineMode(text1, text2, deadline); + } + + return this.diffBisect(text1, text2, deadline); + }; + + /** + * Do the two texts share a substring which is at least half the length of the + * longer text? + * This speedup can produce non-minimal diffs. + * @param {string} text1 First string. + * @param {string} text2 Second string. + * @return {Array.} Five element Array, containing the prefix of + * text1, the suffix of text1, the prefix of text2, the suffix of + * text2 and the common middle. Or null if there was no match. + * @private + */ + DiffMatchPatch.prototype.diffHalfMatch = function (text1, text2) { + var longtext, shorttext, dmp, text1A, text2B, text2A, text1B, midCommon, hm1, hm2, hm; + + longtext = text1.length > text2.length ? text1 : text2; + shorttext = text1.length > text2.length ? text2 : text1; + if (longtext.length < 4 || shorttext.length * 2 < longtext.length) { + return null; // Pointless. + } + dmp = this; // 'this' becomes 'window' in a closure. + + /** + * Does a substring of shorttext exist within longtext such that the substring + * is at least half the length of longtext? + * Closure, but does not reference any external variables. + * @param {string} longtext Longer string. + * @param {string} shorttext Shorter string. + * @param {number} i Start index of quarter length substring within longtext. + * @return {Array.} Five element Array, containing the prefix of + * longtext, the suffix of longtext, the prefix of shorttext, the suffix + * of shorttext and the common middle. Or null if there was no match. + * @private + */ + function diffHalfMatchI(longtext, shorttext, i) { + var seed, j, bestCommon, prefixLength, suffixLength, bestLongtextA, bestLongtextB, bestShorttextA, bestShorttextB; + + // Start with a 1/4 length substring at position i as a seed. + seed = longtext.substring(i, i + Math.floor(longtext.length / 4)); + j = -1; + bestCommon = ""; + while ((j = shorttext.indexOf(seed, j + 1)) !== -1) { + prefixLength = dmp.diffCommonPrefix(longtext.substring(i), shorttext.substring(j)); + suffixLength = dmp.diffCommonSuffix(longtext.substring(0, i), shorttext.substring(0, j)); + if (bestCommon.length < suffixLength + prefixLength) { + bestCommon = shorttext.substring(j - suffixLength, j) + shorttext.substring(j, j + prefixLength); + bestLongtextA = longtext.substring(0, i - suffixLength); + bestLongtextB = longtext.substring(i + prefixLength); + bestShorttextA = shorttext.substring(0, j - suffixLength); + bestShorttextB = shorttext.substring(j + prefixLength); + } + } + if (bestCommon.length * 2 >= longtext.length) { + return [bestLongtextA, bestLongtextB, bestShorttextA, bestShorttextB, bestCommon]; + } else { + return null; + } + } + + // First check if the second quarter is the seed for a half-match. + hm1 = diffHalfMatchI(longtext, shorttext, Math.ceil(longtext.length / 4)); + + // Check again based on the third quarter. + hm2 = diffHalfMatchI(longtext, shorttext, Math.ceil(longtext.length / 2)); + if (!hm1 && !hm2) { + return null; + } else if (!hm2) { + hm = hm1; + } else if (!hm1) { + hm = hm2; + } else { + + // Both matched. Select the longest. + hm = hm1[4].length > hm2[4].length ? hm1 : hm2; + } + + // A half-match was found, sort out the return data. + if (text1.length > text2.length) { + text1A = hm[0]; + text1B = hm[1]; + text2A = hm[2]; + text2B = hm[3]; + } else { + text2A = hm[0]; + text2B = hm[1]; + text1A = hm[2]; + text1B = hm[3]; + } + midCommon = hm[4]; + return [text1A, text1B, text2A, text2B, midCommon]; + }; + + /** + * Do a quick line-level diff on both strings, then rediff the parts for + * greater accuracy. + * This speedup can produce non-minimal diffs. + * @param {string} text1 Old string to be diffed. + * @param {string} text2 New string to be diffed. + * @param {number} deadline Time when the diff should be complete by. + * @return {!Array.} Array of diff tuples. + * @private + */ + DiffMatchPatch.prototype.diffLineMode = function (text1, text2, deadline) { + var a, diffs, linearray, pointer, countInsert, countDelete, textInsert, textDelete, j; + + // Scan the text on a line-by-line basis first. + a = this.diffLinesToChars(text1, text2); + text1 = a.chars1; + text2 = a.chars2; + linearray = a.lineArray; + + diffs = this.DiffMain(text1, text2, false, deadline); + + // Convert the diff back to original text. + this.diffCharsToLines(diffs, linearray); + + // Eliminate freak matches (e.g. blank lines) + this.diffCleanupSemantic(diffs); + + // Rediff any replacement blocks, this time character-by-character. + // Add a dummy entry at the end. + diffs.push([DIFF_EQUAL, ""]); + pointer = 0; + countDelete = 0; + countInsert = 0; + textDelete = ""; + textInsert = ""; + while (pointer < diffs.length) { + switch (diffs[pointer][0]) { + case DIFF_INSERT: + countInsert++; + textInsert += diffs[pointer][1]; + break; + case DIFF_DELETE: + countDelete++; + textDelete += diffs[pointer][1]; + break; + case DIFF_EQUAL: + + // Upon reaching an equality, check for prior redundancies. + if (countDelete >= 1 && countInsert >= 1) { + + // Delete the offending records and add the merged ones. + diffs.splice(pointer - countDelete - countInsert, countDelete + countInsert); + pointer = pointer - countDelete - countInsert; + a = this.DiffMain(textDelete, textInsert, false, deadline); + for (j = a.length - 1; j >= 0; j--) { + diffs.splice(pointer, 0, a[j]); + } + pointer = pointer + a.length; + } + countInsert = 0; + countDelete = 0; + textDelete = ""; + textInsert = ""; + break; + } + pointer++; + } + diffs.pop(); // Remove the dummy entry at the end. + + return diffs; + }; + + /** + * Find the 'middle snake' of a diff, split the problem in two + * and return the recursively constructed diff. + * See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations. + * @param {string} text1 Old string to be diffed. + * @param {string} text2 New string to be diffed. + * @param {number} deadline Time at which to bail if not yet complete. + * @return {!Array.} Array of diff tuples. + * @private + */ + DiffMatchPatch.prototype.diffBisect = function (text1, text2, deadline) { + var text1Length, text2Length, maxD, vOffset, vLength, v1, v2, x, delta, front, k1start, k1end, k2start, k2end, k2Offset, k1Offset, x1, x2, y1, y2, d, k1, k2; + + // Cache the text lengths to prevent multiple calls. + text1Length = text1.length; + text2Length = text2.length; + maxD = Math.ceil((text1Length + text2Length) / 2); + vOffset = maxD; + vLength = 2 * maxD; + v1 = new Array(vLength); + v2 = new Array(vLength); + + // Setting all elements to -1 is faster in Chrome & Firefox than mixing + // integers and undefined. + for (x = 0; x < vLength; x++) { + v1[x] = -1; + v2[x] = -1; + } + v1[vOffset + 1] = 0; + v2[vOffset + 1] = 0; + delta = text1Length - text2Length; + + // If the total number of characters is odd, then the front path will collide + // with the reverse path. + front = delta % 2 !== 0; + + // Offsets for start and end of k loop. + // Prevents mapping of space beyond the grid. + k1start = 0; + k1end = 0; + k2start = 0; + k2end = 0; + for (d = 0; d < maxD; d++) { + + // Bail out if deadline is reached. + if (new Date().getTime() > deadline) { + break; + } + + // Walk the front path one step. + for (k1 = -d + k1start; k1 <= d - k1end; k1 += 2) { + k1Offset = vOffset + k1; + if (k1 === -d || k1 !== d && v1[k1Offset - 1] < v1[k1Offset + 1]) { + x1 = v1[k1Offset + 1]; + } else { + x1 = v1[k1Offset - 1] + 1; + } + y1 = x1 - k1; + while (x1 < text1Length && y1 < text2Length && text1.charAt(x1) === text2.charAt(y1)) { + x1++; + y1++; + } + v1[k1Offset] = x1; + if (x1 > text1Length) { + + // Ran off the right of the graph. + k1end += 2; + } else if (y1 > text2Length) { + + // Ran off the bottom of the graph. + k1start += 2; + } else if (front) { + k2Offset = vOffset + delta - k1; + if (k2Offset >= 0 && k2Offset < vLength && v2[k2Offset] !== -1) { + + // Mirror x2 onto top-left coordinate system. + x2 = text1Length - v2[k2Offset]; + if (x1 >= x2) { + + // Overlap detected. + return this.diffBisectSplit(text1, text2, x1, y1, deadline); + } + } + } + } + + // Walk the reverse path one step. + for (k2 = -d + k2start; k2 <= d - k2end; k2 += 2) { + k2Offset = vOffset + k2; + if (k2 === -d || k2 !== d && v2[k2Offset - 1] < v2[k2Offset + 1]) { + x2 = v2[k2Offset + 1]; + } else { + x2 = v2[k2Offset - 1] + 1; + } + y2 = x2 - k2; + while (x2 < text1Length && y2 < text2Length && text1.charAt(text1Length - x2 - 1) === text2.charAt(text2Length - y2 - 1)) { + x2++; + y2++; + } + v2[k2Offset] = x2; + if (x2 > text1Length) { + + // Ran off the left of the graph. + k2end += 2; + } else if (y2 > text2Length) { + + // Ran off the top of the graph. + k2start += 2; + } else if (!front) { + k1Offset = vOffset + delta - k2; + if (k1Offset >= 0 && k1Offset < vLength && v1[k1Offset] !== -1) { + x1 = v1[k1Offset]; + y1 = vOffset + x1 - k1Offset; + + // Mirror x2 onto top-left coordinate system. + x2 = text1Length - x2; + if (x1 >= x2) { + + // Overlap detected. + return this.diffBisectSplit(text1, text2, x1, y1, deadline); + } + } + } + } + } + + // Diff took too long and hit the deadline or + // number of diffs equals number of characters, no commonality at all. + return [[DIFF_DELETE, text1], [DIFF_INSERT, text2]]; + }; + + /** + * Given the location of the 'middle snake', split the diff in two parts + * and recurse. + * @param {string} text1 Old string to be diffed. + * @param {string} text2 New string to be diffed. + * @param {number} x Index of split point in text1. + * @param {number} y Index of split point in text2. + * @param {number} deadline Time at which to bail if not yet complete. + * @return {!Array.} Array of diff tuples. + * @private + */ + DiffMatchPatch.prototype.diffBisectSplit = function (text1, text2, x, y, deadline) { + var text1a, text1b, text2a, text2b, diffs, diffsb; + text1a = text1.substring(0, x); + text2a = text2.substring(0, y); + text1b = text1.substring(x); + text2b = text2.substring(y); + + // Compute both diffs serially. + diffs = this.DiffMain(text1a, text2a, false, deadline); + diffsb = this.DiffMain(text1b, text2b, false, deadline); + + return diffs.concat(diffsb); + }; + + /** + * Reduce the number of edits by eliminating semantically trivial equalities. + * @param {!Array.} diffs Array of diff tuples. + */ + DiffMatchPatch.prototype.diffCleanupSemantic = function (diffs) { + var changes, equalities, equalitiesLength, lastequality, pointer, lengthInsertions2, lengthDeletions2, lengthInsertions1, lengthDeletions1, deletion, insertion, overlapLength1, overlapLength2; + changes = false; + equalities = []; // Stack of indices where equalities are found. + equalitiesLength = 0; // Keeping our own length var is faster in JS. + /** @type {?string} */ + lastequality = null; + + // Always equal to diffs[equalities[equalitiesLength - 1]][1] + pointer = 0; // Index of current position. + + // Number of characters that changed prior to the equality. + lengthInsertions1 = 0; + lengthDeletions1 = 0; + + // Number of characters that changed after the equality. + lengthInsertions2 = 0; + lengthDeletions2 = 0; + while (pointer < diffs.length) { + if (diffs[pointer][0] === DIFF_EQUAL) { + // Equality found. + equalities[equalitiesLength++] = pointer; + lengthInsertions1 = lengthInsertions2; + lengthDeletions1 = lengthDeletions2; + lengthInsertions2 = 0; + lengthDeletions2 = 0; + lastequality = diffs[pointer][1]; + } else { + // An insertion or deletion. + if (diffs[pointer][0] === DIFF_INSERT) { + lengthInsertions2 += diffs[pointer][1].length; + } else { + lengthDeletions2 += diffs[pointer][1].length; + } + + // Eliminate an equality that is smaller or equal to the edits on both + // sides of it. + if (lastequality && lastequality.length <= Math.max(lengthInsertions1, lengthDeletions1) && lastequality.length <= Math.max(lengthInsertions2, lengthDeletions2)) { + + // Duplicate record. + diffs.splice(equalities[equalitiesLength - 1], 0, [DIFF_DELETE, lastequality]); + + // Change second copy to insert. + diffs[equalities[equalitiesLength - 1] + 1][0] = DIFF_INSERT; + + // Throw away the equality we just deleted. + equalitiesLength--; + + // Throw away the previous equality (it needs to be reevaluated). + equalitiesLength--; + pointer = equalitiesLength > 0 ? equalities[equalitiesLength - 1] : -1; + + // Reset the counters. + lengthInsertions1 = 0; + lengthDeletions1 = 0; + lengthInsertions2 = 0; + lengthDeletions2 = 0; + lastequality = null; + changes = true; + } + } + pointer++; + } + + // Normalize the diff. + if (changes) { + this.diffCleanupMerge(diffs); + } + + // Find any overlaps between deletions and insertions. + // e.g: abcxxxxxxdef + // -> abcxxxdef + // e.g: xxxabcdefxxx + // -> defxxxabc + // Only extract an overlap if it is as big as the edit ahead or behind it. + pointer = 1; + while (pointer < diffs.length) { + if (diffs[pointer - 1][0] === DIFF_DELETE && diffs[pointer][0] === DIFF_INSERT) { + deletion = diffs[pointer - 1][1]; + insertion = diffs[pointer][1]; + overlapLength1 = this.diffCommonOverlap(deletion, insertion); + overlapLength2 = this.diffCommonOverlap(insertion, deletion); + if (overlapLength1 >= overlapLength2) { + if (overlapLength1 >= deletion.length / 2 || overlapLength1 >= insertion.length / 2) { + + // Overlap found. Insert an equality and trim the surrounding edits. + diffs.splice(pointer, 0, [DIFF_EQUAL, insertion.substring(0, overlapLength1)]); + diffs[pointer - 1][1] = deletion.substring(0, deletion.length - overlapLength1); + diffs[pointer + 1][1] = insertion.substring(overlapLength1); + pointer++; + } + } else { + if (overlapLength2 >= deletion.length / 2 || overlapLength2 >= insertion.length / 2) { + + // Reverse overlap found. + // Insert an equality and swap and trim the surrounding edits. + diffs.splice(pointer, 0, [DIFF_EQUAL, deletion.substring(0, overlapLength2)]); + + diffs[pointer - 1][0] = DIFF_INSERT; + diffs[pointer - 1][1] = insertion.substring(0, insertion.length - overlapLength2); + diffs[pointer + 1][0] = DIFF_DELETE; + diffs[pointer + 1][1] = deletion.substring(overlapLength2); + pointer++; + } + } + pointer++; + } + pointer++; + } + }; + + /** + * Determine if the suffix of one string is the prefix of another. + * @param {string} text1 First string. + * @param {string} text2 Second string. + * @return {number} The number of characters common to the end of the first + * string and the start of the second string. + * @private + */ + DiffMatchPatch.prototype.diffCommonOverlap = function (text1, text2) { + var text1Length, text2Length, textLength, best, length, pattern, found; + + // Cache the text lengths to prevent multiple calls. + text1Length = text1.length; + text2Length = text2.length; + + // Eliminate the null case. + if (text1Length === 0 || text2Length === 0) { + return 0; + } + + // Truncate the longer string. + if (text1Length > text2Length) { + text1 = text1.substring(text1Length - text2Length); + } else if (text1Length < text2Length) { + text2 = text2.substring(0, text1Length); + } + textLength = Math.min(text1Length, text2Length); + + // Quick check for the worst case. + if (text1 === text2) { + return textLength; + } + + // Start by looking for a single character match + // and increase length until no match is found. + // Performance analysis: https://neil.fraser.name/news/2010/11/04/ + best = 0; + length = 1; + while (true) { + pattern = text1.substring(textLength - length); + found = text2.indexOf(pattern); + if (found === -1) { + return best; + } + length += found; + if (found === 0 || text1.substring(textLength - length) === text2.substring(0, length)) { + best = length; + length++; + } + } + }; + + /** + * Split two texts into an array of strings. Reduce the texts to a string of + * hashes where each Unicode character represents one line. + * @param {string} text1 First string. + * @param {string} text2 Second string. + * @return {{chars1: string, chars2: string, lineArray: !Array.}} + * An object containing the encoded text1, the encoded text2 and + * the array of unique strings. + * The zeroth element of the array of unique strings is intentionally blank. + * @private + */ + DiffMatchPatch.prototype.diffLinesToChars = function (text1, text2) { + var lineArray, lineHash, chars1, chars2; + lineArray = []; // E.g. lineArray[4] === 'Hello\n' + lineHash = {}; // E.g. lineHash['Hello\n'] === 4 + + // '\x00' is a valid character, but various debuggers don't like it. + // So we'll insert a junk entry to avoid generating a null character. + lineArray[0] = ""; + + /** + * Split a text into an array of strings. Reduce the texts to a string of + * hashes where each Unicode character represents one line. + * Modifies linearray and linehash through being a closure. + * @param {string} text String to encode. + * @return {string} Encoded string. + * @private + */ + function diffLinesToCharsMunge(text) { + var chars, lineStart, lineEnd, lineArrayLength, line; + chars = ""; + + // Walk the text, pulling out a substring for each line. + // text.split('\n') would would temporarily double our memory footprint. + // Modifying text would create many large strings to garbage collect. + lineStart = 0; + lineEnd = -1; + + // Keeping our own length variable is faster than looking it up. + lineArrayLength = lineArray.length; + while (lineEnd < text.length - 1) { + lineEnd = text.indexOf("\n", lineStart); + if (lineEnd === -1) { + lineEnd = text.length - 1; + } + line = text.substring(lineStart, lineEnd + 1); + lineStart = lineEnd + 1; + + if (lineHash.hasOwnProperty ? lineHash.hasOwnProperty(line) : lineHash[line] !== undefined) { + chars += String.fromCharCode(lineHash[line]); + } else { + chars += String.fromCharCode(lineArrayLength); + lineHash[line] = lineArrayLength; + lineArray[lineArrayLength++] = line; + } + } + return chars; + } + + chars1 = diffLinesToCharsMunge(text1); + chars2 = diffLinesToCharsMunge(text2); + return { + chars1: chars1, + chars2: chars2, + lineArray: lineArray + }; + }; + + /** + * Rehydrate the text in a diff from a string of line hashes to real lines of + * text. + * @param {!Array.} diffs Array of diff tuples. + * @param {!Array.} lineArray Array of unique strings. + * @private + */ + DiffMatchPatch.prototype.diffCharsToLines = function (diffs, lineArray) { + var x, chars, text, y; + for (x = 0; x < diffs.length; x++) { + chars = diffs[x][1]; + text = []; + for (y = 0; y < chars.length; y++) { + text[y] = lineArray[chars.charCodeAt(y)]; + } + diffs[x][1] = text.join(""); + } + }; + + /** + * Reorder and merge like edit sections. Merge equalities. + * Any edit section can move as long as it doesn't cross an equality. + * @param {!Array.} diffs Array of diff tuples. + */ + DiffMatchPatch.prototype.diffCleanupMerge = function (diffs) { + var pointer, countDelete, countInsert, textInsert, textDelete, commonlength, changes, diffPointer, position; + diffs.push([DIFF_EQUAL, ""]); // Add a dummy entry at the end. + pointer = 0; + countDelete = 0; + countInsert = 0; + textDelete = ""; + textInsert = ""; + + while (pointer < diffs.length) { + switch (diffs[pointer][0]) { + case DIFF_INSERT: + countInsert++; + textInsert += diffs[pointer][1]; + pointer++; + break; + case DIFF_DELETE: + countDelete++; + textDelete += diffs[pointer][1]; + pointer++; + break; + case DIFF_EQUAL: + + // Upon reaching an equality, check for prior redundancies. + if (countDelete + countInsert > 1) { + if (countDelete !== 0 && countInsert !== 0) { + + // Factor out any common prefixes. + commonlength = this.diffCommonPrefix(textInsert, textDelete); + if (commonlength !== 0) { + if (pointer - countDelete - countInsert > 0 && diffs[pointer - countDelete - countInsert - 1][0] === DIFF_EQUAL) { + diffs[pointer - countDelete - countInsert - 1][1] += textInsert.substring(0, commonlength); + } else { + diffs.splice(0, 0, [DIFF_EQUAL, textInsert.substring(0, commonlength)]); + pointer++; + } + textInsert = textInsert.substring(commonlength); + textDelete = textDelete.substring(commonlength); + } + + // Factor out any common suffixies. + commonlength = this.diffCommonSuffix(textInsert, textDelete); + if (commonlength !== 0) { + diffs[pointer][1] = textInsert.substring(textInsert.length - commonlength) + diffs[pointer][1]; + textInsert = textInsert.substring(0, textInsert.length - commonlength); + textDelete = textDelete.substring(0, textDelete.length - commonlength); + } + } + + // Delete the offending records and add the merged ones. + if (countDelete === 0) { + diffs.splice(pointer - countInsert, countDelete + countInsert, [DIFF_INSERT, textInsert]); + } else if (countInsert === 0) { + diffs.splice(pointer - countDelete, countDelete + countInsert, [DIFF_DELETE, textDelete]); + } else { + diffs.splice(pointer - countDelete - countInsert, countDelete + countInsert, [DIFF_DELETE, textDelete], [DIFF_INSERT, textInsert]); + } + pointer = pointer - countDelete - countInsert + (countDelete ? 1 : 0) + (countInsert ? 1 : 0) + 1; + } else if (pointer !== 0 && diffs[pointer - 1][0] === DIFF_EQUAL) { + + // Merge this equality with the previous one. + diffs[pointer - 1][1] += diffs[pointer][1]; + diffs.splice(pointer, 1); + } else { + pointer++; + } + countInsert = 0; + countDelete = 0; + textDelete = ""; + textInsert = ""; + break; + } + } + if (diffs[diffs.length - 1][1] === "") { + diffs.pop(); // Remove the dummy entry at the end. + } + + // Second pass: look for single edits surrounded on both sides by equalities + // which can be shifted sideways to eliminate an equality. + // e.g: ABAC -> ABAC + changes = false; + pointer = 1; + + // Intentionally ignore the first and last element (don't need checking). + while (pointer < diffs.length - 1) { + if (diffs[pointer - 1][0] === DIFF_EQUAL && diffs[pointer + 1][0] === DIFF_EQUAL) { + + diffPointer = diffs[pointer][1]; + position = diffPointer.substring(diffPointer.length - diffs[pointer - 1][1].length); + + // This is a single edit surrounded by equalities. + if (position === diffs[pointer - 1][1]) { + + // Shift the edit over the previous equality. + diffs[pointer][1] = diffs[pointer - 1][1] + diffs[pointer][1].substring(0, diffs[pointer][1].length - diffs[pointer - 1][1].length); + diffs[pointer + 1][1] = diffs[pointer - 1][1] + diffs[pointer + 1][1]; + diffs.splice(pointer - 1, 1); + changes = true; + } else if (diffPointer.substring(0, diffs[pointer + 1][1].length) === diffs[pointer + 1][1]) { + + // Shift the edit over the next equality. + diffs[pointer - 1][1] += diffs[pointer + 1][1]; + diffs[pointer][1] = diffs[pointer][1].substring(diffs[pointer + 1][1].length) + diffs[pointer + 1][1]; + diffs.splice(pointer + 1, 1); + changes = true; + } + } + pointer++; + } + + // If shifts were made, the diff needs reordering and another shift sweep. + if (changes) { + this.diffCleanupMerge(diffs); + } + }; + + return function (o, n) { + var diff, output, text; + diff = new DiffMatchPatch(); + output = diff.DiffMain(o, n); + diff.diffCleanupEfficiency(output); + text = diff.diffPrettyHtml(output); + + return text; + }; + }(); + +}((function() { return this; }()))); \ No newline at end of file diff --git a/frappe/test_runner.py b/frappe/test_runner.py index 0d22008116..edefaaca11 100644 --- a/frappe/test_runner.py +++ b/frappe/test_runner.py @@ -22,7 +22,8 @@ def xmlrunner_wrapper(output): return xmlrunner.XMLTestRunner(*args, **kwargs) return _runner -def main(app=None, module=None, doctype=None, verbose=False, tests=(), force=False, profile=False, junit_xml_output=None): +def main(app=None, module=None, doctype=None, verbose=False, tests=(), + force=False, profile=False, junit_xml_output=None, ui_tests=False): global unittest_runner xmloutput_fh = None @@ -57,7 +58,7 @@ def main(app=None, module=None, doctype=None, verbose=False, tests=(), force=Fal elif module: ret = run_tests_for_module(module, verbose, tests, profile) else: - ret = run_all_tests(app, verbose, profile) + ret = run_all_tests(app, verbose, profile, ui_tests) frappe.db.commit() @@ -80,7 +81,7 @@ def set_test_email_config(): "admin_password": "admin" }) -def run_all_tests(app=None, verbose=False, profile=False): +def run_all_tests(app=None, verbose=False, profile=False, ui_tests=False): import os apps = [app] if app else frappe.get_installed_apps() @@ -95,9 +96,11 @@ def run_all_tests(app=None, verbose=False, profile=False): # print path for filename in files: filename = cstr(filename) - if filename.startswith("test_") and filename.endswith(".py"): + if filename.startswith("test_") and filename.endswith(".py")\ + and filename != 'test_runner.py': # print filename[:-3] - _add_test(app, path, filename, verbose, test_suite=test_suite) + _add_test(app, path, filename, verbose, + test_suite, ui_tests) if profile: pr = cProfile.Profile() @@ -163,7 +166,7 @@ def _run_unittest(module, verbose=False, tests=(), profile=False): return out -def _add_test(app, path, filename, verbose, test_suite=None): +def _add_test(app, path, filename, verbose, test_suite=None, ui_tests=False): import os if os.path.sep.join(["doctype", "doctype", "boilerplate"]) in path: @@ -179,8 +182,9 @@ def _add_test(app, path, filename, verbose, test_suite=None): relative_path=relative_path.replace('/', '.'), module_name=filename[:-3]) module = frappe.get_module(module_name) + is_ui_test = True if hasattr(module, 'TestDriver') else False - if getattr(module, "selenium_tests", False) and not frappe.conf.run_selenium_tests: + if is_ui_test != ui_tests: return if not test_suite: @@ -325,3 +329,5 @@ def print_mandatory_fields(doctype): for d in meta.get("fields", {"reqd":1}): print(d.parent + ":" + d.fieldname + " | " + d.fieldtype + " | " + (d.options or "")) print() + + diff --git a/frappe/tests/test_client_login.py b/frappe/tests/test_client_login.py deleted file mode 100644 index a86d36c86d..0000000000 --- a/frappe/tests/test_client_login.py +++ /dev/null @@ -1,27 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt -from __future__ import unicode_literals - -import unittest, frappe -from frappe.utils import sel - -selenium_tests = True - -class TestLogin(unittest.TestCase): - def setUp(self): - return - sel.login() - - def test_login(self): - return - self.assertEquals(sel._driver.current_url, sel.get_localhost() + "/desk") - - def test_to_do(self): - return - # too unpredictable in travis - sel.go_to_module("ToDo") - sel.primary_action() - sel.wait_for_page("Form/ToDo") - sel.set_field("description", "test description", "textarea") - sel.primary_action() - self.assertTrue(sel.wait_for_state("clean")) diff --git a/frappe/tests/ui/__init__.py b/frappe/tests/ui/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/tests/ui/login.js b/frappe/tests/ui/login.js deleted file mode 100644 index 4d9c8ef21b..0000000000 --- a/frappe/tests/ui/login.js +++ /dev/null @@ -1,19 +0,0 @@ -module.exports = { - beforeEach: browser => { - browser - .url(browser.launch_url + '/login') - .waitForElementVisible('body', 5000) - }, - 'Login': browser => { - browser - .assert.title('Login') - .assert.visible('#login_email', 'Check if login box is visible') - .setValue("#login_email", "Administrator") - .setValue("#login_password", "admin") - .click(".btn-login") - .waitForElementVisible("#body_div", 15000); - }, - after: browser => { - browser.end(); - }, -}; \ No newline at end of file diff --git a/frappe/tests/ui/test_lib.js b/frappe/tests/ui/test_lib.js new file mode 100644 index 0000000000..e7a7e909f9 --- /dev/null +++ b/frappe/tests/ui/test_lib.js @@ -0,0 +1,93 @@ +frappe.tests = { + data: {}, + get_fixture_names: (doctype) => { + return Object.keys(frappe.test_data[doctype]); + }, + make: function(doctype, data) { + return frappe.run_serially([ + () => frappe.set_route('List', doctype), + () => frappe.new_doc(doctype), + () => { + let frm = frappe.quick_entry ? frappe.quick_entry.dialog : cur_frm; + return frappe.tests.set_form_values(frm, data); + }, + () => frappe.timeout(1), + () => (frappe.quick_entry ? frappe.quick_entry.insert() : cur_frm.save()) + ]); + }, + set_form_values: (frm, data) => { + let tasks = []; + + data.forEach(item => { + for (let key in item) { + let task = () => { + let value = item[key]; + if ($.isArray(value)) { + return frappe.tests.set_grid_values(frm, key, value); + } else { + // single value + return frm.set_value(key, value); + } + }; + tasks.push(task); + } + }); + + // set values + return frappe.run_serially(tasks); + + }, + set_grid_values: (frm, key, value) => { + // set value in grid + let grid = frm.get_field(key).grid; + grid.remove_all(); + + let grid_row_tasks = []; + + // build tasks for each row + value.forEach(d => { + grid_row_tasks.push(() => { + grid.add_new_row(); + let grid_row = grid.get_row(-1).toggle_view(true); + let grid_value_tasks = []; + + // build tasks to set each row value + d.forEach(child_value => { + for (let child_key in child_value) { + grid_value_tasks.push(() => { + return frappe.model.set_value(grid_row.doc.doctype, + grid_row.doc.name, child_key, child_value[child_key]); + }); + } + }); + + return frappe.run_serially(grid_value_tasks); + }); + }); + return frappe.run_serially(grid_row_tasks); + }, + setup_doctype: (doctype) => { + return frappe.set_route('List', doctype) + .then(() => { + frappe.tests.data[doctype] = []; + let expected = frappe.tests.get_fixture_names(doctype); + cur_list.data.forEach((d) => { + frappe.tests.data[doctype].push(d.name); + if(expected.indexOf(d.name) !== -1) { + expected[expected.indexOf(d.name)] = null; + } + }); + + let tasks = []; + + expected.forEach(function(d) { + if(d) { + tasks.push(() => frappe.tests.make(doctype, + frappe.test_data[doctype][d])); + } + }); + + return frappe.run_serially(tasks); + }); + } +}; \ No newline at end of file diff --git a/frappe/tests/ui/test_list.js b/frappe/tests/ui/test_list.js new file mode 100644 index 0000000000..94f1cfd21a --- /dev/null +++ b/frappe/tests/ui/test_list.js @@ -0,0 +1,33 @@ +QUnit.module('views'); + +QUnit.test("test quick entry", function(assert) { + assert.expect(2); + let done = assert.async(); + let random = frappe.utils.get_random(10); + + frappe.run_serially([ + () => frappe.set_route('List', 'ToDo'), + () => frappe.new_doc('ToDo'), + () => frappe.quick_entry.dialog.set_value('description', random), + () => frappe.quick_entry.insert(), + (doc) => { + assert.ok(doc && !doc.__islocal); + return frappe.set_route('Form', 'ToDo', doc.name); + }, + () => { + assert.ok(cur_frm.doc.description.includes(random)); + return done(); + } + ]); +}); + +QUnit.test("test list values", function(assert) { + assert.expect(2); + let done = assert.async(); + frappe.set_route('List', 'DocType') + .then(() => { + assert.deepEqual(['List', 'DocType', 'List'], frappe.get_route()); + assert.ok($('.list-item:visible').length > 10); + done(); + }); +}); \ No newline at end of file diff --git a/frappe/tests/ui/test_test_runner.py b/frappe/tests/ui/test_test_runner.py new file mode 100644 index 0000000000..7cd6529b3d --- /dev/null +++ b/frappe/tests/ui/test_test_runner.py @@ -0,0 +1,17 @@ +from __future__ import print_function +from frappe.utils.selenium_testdriver import TestDriver +import unittest + +class TestLogin(unittest.TestCase): + def setUp(self): + self.driver = TestDriver() + + def test_test_runner(self): + self.driver.login() + self.driver.set_route('Form', 'Test Runner') + self.driver.click_primary_action() + self.driver.wait_for('#qunit-testresult-display', timeout=60) + self.driver.print_console() + + def tearDown(self): + self.driver.close() diff --git a/frappe/tests/ui/test_todo.py b/frappe/tests/ui/test_todo.py new file mode 100644 index 0000000000..2d6518f650 --- /dev/null +++ b/frappe/tests/ui/test_todo.py @@ -0,0 +1,50 @@ +from __future__ import print_function +from frappe.utils.selenium_testdriver import TestDriver +import unittest +import time, os + +class TestToDo(unittest.TestCase): + def setUp(self): + self.driver = TestDriver() + + def test_todo(self): + self.driver.login() + + # list view + self.driver.set_route('List', 'ToDo') + + time.sleep(2) + + # new + self.driver.click_primary_action() + + time.sleep(2) + + # set input + self.driver.set_text_editor('description', 'hello') + + # save + self.driver.click_modal_primary_action() + + time.sleep(2) + + # refresh + self.driver.click_secondary_action() + + time.sleep(2) + + result_list = self.driver.get_visible_element('.result-list') + first_element_text = (result_list + .find_element_by_css_selector('.list-item') + .find_element_by_css_selector('.list-id').text) + + # if os.environ.get('CI'): + # # we don't run this test in Travis as it always fails + # # reinforcing why we use Unit Testing instead of integration + # # testing + # return + + self.assertTrue('hello' in first_element_text) + + def tearDown(self): + self.driver.close() diff --git a/frappe/utils/install.py b/frappe/utils/install.py index a557c48ff3..43fdaf171b 100644 --- a/frappe/utils/install.py +++ b/frappe/utils/install.py @@ -94,6 +94,22 @@ def before_tests(): frappe.db.commit() frappe.clear_cache() + # complete setup if missing + from frappe.desk.page.setup_wizard.setup_wizard import setup_complete + if not int(frappe.db.get_single_value('System Settings', 'setup_complete') or 0): + setup_complete({ + "language" :"english", + "email" :"test@erpnext.com", + "full_name" :"Test User", + "password" :"test", + "country" :"United States", + "timezone" :"America/New_York", + "currency" :"USD" + }) + + frappe.db.commit() + frappe.clear_cache() + def import_country_and_currency(): from frappe.geo.country_info import get_all from frappe.utils import update_progress_bar diff --git a/frappe/utils/nestedset.py b/frappe/utils/nestedset.py index 8065382aaa..bb29077ca6 100644 --- a/frappe/utils/nestedset.py +++ b/frappe/utils/nestedset.py @@ -186,7 +186,7 @@ class NestedSet(Document): self.validate_ledger() def on_trash(self): - if not self.nsm_parent_field: + if not getattr(self, 'nsm_parent_field', None): self.nsm_parent_field = frappe.scrub(self.doctype) + "_parent" parent = self.get(self.nsm_parent_field) diff --git a/frappe/utils/sel.py b/frappe/utils/sel.py deleted file mode 100644 index 8c7856fd41..0000000000 --- a/frappe/utils/sel.py +++ /dev/null @@ -1,187 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt - -from __future__ import unicode_literals, print_function - -from selenium.webdriver.common.keys import Keys -from selenium import webdriver -from selenium.webdriver.common.by import By -from selenium.webdriver.support.ui import WebDriverWait -from selenium.webdriver.support.select import Select -from selenium.webdriver.support import expected_conditions as EC -from selenium.common.exceptions import TimeoutException -from urllib import unquote -import time, subprocess -import signal -import sys - -host = "http://localhost" -pipe = None -port = "8000" -_driver = None -_verbose = None -logged_in = False -cur_route = False -input_wait = 0 - -def get_localhost(): - return "{host}:{port}".format(host=host, port=port) - -def start(verbose=None, driver=None): - global _driver, _verbose - _verbose = verbose - - _driver = getattr(webdriver, driver or "PhantomJS")() - _driver.set_window_size(1080,800) - - signal.signal(signal.SIGINT, signal_handler) - -def signal_handler(signal, frame): - close() - sys.exit(0) - -def start_test_server(verbose): - global pipe - pipe = subprocess.Popen(["bench", "serve", "--port", port], stdout=subprocess.PIPE, stderr=subprocess.PIPE) - #time.sleep(5) - while not pipe.stderr.readline(): - time.sleep(0.5) - if verbose: - print("Test server started") - -def get(url): - _driver.get(url) - -def login(wait_for_id="#page-desktop"): - global logged_in - if logged_in: - return - get(get_localhost() + "/login") - wait("#login_email") - set_input("#login_email", "Administrator") - set_input("#login_password", "admin", key=Keys.RETURN) - wait(wait_for_id) - logged_in = True - - -def go_to_module(module_name, item=None): - global cur_route - - # desktop - find(".navbar-home", True)[0].click() - cur_route = None - wait("#page-desktop") - - page = "Module/" + module_name - m = find('#page-desktop [data-link="{0}"] .app-icon'.format(page)) - if not m: - page = "List/" + module_name - m = find('#page-desktop [data-link="{0}"] .app-icon'.format(page)) - if not m: - raise Exception("Module {0} not found".format(module_name)) - - m[0].click() - wait_for_page(page) - - if item: - elem = find('[data-label="{0}"]'.format(item))[0] - elem.click() - page = elem.get_attribute("data-route") - wait_for_page(page) - -def new_doc(module, doctype): - go_to_module(module, doctype) - primary_action() - wait_for_page("Form/" + doctype) - -def add_child(fieldname): - find('[data-fieldname="{0}"] .grid-add-row'.format(fieldname))[0].click() - wait('[data-fieldname="{0}"] .form-grid'.format(fieldname)) - -def done_add_child(fieldname): - selector = '[data-fieldname="{0}"] .grid-row-open .btn-success'.format(fieldname) - scroll_to(selector) - wait_till_clickable(selector).click() - -def find(selector, everywhere=False): - if cur_route and not everywhere: - selector = cur_route + " " + selector - return _driver.find_elements_by_css_selector(selector) - -def set_field(fieldname, value, fieldtype="input"): - _driver.switch_to.window(_driver.current_window_handle) - selector = '{0}[data-fieldname="{1}"]'.format(fieldtype, fieldname) - set_input(selector, value, key=Keys.TAB) - wait_for_ajax() - -def set_select(fieldname, value): - select = Select(find('select[data-fieldname="{0}"]'.format(fieldname))[0]) - select.select_by_value(value) - wait_for_ajax() - -def primary_action(): - selector = ".page-actions .primary-action" - scroll_to(selector) - wait_till_clickable(selector).click() - wait_for_ajax() - -def wait_for_page(name): - global cur_route - cur_route = None - route = '[data-page-route="{0}"]'.format(name) - wait_for_ajax() - elem = wait(route) - wait_for_ajax() - cur_route = route - return elem - -def wait_till_clickable(selector): - if cur_route: - selector = cur_route + " " + selector - return get_wait().until(EC.element_to_be_clickable((By.CSS_SELECTOR, selector))) - -def wait_till_visible(selector): - if cur_route: - selector = cur_route + " " + selector - return get_wait().until(EC.visibility_of_element_located((By.CSS_SELECTOR, selector))) - -def wait_for_ajax(): - wait('body[data-ajax-state="complete"]', True) - -def wait_for_state(state): - return wait(cur_route + '[data-state="{0}"]'.format(state), True) - -def wait(selector, everywhere=False): - if cur_route and not everywhere: - selector = cur_route + " " + selector - - time.sleep(0.5) - elem = get_wait().until(EC.presence_of_element_located((By.CSS_SELECTOR, selector))) - return elem - -def get_wait(): - return WebDriverWait(_driver, 20) - -def set_input(selector, text, key=None): - elem = find(selector)[0] - elem.clear() - elem.send_keys(text) - if key: - time.sleep(0.5) - elem.send_keys(key) - if input_wait: - time.sleep(input_wait) - -def scroll_to(selector): - execute_script("frappe.ui.scroll('{0}')".format(selector)) - -def execute_script(js): - _driver.execute_script(js) - -def close(): - global _driver, pipe - if _driver: - _driver.quit() - if pipe: - pipe.kill() - _driver = pipe = None diff --git a/frappe/utils/selenium_testdriver.py b/frappe/utils/selenium_testdriver.py new file mode 100644 index 0000000000..b39bd93d23 --- /dev/null +++ b/frappe/utils/selenium_testdriver.py @@ -0,0 +1,260 @@ +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# MIT License. See license.txt + +from __future__ import unicode_literals, print_function + +from selenium import webdriver +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +#from selenium.webdriver.support.select import Select +from selenium.webdriver.support import expected_conditions as EC +#from selenium.common.exceptions import TimeoutException +from selenium.webdriver.chrome.options import Options +from selenium.webdriver.common.desired_capabilities import DesiredCapabilities + +import time +import signal +import os, sys +import frappe +from ast import literal_eval + +class TestDriver(object): + def __init__(self, port='8000'): + self.port = port + + chrome_options = Options() + capabilities = DesiredCapabilities.CHROME + + if os.environ.get('CI'): + self.host = 'localhost' + else: + self.host = frappe.local.site + + # enable browser logging + capabilities['loggingPrefs'] = {'browser':'ALL'} + + chrome_options.add_argument('--no-sandbox') + chrome_options.add_argument('--start-maximized') + self.driver = webdriver.Chrome(chrome_options=chrome_options, + desired_capabilities=capabilities, port=9515) + + self.driver.set_window_size(1080,800) + self.cur_route = None + self.logged_in = False + + @property + def localhost(self): + return "http://{host}:{port}".format(host=self.host, port=self.port) + + def get(self, url): + return self.driver.get(os.path.join(self.localhost, url)) + + def start(self): + def signal_handler(signal, frame): + self.close() + sys.exit(0) + signal.signal(signal.SIGINT, signal_handler) + + def close(self): + if self.driver: + self.driver.quit() + self.driver = None + + def login(self, wait_for_id="#page-desktop"): + if self.logged_in: + return + self.get('login') + self.wait_for("#login_email") + self.set_input("#login_email", "Administrator") + self.set_input("#login_password", "admin") + self.click('.btn-login') + self.wait_for(wait_for_id) + self.logged_in = True + + def set_input(self, selector, text, key=None, xpath=None): + elem = self.find(selector, xpath=xpath)[0] + elem.clear() + elem.send_keys(text) + if key: + time.sleep(0.5) + elem.send_keys(key) + time.sleep(0.2) + + def set_field(self, fieldname, text): + elem = self.find(xpath='//input[@data-fieldname="{0}"]'.format(fieldname)) + elem[0].send_keys(text) + + def set_text_editor(self, fieldname, text): + elem = self.find(xpath='//div[@data-fieldname="{0}"]//div[@contenteditable="true"]'.format(fieldname)) + elem[0].send_keys(text) + + def find(self, selector=None, everywhere=False, xpath=None): + if xpath: + return self.driver.find_elements_by_xpath(xpath) + else: + if self.cur_route and not everywhere: + selector = self.cur_route + " " + selector + return self.driver.find_elements_by_css_selector(selector) + + def wait_for(self, selector=None, everywhere=False, timeout=20, xpath=None): + if self.cur_route and not everywhere: + selector = self.cur_route + " " + selector + + time.sleep(0.5) + + if selector: + _by = By.CSS_SELECTOR + if xpath: + _by = By.XPATH + selector = xpath + + try: + elem = self.get_wait(timeout).until( + EC.presence_of_element_located((_by, selector))) + return elem + except Exception, e: + # body = self.driver.find_element_by_id('body_div') + # print(body.get_attribute('innerHTML')) + self.print_console() + raise e + + def print_console(self): + for entry in self.driver.get_log('browser'): + source, line_no, message = entry.get('message').split(' ', 2) + + if message[0] in ('"', "'"): + # message is a quoted/escaped string + message = literal_eval(message) + + print(source + ' ' + line_no) + print(message) + print('-'*40) + + def get_wait(self, timeout=20): + return WebDriverWait(self.driver, timeout) + + def scroll_to(self, selector): + self.execute_script("frappe.ui.scroll('{0}')".format(selector)) + + def set_route(self, *args): + self.execute_script('frappe.set_route({0})'\ + .format(', '.join(['"{0}"'.format(r) for r in args]))) + + self.wait_for(xpath='//div[@data-page-route="{0}"]'.format('/'.join(args)), timeout=4) + + def click(self, css_selector, xpath=None): + self.wait_till_clickable(css_selector, xpath).click() + + def click_primary_action(self): + selector = ".page-actions .primary-action" + #self.scroll_to(selector) + self.wait_till_clickable(selector).click() + self.wait_for_ajax() + + def click_secondary_action(self): + selector = ".page-actions .btn-secondary" + #self.scroll_to(selector) + self.wait_till_clickable(selector).click() + self.wait_for_ajax() + + def click_modal_primary_action(self): + self.get_visible_modal().find_element_by_css_selector('.btn-primary').click() + + def get_visible_modal(self): + return self.get_visible_element('.modal-content') + + def get_visible_element(self, selector=None, xpath=None): + for elem in self.find(selector=selector, xpath=xpath): + if elem.is_displayed(): + return elem + + def wait_till_clickable(self, selector=None, xpath=None): + if self.cur_route: + selector = self.cur_route + " " + selector + + by = By.CSS_SELECTOR + if xpath: + by = By.XPATH + selector = xpath + + return self.get_wait().until(EC.element_to_be_clickable( + (by, selector))) + + def execute_script(self, js): + self.driver.execute_script(js) + + def wait_for_ajax(self): + self.wait_for('body[data-ajax-state="complete"]', True) + +# def go_to_module(module_name, item=None): +# global cur_route +# +# # desktop +# find(".navbar-home", True)[0].click() +# cur_route = None +# wait("#page-desktop") +# +# page = "Module/" + module_name +# m = find('#page-desktop [data-link="{0}"] .app-icon'.format(page)) +# if not m: +# page = "List/" + module_name +# m = find('#page-desktop [data-link="{0}"] .app-icon'.format(page)) +# if not m: +# raise Exception("Module {0} not found".format(module_name)) +# +# m[0].click() +# wait_for_page(page) +# +# if item: +# elem = find('[data-label="{0}"]'.format(item))[0] +# elem.click() +# page = elem.get_attribute("data-route") +# wait_for_page(page) +# +# def new_doc(module, doctype): +# go_to_module(module, doctype) +# primary_action() +# wait_for_page("Form/" + doctype) +# +# def add_child(fieldname): +# find('[data-fieldname="{0}"] .grid-add-row'.format(fieldname))[0].click() +# wait('[data-fieldname="{0}"] .form-grid'.format(fieldname)) +# +# def done_add_child(fieldname): +# selector = '[data-fieldname="{0}"] .grid-row-open .btn-success'.format(fieldname) +# scroll_to(selector) +# wait_till_clickable(selector).click() +# +# def set_field(fieldname, value, fieldtype="input"): +# _driver.switch_to.window(_driver.current_window_handle) +# selector = '{0}[data-fieldname="{1}"]'.format(fieldtype, fieldname) +# set_input(selector, value, key=Keys.TAB) +# wait_for_ajax() +# +# def set_select(fieldname, value): +# select = Select(find('select[data-fieldname="{0}"]'.format(fieldname))[0]) +# select.select_by_value(value) +# wait_for_ajax() +# +# +# def wait_for_page(name): +# global cur_route +# cur_route = None +# route = '[data-page-route="{0}"]'.format(name) +# wait_for_ajax() +# elem = wait(route) +# wait_for_ajax() +# cur_route = route +# return elem +# +# +# def wait_till_visible(selector): +# if cur_route: +# selector = cur_route + " " + selector +# return get_wait().until(EC.visibility_of_element_located((By.CSS_SELECTOR, selector))) +# +# +# def wait_for_state(state): +# return wait(cur_route + '[data-state="{0}"]'.format(state), True) +# +# From 2c3037a0944d34b1ef6a38d5df668d777334eeac Mon Sep 17 00:00:00 2001 From: KanchanChauhan Date: Mon, 3 Jul 2017 12:10:53 +0530 Subject: [PATCH 03/41] In currency if no value after decimal, default precion set to 2 (#3480) * In currency if no value after decimal, default precion set to 2 * Update formatters.js --- frappe/public/js/frappe/form/formatters.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/frappe/public/js/frappe/form/formatters.js b/frappe/public/js/frappe/form/formatters.js index c23d440f47..6c4fc6c72d 100644 --- a/frappe/public/js/frappe/form/formatters.js +++ b/frappe/public/js/frappe/form/formatters.js @@ -53,6 +53,17 @@ frappe.form.formatters = { Currency: function(value, docfield, options, doc) { var currency = frappe.meta.get_field_currency(docfield, doc); var precision = docfield.precision || cint(frappe.boot.sysdefaults.currency_precision) || 2; + if (precision > 2) { + let parts = cstr(value).split('.'); + let decimals = parts.length > 1 ? parts[1] : ''; + if (decimals.length < 3) { + // min precision 2 + precision = 2; + } else if (decimals.length < precision) { + // or min decimals + precision = decimals.length; + } + } return frappe.form.formatters._right((value==null || value==="") ? "" : format_currency(value, currency, precision), options); }, From fbce6b67becb3bac9b5872cf99768e03720730ab Mon Sep 17 00:00:00 2001 From: Vishal Dhayagude Date: Mon, 3 Jul 2017 12:21:49 +0530 Subject: [PATCH 04/41] Redirect page (#3579) * [fix]redirect to previous page * Removed unused flag * Update address.js --- frappe/contacts/doctype/address/address.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/frappe/contacts/doctype/address/address.js b/frappe/contacts/doctype/address/address.js index f20093a21f..7809f426ea 100644 --- a/frappe/contacts/doctype/address/address.js +++ b/frappe/contacts/doctype/address/address.js @@ -30,5 +30,12 @@ frappe.ui.form.on("Address", { frappe.model.remove_from_locals(d.link_doctype, d.link_name); }); } + }, + after_save: function() { + var last_route = frappe.route_history.slice(-2, -1)[0]; + if(frappe.dynamic_link && frappe.dynamic_link.doc + && frappe.dynamic_link.doc.name == last_route[2]){ + frappe.set_route(last_route[0], last_route[1], last_route[2]); + } } }); From 78c03b90c354bffc507cb9de69da2276ab0556bb Mon Sep 17 00:00:00 2001 From: Alex Ford Date: Mon, 3 Jul 2017 07:55:07 +0100 Subject: [PATCH 05/41] lgtm minor fixes (#3580) * Replace a typeof test against undefined with comparison against a string * Add rel='noopener noreferrer' attributes to target='_blank' links * Remove a duplicate object property definition * Remove superfluous argument to function call * Remove some variable declarations for which the assigned value is never read * Remove an assignment to a variable that is never read --- .../core/page/permission_manager/permission_manager_help.html | 2 +- frappe/desk/page/applications/application_row.html | 2 +- frappe/desk/page/backups/backups.html | 2 +- frappe/public/js/frappe/form/footer/timeline_item.html | 2 +- frappe/public/js/frappe/misc/common.js | 1 - frappe/public/js/frappe/query_string.js | 2 +- frappe/public/js/frappe/toolbar.js | 4 +--- frappe/public/js/frappe/ui/toolbar/navbar.html | 4 ++-- frappe/public/js/frappe/views/reports/grid_report.js | 1 - frappe/public/js/frappe/views/reports/query_report.js | 1 - frappe/website/doctype/web_form/templates/web_form.html | 4 ++-- frappe/website/js/web_form.js | 2 +- 12 files changed, 11 insertions(+), 16 deletions(-) diff --git a/frappe/core/page/permission_manager/permission_manager_help.html b/frappe/core/page/permission_manager/permission_manager_help.html index 9c88fbcd01..d2f4136082 100644 --- a/frappe/core/page/permission_manager/permission_manager_help.html +++ b/frappe/core/page/permission_manager/permission_manager_help.html @@ -36,6 +36,6 @@
      1. {%= __("Apart from System Manager, roles with Set User Permissions right can set permissions for other users for that Document Type.") %}
      2. {%= __("If these instructions where not helpful, please add in your suggestions on GitHub Issues.") %} - {%= __("Submit an Issue") %} + {%= __("Submit an Issue") %}

        diff --git a/frappe/desk/page/applications/application_row.html b/frappe/desk/page/applications/application_row.html index 3d04c40349..bac68e2d60 100644 --- a/frappe/desk/page/applications/application_row.html +++ b/frappe/desk/page/applications/application_row.html @@ -5,7 +5,7 @@
        {{ __("Website") }} + href="{{ app.app_url }}" target="_blank" rel="noopener noreferrer">{{ __("Website") }} {% if (app.installed) { %}