mediawiki/extensions/MediaUploader: main (log #957863)

sourcepatches

This run took 51 seconds.

$ date
--- stdout ---
Fri Mar 17 20:27:20 UTC 2023

--- end ---
$ git clone file:///srv/git/mediawiki-extensions-MediaUploader.git repo --depth=1 -b master
--- stderr ---
Cloning into 'repo'...
--- stdout ---

--- end ---
$ git config user.name libraryupgrader
--- stdout ---

--- end ---
$ git config user.email tools.libraryupgrader@tools.wmflabs.org
--- stdout ---

--- end ---
$ git submodule update --init
--- stdout ---

--- end ---
$ grr init
--- stdout ---
Installed commit-msg hook.

--- end ---
$ git show-ref refs/heads/master
--- stdout ---
00db9eafd0d85fedc93a1255e0c6eb5755e64806 refs/heads/master

--- end ---
$ /usr/bin/npm audit --json --legacy-peer-deps
--- stdout ---
{
  "auditReportVersion": 2,
  "vulnerabilities": {
    "qs": {
      "name": "qs",
      "severity": "high",
      "isDirect": false,
      "via": [
        {
          "source": 1090137,
          "name": "qs",
          "dependency": "qs",
          "title": "qs vulnerable to Prototype Pollution",
          "url": "https://github.com/advisories/GHSA-hrpp-h998-j3pp",
          "severity": "high",
          "cwe": [
            "CWE-1321"
          ],
          "cvss": {
            "score": 7.5,
            "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
          },
          "range": ">=6.7.0 <6.7.3"
        }
      ],
      "effects": [],
      "range": "6.7.0 - 6.7.2",
      "nodes": [
        "node_modules/qs"
      ],
      "fixAvailable": true
    }
  },
  "metadata": {
    "vulnerabilities": {
      "info": 0,
      "low": 0,
      "moderate": 0,
      "high": 1,
      "critical": 0,
      "total": 1
    },
    "dependencies": {
      "prod": 1,
      "dev": 430,
      "optional": 0,
      "peer": 0,
      "peerOptional": 0,
      "total": 430
    }
  }
}

--- end ---
$ /usr/bin/composer install
--- stderr ---
No lock file found. Updating dependencies instead of installing from lock file. Use composer update over composer install if you do not have a lock file.
Loading composer repositories with package information
Info from https://repo.packagist.org: #StandWithUkraine
Updating dependencies
Lock file operations: 38 installs, 0 updates, 0 removals
  - Locking composer/pcre (3.1.0)
  - Locking composer/semver (3.3.2)
  - Locking composer/spdx-licenses (1.5.7)
  - Locking composer/xdebug-handler (3.0.3)
  - Locking doctrine/deprecations (v1.0.0)
  - Locking felixfbecker/advanced-json-rpc (v3.2.1)
  - Locking justinrainbow/json-schema (5.2.12)
  - Locking mediawiki/mediawiki-codesniffer (v40.0.1)
  - Locking mediawiki/mediawiki-phan-config (0.12.0)
  - Locking mediawiki/minus-x (1.1.1)
  - Locking mediawiki/phan-taint-check-plugin (4.0.0)
  - Locking microsoft/tolerant-php-parser (v0.1.1)
  - Locking netresearch/jsonmapper (v4.1.0)
  - Locking phan/phan (5.4.1)
  - Locking php-parallel-lint/php-console-color (v1.0.1)
  - Locking php-parallel-lint/php-console-highlighter (v1.0.0)
  - Locking php-parallel-lint/php-parallel-lint (v1.3.2)
  - Locking phpdocumentor/reflection-common (2.2.0)
  - Locking phpdocumentor/reflection-docblock (5.3.0)
  - Locking phpdocumentor/type-resolver (1.7.0)
  - Locking phpstan/phpdoc-parser (1.16.1)
  - Locking psr/container (1.1.2)
  - Locking psr/log (1.1.4)
  - Locking sabre/event (5.1.4)
  - Locking squizlabs/php_codesniffer (3.7.1)
  - Locking symfony/console (v5.4.21)
  - Locking symfony/deprecation-contracts (v2.5.2)
  - Locking symfony/polyfill-ctype (v1.27.0)
  - Locking symfony/polyfill-intl-grapheme (v1.27.0)
  - Locking symfony/polyfill-intl-normalizer (v1.27.0)
  - Locking symfony/polyfill-mbstring (v1.27.0)
  - Locking symfony/polyfill-php73 (v1.27.0)
  - Locking symfony/polyfill-php80 (v1.27.0)
  - Locking symfony/service-contracts (v2.5.2)
  - Locking symfony/string (v5.4.21)
  - Locking symfony/yaml (v5.4.21)
  - Locking tysonandre/var_representation_polyfill (0.1.3)
  - Locking webmozart/assert (1.11.0)
Writing lock file
Installing dependencies from lock file (including require-dev)
Package operations: 38 installs, 0 updates, 0 removals
    0 [>---------------------------]    0 [->--------------------------]    0 [--->------------------------]  - Installing composer/pcre (3.1.0): Extracting archive
  - Installing justinrainbow/json-schema (5.2.12): Extracting archive
  - Installing symfony/polyfill-php80 (v1.27.0): Extracting archive
  - Installing squizlabs/php_codesniffer (3.7.1): Extracting archive
  - Installing symfony/polyfill-mbstring (v1.27.0): Extracting archive
  - Installing composer/spdx-licenses (1.5.7): Extracting archive
  - Installing composer/semver (3.3.2): Extracting archive
  - Installing mediawiki/mediawiki-codesniffer (v40.0.1): Extracting archive
  - Installing tysonandre/var_representation_polyfill (0.1.3): Extracting archive
  - Installing symfony/polyfill-intl-normalizer (v1.27.0): Extracting archive
  - Installing symfony/polyfill-intl-grapheme (v1.27.0): Extracting archive
  - Installing symfony/polyfill-ctype (v1.27.0): Extracting archive
  - Installing symfony/string (v5.4.21): Extracting archive
  - Installing symfony/deprecation-contracts (v2.5.2): Extracting archive
  - Installing psr/container (1.1.2): Extracting archive
  - Installing symfony/service-contracts (v2.5.2): Extracting archive
  - Installing symfony/polyfill-php73 (v1.27.0): Extracting archive
  - Installing symfony/console (v5.4.21): Extracting archive
  - Installing sabre/event (5.1.4): Extracting archive
  - Installing netresearch/jsonmapper (v4.1.0): Extracting archive
  - Installing microsoft/tolerant-php-parser (v0.1.1): Extracting archive
  - Installing webmozart/assert (1.11.0): Extracting archive
  - Installing phpstan/phpdoc-parser (1.16.1): Extracting archive
  - Installing phpdocumentor/reflection-common (2.2.0): Extracting archive
  - Installing doctrine/deprecations (v1.0.0): Extracting archive
  - Installing phpdocumentor/type-resolver (1.7.0): Extracting archive
  - Installing phpdocumentor/reflection-docblock (5.3.0): Extracting archive
  - Installing felixfbecker/advanced-json-rpc (v3.2.1): Extracting archive
  - Installing psr/log (1.1.4): Extracting archive
  - Installing composer/xdebug-handler (3.0.3): Extracting archive
  - Installing phan/phan (5.4.1): Extracting archive
  - Installing mediawiki/phan-taint-check-plugin (4.0.0): Extracting archive
  - Installing mediawiki/mediawiki-phan-config (0.12.0): Extracting archive
  - Installing mediawiki/minus-x (1.1.1): Extracting archive
  - Installing php-parallel-lint/php-console-color (v1.0.1): Extracting archive
  - Installing php-parallel-lint/php-console-highlighter (v1.0.0): Extracting archive
  - Installing php-parallel-lint/php-parallel-lint (v1.3.2): Extracting archive
  - Installing symfony/yaml (v5.4.21): Extracting archive
  0/29 [>---------------------------]   0%
 10/29 [=========>------------------]  34%
 20/29 [===================>--------]  68%
 28/29 [===========================>]  96%
 29/29 [============================] 100%4 package suggestions were added by new dependencies, use `composer suggest` to see details.
Generating autoload files
15 packages you are using are looking for funding.
Use the `composer fund` command to find out more!
--- stdout ---

--- end ---
Upgrading n:eslint-config-wikimedia from 0.20.0 -> 0.24.0
Upgrading n:grunt from 1.5.3 -> 1.6.1
Upgrading n:stylelint-config-wikimedia from 0.13.1 -> 0.14.0
$ /usr/bin/npm install
--- stdout ---

added 437 packages, and audited 438 packages in 5s

66 packages are looking for funding
  run `npm fund` for details

1 high severity vulnerability

To address all issues, run:
  npm audit fix

Run `npm audit` for details.

--- end ---
$ package-lock-lint package-lock.json
--- stdout ---
Checking package-lock.json

--- end ---
$ /usr/bin/npm install grunt-eslint@24.0.0 --save-exact
--- stdout ---

up to date, audited 438 packages in 769ms

66 packages are looking for funding
  run `npm fund` for details

1 high severity vulnerability

To address all issues, run:
  npm audit fix

Run `npm audit` for details.

--- end ---
$ package-lock-lint package-lock.json
--- stdout ---
Checking package-lock.json

--- end ---
$ ./node_modules/.bin/eslint . --fix
--- stdout ---

/src/repo/resources/mw.UploadWizard.js
  4:0  warning  Missing JSDoc @param "uw" type  jsdoc/require-param-type

/src/repo/resources/mw.UploadWizardDetails.js
  796:10  error  ES2015 RegExp 'u' flag is forbidden  es-x/no-regexp-u-flag

/src/repo/resources/mw.UploadWizardUpload.js
    8:0   warning  Missing JSDoc @param "uw" type    jsdoc/require-param-type
  222:16  error    ES2015 'Uint8Array' is forbidden  es-x/no-typed-arrays

/src/repo/tests/qunit/controller/uw.controller.Deed.test.js
  23:3  error  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual  qunit/no-loose-assertions
  24:3  error  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual  qunit/no-loose-assertions
  25:3  error  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual  qunit/no-loose-assertions

/src/repo/tests/qunit/controller/uw.controller.Details.test.js
   51:3  error  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual  qunit/no-loose-assertions
   52:3  error  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual  qunit/no-loose-assertions
   53:3  error  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual  qunit/no-loose-assertions
   71:3  error  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual  qunit/no-loose-assertions
   76:3  error  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual  qunit/no-loose-assertions
   78:3  error  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual  qunit/no-loose-assertions
   85:3  error  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual  qunit/no-loose-assertions
   92:3  error  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual  qunit/no-loose-assertions
  149:6  error  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual  qunit/no-loose-assertions

/src/repo/tests/qunit/controller/uw.controller.Step.test.js
  23:3  error  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual  qunit/no-loose-assertions
  24:3  error  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual  qunit/no-loose-assertions

/src/repo/tests/qunit/controller/uw.controller.Thanks.test.js
  23:3  error  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual  qunit/no-loose-assertions
  24:3  error  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual  qunit/no-loose-assertions
  25:3  error  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual  qunit/no-loose-assertions

/src/repo/tests/qunit/controller/uw.controller.Tutorial.test.js
  23:3  error  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual     qunit/no-loose-assertions
  24:3  error  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual     qunit/no-loose-assertions
  25:3  error  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual     qunit/no-loose-assertions
  26:3  error  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual     qunit/no-loose-assertions
  41:3  error  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual     qunit/no-loose-assertions
  42:3  error  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual     qunit/no-loose-assertions
  48:3  error  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual     qunit/no-loose-assertions
  59:3  error  Unexpected assert.notOk. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual  qunit/no-loose-assertions
  62:3  error  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual     qunit/no-loose-assertions

/src/repo/tests/qunit/controller/uw.controller.Upload.test.js
  26:3  error  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual  qunit/no-loose-assertions
  27:3  error  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual  qunit/no-loose-assertions
  28:3  error  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual  qunit/no-loose-assertions
  40:3  error  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual  qunit/no-loose-assertions
  45:3  error  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual  qunit/no-loose-assertions
  50:3  error  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual  qunit/no-loose-assertions
  77:3  error  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual  qunit/no-loose-assertions

/src/repo/tests/qunit/mw.UploadWizardLicenseInput.test.js
  23:2  error  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual  qunit/no-loose-assertions

/src/repo/tests/qunit/mw.UploadWizardUpload.test.js
  45:3  error  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual  qunit/no-loose-assertions

/src/repo/tests/qunit/transports/mw.FormDataTransport.test.js
   39:3  error  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual     qunit/no-loose-assertions
   48:3  error  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual     qunit/no-loose-assertions
   52:3  error  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual     qunit/no-loose-assertions
   53:3  error  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual     qunit/no-loose-assertions
   60:3  error  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual     qunit/no-loose-assertions
   78:3  error  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual     qunit/no-loose-assertions
   82:3  error  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual     qunit/no-loose-assertions
  102:3  error  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual     qunit/no-loose-assertions
  131:3  error  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual     qunit/no-loose-assertions
  150:4  error  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual     qunit/no-loose-assertions
  183:4  error  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual     qunit/no-loose-assertions
  184:4  error  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual     qunit/no-loose-assertions
  202:4  error  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual     qunit/no-loose-assertions
  203:4  error  Unexpected assert.notOk. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual  qunit/no-loose-assertions
  222:4  error  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual     qunit/no-loose-assertions
  223:4  error  Unexpected assert.notOk. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual  qunit/no-loose-assertions

✖ 55 problems (53 errors, 2 warnings)


--- end ---
$ ./node_modules/.bin/eslint . -f json
--- stdout ---
[{"filePath":"/src/repo/.eslintrc.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/.stylelintrc.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/Gruntfile.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/composer.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/docs/external.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/docs/jsduck-config.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/extension.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/api/en.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/api/qqq.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/ar.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/bg.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/cs.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/de.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/en.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/es.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/fa.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/fi.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/fr.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/gu.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/he.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/hi.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/hy.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/ia.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/id.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/it.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/ja.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/kaa.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/krc.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/ks-arab.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/lb.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/license/en.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/license/qqq.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/mk.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/ms.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/nb.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/pl.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/pnb.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/pt-br.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/pt.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/qqq.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/ru.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/rw.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/se.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/sh.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/shn.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/si.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/skr-arab.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/sl.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/smn.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/sms.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/sr-ec.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/sr-el.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/sv.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/tly.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/tr.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/uk.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/vi.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/zh-hans.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/package-lock.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/package.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/resources/controller/uw.controller.Deed.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/resources/controller/uw.controller.Details.js","messages":[],"suppressedMessages":[{"ruleId":"no-jquery/no-global-selector","severity":2,"message":"Avoid queries which search the entire DOM. Keep DOM nodes in memory where possible.","line":143,"column":56,"nodeType":"CallExpression","endLine":143,"endColumn":89,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/resources/controller/uw.controller.Step.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/resources/controller/uw.controller.Thanks.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/resources/controller/uw.controller.Tutorial.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/resources/controller/uw.controller.Upload.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/resources/controller/uw.controller.base.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/resources/deed/uw.deed.Abstract.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/resources/deed/uw.deed.Custom.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/resources/deed/uw.deed.External.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/resources/deed/uw.deed.None.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/resources/deed/uw.deed.OwnWork.js","messages":[],"suppressedMessages":[{"ruleId":"mediawiki/msg-doc","severity":1,"message":"All possible message keys should be documented. See https://w.wiki/4r9a for details.","line":111,"column":28,"nodeType":"CallExpression","endLine":113,"endColumn":5,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"mediawiki/msg-doc","severity":1,"message":"All possible message keys should be documented. See https://w.wiki/4r9a for details.","line":118,"column":5,"nodeType":"CallExpression","endLine":121,"endColumn":6,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-slide","severity":2,"message":"Prefer CSS transitions to .slideUp","line":284,"column":3,"nodeType":"CallExpression","endLine":285,"endColumn":14,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-animate","severity":2,"message":"Prefer CSS transitions to .animate","line":284,"column":3,"nodeType":"CallExpression","endLine":286,"endColumn":66,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-slide","severity":2,"message":"Prefer CSS transitions to .slideDown","line":304,"column":3,"nodeType":"CallExpression","endLine":305,"endColumn":16,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-animate","severity":2,"message":"Prefer CSS transitions to .animate","line":304,"column":3,"nodeType":"CallExpression","endLine":306,"endColumn":88,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/resources/deed/uw.deed.ThirdParty.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/resources/deed/uw.deed.base.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/resources/details/uw.CategoriesDetailsWidget.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/resources/details/uw.DateDetailsWidget.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/resources/details/uw.DeedChooserDetailsWidget.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/resources/details/uw.DropdownWidget.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/resources/details/uw.LanguageDropdownWidget.js","messages":[],"suppressedMessages":[{"ruleId":"mediawiki/class-doc","severity":1,"message":"All possible CSS classes should be documented. See https://w.wiki/PS2 for details.","line":17,"column":53,"nodeType":"ObjectExpression","endLine":21,"endColumn":4,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/resources/details/uw.LocationDetailsWidget.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/resources/details/uw.MultipleLanguageInputWidget.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/resources/details/uw.SingleLanguageInputWidget.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/resources/details/uw.TextWidget.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/resources/details/uw.TitleDetailsWidget.js","messages":[],"suppressedMessages":[{"ruleId":"mediawiki/msg-doc","severity":1,"message":"All possible message keys should be documented. See https://w.wiki/4r9a for details.","line":216,"column":22,"nodeType":"CallExpression","endLine":216,"endColumn":75,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/resources/details/uw.UlsWidget.js","messages":[],"suppressedMessages":[{"ruleId":"mediawiki/class-doc","severity":1,"message":"All possible CSS classes should be documented. See https://w.wiki/PS2 for details.","line":34,"column":4,"nodeType":"CallExpression","endLine":34,"endColumn":49,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/resources/ext.mediaUploader.campaignEditor.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/resources/handlers/mw.ApiUploadFormDataHandler.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/resources/handlers/mw.ApiUploadHandler.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/resources/jquery.arrowSteps/jquery.arrowSteps.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/resources/jquery/jquery.morphCrossfade.js","messages":[],"suppressedMessages":[{"ruleId":"no-jquery/no-animate","severity":2,"message":"Prefer CSS transitions to .animate","line":105,"column":6,"nodeType":"CallExpression","endLine":107,"endColumn":9,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-animate","severity":2,"message":"Prefer CSS transitions to .animate","line":113,"column":5,"nodeType":"CallExpression","endLine":118,"endColumn":8,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-animate","severity":2,"message":"Prefer CSS transitions to .animate","line":120,"column":5,"nodeType":"CallExpression","endLine":120,"endColumn":54,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/resources/mw.DestinationChecker.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/resources/mw.Escaper.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/resources/mw.GroupProgressBar.js","messages":[],"suppressedMessages":[{"ruleId":"no-jquery/no-fade","severity":2,"message":"Prefer CSS transitions to .fadeIn","line":52,"column":4,"nodeType":"CallExpression","endLine":52,"endColumn":74,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-fade","severity":2,"message":"Prefer CSS transitions to .fadeOut","line":123,"column":4,"nodeType":"CallExpression","endLine":123,"endColumn":75,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/resources/mw.QuickTitleChecker.js","messages":[],"suppressedMessages":[{"ruleId":"no-control-regex","severity":2,"message":"Unexpected control character(s) in regular expression: \\x00, \\x1f.","line":27,"column":4,"nodeType":"Literal","messageId":"unexpected","endLine":27,"endColumn":17,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/resources/mw.UploadWizard.js","messages":[{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"uw\" type.","line":4,"column":null,"nodeType":"Block","endLine":4,"endColumn":null}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/**\n * Object that represents the entire multi-step Upload Wizard\n *\n * @param uw\n */\n( function ( uw ) {\n\n\tmw.UploadWizard = function ( config ) {\n\t\tvar maxSimPref;\n\n\t\tthis.api = this.getApi( { ajax: { timeout: 0 } } );\n\n\t\t// making a sort of global for now, should be done by passing in config or fragments of config\n\t\t// when needed elsewhere\n\t\tmw.UploadWizard.config = config;\n\t\t// Shortcut for local references\n\t\tthis.config = config;\n\n\t\tthis.steps = {};\n\n\t\tmaxSimPref = mw.user.options.get( 'upwiz_maxsimultaneous' );\n\n\t\tif ( maxSimPref !== 'default' ) {\n\t\t\tif ( maxSimPref > 0 ) {\n\t\t\t\tconfig.maxSimultaneousConnections = maxSimPref;\n\t\t\t} else {\n\t\t\t\tconfig.maxSimultaneousConnections = 1;\n\t\t\t}\n\t\t}\n\n\t\tthis.maxSimultaneousConnections = config.maxSimultaneousConnections;\n\n\t\tif ( mw.loader.getState( 'ext.uls.mediawiki' ) !== null ) {\n\t\t\tmw.loader.load( 'ext.uls.mediawiki' );\n\t\t}\n\t};\n\n\tmw.UploadWizard.DEBUG = true;\n\n\tmw.UploadWizard.userAgent = 'UploadWizard';\n\n\tmw.UploadWizard.prototype = {\n\t\t/**\n\t\t * Create the basic interface to make an upload in this div\n\t\t *\n\t\t * @param {string} selector\n\t\t */\n\t\tcreateInterface: function ( selector ) {\n\t\t\tthis.ui = new uw.ui.Wizard( selector );\n\n\t\t\tthis.initialiseSteps().then( function ( steps ) {\n\t\t\t\t// \"select\" the first step - highlight, make it visible, hide all others\n\t\t\t\tsteps[ 0 ].load( [] );\n\t\t\t} );\n\t\t},\n\n\t\t/**\n\t\t * Initialise the steps in the wizard\n\t\t *\n\t\t * @return {jQuery.Promise}\n\t\t */\n\t\tinitialiseSteps: function () {\n\t\t\tvar self = this,\n\t\t\t\tsteps = [],\n\t\t\t\ti,\n\t\t\t\tuploadStep;\n\n\t\t\t// Add the tutorial step if it's enabled\n\t\t\tif ( this.config.tutorial.enabled ) {\n\t\t\t\tsteps.push( new uw.controller.Tutorial( this.api, this.config ) );\n\t\t\t}\n\n\t\t\tuploadStep = new uw.controller.Upload( this.api, this.config );\n\t\t\tsteps.push( uploadStep );\n\n\t\t\t// Add the licensing step if it's enabled\n\t\t\tif ( this.config.licensing.enabled ) {\n\t\t\t\tsteps.push( new uw.controller.Deed( this.api, this.config ) );\n\t\t\t}\n\n\t\t\tsteps.push(\n\t\t\t\tnew uw.controller.Details( this.api, this.config ),\n\t\t\t\tnew uw.controller.Thanks( this.api, this.config )\n\t\t\t);\n\n\t\t\t// The first step obviously does not have a previous step\n\t\t\tsteps[ 0 ].setNextStep( steps[ 1 ] );\n\n\t\t\t// The \"intermediate\" steps can navigate in both directions\n\t\t\tfor ( i = 1; i < steps.length - 1; i++ ) {\n\t\t\t\tsteps[ i ].setPreviousStep( steps[ i - 1 ] );\n\t\t\t\tsteps[ i ].setNextStep( steps[ i + 1 ] );\n\t\t\t}\n\n\t\t\t// The last step does not have a \"previous\" step, there's no undoing uploads!\n\t\t\t// The \"next\" one is always looping back to the upload step\n\t\t\tsteps[ steps.length - 1 ].setNextStep( uploadStep );\n\n\t\t\treturn $.Deferred().resolve( steps ).promise()\n\t\t\t\t.always( function ( stepsInner ) {\n\t\t\t\t\tself.steps = stepsInner;\n\t\t\t\t\tself.ui.initialiseSteps( stepsInner );\n\t\t\t\t} );\n\t\t},\n\n\t\t/**\n\t\t * mw.Api's ajax calls are not very consistent in their error handling.\n\t\t * As long as the response comes back, the response will be fine: it'll\n\t\t * get rejected with the error details there. However, if no response\n\t\t * comes back for whatever reason, things can get confusing.\n\t\t * I'll monkeypatch around such cases so that we can always rely on the\n\t\t * error response the way we want it to be.\n\t\t *\n\t\t * TODO: Instead of this monkeypatching, we could call api.getErrorMessage()\n\t\t * in the error handlers to get nice messages.\n\t\t *\n\t\t * @param {Object} options\n\t\t * @return {mw.Api}\n\t\t */\n\t\tgetApi: function ( options ) {\n\t\t\tvar api = new mw.Api( options );\n\n\t\t\tapi.ajax = function ( parameters, ajaxOptions ) {\n\t\t\t\tvar original, override;\n\n\t\t\t\t$.extend( parameters, {\n\t\t\t\t\terrorformat: 'html',\n\t\t\t\t\terrorlang: mw.config.get( 'wgUserLanguage' ),\n\t\t\t\t\terrorsuselocal: 1,\n\t\t\t\t\tformatversion: 2\n\t\t\t\t} );\n\n\t\t\t\toriginal = mw.Api.prototype.ajax.apply( this, [ parameters, ajaxOptions ] );\n\n\t\t\t\t// we'll attach a default error handler that makes sure error\n\t\t\t\t// output is always, reliably, in the same format\n\t\t\t\toverride = original.then(\n\t\t\t\t\tnull, // done handler - doesn't need overriding\n\t\t\t\t\tfunction ( code, result ) { // fail handler\n\t\t\t\t\t\tvar response = { errors: [ {\n\t\t\t\t\t\t\tcode: code,\n\t\t\t\t\t\t\thtml: result.textStatus || mw.message( 'api-clientside-error-invalidresponse' ).parse()\n\t\t\t\t\t\t} ] };\n\n\t\t\t\t\t\tif ( result.errors && result.errors[ 0 ] ) {\n\t\t\t\t\t\t\t// in case of success-but-has-errors, we have a valid result\n\t\t\t\t\t\t\tresponse = result;\n\t\t\t\t\t\t} else if ( result && result.textStatus === 'timeout' ) {\n\t\t\t\t\t\t\t// in case of $.ajax.fail(), there is no response json\n\t\t\t\t\t\t\tresponse.errors[ 0 ].html = mw.message( 'api-clientside-error-timeout' ).parse();\n\t\t\t\t\t\t} else if ( result && result.textStatus === 'parsererror' ) {\n\t\t\t\t\t\t\tresponse.errors[ 0 ].html = mw.message( 'mediauploader-api-error-parsererror' ).parse();\n\t\t\t\t\t\t} else if ( code === 'http' && result && result.xhr && result.xhr.status === 0 ) {\n\t\t\t\t\t\t\t// failed to even connect to server\n\t\t\t\t\t\t\tresponse.errors[ 0 ].html = mw.message( 'api-clientside-error-noconnect' ).parse();\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn $.Deferred().reject( code, response, response );\n\t\t\t\t\t}\n\t\t\t\t);\n\n\t\t\t\t/*\n\t\t\t\t * After attaching (.then) our error handler, a new promise is\n\t\t\t\t * returned. The original promise had an 'abort' method, which\n\t\t\t\t * we'll also want to make use of...\n\t\t\t\t */\n\t\t\t\treturn override.promise( { abort: original.abort } );\n\t\t\t};\n\n\t\t\treturn api;\n\t\t}\n\t};\n\n\t/**\n\t * Get the own work and third party licensing deeds if they are needed.\n\t *\n\t * @static\n\t * @since 1.2\n\t * @param {mw.UploadWizardUpload[]} uploads\n\t * @param {Object} config The UW config object.\n\t * @param {string[]} [config.licensing.showTypes]\n\t * @return {mw.deed.Abstract[]}\n\t */\n\tmw.UploadWizard.getLicensingDeeds = function ( uploads, config ) {\n\t\tvar deed, api,\n\t\t\tdeeds = {},\n\t\t\tdoOwnWork = config.licensing.showTypes.indexOf( 'ownWork' ) > -1,\n\t\t\tdoThirdParty = config.licensing.showTypes.indexOf( 'thirdParty' ) > -1;\n\n\t\tif ( !config.licensing.enabled ) {\n\t\t\treturn {\n\t\t\t\tnone: new uw.deed.None( config )\n\t\t\t};\n\t\t}\n\n\t\tapi = this.prototype.getApi( { ajax: { timeout: 0 } } );\n\n\t\tif ( doOwnWork ) {\n\t\t\tdeed = new uw.deed.OwnWork( config, uploads, api );\n\t\t\tdeeds[ deed.name ] = deed;\n\t\t}\n\t\tif ( doThirdParty ) {\n\t\t\tdeed = new uw.deed.ThirdParty( config, uploads, api );\n\t\t\tdeeds[ deed.name ] = deed;\n\t\t}\n\n\t\treturn deeds;\n\t};\n\n\t/**\n\t * Helper method to put a thumbnail somewhere.\n\t *\n\t * @param {string|jQuery} selector String representing a jQuery selector, or a jQuery object\n\t * @param {HTMLCanvasElement|HTMLImageElement|null} image\n\t */\n\tmw.UploadWizard.placeThumbnail = function ( selector, image ) {\n\t\tif ( image === null ) {\n\t\t\t$( selector ).addClass( 'mediauploader-file-preview-broken' );\n\t\t\treturn;\n\t\t}\n\n\t\t$( selector )\n\t\t\t.css( { background: 'none' } )\n\t\t\t.prepend(\n\t\t\t\t$( '<a>' )\n\t\t\t\t\t.addClass( 'mediauploader-thumbnail-link' )\n\t\t\t\t\t.append( image )\n\t\t\t);\n\t};\n\n}( mw.uploadWizard ) );\n","usedDeprecatedRules":[]},{"filePath":"/src/repo/resources/mw.UploadWizardDeedChooser.js","messages":[],"suppressedMessages":[{"ruleId":"mediawiki/msg-doc","severity":1,"message":"All possible message keys should be documented. See https://w.wiki/4r9a for details.","line":28,"column":14,"nodeType":"CallExpression","endLine":28,"endColumn":87,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"mediawiki/class-doc","severity":1,"message":"All possible CSS classes should be documented. See https://w.wiki/PS2 for details.","line":32,"column":22,"nodeType":"CallExpression","endLine":32,"endColumn":99,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-global-selector","severity":2,"message":"Avoid queries which search the entire DOM. Keep DOM nodes in memory where possible.","line":104,"column":4,"nodeType":"CallExpression","endLine":104,"endColumn":66,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-sizzle","severity":2,"message":"Selector extensions are not allowed","line":119,"column":5,"nodeType":"CallExpression","endLine":119,"endColumn":27,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-sizzle","severity":2,"message":"Selector extensions are not allowed","line":121,"column":10,"nodeType":"CallExpression","endLine":121,"endColumn":41,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-slide","severity":2,"message":"Prefer CSS transitions to .slideUp","line":126,"column":6,"nodeType":"CallExpression","endLine":126,"endColumn":26,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-fade","severity":2,"message":"Prefer CSS transitions to .fadeTo","line":141,"column":4,"nodeType":"CallExpression","endLine":141,"endColumn":62,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-sizzle","severity":2,"message":"Selector extensions are not allowed","line":147,"column":5,"nodeType":"CallExpression","endLine":147,"endColumn":27,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-sizzle","severity":2,"message":"Selector extensions are not allowed","line":149,"column":10,"nodeType":"CallExpression","endLine":149,"endColumn":31,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-slide","severity":2,"message":"Prefer CSS transitions to .slideUp","line":153,"column":6,"nodeType":"CallExpression","endLine":153,"endColumn":31,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-slide","severity":2,"message":"Prefer CSS transitions to .slideDown","line":157,"column":5,"nodeType":"CallExpression","endLine":157,"endColumn":27,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/resources/mw.UploadWizardDetails.js","messages":[{"ruleId":"es-x/no-regexp-u-flag","severity":2,"message":"ES2015 RegExp 'u' flag is forbidden.","line":796,"column":10,"nodeType":"NewExpression","messageId":"forbidden","endLine":796,"endColumn":84}],"suppressedMessages":[],"errorCount":1,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"source":"( function ( uw ) {\n\n\tvar NS_FILE = mw.config.get( 'wgNamespaceIds' ).file;\n\n\t/**\n\t * Object that represents the Details (step 2) portion of the UploadWizard\n\t * n.b. each upload gets its own details.\n\t *\n\t * @param {mw.UploadWizardUpload} upload\n\t * @param {jQuery} $containerDiv The `div` to put the interface into\n\t */\n\tmw.UploadWizardDetails = function ( upload, $containerDiv ) {\n\t\tthis.upload = upload;\n\t\tthis.$containerDiv = $containerDiv;\n\t\tthis.api = upload.api;\n\n\t\tthis.fieldList = [];\n\t\tthis.fieldMap = {};\n\t\tthis.fieldWrapperList = [];\n\t\tthis.fieldWrapperMap = {};\n\n\t\t// This widget has to be initialized early for\n\t\t// useCustomDeedChooser() to work.\n\t\tthis.deedChooserDetails = new uw.DeedChooserDetailsWidget();\n\t\tthis.customDeedChooser = false;\n\n\t\tif ( !mw.UploadWizard.config.licensing.enabled ) {\n\t\t\t// Create a pseudo-deed chooser if licensing is disabled\n\t\t\tthis.upload.deedChooser = {\n\t\t\t\tdeed: new uw.deed.None( mw.UploadWizard.config )\n\t\t\t};\n\t\t}\n\n\t\tthis.$div = $( '<div>' ).addClass( 'mediauploader-info-file ui-helper-clearfix filled' );\n\t};\n\n\tmw.UploadWizardDetails.prototype = {\n\t\t// Has this details object been attached to the DOM already?\n\t\tisAttached: false,\n\n\t\t/**\n\t\t * Build the interface and attach all elements - do this on demand.\n\t\t */\n\t\tbuildInterface: function () {\n\t\t\tvar $moreDetailsWrapperDiv, $moreDetailsDiv,\n\t\t\t\tfKey, fSpec, fieldWidget, fieldWrapper, fConfigBase,\n\t\t\t\tdetails = this,\n\t\t\t\tconfig = mw.UploadWizard.config;\n\n\t\t\tthis.$thumbnailDiv = $( '<div>' ).addClass( 'mediauploader-thumbnail mediauploader-thumbnail-side' );\n\n\t\t\tthis.$dataDiv = $( '<div>' ).addClass( 'mediauploader-data' );\n\n\t\t\tfor ( fKey in config.fields ) {\n\t\t\t\t// Make a deep copy\n\t\t\t\tfSpec = $.extend( {}, config.fields[ fKey ] );\n\t\t\t\tfSpec.key = fKey;\n\t\t\t\tfSpec.enabled = fSpec.enabled === undefined ? true : fSpec.enabled;\n\t\t\t\t// Override the label in case it wasn't set\n\t\t\t\tfSpec.label = fSpec.label ? $( $.parseHTML( fSpec.label ) ) : fSpec.key;\n\n\t\t\t\t// Common settings for all fields\n\t\t\t\tfConfigBase = {\n\t\t\t\t\trequired: fSpec.required === 'required',\n\t\t\t\t\trecommended: fSpec.required === 'recommended',\n\t\t\t\t\tfieldName: fSpec.label,\n\t\t\t\t\tdisabled: !fSpec.enabled\n\t\t\t\t};\n\n\t\t\t\tfieldWidget = null;\n\t\t\t\tswitch ( fSpec.type ) {\n\t\t\t\t\tcase 'title':\n\t\t\t\t\t\tfieldWidget = new uw.TitleDetailsWidget( $.extend( {}, fConfigBase, {\n\t\t\t\t\t\t\t// Normalize file extension, e.g. 'JPEG' to 'jpg'\n\t\t\t\t\t\t\textension: mw.UploadWizard.config.content.titleField === fKey ?\n\t\t\t\t\t\t\t\tmw.Title.normalizeExtension( this.upload.title.getExtension() ) : '',\n\t\t\t\t\t\t\tminLength: fSpec.minLength || 5,\n\t\t\t\t\t\t\tmaxLength: fSpec.maxLength || 240\n\t\t\t\t\t\t} ) );\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 'text':\n\t\t\t\t\tcase 'textarea':\n\t\t\t\t\t\tfieldWidget = new uw.TextWidget( $.extend( {}, fConfigBase, {\n\t\t\t\t\t\t\tdisabled: !fSpec.enabled,\n\t\t\t\t\t\t\tminLength: fSpec.minLength,\n\t\t\t\t\t\t\tmaxLength: fSpec.maxLength\n\t\t\t\t\t\t} ) );\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 'singlelang':\n\t\t\t\t\t\tfieldWidget = new uw.SingleLanguageInputWidget( $.extend( {}, fConfigBase, {\n\t\t\t\t\t\t\tcanBeRemoved: false,\n\t\t\t\t\t\t\tlanguages: this.getLanguageOptions(),\n\t\t\t\t\t\t\tminLength: fSpec.minLength,\n\t\t\t\t\t\t\tmaxLength: fSpec.maxLength\n\t\t\t\t\t\t} ) );\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 'multilang':\n\t\t\t\t\t\tfieldWidget = new uw.MultipleLanguageInputWidget( $.extend( {}, fConfigBase, {\n\t\t\t\t\t\t\tlanguages: this.getLanguageOptions(),\n\t\t\t\t\t\t\tminLength: fSpec.minLength,\n\t\t\t\t\t\t\tmaxLength: fSpec.maxLength\n\t\t\t\t\t\t} ) );\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 'select':\n\t\t\t\t\t\tfieldWidget = new uw.DropdownWidget( $.extend( {}, fConfigBase, {\n\t\t\t\t\t\t\toptions: fSpec.options\n\t\t\t\t\t\t} ) );\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 'license':\n\t\t\t\t\t\tfieldWidget = this.deedChooserDetails;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 'date':\n\t\t\t\t\t\tfieldWidget = new uw.DateDetailsWidget( $.extend( {}, fConfigBase, {\n\t\t\t\t\t\t\tupload: this.upload\n\t\t\t\t\t\t} ) );\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 'location':\n\t\t\t\t\t\tfieldWidget = new uw.LocationDetailsWidget( $.extend( {}, fConfigBase, {\n\t\t\t\t\t\t\tfields: fSpec.fields\n\t\t\t\t\t\t} ) );\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 'categories':\n\t\t\t\t\t\tfieldWidget = new uw.CategoriesDetailsWidget( $.extend( {}, fConfigBase, {\n\t\t\t\t\t\t\thiddenDefault: fSpec.hiddenDefault,\n\t\t\t\t\t\t\tmissingWikitext: fSpec.missingWikitext\n\t\t\t\t\t\t} ) );\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\t// Can't build the widget, ignore it\n\t\t\t\t\t\tmw.error( \"Can't build details widget\", fSpec );\n\t\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tthis.fieldList.push( fSpec );\n\t\t\t\tthis.fieldMap[ fKey ] = fieldWidget;\n\t\t\t}\n\n\t\t\tthis.fieldList.sort( function ( a, b ) {\n\t\t\t\tif ( a.order < b.order ) {\n\t\t\t\t\treturn -1;\n\t\t\t\t}\n\t\t\t\tif ( a.order > b.order ) {\n\t\t\t\t\treturn 1;\n\t\t\t\t}\n\t\t\t\treturn 0;\n\t\t\t} );\n\n\t\t\t// Build the form for the file upload\n\t\t\tthis.$form = $( '<form id=\"mediauploader-detailsform' + this.upload.index + '\"></form>' )\n\t\t\t\t.addClass( 'detailsForm' );\n\t\t\t$moreDetailsDiv = $( '<div>' );\n\n\t\t\tthis.fieldList.forEach( function ( spec ) {\n\t\t\t\tfieldWidget = this.fieldMap[ spec.key ];\n\t\t\t\tfieldWrapper = new uw.FieldLayout( fieldWidget, {\n\t\t\t\t\trequired: spec.type === 'license' || spec.required === 'required',\n\t\t\t\t\tlabel: spec.label,\n\t\t\t\t\thelp: spec.help ? $( $.parseHTML( spec.help ) ) : null\n\t\t\t\t} );\n\t\t\t\tif ( spec.type === 'license' ) {\n\t\t\t\t\tfieldWrapper.toggle( this.customDeedChooser ); // See useCustomDeedChooser()\n\t\t\t\t} else if ( spec.hidden ) {\n\t\t\t\t\tfieldWrapper.toggle( false );\n\t\t\t\t}\n\n\t\t\t\t// Apply field defaults\n\t\t\t\tthis.prefillField( spec, fieldWidget );\n\n\t\t\t\t// List of fields for validation etc.\n\t\t\t\tthis.fieldWrapperList.push( fieldWrapper );\n\t\t\t\tthis.fieldWrapperMap[ spec.key ] = fieldWrapper;\n\n\t\t\t\t// Add the field wrapper to HTML of the form\n\t\t\t\tif ( spec.auxiliary ) {\n\t\t\t\t\t$moreDetailsDiv.append( fieldWrapper.$element );\n\t\t\t\t\t// If something changes the input \"hidden\" in the collapsed section,\n\t\t\t\t\t// expand it.\n\t\t\t\t\tfieldWidget.on( 'change', function () {\n\t\t\t\t\t\t$moreDetailsWrapperDiv.data( 'mw-collapsible' ).expand();\n\t\t\t\t\t} );\n\t\t\t\t} else {\n\t\t\t\t\tthis.$form.append( fieldWrapper.$element );\n\t\t\t\t}\n\t\t\t}, this );\n\n\t\t\t// Wrap the auxiliary fields in a dropdown\n\t\t\t$moreDetailsWrapperDiv = $( '<div>' ).addClass( 'mwe-more-details' );\n\t\t\t$moreDetailsWrapperDiv\n\t\t\t\t.append(\n\t\t\t\t\t$( '<a>' ).text( mw.msg( 'mediauploader-more-options' ) )\n\t\t\t\t\t\t.addClass( 'mediauploader-details-more-options mw-collapsible-toggle mw-collapsible-arrow' ),\n\t\t\t\t\t$moreDetailsDiv.addClass( 'mw-collapsible-content' )\n\t\t\t\t)\n\t\t\t\t.makeCollapsible( { collapsed: true } );\n\n\t\t\tthis.$form.on( 'submit', function ( e ) {\n\t\t\t\t// Prevent actual form submission\n\t\t\t\te.preventDefault();\n\t\t\t} );\n\n\t\t\tthis.$form.append(\n\t\t\t\t$moreDetailsWrapperDiv\n\t\t\t);\n\n\t\t\t// Add in remove control to form\n\t\t\tthis.removeCtrl = new OO.ui.ButtonWidget( {\n\t\t\t\tlabel: mw.message( 'mediauploader-remove' ).text(),\n\t\t\t\ttitle: mw.message( 'mediauploader-remove-upload' ).text(),\n\t\t\t\tclasses: [ 'mediauploader-remove-upload' ],\n\t\t\t\tflags: 'destructive',\n\t\t\t\ticon: 'trash',\n\t\t\t\tframed: false\n\t\t\t} ).on( 'click', function () {\n\t\t\t\tOO.ui.confirm( mw.message( 'mediauploader-license-confirm-remove' ).text(), {\n\t\t\t\t\ttitle: mw.message( 'mediauploader-license-confirm-remove-title' ).text()\n\t\t\t\t} ).done( function ( confirmed ) {\n\t\t\t\t\tif ( confirmed ) {\n\t\t\t\t\t\tdetails.upload.emit( 'remove-upload' );\n\t\t\t\t\t}\n\t\t\t\t} );\n\t\t\t} );\n\n\t\t\tthis.$thumbnailDiv.append( this.removeCtrl.$element );\n\n\t\t\tthis.statusMessage = new OO.ui.MessageWidget( { inline: true } );\n\t\t\tthis.statusMessage.toggle( false );\n\t\t\tthis.$spinner = $.createSpinner( { size: 'small', type: 'inline' } );\n\t\t\tthis.$spinner.hide();\n\t\t\tthis.$indicator = $( '<div>' ).addClass( 'mediauploader-file-indicator' ).append(\n\t\t\t\tthis.$spinner,\n\t\t\t\tthis.statusMessage.$element\n\t\t\t);\n\n\t\t\tthis.$submittingDiv = $( '<div>' ).addClass( 'mediauploader-submitting' )\n\t\t\t\t.append(\n\t\t\t\t\tthis.$indicator,\n\t\t\t\t\t$( '<div>' ).addClass( 'mediauploader-details-texts' ).append(\n\t\t\t\t\t\t$( '<div>' ).addClass( 'mediauploader-visible-file-filename-text' ),\n\t\t\t\t\t\t$( '<div>' ).addClass( 'mediauploader-file-status-line' )\n\t\t\t\t\t)\n\t\t\t\t);\n\n\t\t\tthis.$dataDiv.append(\n\t\t\t\tthis.$form,\n\t\t\t\tthis.$submittingDiv\n\t\t\t).morphCrossfader();\n\n\t\t\tthis.$div.append(\n\t\t\t\tthis.$thumbnailDiv,\n\t\t\t\tthis.$dataDiv\n\t\t\t);\n\n\t\t\t// This must match the CSS dimensions of .mediauploader-thumbnail\n\t\t\tthis.upload.getThumbnail( 230 ).done( function ( thumb ) {\n\t\t\t\tmw.UploadWizard.placeThumbnail( this.$thumbnailDiv, thumb );\n\t\t\t}, this );\n\n\t\t\tthis.interfaceBuilt = true;\n\n\t\t\tif ( this.savedSerialData ) {\n\t\t\t\tthis.setSerialized( this.savedSerialData );\n\t\t\t\tthis.savedSerialData = undefined;\n\t\t\t}\n\t\t},\n\n\t\t/*\n\t\t * Append the div for this details object to the DOM.\n\t\t * We need to ensure that we add divs in the right order\n\t\t * (the order in which the user selected files).\n\t\t *\n\t\t * Will only append once.\n\t\t */\n\t\tattach: function () {\n\t\t\tvar $window = $( window ),\n\t\t\t\tdetails = this;\n\n\t\t\tfunction maybeBuild() {\n\t\t\t\tif ( !this.interfaceBuilt && $window.scrollTop() + $window.height() + 1000 >= details.$div.offset().top ) {\n\t\t\t\t\tdetails.buildInterface();\n\t\t\t\t\t$window.off( 'scroll', maybeBuild );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif ( !this.isAttached ) {\n\t\t\t\tthis.$containerDiv.append( this.$div );\n\n\t\t\t\tif ( $window.scrollTop() + $window.height() + 1000 >= this.$div.offset().top ) {\n\t\t\t\t\tthis.buildInterface();\n\t\t\t\t} else {\n\t\t\t\t\t$window.on( 'scroll', maybeBuild );\n\t\t\t\t}\n\n\t\t\t\tthis.isAttached = true;\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Get file page title for this upload.\n\t\t *\n\t\t * @return {mw.Title|null}\n\t\t */\n\t\tgetTitle: function () {\n\t\t\tvar titleField = mw.UploadWizard.config.content.titleField;\n\n\t\t\t// title will not be set until we've actually submitted the file\n\t\t\tif ( this.title === undefined ) {\n\t\t\t\treturn this.fieldMap[ titleField ].getTitle();\n\t\t\t}\n\n\t\t\t// once the file has been submitted, we'll have confirmation on\n\t\t\t// the filename and trust the authoritative source over own input\n\t\t\treturn this.title;\n\t\t},\n\n\t\t/**\n\t\t * Display error message about multiple uploaded files with the same title specified\n\t\t *\n\t\t * @return {mw.UploadWizardDetails}\n\t\t * @chainable\n\t\t */\n\t\tsetDuplicateTitleError: function () {\n\t\t\tvar titleField = mw.UploadWizard.config.content.titleField;\n\t\t\t// TODO This should give immediate response, not only when submitting the form\n\t\t\tthis.fieldWrapperMap[ titleField ].setErrors(\n\t\t\t\t[ mw.message( 'mediauploader-error-title-duplicate' ) ]\n\t\t\t);\n\t\t\treturn this;\n\t\t},\n\n\t\t/**\n\t\t * Toggles whether we use the 'macro' deed or our own.\n\t\t */\n\t\tuseCustomDeedChooser: function () {\n\t\t\tthis.customDeedChooser = true;\n\t\t\tthis.deedChooserDetails.useCustomDeedChooser( this.upload );\n\t\t},\n\n\t\t/**\n\t\t * @private\n\t\t *\n\t\t * @return {uw.FieldLayout[]}\n\t\t */\n\t\tgetAllFields: function () {\n\t\t\treturn [].concat(\n\t\t\t\tthis.fieldWrapperList,\n\t\t\t\tthis.upload.deedChooser.deed ? this.upload.deedChooser.deed.getFields() : []\n\t\t\t);\n\t\t},\n\n\t\t/**\n\t\t * Check all the fields for validity.\n\t\t *\n\t\t * @return {jQuery.Promise} Promise resolved with multiple array arguments, each containing a\n\t\t *   list of error messages for a single field. If API requests necessary to check validity\n\t\t *   fail, the promise may be rejected. The form is valid if the promise is resolved with all\n\t\t *   empty arrays.\n\t\t */\n\t\tgetErrors: function () {\n\t\t\treturn $.when.apply( $, this.getAllFields().map( function ( fieldLayout ) {\n\t\t\t\treturn fieldLayout.fieldWidget.getErrors();\n\t\t\t} ) );\n\t\t},\n\n\t\t/**\n\t\t * Check all the fields for warnings.\n\t\t *\n\t\t * @return {jQuery.Promise} Same as #getErrors\n\t\t */\n\t\tgetWarnings: function () {\n\t\t\treturn $.when.apply( $, this.getAllFields().map( function ( fieldLayout ) {\n\t\t\t\treturn fieldLayout.fieldWidget.getWarnings();\n\t\t\t} ) );\n\t\t},\n\n\t\t/**\n\t\t * Check all the fields for errors and warnings and display them in the UI.\n\t\t *\n\t\t * @param {boolean} thorough True to perform a thorough validity check. Defaults to false for a fast on-change check.\n\t\t * @return {jQuery.Promise} Combined promise of all fields' validation results.\n\t\t */\n\t\tcheckValidity: function ( thorough ) {\n\t\t\tvar fields = this.getAllFields();\n\n\t\t\treturn $.when.apply( $, fields.map( function ( fieldLayout ) {\n\t\t\t\t// Update any error/warning messages\n\t\t\t\treturn fieldLayout.checkValidity( thorough );\n\t\t\t} ) );\n\t\t},\n\n\t\t/**\n\t\t * Get a thumbnail caption for this upload (basically, the first caption).\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tgetThumbnailCaption: function () {\n\t\t\tvar captionField = mw.UploadWizard.config.content.captionField;\n\n\t\t\t// The caption field should be one of:\n\t\t\t// TextWidget, SingleLanguageInputWidget, MultipleLanguageInputWidget\n\t\t\treturn this.fieldMap[ captionField ].getCaption();\n\t\t},\n\n\t\t/**\n\t\t * Prefills the statically (defaults) and dynamically available info\n\t\t * for the file (from EXIF etc.), for the given field.\n\t\t *\n\t\t * @param {Object} fSpec\n\t\t * @param {uw.DetailsWidget} widget\n\t\t */\n\t\tprefillField: function ( fSpec, widget ) {\n\t\t\tvar dynPrefilled = false;\n\n\t\t\t// Try dynamic prefilling, if requested and available for this type\n\t\t\tif ( fSpec.autoFill ) {\n\t\t\t\tswitch ( fSpec.type ) {\n\t\t\t\t\tcase 'title':\n\t\t\t\t\t\tdynPrefilled = this.prefillTitle( widget );\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 'text':\n\t\t\t\t\tcase 'textarea':\n\t\t\t\t\tcase 'singlelang':\n\t\t\t\t\tcase 'multilang':\n\t\t\t\t\t\tdynPrefilled = this.prefillDescription( fSpec.type, widget );\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 'date':\n\t\t\t\t\t\tdynPrefilled = this.prefillDate( widget );\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 'location':\n\t\t\t\t\t\tdynPrefilled = this.prefillLocation( widget );\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif ( !dynPrefilled && fSpec.default !== undefined ) {\n\t\t\t\twidget.setSerialized( fSpec.default );\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Check if we got an EXIF date back and enter it into the details\n\t\t * XXX We ought to be using date + time here...\n\t\t * EXIF examples tend to be in ISO 8601, but the separators are sometimes things like colons, and they have lots of trailing info\n\t\t * (which we should actually be using, such as time and timezone)\n\t\t *\n\t\t * @param {uw.DateDetailsWidget} widget\n\t\t * @return {boolean}\n\t\t */\n\t\tprefillDate: function ( widget ) {\n\t\t\tvar dateObj, metadata, dateStr, saneTime,\n\t\t\t\tdateMode = 'calendar',\n\t\t\t\tyyyyMmDdRegex = /^(\\d\\d\\d\\d)[:/-](\\d\\d)[:/-](\\d\\d)\\D.*/,\n\t\t\t\ttimeRegex = /\\D(\\d\\d):(\\d\\d):(\\d\\d)/;\n\n\t\t\t// XXX surely we have this function somewhere already\n\t\t\tfunction pad( n ) {\n\t\t\t\treturn ( n < 10 ? '0' : '' ) + String( n );\n\t\t\t}\n\n\t\t\tfunction getSaneTime( date ) {\n\t\t\t\tvar str = '';\n\n\t\t\t\tstr += pad( date.getHours() ) + ':';\n\t\t\t\tstr += pad( date.getMinutes() ) + ':';\n\t\t\t\tstr += pad( date.getSeconds() );\n\n\t\t\t\treturn str;\n\t\t\t}\n\n\t\t\tif ( this.upload.imageinfo.metadata ) {\n\t\t\t\tmetadata = this.upload.imageinfo.metadata;\n\t\t\t\t[ 'datetimeoriginal', 'datetimedigitized', 'datetime', 'date' ].some( function ( propName ) {\n\t\t\t\t\tvar matches, timeMatches,\n\t\t\t\t\t\tdateInfo = metadata[ propName ];\n\t\t\t\t\tif ( dateInfo ) {\n\t\t\t\t\t\tmatches = dateInfo.trim().match( yyyyMmDdRegex );\n\t\t\t\t\t\tif ( matches ) {\n\t\t\t\t\t\t\ttimeMatches = dateInfo.trim().match( timeRegex );\n\t\t\t\t\t\t\tif ( timeMatches ) {\n\t\t\t\t\t\t\t\tdateObj = new Date( parseInt( matches[ 1 ], 10 ),\n\t\t\t\t\t\t\t\t\tparseInt( matches[ 2 ], 10 ) - 1,\n\t\t\t\t\t\t\t\t\tparseInt( matches[ 3 ], 10 ),\n\t\t\t\t\t\t\t\t\tparseInt( timeMatches[ 1 ], 10 ),\n\t\t\t\t\t\t\t\t\tparseInt( timeMatches[ 2 ], 10 ),\n\t\t\t\t\t\t\t\t\tparseInt( timeMatches[ 3 ], 10 ) );\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tdateObj = new Date( parseInt( matches[ 1 ], 10 ),\n\t\t\t\t\t\t\t\t\tparseInt( matches[ 2 ], 10 ) - 1,\n\t\t\t\t\t\t\t\t\tparseInt( matches[ 3 ], 10 ) );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn true; // break from Array.some\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn false;\n\t\t\t\t} );\n\t\t\t}\n\n\t\t\t// if we don't have EXIF or other metadata, just don't put a date in.\n\t\t\t// XXX if we have FileAPI, it might be clever to look at file attrs, saved\n\t\t\t// in the upload object for use here later, perhaps\n\t\t\tif ( dateObj === undefined ) {\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tdateStr = dateObj.getFullYear() + '-' + pad( dateObj.getMonth() + 1 ) + '-' + pad( dateObj.getDate() );\n\n\t\t\t// Add the time\n\t\t\t// If the date but not the time is set in EXIF data, we'll get a bogus\n\t\t\t// time value of '00:00:00'.\n\t\t\t// FIXME: Check for missing time value earlier rather than blacklisting\n\t\t\t// a potentially legitimate time value.\n\t\t\tsaneTime = getSaneTime( dateObj );\n\t\t\tif ( saneTime !== '00:00:00' ) {\n\t\t\t\tdateStr += ' ' + saneTime;\n\n\t\t\t\t// Switch to freeform date field. DateInputWidget (with calendar) handles dates only, not times.\n\t\t\t\tdateMode = 'arbitrary';\n\t\t\t}\n\n\t\t\t// ok by now we should definitely have a dateObj and a date string\n\t\t\twidget.setSerialized( {\n\t\t\t\tmode: dateMode,\n\t\t\t\tvalue: dateStr\n\t\t\t} );\n\n\t\t\treturn true;\n\t\t},\n\n\t\t/**\n\t\t * Set the title of the thing we just uploaded, visibly\n\t\t *\n\t\t * @param {uw.TitleDetailsWidget} widget\n\t\t * @return {boolean}\n\t\t */\n\t\tprefillTitle: function ( widget ) {\n\t\t\twidget.setSerialized( {\n\t\t\t\ttitle: this.upload.title.getNameText()\n\t\t\t} );\n\t\t\treturn true;\n\t\t},\n\n\t\t/**\n\t\t * Prefill the image description if we have a description\n\t\t *\n\t\t * Note that this is not related to specifying the description from the query\n\t\t * string (that happens earlier). This is for when we have retrieved a\n\t\t * description from an upload_by_url upload or from the metadata.\n\t\t *\n\t\t * @param {string} type\n\t\t * @param {uw.TextWidget} widget\n\t\t * @return {boolean}\n\t\t */\n\t\tprefillDescription: function ( type, widget ) {\n\t\t\tvar m, descText;\n\n\t\t\tif (\n\t\t\t\twidget.getWikiText() === '' &&\n\t\t\t\tthis.upload.file !== undefined\n\t\t\t) {\n\t\t\t\tm = this.upload.imageinfo.metadata;\n\t\t\t\tdescText = this.upload.file.description ||\n\t\t\t\t\t( m && m.imagedescription && m.imagedescription[ 0 ] && m.imagedescription[ 0 ].value );\n\n\t\t\t\tif ( !descText ) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\t// strip out any HTML tags\n\t\t\t\tdescText = descText.replace( /<[^>]+>/g, '' );\n\n\t\t\t\t// Set the text – both singlelang and multilang can fall back to\n\t\t\t\t// a simple string serialization.\n\t\t\t\twidget.setSerialized( descText.trim() );\n\n\t\t\t\t// Set the language – probably wrong in many cases...\n\t\t\t\tif ( type === 'singlelang' ) {\n\t\t\t\t\twidget.setLanguage(\n\t\t\t\t\t\twidget.getClosestAllowedLanguage( mw.config.get( 'wgContentLanguage' ) )\n\t\t\t\t\t);\n\t\t\t\t} else if ( type === 'multilang' ) {\n\t\t\t\t\twidget.getItems()[ 0 ].setLanguage(\n\t\t\t\t\t\twidget.getItems()[ 0 ].getClosestAllowedLanguage(\n\t\t\t\t\t\t\tmw.config.get( 'wgContentLanguage' )\n\t\t\t\t\t\t)\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\treturn false;\n\t\t},\n\n\t\t/**\n\t\t * Prefill location input from image info and metadata\n\t\t *\n\t\t * As of MediaWiki 1.18, the exif parser translates the rational GPS data tagged by the camera\n\t\t * to decimal format. Let's just use that.\n\t\t *\n\t\t * @param {uw.LocationDetailsWidget} widget\n\t\t * @return {boolean}\n\t\t */\n\t\tprefillLocation: function ( widget ) {\n\t\t\tvar dir,\n\t\t\t\tm = this.upload.imageinfo.metadata,\n\t\t\t\tmodified = false,\n\t\t\t\tvalues = {};\n\n\t\t\tif ( m ) {\n\t\t\t\tdir = m.gpsimgdirection || m.gpsdestbearing;\n\n\t\t\t\tif ( dir ) {\n\t\t\t\t\tif ( dir.match( /^\\d+\\/\\d+$/ ) !== null ) {\n\t\t\t\t\t\t// Apparently it can take the form \"x/y\" instead of\n\t\t\t\t\t\t// a decimal value. Mighty silly, but let's save it.\n\t\t\t\t\t\tdir = dir.split( '/' );\n\t\t\t\t\t\tdir = parseInt( dir[ 0 ], 10 ) / parseInt( dir[ 1 ], 10 );\n\t\t\t\t\t}\n\n\t\t\t\t\tvalues.heading = dir;\n\n\t\t\t\t\tmodified = true;\n\t\t\t\t}\n\n\t\t\t\t// Prefill useful stuff only\n\t\t\t\tif ( Number( m.gpslatitude ) && Number( m.gpslongitude ) ) {\n\t\t\t\t\tvalues.latitude = m.gpslatitude;\n\t\t\t\t\tvalues.longitude = m.gpslongitude;\n\t\t\t\t\tmodified = true;\n\t\t\t\t} else if (\n\t\t\t\t\tthis.upload.file &&\n\t\t\t\t\tthis.upload.file.location &&\n\t\t\t\t\tthis.upload.file.location.latitude &&\n\t\t\t\t\tthis.upload.file.location.longitude\n\t\t\t\t) {\n\t\t\t\t\tvalues.latitude = this.upload.file.location.latitude;\n\t\t\t\t\tvalues.longitude = this.upload.file.location.longitude;\n\t\t\t\t\tmodified = true;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif ( modified ) {\n\t\t\t\twidget.setSerialized( values );\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\treturn false;\n\t\t},\n\n\t\t/**\n\t\t * Returns the language list to use in (Single|Multiple)LanguageInputWidget\n\t\t *\n\t\t * @return {Object}\n\t\t */\n\t\tgetLanguageOptions: function () {\n\t\t\tvar languages, code;\n\n\t\t\tlanguages = {};\n\t\t\tfor ( code in mw.UploadWizard.config.languages ) {\n\t\t\t\tif ( Object.prototype.hasOwnProperty.call( mw.UploadWizard.config.languages, code ) ) {\n\t\t\t\t\tlanguages[ code ] = mw.UploadWizard.config.languages[ code ];\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn languages;\n\t\t},\n\n\t\t/**\n\t\t * Get a machine-readable representation of the current state of the upload details. It can be\n\t\t * passed to #setSerialized to restore this state (or to set it for another instance of the same\n\t\t * class).\n\t\t *\n\t\t * Note that this doesn't include custom deed's state.\n\t\t *\n\t\t * @return {Object.<string,Object>}\n\t\t */\n\t\tgetSerialized: function () {\n\t\t\tvar fieldWidget, serialized = {};\n\n\t\t\tif ( !this.interfaceBuilt ) {\n\t\t\t\t// We don't have the interface yet, but it'll get filled out as\n\t\t\t\t// needed.\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tthis.fieldList.forEach( function ( fSpec ) {\n\t\t\t\tfieldWidget = this.fieldMap[ fSpec.key ];\n\t\t\t\tserialized[ fSpec.key ] = fieldWidget.getSerialized();\n\t\t\t}, this );\n\n\t\t\treturn serialized;\n\t\t},\n\n\t\t/**\n\t\t * Set the state of this widget from machine-readable representation, as returned by\n\t\t * #getSerialized.\n\t\t *\n\t\t * Fields from the representation can be omitted to keep the current value.\n\t\t *\n\t\t * @param {Object.<string,Object>} [serialized]\n\t\t */\n\t\tsetSerialized: function ( serialized ) {\n\t\t\tif ( !this.interfaceBuilt ) {\n\t\t\t\t// There's no interface yet! Don't load the data, just keep it\n\t\t\t\t// around.\n\t\t\t\tif ( serialized === undefined ) {\n\t\t\t\t\t// Note: This will happen if we \"undo\" a copy operation while\n\t\t\t\t\t// some of the details interfaces aren't loaded.\n\t\t\t\t\tthis.savedSerialData = undefined;\n\t\t\t\t} else {\n\t\t\t\t\tthis.savedSerialData = $.extend( true,\n\t\t\t\t\t\tthis.savedSerialData || {},\n\t\t\t\t\t\tserialized\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif ( serialized === undefined ) {\n\t\t\t\t// This is meaningless if the interface is already built.\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tthis.fieldList.forEach( function ( fSpec ) {\n\t\t\t\tif ( serialized[ fSpec.key ] ) {\n\t\t\t\t\tthis.fieldMap[ fSpec.key ].setSerialized( serialized[ fSpec.key ] );\n\t\t\t\t}\n\t\t\t}, this );\n\t\t},\n\n\t\t/**\n\t\t * Convert entire details for this file into wikiText, which will then be posted to the file\n\t\t *\n\t\t * This function assumes that all input is valid.\n\t\t *\n\t\t * @return {string} wikitext representing all details\n\t\t */\n\t\tgetWikiText: function () {\n\t\t\tvar wikiText = mw.UploadWizard.config.content.wikitext,\n\t\t\t\tsubstitutions = {}, substList = [],\n\t\t\t\tdeed = this.upload.deedChooser.deed,\n\t\t\t\tfieldWidget, serialized, valueType, re, escapedKey, replaceValue;\n\n\t\t\tif ( !wikiText ) {\n\t\t\t\twikiText = mw.message( 'mediauploader-default-content-wikitext' ).plain();\n\t\t\t}\n\t\t\tif ( mw.UploadWizard.config.content.prepend ) {\n\t\t\t\twikiText = mw.UploadWizard.config.content.prepend + '\\n' + wikiText;\n\t\t\t}\n\t\t\tif ( mw.UploadWizard.config.content.append ) {\n\t\t\t\twikiText += '\\n' + mw.UploadWizard.config.content.append;\n\t\t\t}\n\n\t\t\tfunction addSubstitution( key, value ) {\n\t\t\t\tvar v = value;\n\t\t\t\tif ( key in substitutions ) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\t// Discard funny values that toString poorly\n\t\t\t\tif ( v === undefined || v === null || ( typeof v === 'number' && isNaN( v ) ) ) {\n\t\t\t\t\tv = '';\n\t\t\t\t}\n\t\t\t\tsubstList.push( key );\n\t\t\t\tsubstitutions[ key ] = v.toString();\n\t\t\t}\n\n\t\t\t// Add hardcoded substitutions\n\t\t\taddSubstitution( 'source', deed.getSourceWikiText( this.upload ) );\n\t\t\taddSubstitution( 'author', deed.getAuthorWikiText( this.upload ) );\n\t\t\taddSubstitution( 'license', deed.getLicenseWikiText() );\n\n\t\t\t// Add substitutions for all the defined details fields\n\t\t\tthis.fieldList.forEach( function ( spec ) {\n\t\t\t\tif ( spec.type === 'license' ) {\n\t\t\t\t\t// Skip the license input... it is handled separately above.\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tfieldWidget = this.fieldMap[ spec.key ];\n\t\t\t\taddSubstitution( spec.key, fieldWidget.getWikiText() );\n\t\t\t\tserialized = fieldWidget.getSerializedParsed();\n\t\t\t\tif ( typeof serialized !== 'object' ) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\t// Also add \"subfields\" based on the serialized values. Just in case.\n\t\t\t\tObject.keys( serialized ).forEach( function ( key ) {\n\t\t\t\t\treplaceValue = serialized[ key ];\n\t\t\t\t\tvalueType = typeof replaceValue;\n\t\t\t\t\tif ( valueType === 'string' || valueType === 'number' || valueType === 'boolean' ) {\n\t\t\t\t\t\taddSubstitution( spec.key + '.' + key, replaceValue );\n\t\t\t\t\t}\n\t\t\t\t}, this );\n\t\t\t}, this );\n\n\t\t\t// Do the substitutions\n\t\t\tsubstList.forEach( function ( substKey ) {\n\t\t\t\treplaceValue = substitutions[ substKey ].trim();\n\t\t\t\tescapedKey = substKey.replace( /[.*+?^${}()|[\\]\\\\]/g, '\\\\$&' );\n\t\t\t\tre = new RegExp( '\\\\{\\\\{\\\\{ *' + escapedKey + ' *(\\\\|(.*?))?\\\\}\\\\}\\\\}', 'giu' );\n\t\t\t\twikiText = wikiText.replace( re, function ( match, _, defaultValue ) {\n\t\t\t\t\tif ( !replaceValue ) {\n\t\t\t\t\t\treturn defaultValue || '';\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn replaceValue;\n\t\t\t\t\t}\n\t\t\t\t} );\n\t\t\t}, this );\n\n\t\t\t// remove too many newlines in a row\n\t\t\twikiText = wikiText.replace( /\\n{3,}/g, '\\n\\n' );\n\n\t\t\treturn wikiText;\n\t\t},\n\n\t\t/**\n\t\t * @return {jQuery.Promise}\n\t\t */\n\t\tsubmit: function () {\n\t\t\tvar details = this,\n\t\t\t\twikitext, promise;\n\n\t\t\tthis.$containerDiv.find( 'form' ).trigger( 'submit' );\n\n\t\t\tthis.upload.title = this.getTitle();\n\t\t\tthis.upload.state = 'submitting-details';\n\t\t\tthis.setStatus( mw.message( 'mediauploader-submitting-details' ).text() );\n\t\t\tthis.showIndicator( 'progress' );\n\n\t\t\twikitext = this.getWikiText();\n\t\t\tpromise = this.submitWikiText( wikitext );\n\n\t\t\treturn promise.then( function () {\n\t\t\t\tdetails.showIndicator( 'success' );\n\t\t\t\tdetails.setStatus( mw.message( 'mediauploader-published' ).text() );\n\t\t\t} );\n\t\t},\n\n\t\t/**\n\t\t * Post wikitext as edited here, to the file\n\t\t *\n\t\t * This function is only called if all input seems valid (which doesn't mean that we can't get\n\t\t * an error, see #processError).\n\t\t *\n\t\t * @param {string} wikiText\n\t\t * @return {jQuery.Promise}\n\t\t */\n\t\tsubmitWikiText: function ( wikiText ) {\n\t\t\tvar params,\n\t\t\t\ttags = [ 'uploadwizard' ],\n\t\t\t\tdeed = this.upload.deedChooser.deed,\n\t\t\t\tcomment = '',\n\t\t\t\tconfig = mw.UploadWizard.config;\n\n\t\t\tthis.firstPoll = Date.now();\n\n\t\t\tif ( this.upload.file.source ) {\n\t\t\t\ttags.push( 'uploadwizard-' + this.upload.file.source );\n\t\t\t}\n\n\t\t\tif ( deed.name === 'ownwork' ) {\n\t\t\t\t// This message does not have any parameters, so there's nothing to substitute\n\t\t\t\tcomment = config.uploadComment.ownWork;\n\t\t\t} else {\n\t\t\t\tmw.messages.set(\n\t\t\t\t\t'mediauploader-upload-comment-third-party',\n\t\t\t\t\tconfig.uploadComment.thirdParty\n\t\t\t\t);\n\t\t\t\tcomment = mw.message(\n\t\t\t\t\t'mediauploader-upload-comment-third-party',\n\t\t\t\t\tdeed.getAuthorWikiText(),\n\t\t\t\t\tdeed.getSourceWikiText()\n\t\t\t\t).plain();\n\t\t\t}\n\n\t\t\tparams = {\n\t\t\t\taction: 'upload',\n\t\t\t\tfilekey: this.upload.fileKey,\n\t\t\t\tfilename: this.getTitle().getMain(),\n\t\t\t\tcomment: comment,\n\t\t\t\ttags: config.CanAddTags ? tags : [],\n\t\t\t\t// we can ignore upload warnings here, we've already checked\n\t\t\t\t// when stashing the file\n\t\t\t\t// not ignoring warnings would prevent us from uploading a file\n\t\t\t\t// that is a duplicate of something in a foreign repo\n\t\t\t\tignorewarnings: true,\n\t\t\t\ttext: wikiText\n\t\t\t};\n\n\t\t\t// Only enable async publishing if file is larger than 10MiB\n\t\t\tif ( this.upload.transportWeight > 10 * 1024 * 1024 ) {\n\t\t\t\tparams.async = true;\n\t\t\t}\n\n\t\t\treturn this.submitWikiTextInternal( params );\n\t\t},\n\n\t\t/**\n\t\t * Perform the API call with given parameters (which is expected to publish this file) and\n\t\t * handle the result.\n\t\t *\n\t\t * @param {Object} params API call parameters\n\t\t * @return {jQuery.Promise}\n\t\t */\n\t\tsubmitWikiTextInternal: function ( params ) {\n\t\t\tvar details = this,\n\t\t\t\tapiPromise = this.upload.api.postWithEditToken( params );\n\n\t\t\treturn apiPromise\n\t\t\t\t// process the successful (in terms of HTTP status...) API call first:\n\t\t\t\t// there may be warnings or other issues with the upload that need\n\t\t\t\t// to be dealt with\n\t\t\t\t.then( this.validateWikiTextSubmitResult.bind( this, params ) )\n\t\t\t\t// making it here means the upload is a success, or it would've been\n\t\t\t\t// rejected by now (either by HTTP status code, or in validateWikiTextSubmitResult)\n\t\t\t\t.then( function ( result ) {\n\t\t\t\t\tdetails.title = mw.Title.makeTitle( 6, result.upload.filename );\n\t\t\t\t\tdetails.upload.extractImageInfo( result.upload.imageinfo );\n\t\t\t\t\tdetails.upload.thisProgress = 1.0;\n\t\t\t\t\tdetails.upload.state = 'complete';\n\t\t\t\t\treturn result;\n\t\t\t\t} )\n\t\t\t\t// uh-oh - something went wrong!\n\t\t\t\t.catch( function ( code, result ) {\n\t\t\t\t\tdetails.upload.state = 'error';\n\t\t\t\t\tdetails.processError( code, result );\n\t\t\t\t\treturn $.Deferred().reject( code, result );\n\t\t\t\t} )\n\t\t\t\t.promise( { abort: apiPromise.abort } );\n\t\t},\n\n\t\t/**\n\t\t * Validates the result of a submission & returns a resolved promise with\n\t\t * the API response if all went well, or rejects with error code & error\n\t\t * message as you would expect from failed mediawiki API calls.\n\t\t *\n\t\t * @param {Object} params What we passed to the API that caused this response.\n\t\t * @param {Object} result API result of an upload or status check.\n\t\t * @return {jQuery.Promise}\n\t\t */\n\t\tvalidateWikiTextSubmitResult: function ( params, result ) {\n\t\t\tvar wx, warningsKeys, existingFile, existingFileUrl, existingFileExt, ourFileExt, code, message,\n\t\t\t\tdetails = this,\n\t\t\t\twarnings = null,\n\t\t\t\tignoreTheseWarnings = false,\n\t\t\t\tdeferred = $.Deferred();\n\n\t\t\tif ( result && result.upload && result.upload.result === 'Poll' ) {\n\t\t\t\t// if async publishing takes longer than 10 minutes give up\n\t\t\t\tif ( ( Date.now() - this.firstPoll ) > 10 * 60 * 1000 ) {\n\t\t\t\t\treturn deferred.reject( 'server-error', { errors: [ {\n\t\t\t\t\t\tcode: 'server-error',\n\t\t\t\t\t\thtml: 'Unknown server error'\n\t\t\t\t\t} ] } );\n\t\t\t\t} else {\n\t\t\t\t\tif ( result.upload.stage === undefined ) {\n\t\t\t\t\t\treturn deferred.reject( 'no-stage', { errors: [ {\n\t\t\t\t\t\t\tcode: 'no-stage',\n\t\t\t\t\t\t\thtml: 'Unable to check file\\'s status'\n\t\t\t\t\t\t} ] } );\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Messages that can be returned:\n\t\t\t\t\t\t// * mediauploader-queued\n\t\t\t\t\t\t// * mediauploader-publish\n\t\t\t\t\t\t// * mediauploader-assembling\n\t\t\t\t\t\tthis.setStatus( mw.message( 'mediauploader-' + result.upload.stage ).text() );\n\t\t\t\t\t\tsetTimeout( function () {\n\t\t\t\t\t\t\tif ( details.upload.state !== 'aborted' ) {\n\t\t\t\t\t\t\t\tdetails.submitWikiTextInternal( {\n\t\t\t\t\t\t\t\t\taction: 'upload',\n\t\t\t\t\t\t\t\t\tcheckstatus: true,\n\t\t\t\t\t\t\t\t\tfilekey: details.upload.fileKey\n\t\t\t\t\t\t\t\t} ).then( deferred.resolve, deferred.reject );\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tdeferred.resolve( 'aborted' );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}, 3000 );\n\n\t\t\t\t\t\treturn deferred.promise();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif ( result && result.upload && result.upload.warnings ) {\n\t\t\t\twarnings = result.upload.warnings;\n\t\t\t}\n\t\t\tif ( warnings && warnings.exists ) {\n\t\t\t\texistingFile = warnings.exists;\n\t\t\t} else if ( warnings && warnings[ 'exists-normalized' ] ) {\n\t\t\t\texistingFile = warnings[ 'exists-normalized' ];\n\t\t\t\texistingFileExt = mw.Title.normalizeExtension( existingFile.split( '.' ).pop() );\n\t\t\t\tourFileExt = mw.Title.normalizeExtension( this.getTitle().getExtension() );\n\n\t\t\t\tif ( existingFileExt !== ourFileExt ) {\n\t\t\t\t\tdelete warnings[ 'exists-normalized' ];\n\t\t\t\t\tignoreTheseWarnings = true;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif ( warnings && warnings[ 'was-deleted' ] ) {\n\t\t\t\tdelete warnings[ 'was-deleted' ];\n\t\t\t\tignoreTheseWarnings = true;\n\t\t\t}\n\t\t\tfor ( wx in warnings ) {\n\t\t\t\tif ( Object.prototype.hasOwnProperty.call( warnings, wx ) ) {\n\t\t\t\t\t// if there are other warnings, deal with those first\n\t\t\t\t\tignoreTheseWarnings = false;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif ( result && result.upload && result.upload.imageinfo ) {\n\t\t\t\treturn $.Deferred().resolve( result );\n\t\t\t} else if ( ignoreTheseWarnings ) {\n\t\t\t\tparams.ignorewarnings = 1;\n\t\t\t\treturn this.submitWikiTextInternal( params );\n\t\t\t} else if ( result && result.upload && result.upload.warnings ) {\n\t\t\t\tif ( warnings.thumb || warnings[ 'thumb-name' ] ) {\n\t\t\t\t\tcode = 'error-title-thumbnail';\n\t\t\t\t\tmessage = mw.message( 'mediauploader-error-title-thumbnail' ).parse();\n\t\t\t\t} else if ( warnings.badfilename ) {\n\t\t\t\t\tcode = 'title-invalid';\n\t\t\t\t\tmessage = mw.message( 'mediauploader-error-title-invalid' ).parse();\n\t\t\t\t} else if ( warnings[ 'bad-prefix' ] ) {\n\t\t\t\t\tcode = 'title-senselessimagename';\n\t\t\t\t\tmessage = mw.message( 'mediauploader-error-title-senselessimagename' ).parse();\n\t\t\t\t} else if ( existingFile ) {\n\t\t\t\t\texistingFileUrl = mw.config.get( 'wgServer' ) + mw.Title.makeTitle( NS_FILE, existingFile ).getUrl();\n\t\t\t\t\tcode = 'api-warning-exists';\n\t\t\t\t\tmessage = mw.message( 'mediauploader-api-warning-exists', existingFileUrl ).parse();\n\t\t\t\t} else if ( warnings.duplicate ) {\n\t\t\t\t\tcode = 'upload-error-duplicate';\n\t\t\t\t\tmessage = mw.message( 'mediauploader-upload-error-duplicate' ).parse();\n\t\t\t\t} else if ( warnings[ 'duplicate-archive' ] !== undefined ) {\n\t\t\t\t\t// warnings[ 'duplicate-archive' ] may be '' (empty string) for revdeleted files\n\t\t\t\t\tif ( this.upload.handler.isIgnoredWarning( 'duplicate-archive' ) ) {\n\t\t\t\t\t\t// We already told the interface to ignore this warning, so\n\t\t\t\t\t\t// let's steamroll over it and re-call this handler.\n\t\t\t\t\t\tparams.ignorewarnings = true;\n\t\t\t\t\t\treturn this.submitWikiTextInternal( params );\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// This should _never_ happen, but just in case....\n\t\t\t\t\t\tcode = 'upload-error-duplicate-archive';\n\t\t\t\t\t\tmessage = mw.message( 'mediauploader-upload-error-duplicate-archive' ).parse();\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\twarningsKeys = Object.keys( warnings );\n\t\t\t\t\tcode = 'unknown-warning';\n\t\t\t\t\tmessage = mw.message( 'mediauploader-api-error-unknown-warning', warningsKeys.join( ', ' ) ).parse();\n\t\t\t\t}\n\n\t\t\t\treturn $.Deferred().reject( code, { errors: [ { html: message } ] } );\n\t\t\t} else {\n\t\t\t\treturn $.Deferred().reject( 'this-info-missing', result );\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Create a recoverable error -- show the form again, and highlight the problematic field.\n\t\t *\n\t\t * @param {string} code\n\t\t * @param {string} html Error message to show.\n\t\t */\n\t\trecoverFromError: function ( code, html ) {\n\t\t\tvar titleField = mw.UploadWizard.config.content.titleField;\n\n\t\t\tthis.upload.state = 'recoverable-error';\n\t\t\tthis.$dataDiv.morphCrossfade( '.detailsForm' );\n\t\t\tthis.fieldWrapperMap[ titleField ].setErrors( [ { code: code, html: html } ] );\n\t\t},\n\n\t\t/**\n\t\t * Show error state, possibly using a recoverable error form\n\t\t *\n\t\t * @param {string} code Error code\n\t\t * @param {string} html Error message\n\t\t */\n\t\tshowError: function ( code, html ) {\n\t\t\tthis.showIndicator( 'error' );\n\t\t\tthis.setStatus( html );\n\t\t},\n\n\t\t/**\n\t\t * Decide how to treat various errors\n\t\t *\n\t\t * @param {string} code Error code\n\t\t * @param {Object} result Result from ajax call\n\t\t */\n\t\tprocessError: function ( code, result ) {\n\t\t\tvar recoverable = [\n\t\t\t\t'abusefilter-disallowed',\n\t\t\t\t'abusefilter-warning',\n\t\t\t\t'spamblacklist',\n\t\t\t\t'fileexists-shared-forbidden',\n\t\t\t\t'protectedpage',\n\t\t\t\t'titleblacklist-forbidden',\n\n\t\t\t\t// below are not actual API errors, but recoverable warnings that have\n\t\t\t\t// been discovered in validateWikiTextSubmitResult and fabricated to resemble\n\t\t\t\t// API errors and end up here to be dealt with\n\t\t\t\t'error-title-thumbnail',\n\t\t\t\t'title-invalid',\n\t\t\t\t'title-senselessimagename',\n\t\t\t\t'api-warning-exists',\n\t\t\t\t'upload-error-duplicate',\n\t\t\t\t'upload-error-duplicate',\n\t\t\t\t'upload-error-duplicate-archive',\n\t\t\t\t'unknown-warning'\n\t\t\t];\n\n\t\t\tif ( code === 'badtoken' ) {\n\t\t\t\tthis.api.badToken( 'csrf' );\n\t\t\t\t// TODO Automatically try again instead of requiring the user to bonk the button\n\t\t\t}\n\n\t\t\tif ( code === 'ratelimited' ) {\n\t\t\t\t// None of the remaining uploads is going to succeed, and every failed one is going to\n\t\t\t\t// ping the rate limiter again.\n\t\t\t\tthis.upload.wizard.steps.details.queue.abortExecuting();\n\t\t\t} else if ( code === 'http' && result && result.exception === 'abort' ) {\n\t\t\t\t// This upload has just been aborted because an earlier one got the 'ratelimited' error.\n\t\t\t\t// This could potentially also come up when an upload is removed by the user, but in that\n\t\t\t\t// case the UI is invisible anyway, so whatever.\n\t\t\t\tcode = 'ratelimited';\n\t\t\t}\n\n\t\t\tif ( recoverable.indexOf( code ) > -1 ) {\n\t\t\t\tthis.recoverFromError( code, result.errors[ 0 ].html );\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tthis.showError( code, result.errors[ 0 ].html );\n\t\t},\n\n\t\tsetStatus: function ( s ) {\n\t\t\tthis.$div.find( '.mediauploader-file-status-line' ).html( s ).show();\n\t\t},\n\n\t\t// TODO: De-duplicate with code form mw.UploadWizardUploadInterface.js\n\t\tshowIndicator: function ( status ) {\n\t\t\tthis.$spinner.hide();\n\t\t\tthis.statusMessage.toggle( false );\n\n\t\t\tif ( status === 'progress' ) {\n\t\t\t\tthis.$spinner.show();\n\t\t\t} else if ( status ) {\n\t\t\t\tthis.statusMessage.toggle( true ).setType( status );\n\t\t\t}\n\t\t\tthis.$indicator.toggleClass( 'mediauploader-file-indicator-visible', !!status );\n\t\t},\n\n\t\tsetVisibleTitle: function ( s ) {\n\t\t\t$( this.$submittingDiv )\n\t\t\t\t.find( '.mediauploader-visible-file-filename-text' )\n\t\t\t\t.text( s );\n\t\t}\n\t};\n\n}( mw.uploadWizard ) );\n","usedDeprecatedRules":[]},{"filePath":"/src/repo/resources/mw.UploadWizardLicenseInput.js","messages":[],"suppressedMessages":[{"ruleId":"mediawiki/msg-doc","severity":1,"message":"All possible message keys should be documented. See https://w.wiki/4r9a for details.","line":214,"column":24,"nodeType":"CallExpression","endLine":214,"endColumn":45,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/resources/mw.UploadWizardPage.js","messages":[],"suppressedMessages":[{"ruleId":"no-jquery/no-global-selector","severity":2,"message":"Avoid queries which search the entire DOM. Keep DOM nodes in memory where possible.","line":38,"column":3,"nodeType":"CallExpression","endLine":38,"endColumn":28,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-global-selector","severity":2,"message":"Avoid queries which search the entire DOM. Keep DOM nodes in memory where possible.","line":41,"column":8,"nodeType":"CallExpression","endLine":41,"endColumn":29,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-global-selector","severity":2,"message":"Avoid queries which search the entire DOM. Keep DOM nodes in memory where possible.","line":49,"column":4,"nodeType":"CallExpression","endLine":49,"endColumn":37,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/resources/mw.UploadWizardUpload.js","messages":[{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"uw\" type.","line":8,"column":null,"nodeType":"Block","endLine":8,"endColumn":null},{"ruleId":"es-x/no-typed-arrays","severity":2,"message":"ES2015 'Uint8Array' is forbidden.","line":222,"column":16,"nodeType":"Identifier","messageId":"forbidden","endLine":222,"endColumn":26}],"suppressedMessages":[{"ruleId":"no-underscore-dangle","severity":2,"message":"Unexpected dangling '_' in '_binary_data'.","line":232,"column":6,"nodeType":"MemberExpression","messageId":"unexpectedUnderscore","endLine":232,"endColumn":23,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier '_binary_data' is not in camel case.","line":232,"column":11,"nodeType":"Identifier","messageId":"notCamelCase","endLine":232,"endColumn":23,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"compat/compat","severity":2,"message":"URL is not supported in IE 11","line":906,"column":10,"nodeType":"MemberExpression","endLine":906,"endColumn":20,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":1,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/**\n * Represents the upload -- in its local and remote state. (Possibly those could be separate objects too...)\n * This is our 'model' object if we are thinking MVC. Needs to be better factored, lots of feature envy with the UploadWizard\n * states:\n *   'new' 'transporting' 'transported' 'metadata' 'stashed' 'details' 'submitting-details' 'complete' 'error'\n * should fork this into two -- local and remote, e.g. filename\n *\n * @param uw\n */\n( function ( uw ) {\n\t/**\n\t * Constructor for objects representing uploads. The workhorse of this entire extension.\n\t *\n\t * The upload knows nothing of other uploads. It manages its own interface, and transporting its own data, to\n\t * the server.\n\t *\n\t * Upload objects are usually created without a file, they are just associated with a form.\n\t * There is an \"empty\" fileInput which is invisibly floating above certain buttons in the interface, like \"Add a file\". When\n\t * this fileInput gets a file, this upload becomes 'filled'.\n\t *\n\t * @class mw.UploadWizardUpload\n\t * @constructor\n\t * @param {uw.controller.Step} controller\n\t * @param {File} file\n\t */\n\tmw.UploadWizardUpload = function MWUploadWizardUpload( controller, file ) {\n\t\tOO.EventEmitter.call( this );\n\n\t\tthis.index = mw.UploadWizardUpload.prototype.count;\n\t\tmw.UploadWizardUpload.prototype.count++;\n\n\t\tthis.controller = controller;\n\t\tthis.api = controller.api;\n\t\tthis.file = file;\n\t\tthis.state = 'new';\n\t\tthis.imageinfo = {};\n\t\tthis.title = undefined;\n\t\tthis.thumbnailPromise = {};\n\n\t\tthis.fileKey = undefined;\n\n\t\t// this should be moved to the interface, if we even keep this\n\t\tthis.transportWeight = 1; // default all same\n\n\t\t// details\n\t\tthis.ui = new mw.UploadWizardUploadInterface( this )\n\t\t\t.connect( this, {\n\t\t\t\t/*\n\t\t\t\t * This may be confusing!\n\t\t\t\t * This object also has a `remove` method, which will also be\n\t\t\t\t * called when an upload is removed. But an upload can be\n\t\t\t\t * removed for multiple reasons (one being clicking the \"remove\"\n\t\t\t\t * button, which triggers this event - but another could be\n\t\t\t\t * removing faulty uploads).\n\t\t\t\t * To simplify things, we'll always initiate the remove from the\n\t\t\t\t * controllers, so we'll relay this event to the controllers,\n\t\t\t\t * which will then eventually come back to call `remove` on this\n\t\t\t\t * object.\n\t\t\t\t */\n\t\t\t\t'upload-removed': [ 'emit', 'remove-upload' ]\n\t\t\t} );\n\t};\n\n\tOO.mixinClass( mw.UploadWizardUpload, OO.EventEmitter );\n\n\t// Upload handler\n\tmw.UploadWizardUpload.prototype.uploadHandler = null;\n\n\t// increments with each upload\n\tmw.UploadWizardUpload.prototype.count = 0;\n\n\t/**\n\t * start\n\t *\n\t * @return {jQuery.Promise}\n\t */\n\tmw.UploadWizardUpload.prototype.start = function () {\n\t\tthis.setTransportProgress( 0.0 );\n\n\t\t// handler -- usually ApiUploadFormDataHandler\n\t\tthis.handler = this.getUploadHandler();\n\t\treturn this.handler.start();\n\t};\n\n\t/**\n\t * Remove this upload. n.b. we trigger a removeUpload this is usually triggered from\n\t */\n\tmw.UploadWizardUpload.prototype.remove = function () {\n\t\t// remove the div that passed along the trigger\n\t\tthis.ui.$div.remove();\n\n\t\tthis.state = 'aborted';\n\t};\n\n\t/**\n\t * Wear our current progress, for observing processes to see\n\t *\n\t * @param {number} fraction\n\t */\n\tmw.UploadWizardUpload.prototype.setTransportProgress = function ( fraction ) {\n\t\tif ( this.state === 'aborted' ) {\n\t\t\t// We shouldn't be transporting anything anymore.\n\t\t\treturn;\n\t\t}\n\t\tthis.state = 'transporting';\n\t\tthis.transportProgress = fraction;\n\t\tthis.ui.$div.trigger( 'transportProgressEvent' );\n\t};\n\n\t/**\n\t * Stop the upload -- we have failed for some reason\n\t *\n\t * @param {string} code Error code from API\n\t * @param {string} html Error message\n\t * @param {jQuery} [$additionalStatus]\n\t */\n\tmw.UploadWizardUpload.prototype.setError = function ( code, html, $additionalStatus ) {\n\t\tif ( this.state === 'aborted' ) {\n\t\t\t// There's no point in reporting an error anymore.\n\t\t\treturn;\n\t\t}\n\t\tthis.state = 'error';\n\t\tthis.transportProgress = 0;\n\t\tthis.ui.showError( code, html, $additionalStatus );\n\t};\n\n\t/**\n\t * Called from any upload success condition\n\t *\n\t * @param {Object} result -- result of AJAX call\n\t */\n\tmw.UploadWizardUpload.prototype.setSuccess = function ( result ) {\n\t\tthis.state = 'transported';\n\t\tthis.transportProgress = 1;\n\n\t\tthis.ui.setStatus( 'mediauploader-getting-metadata' );\n\n\t\tthis.extractUploadInfo( result.upload );\n\t\tthis.state = 'stashed';\n\t\tthis.ui.showStashed();\n\n\t\tthis.emit( 'success' );\n\t\t// check all uploads, if they're complete, show the next button\n\t\t// TODO Make wizard connect to 'success' event\n\t\tthis.controller.showNext();\n\t};\n\n\t/**\n\t * Get just the filename.\n\t *\n\t * @return {string}\n\t */\n\tmw.UploadWizardUpload.prototype.getFilename = function () {\n\t\tif ( this.file.fileName ) {\n\t\t\treturn this.file.fileName;\n\t\t} else {\n\t\t\t// this property has a different name in FF vs Chrome.\n\t\t\treturn this.file.name;\n\t\t}\n\t};\n\n\t/**\n\t * Get the basename of a path.\n\t * For error conditions, returns the empty string.\n\t *\n\t * @return {string} basename\n\t */\n\tmw.UploadWizardUpload.prototype.getBasename = function () {\n\t\tvar path = this.getFilename();\n\n\t\tif ( path === undefined || path === null ) {\n\t\t\treturn '';\n\t\t}\n\n\t\t// find index of last path separator in the path, add 1. (If no separator found, yields 0)\n\t\t// then take the entire string after that.\n\t\treturn path.slice( Math.max( path.lastIndexOf( '/' ), path.lastIndexOf( '\\\\' ) ) + 1 );\n\t};\n\n\t/**\n\t * Sanitize and set the title of the upload.\n\t *\n\t * @param {string} title Unsanitized title.\n\t */\n\tmw.UploadWizardUpload.prototype.setTitle = function ( title ) {\n\t\tthis.title = mw.Title.newFromFileName( title );\n\t};\n\n\t/**\n\t * Extract some JPEG metadata that we need to render thumbnails (EXIF rotation mostly).\n\t *\n\t * For JPEGs, we use the JsJpegMeta library in core to extract metadata,\n\t * including EXIF tags. This is done asynchronously once each file has been\n\t * read.\n\t *\n\t * For all other file types, we don't need or want to run this, and this function does nothing.\n\t *\n\t * @private\n\t * @return {jQuery.Promise} A promise, resolved when we're done\n\t */\n\tmw.UploadWizardUpload.prototype.extractMetadataFromJpegMeta = function () {\n\t\tvar binReader, jpegmeta,\n\t\t\tdeferred = $.Deferred(),\n\t\t\tupload = this;\n\t\tif ( this.file && this.file.type === 'image/jpeg' ) {\n\t\t\tbinReader = new FileReader();\n\t\t\tbinReader.onerror = function () {\n\t\t\t\tdeferred.resolve();\n\t\t\t};\n\t\t\tbinReader.onload = function () {\n\t\t\t\tvar binStr, arr, i, meta;\n\t\t\t\tif ( binReader.result === null ) {\n\t\t\t\t\t// Contrary to documentation, this sometimes fires for unsuccessful loads (T136235)\n\t\t\t\t\tdeferred.resolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif ( typeof binReader.result === 'string' ) {\n\t\t\t\t\tbinStr = binReader.result;\n\t\t\t\t} else {\n\t\t\t\t\t// Array buffer; convert to binary string for the library.\n\t\t\t\t\t/* global Uint8Array */\n\t\t\t\t\tarr = new Uint8Array( binReader.result );\n\t\t\t\t\tbinStr = '';\n\t\t\t\t\tfor ( i = 0; i < arr.byteLength; i++ ) {\n\t\t\t\t\t\tbinStr += String.fromCharCode( arr[ i ] );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\ttry {\n\t\t\t\t\tjpegmeta = require( 'mediawiki.libs.jpegmeta' );\n\t\t\t\t\tmeta = jpegmeta( binStr, upload.file.fileName );\n\t\t\t\t\t// eslint-disable-next-line camelcase, no-underscore-dangle\n\t\t\t\t\tmeta._binary_data = null;\n\t\t\t\t} catch ( e ) {\n\t\t\t\t\tmeta = null;\n\t\t\t\t}\n\t\t\t\tupload.extractMetadataFromJpegMetaCallback( meta );\n\t\t\t\tdeferred.resolve();\n\t\t\t};\n\t\t\tif ( 'readAsBinaryString' in binReader ) {\n\t\t\t\tbinReader.readAsBinaryString( upload.file );\n\t\t\t} else if ( 'readAsArrayBuffer' in binReader ) {\n\t\t\t\tbinReader.readAsArrayBuffer( upload.file );\n\t\t\t}\n\t\t} else {\n\t\t\tdeferred.resolve();\n\t\t}\n\t\treturn deferred.promise();\n\t};\n\n\t/**\n\t * Map fields from jpegmeta's metadata return into our format (which is more like the imageinfo returned from the API\n\t *\n\t * @param {Object} meta As returned by jpegmeta\n\t */\n\tmw.UploadWizardUpload.prototype.extractMetadataFromJpegMetaCallback = function ( meta ) {\n\t\tvar pixelHeightDim, pixelWidthDim, degrees;\n\n\t\tif ( meta !== undefined && meta !== null && typeof meta === 'object' ) {\n\t\t\tif ( this.imageinfo.metadata === undefined ) {\n\t\t\t\tthis.imageinfo.metadata = {};\n\t\t\t}\n\t\t\tif ( meta.tiff && meta.tiff.Orientation ) {\n\t\t\t\tthis.imageinfo.metadata.orientation = meta.tiff.Orientation.value;\n\t\t\t}\n\t\t\tif ( meta.general ) {\n\t\t\t\tpixelHeightDim = 'height';\n\t\t\t\tpixelWidthDim = 'width';\n\t\t\t\t// this must be called after orientation is set above. If no orientation set, defaults to 0\n\t\t\t\tdegrees = this.getOrientationDegrees();\n\n\t\t\t\t// jpegmeta reports pixelHeight & width\n\t\t\t\tif ( degrees === 90 || degrees === 270 ) {\n\t\t\t\t\tpixelHeightDim = 'width';\n\t\t\t\t\tpixelWidthDim = 'height';\n\t\t\t\t}\n\t\t\t\tif ( meta.general.pixelHeight ) {\n\t\t\t\t\tthis.imageinfo[ pixelHeightDim ] = meta.general.pixelHeight.value;\n\t\t\t\t}\n\t\t\t\tif ( meta.general.pixelWidth ) {\n\t\t\t\t\tthis.imageinfo[ pixelWidthDim ] = meta.general.pixelWidth.value;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t};\n\n\t/**\n\t * Accept the result from a successful API upload transport, and fill our own info\n\t *\n\t * @param {Object} resultUpload The JSON object from a successful API upload result.\n\t */\n\tmw.UploadWizardUpload.prototype.extractUploadInfo = function ( resultUpload ) {\n\t\tif ( resultUpload.filekey ) {\n\t\t\tthis.fileKey = resultUpload.filekey;\n\t\t}\n\n\t\tif ( resultUpload.imageinfo ) {\n\t\t\tthis.extractImageInfo( resultUpload.imageinfo );\n\t\t} else if ( resultUpload.stashimageinfo ) {\n\t\t\tthis.extractImageInfo( resultUpload.stashimageinfo );\n\t\t}\n\n\t};\n\n\t/**\n\t * Extract image info into our upload object\n\t * Image info is obtained from various different API methods\n\t * This may overwrite metadata obtained from FileReader.\n\t *\n\t * @param {Object} imageinfo JSON object obtained from API result.\n\t */\n\tmw.UploadWizardUpload.prototype.extractImageInfo = function ( imageinfo ) {\n\t\tvar key,\n\t\t\tupload = this;\n\n\t\tfor ( key in imageinfo ) {\n\t\t\t// we get metadata as list of key-val pairs; convert to object for easier lookup. Assuming that EXIF fields are unique.\n\t\t\tif ( key === 'metadata' ) {\n\t\t\t\tif ( this.imageinfo.metadata === undefined ) {\n\t\t\t\t\tthis.imageinfo.metadata = {};\n\t\t\t\t}\n\t\t\t\tif ( imageinfo.metadata && imageinfo.metadata.length ) {\n\t\t\t\t\timageinfo.metadata.forEach( function ( pair ) {\n\t\t\t\t\t\tif ( pair !== undefined ) {\n\t\t\t\t\t\t\tupload.imageinfo.metadata[ pair.name.toLowerCase() ] = pair.value;\n\t\t\t\t\t\t}\n\t\t\t\t\t} );\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tthis.imageinfo[ key ] = imageinfo[ key ];\n\t\t\t}\n\t\t}\n\t};\n\n\t/**\n\t * Get information about stashed images\n\t *\n\t * See API documentation for prop=stashimageinfo for what 'props' can contain\n\t *\n\t * @param {Function} callback Called with null if failure, with imageinfo data structure if success\n\t * @param {Array} props Properties to extract\n\t * @param {number} [width] Width of thumbnail. Will force 'url' to be added to props\n\t * @param {number} [height] Height of thumbnail. Will force 'url' to be added to props\n\t */\n\tmw.UploadWizardUpload.prototype.getStashImageInfo = function ( callback, props, width, height ) {\n\t\tvar params = {\n\t\t\tprop: 'stashimageinfo',\n\t\t\tsiifilekey: this.fileKey,\n\t\t\tsiiprop: props.join( '|' )\n\t\t};\n\n\t\tfunction ok( data ) {\n\t\t\tif ( !data || !data.query || !data.query.stashimageinfo ) {\n\t\t\t\tmw.log.warn( 'mw.UploadWizardUpload::getStashImageInfo> No data?' );\n\t\t\t\tcallback( null );\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcallback( data.query.stashimageinfo );\n\t\t}\n\n\t\tfunction err( code ) {\n\t\t\tmw.log.warn( 'mw.UploadWizardUpload::getStashImageInfo> ' + code );\n\t\t\tcallback( null );\n\t\t}\n\n\t\tif ( props === undefined ) {\n\t\t\tprops = [];\n\t\t}\n\n\t\tif ( width !== undefined || height !== undefined ) {\n\t\t\tif ( props.indexOf( 'url' ) === -1 ) {\n\t\t\t\tprops.push( 'url' );\n\t\t\t}\n\t\t\tif ( width !== undefined ) {\n\t\t\t\tparams.siiurlwidth = width;\n\t\t\t}\n\t\t\tif ( height !== undefined ) {\n\t\t\t\tparams.siiurlheight = height;\n\t\t\t}\n\t\t}\n\n\t\tthis.api.get( params ).done( ok ).fail( err );\n\t};\n\n\t/**\n\t * Get information about published images\n\t * (There is some overlap with getStashedImageInfo, but it's different at every stage so it's clearer to have separate functions)\n\t * See API documentation for prop=imageinfo for what 'props' can contain\n\t *\n\t * @param {Function} callback Called with null if failure, with imageinfo data structure if success\n\t * @param {Array} props Properties to extract\n\t * @param {number} [width] Width of thumbnail. Will force 'url' to be added to props\n\t * @param {number} [height] Height of thumbnail. Will force 'url' to be added to props\n\t */\n\tmw.UploadWizardUpload.prototype.getImageInfo = function ( callback, props, width, height ) {\n\t\tvar requestedTitle, params;\n\n\t\tfunction ok( data ) {\n\t\t\tvar found;\n\n\t\t\tif ( data && data.query && data.query.pages ) {\n\t\t\t\tfound = false;\n\t\t\t\tObject.keys( data.query.pages ).forEach( function ( pageId ) {\n\t\t\t\t\tvar page = data.query.pages[ pageId ];\n\t\t\t\t\tif ( page.title && page.title === requestedTitle && page.imageinfo ) {\n\t\t\t\t\t\tfound = true;\n\t\t\t\t\t\tcallback( page.imageinfo );\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t} );\n\t\t\t\tif ( found ) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tmw.log.warn( 'mw.UploadWizardUpload::getImageInfo> No data matching ' + requestedTitle + ' ?' );\n\t\t\tcallback( null );\n\t\t}\n\n\t\tfunction err( code ) {\n\t\t\tmw.log.warn( 'mw.UploadWizardUpload::getImageInfo> ' + code );\n\t\t\tcallback( null );\n\t\t}\n\n\t\tif ( props === undefined ) {\n\t\t\tprops = [];\n\t\t}\n\n\t\trequestedTitle = this.title.getPrefixedText();\n\t\tparams = {\n\t\t\tprop: 'imageinfo',\n\t\t\ttitles: requestedTitle,\n\t\t\tiiprop: props.join( '|' )\n\t\t};\n\n\t\tif ( width !== undefined || height !== undefined ) {\n\t\t\tif ( props.indexOf( 'url' ) === -1 ) {\n\t\t\t\tprops.push( 'url' );\n\t\t\t}\n\t\t\tif ( width !== undefined ) {\n\t\t\t\tparams.iiurlwidth = width;\n\t\t\t}\n\t\t\tif ( height !== undefined ) {\n\t\t\t\tparams.iiurlheight = height;\n\t\t\t}\n\t\t}\n\n\t\tthis.api.get( params ).done( ok ).fail( err );\n\t};\n\n\t/**\n\t * Get the upload handler per browser capabilities\n\t *\n\t * @return {mw.ApiUploadFormDataHandler} upload handler object\n\t */\n\tmw.UploadWizardUpload.prototype.getUploadHandler = function () {\n\t\tvar constructor; // must be the name of a function in 'mw' namespace\n\n\t\tif ( !this.uploadHandler ) {\n\t\t\tconstructor = 'ApiUploadFormDataHandler';\n\t\t\tthis.uploadHandler = new mw[ constructor ]( this, this.api );\n\t\t}\n\t\treturn this.uploadHandler;\n\t};\n\n\t/**\n\t * Explicitly fetch a thumbnail for a stashed upload of the desired width.\n\t *\n\t * @private\n\t * @param {number} width Desired width of thumbnail\n\t * @param {number} height Maximum height of thumbnail\n\t * @return {jQuery.Promise} Promise resolved with a HTMLImageElement, or null if thumbnail\n\t *     couldn't be generated\n\t */\n\tmw.UploadWizardUpload.prototype.getApiThumbnail = function ( width, height ) {\n\t\tvar deferred = $.Deferred();\n\n\t\tfunction thumbnailPublisher( thumbnails ) {\n\t\t\tif ( thumbnails === null ) {\n\t\t\t\t// the api call failed somehow, no thumbnail data.\n\t\t\t\tdeferred.resolve( null );\n\t\t\t} else {\n\t\t\t\t// ok, the api callback has returned us information on where the thumbnail(s) ARE, but that doesn't mean\n\t\t\t\t// they are actually there yet. Keep trying to set the source ( which should trigger \"error\" or \"load\" event )\n\t\t\t\t// on the image. If it loads publish the event with the image. If it errors out too many times, give up and publish\n\t\t\t\t// the event with a null.\n\t\t\t\tthumbnails.forEach( function ( thumb ) {\n\t\t\t\t\tvar timeoutMs, image;\n\n\t\t\t\t\tif ( thumb.thumberror || ( !( thumb.thumburl && thumb.thumbwidth && thumb.thumbheight ) ) ) {\n\t\t\t\t\t\tmw.log.warn( 'mw.UploadWizardUpload::getThumbnail> Thumbnail error or missing information' );\n\t\t\t\t\t\tdeferred.resolve( null );\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\t// executing this should cause a .load() or .error() event on the image\n\t\t\t\t\tfunction setSrc() {\n\t\t\t\t\t\t// IE 11 and Opera 12 will not, ever, re-request an image that they have already loaded\n\t\t\t\t\t\t// once, regardless of caching headers. Append bogus stuff to the URL to make it work.\n\t\t\t\t\t\timage.src = thumb.thumburl + '?' + Math.random();\n\t\t\t\t\t}\n\n\t\t\t\t\t// try to load this image with exponential backoff\n\t\t\t\t\t// if the delay goes past 8 seconds, it gives up and publishes the event with null\n\t\t\t\t\ttimeoutMs = 100;\n\t\t\t\t\timage = document.createElement( 'img' );\n\t\t\t\t\timage.width = thumb.thumbwidth;\n\t\t\t\t\timage.height = thumb.thumbheight;\n\t\t\t\t\t$( image )\n\t\t\t\t\t\t.on( 'load', function () {\n\t\t\t\t\t\t\t// publish the image to anyone who wanted it\n\t\t\t\t\t\t\tdeferred.resolve( image );\n\t\t\t\t\t\t} )\n\t\t\t\t\t\t.on( 'error', function () {\n\t\t\t\t\t\t\t// retry with exponential backoff\n\t\t\t\t\t\t\tif ( timeoutMs < 8000 ) {\n\t\t\t\t\t\t\t\tsetTimeout( function () {\n\t\t\t\t\t\t\t\t\ttimeoutMs = timeoutMs * 2 + Math.round( Math.random() * ( timeoutMs / 10 ) );\n\t\t\t\t\t\t\t\t\tsetSrc();\n\t\t\t\t\t\t\t\t}, timeoutMs );\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tdeferred.resolve( null );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} );\n\n\t\t\t\t\t// and, go!\n\t\t\t\t\tsetSrc();\n\t\t\t\t} );\n\t\t\t}\n\t\t}\n\n\t\tif ( this.state !== 'complete' ) {\n\t\t\tthis.getStashImageInfo( thumbnailPublisher, [ 'url' ], width, height );\n\t\t} else {\n\t\t\tthis.getImageInfo( thumbnailPublisher, [ 'url' ], width, height );\n\t\t}\n\n\t\treturn deferred.promise();\n\t};\n\n\t/**\n\t * Return the orientation of the image in degrees. Relies on metadata that\n\t * may have been extracted at filereader stage, or after the upload when we fetch metadata. Default returns 0.\n\t *\n\t * @return {number} orientation in degrees: 0, 90, 180 or 270\n\t */\n\tmw.UploadWizardUpload.prototype.getOrientationDegrees = function () {\n\t\tvar orientation = 0;\n\t\tif ( this.imageinfo && this.imageinfo.metadata && this.imageinfo.metadata.orientation ) {\n\t\t\tswitch ( this.imageinfo.metadata.orientation ) {\n\t\t\t\tcase 8:\n\t\t\t\t\t// 'top left' -> 'left bottom'\n\t\t\t\t\torientation = 90;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 3:\n\t\t\t\t\t// 'top left' -> 'bottom right'\n\t\t\t\t\torientation = 180;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 6:\n\t\t\t\t\t// 'top left' -> 'right top'\n\t\t\t\t\torientation = 270;\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\t// 'top left' -> 'top left'\n\t\t\t\t\torientation = 0;\n\t\t\t\t\tbreak;\n\n\t\t\t}\n\t\t}\n\t\treturn orientation;\n\t};\n\n\t/**\n\t * Fit an image into width & height constraints with scaling factor\n\t *\n\t * @private\n\t * @param {HTMLImageElement} image\n\t * @param {Object} constraints Width & height properties\n\t * @return {number}\n\t */\n\tmw.UploadWizardUpload.prototype.getScalingFromConstraints = function ( image, constraints ) {\n\t\tvar scaling = 1;\n\t\tObject.keys( constraints ).forEach( function ( dim ) {\n\t\t\tvar s,\n\t\t\t\tconstraint = constraints[ dim ];\n\t\t\tif ( constraint && image[ dim ] > constraint ) {\n\t\t\t\ts = constraint / image[ dim ];\n\t\t\t\tif ( s < scaling ) {\n\t\t\t\t\tscaling = s;\n\t\t\t\t}\n\t\t\t}\n\t\t} );\n\t\treturn scaling;\n\t};\n\n\t/**\n\t * Given an image (already loaded), dimension constraints\n\t * return canvas object scaled & transformed ( & rotated if metadata indicates it's needed )\n\t *\n\t * @private\n\t * @param {HTMLImageElement} image\n\t * @param {Object} constraints Width & height constraints\n\t * @return {HTMLCanvasElement|null}\n\t */\n\tmw.UploadWizardUpload.prototype.getTransformedCanvasElement = function ( image, constraints ) {\n\t\tvar angle, scaling, width, height,\n\t\t\tdimensions, dx, dy, x, y, $canvas, ctx,\n\t\t\tscaleConstraints = constraints,\n\t\t\trotation = 0;\n\n\t\t// if this wiki can rotate images to match their EXIF metadata,\n\t\t// we should do the same in our preview\n\t\tif ( mw.config.get( 'wgFileCanRotate' ) ) {\n\t\t\tangle = this.getOrientationDegrees();\n\t\t\trotation = angle ? 360 - angle : 0;\n\t\t}\n\n\t\t// swap scaling constraints if needed by rotation...\n\t\tif ( rotation === 90 || rotation === 270 ) {\n\t\t\tscaleConstraints = {};\n\t\t\tif ( 'height' in constraints ) {\n\t\t\t\tscaleConstraints.width = constraints.height;\n\t\t\t}\n\t\t\tif ( 'width' in constraints ) {\n\t\t\t\tscaleConstraints.height = constraints.width;\n\t\t\t}\n\t\t}\n\n\t\tscaling = this.getScalingFromConstraints( image, scaleConstraints );\n\n\t\twidth = image.width * scaling;\n\t\theight = image.height * scaling;\n\n\t\tdimensions = { width: width, height: height };\n\t\tif ( rotation === 90 || rotation === 270 ) {\n\t\t\tdimensions = { width: height, height: width };\n\t\t}\n\n\t\t// Start drawing at offset 0,0\n\t\tdx = 0;\n\t\tdy = 0;\n\n\t\tswitch ( rotation ) {\n\t\t\t// If a rotation is applied, the direction of the axis\n\t\t\t// changes as well. You can derive the values below by\n\t\t\t// drawing on paper an axis system, rotate it and see\n\t\t\t// where the positive axis direction is\n\t\t\tcase 90:\n\t\t\t\tx = dx;\n\t\t\t\ty = dy - height;\n\t\t\t\tbreak;\n\t\t\tcase 180:\n\t\t\t\tx = dx - width;\n\t\t\t\ty = dy - height;\n\t\t\t\tbreak;\n\t\t\tcase 270:\n\t\t\t\tx = dx - width;\n\t\t\t\ty = dy;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tx = dx;\n\t\t\t\ty = dy;\n\t\t\t\tbreak;\n\t\t}\n\n\t\t$canvas = $( '<canvas>' ).attr( dimensions );\n\t\tctx = $canvas[ 0 ].getContext( '2d' );\n\t\tctx.clearRect( dx, dy, width, height );\n\t\tctx.rotate( rotation / 180 * Math.PI );\n\t\ttry {\n\t\t\t// Calling #drawImage likes to throw all kinds of ridiculous exceptions in various browsers,\n\t\t\t// including but not limited to:\n\t\t\t// * (Firefox) NS_ERROR_NOT_AVAILABLE:\n\t\t\t// * (Internet Explorer / Edge) Not enough storage is available to complete this operation.\n\t\t\t// * (Internet Explorer / Edge) Unspecified error.\n\t\t\t// * (Internet Explorer / Edge) The GPU device instance has been suspended. Use GetDeviceRemovedReason to determine the appropriate action.\n\t\t\t// * (Safari) IndexSizeError: Index or size was negative, or greater than the allowed value.\n\t\t\t// There is nothing we can do about this. It's okay though, there just won't be a thumbnail.\n\t\t\tctx.drawImage( image, x, y, width, height );\n\t\t} catch ( err ) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn $canvas;\n\t};\n\n\t/**\n\t * Return a browser-scaled image element, given an image and constraints.\n\t *\n\t * @private\n\t * @param {HTMLImageElement} image\n\t * @param {Object} constraints Width and height properties\n\t * @return {HTMLImageElement} with same src, but different attrs\n\t */\n\tmw.UploadWizardUpload.prototype.getBrowserScaledImageElement = function ( image, constraints ) {\n\t\tvar scaling = this.getScalingFromConstraints( image, constraints );\n\t\treturn $( '<img>' )\n\t\t\t.attr( {\n\t\t\t\twidth: parseInt( image.width * scaling, 10 ),\n\t\t\t\theight: parseInt( image.height * scaling, 10 ),\n\t\t\t\tsrc: image.src\n\t\t\t} );\n\t};\n\n\t/**\n\t * Return an element suitable for the preview of a certain size. Uses canvas when possible\n\t *\n\t * @private\n\t * @param {HTMLImageElement} image\n\t * @param {number} width\n\t * @param {number} height\n\t * @return {HTMLCanvasElement|HTMLImageElement}\n\t */\n\tmw.UploadWizardUpload.prototype.getScaledImageElement = function ( image, width, height ) {\n\t\tvar constraints = {},\n\t\t\ttransform;\n\n\t\tif ( width ) {\n\t\t\tconstraints.width = width;\n\t\t}\n\t\tif ( height ) {\n\t\t\tconstraints.height = height;\n\t\t}\n\n\t\tif ( mw.canvas.isAvailable() ) {\n\t\t\ttransform = this.getTransformedCanvasElement( image, constraints );\n\t\t\tif ( transform ) {\n\t\t\t\treturn transform;\n\t\t\t}\n\t\t}\n\n\t\t// No canvas support or canvas drawing failed mysteriously, fall back\n\t\treturn this.getBrowserScaledImageElement( image, constraints );\n\t};\n\n\t/**\n\t * Acquire a thumbnail for this upload.\n\t *\n\t * @param {number} width\n\t * @param {number} height\n\t * @return {jQuery.Promise} Promise resolved with the HTMLImageElement or HTMLCanvasElement\n\t *   containing a thumbnail, or resolved with `null` when one can't be produced\n\t */\n\tmw.UploadWizardUpload.prototype.getThumbnail = function ( width, height ) {\n\t\tvar upload = this,\n\t\t\tdeferred = $.Deferred();\n\n\t\tif ( this.thumbnailPromise[ width + 'x' + height ] ) {\n\t\t\treturn this.thumbnailPromise[ width + 'x' + height ];\n\t\t}\n\t\tthis.thumbnailPromise[ width + 'x' + height ] = deferred.promise();\n\n\t\t/**\n\t\t * @param {HTMLImageElement|null} image\n\t\t */\n\t\tfunction imageCallback( image ) {\n\t\t\tif ( image === null ) {\n\t\t\t\tupload.ui.setStatus( 'mediauploader-thumbnail-failed' );\n\t\t\t\tdeferred.resolve( image );\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\timage = upload.getScaledImageElement( image, width, height );\n\t\t\tdeferred.resolve( image );\n\t\t}\n\n\t\tthis.extractMetadataFromJpegMeta()\n\t\t\t.then( upload.makePreview.bind( upload, width ) )\n\t\t\t.done( imageCallback )\n\t\t\t.fail( function () {\n\t\t\t\t// Can't generate the thumbnail locally, get the thumbnail via API after\n\t\t\t\t// the file is uploaded. Queries are cached, so if this thumbnail was\n\t\t\t\t// already fetched for some reason, we'll get it immediately.\n\t\t\t\tif ( upload.state !== 'new' && upload.state !== 'transporting' && upload.state !== 'error' ) {\n\t\t\t\t\tupload.getApiThumbnail( width, height ).done( imageCallback );\n\t\t\t\t} else {\n\t\t\t\t\tupload.once( 'success', function () {\n\t\t\t\t\t\tupload.getApiThumbnail( width, height ).done( imageCallback );\n\t\t\t\t\t} );\n\t\t\t\t}\n\t\t\t} );\n\n\t\treturn this.thumbnailPromise[ width + 'x' + height ];\n\t};\n\n\t/**\n\t * Notification that the file input has changed and it's fine...set info.\n\t */\n\tmw.UploadWizardUpload.prototype.fileChangedOk = function () {\n\t\tthis.ui.fileChangedOk( this.imageinfo, this.file );\n\t};\n\n\t/**\n\t * Make a preview for the file.\n\t *\n\t * @private\n\t * @param {number} width\n\t * @return {jQuery.Promise}\n\t */\n\tmw.UploadWizardUpload.prototype.makePreview = function ( width ) {\n\t\tvar first, video, url, dataUrlReader,\n\t\t\tdeferred = $.Deferred(),\n\t\t\tupload = this;\n\n\t\t// do preview if we can\n\t\tif ( this.isPreviewable() ) {\n\t\t\t// open video and get frame via canvas\n\t\t\tif ( this.isVideo() ) {\n\t\t\t\tfirst = true;\n\t\t\t\tvideo = document.createElement( 'video' );\n\n\t\t\t\tvideo.addEventListener( 'loadedmetadata', function () {\n\t\t\t\t\t// seek 2 seconds into video or to half if shorter\n\t\t\t\t\tvideo.currentTime = Math.min( 2, video.duration / 2 );\n\t\t\t\t\tvideo.volume = 0;\n\t\t\t\t} );\n\t\t\t\tvideo.addEventListener( 'seeked', function () {\n\t\t\t\t\t// Firefox 16 sometimes does not work on first seek, seek again\n\t\t\t\t\tif ( first ) {\n\t\t\t\t\t\tfirst = false;\n\t\t\t\t\t\tvideo.currentTime = Math.min( 2, video.duration / 2 );\n\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Chrome sometimes shows black frames if grabbing right away.\n\t\t\t\t\t\t// wait 500ms before grabbing frame\n\t\t\t\t\t\tsetTimeout( function () {\n\t\t\t\t\t\t\tvar context,\n\t\t\t\t\t\t\t\tcanvas = document.createElement( 'canvas' );\n\t\t\t\t\t\t\tcanvas.width = width;\n\t\t\t\t\t\t\tcanvas.height = Math.round( canvas.width * video.videoHeight / video.videoWidth );\n\t\t\t\t\t\t\tcontext = canvas.getContext( '2d' );\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t// More ridiculous exceptions, see the comment in #getTransformedCanvasElement\n\t\t\t\t\t\t\t\tcontext.drawImage( video, 0, 0, canvas.width, canvas.height );\n\t\t\t\t\t\t\t} catch ( err ) {\n\t\t\t\t\t\t\t\tdeferred.reject();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tupload.loadImage( canvas.toDataURL(), deferred );\n\t\t\t\t\t\t\tupload.URL().revokeObjectURL( video.url );\n\t\t\t\t\t\t}, 500 );\n\t\t\t\t\t}\n\t\t\t\t} );\n\t\t\t\turl = this.URL().createObjectURL( this.file );\n\t\t\t\tvideo.src = url;\n\t\t\t\t// If we can't get a frame within 10 seconds, something is probably seriously wrong.\n\t\t\t\t// This can happen for broken files where we can't actually seek to the time we wanted.\n\t\t\t\tsetTimeout( function () {\n\t\t\t\t\tdeferred.reject();\n\t\t\t\t\tupload.URL().revokeObjectURL( video.url );\n\t\t\t\t}, 10000 );\n\t\t\t} else {\n\t\t\t\tdataUrlReader = new FileReader();\n\t\t\t\tdataUrlReader.onload = function () {\n\t\t\t\t\t// this step (inserting image-as-dataurl into image object) is slow for large images, which\n\t\t\t\t\t// is why this is optional and has a control attached to it to load the preview.\n\t\t\t\t\tupload.loadImage( dataUrlReader.result, deferred );\n\t\t\t\t};\n\t\t\t\tdataUrlReader.readAsDataURL( this.file );\n\t\t\t}\n\t\t} else {\n\t\t\tdeferred.reject();\n\t\t}\n\n\t\treturn deferred.promise();\n\t};\n\n\t/**\n\t * Loads an image preview.\n\t *\n\t * @param {string} url\n\t * @param {jQuery.Deferred} deferred\n\t */\n\tmw.UploadWizardUpload.prototype.loadImage = function ( url, deferred ) {\n\t\tvar image = document.createElement( 'img' );\n\t\timage.onload = function () {\n\t\t\tdeferred.resolve( image );\n\t\t};\n\t\timage.onerror = function () {\n\t\t\tdeferred.reject();\n\t\t};\n\t\ttry {\n\t\t\timage.src = url;\n\t\t} catch ( er ) {\n\t\t\t// On Internet Explorer 11 and Edge, this occasionally causes an exception (possibly\n\t\t\t// localised) like \"Not enough storage is available to complete this operation\". (T136239)\n\t\t\tdeferred.reject();\n\t\t}\n\t};\n\n\t/**\n\t * Check if the file is previewable.\n\t *\n\t * @return {boolean}\n\t */\n\tmw.UploadWizardUpload.prototype.isPreviewable = function () {\n\t\treturn this.file && mw.fileApi.isPreviewableFile( this.file );\n\t};\n\n\t/**\n\t * Finds the right URL object to use.\n\t *\n\t * @return {URL}\n\t */\n\tmw.UploadWizardUpload.prototype.URL = function () {\n\t\t// This functionality is missing on IE 11\n\t\t// eslint-disable-next-line compat/compat\n\t\treturn window.URL || window.webkitURL || window.mozURL;\n\t};\n\n\t/**\n\t * Checks if this upload is a video.\n\t *\n\t * @return {boolean}\n\t */\n\tmw.UploadWizardUpload.prototype.isVideo = function () {\n\t\treturn mw.fileApi.isPreviewableVideo( this.file );\n\t};\n\n}( mw.uploadWizard ) );\n","usedDeprecatedRules":[]},{"filePath":"/src/repo/resources/mw.UploadWizardUploadInterface.js","messages":[],"suppressedMessages":[{"ruleId":"mediawiki/msg-doc","severity":1,"message":"All possible message keys should be documented. See https://w.wiki/4r9a for details.","line":101,"column":3,"nodeType":"CallExpression","endLine":101,"endColumn":36,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/resources/mw.canvas.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/resources/mw.errorDialog.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/resources/mw.fileApi.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/resources/transports/mw.FormDataTransport.js","messages":[],"suppressedMessages":[{"ruleId":"no-loop-func","severity":2,"message":"Function declared in a loop contains unsafe references to variable(s) 'prevPromise', 'prevPromise'.","line":164,"column":6,"nodeType":"FunctionExpression","messageId":"unsafeRefs","endLine":180,"endColumn":5,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/resources/ui/steps/uw.ui.Deed.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/resources/ui/steps/uw.ui.Details.js","messages":[],"suppressedMessages":[{"ruleId":"no-jquery/no-global-selector","severity":2,"message":"Avoid queries which search the entire DOM. Keep DOM nodes in memory where possible.","line":183,"column":4,"nodeType":"CallExpression","endLine":183,"endColumn":21,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-global-selector","severity":2,"message":"Avoid queries which search the entire DOM. Keep DOM nodes in memory where possible.","line":191,"column":4,"nodeType":"CallExpression","endLine":191,"endColumn":21,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-global-selector","severity":2,"message":"Avoid queries which search the entire DOM. Keep DOM nodes in memory where possible.","line":222,"column":4,"nodeType":"CallExpression","endLine":222,"endColumn":21,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/resources/ui/steps/uw.ui.Thanks.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/resources/ui/steps/uw.ui.Tutorial.js","messages":[],"suppressedMessages":[{"ruleId":"no-jquery/no-global-selector","severity":2,"message":"Avoid queries which search the entire DOM. Keep DOM nodes in memory where possible.","line":87,"column":24,"nodeType":"CallExpression","endLine":87,"endColumn":59,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/resources/ui/steps/uw.ui.Upload.js","messages":[],"suppressedMessages":[{"ruleId":"no-jquery/no-sizzle","severity":2,"message":"Positional selector extensions are not allowed","line":132,"column":4,"nodeType":"CallExpression","endLine":132,"endColumn":39,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-sizzle","severity":2,"message":"Positional selector extensions are not allowed","line":134,"column":4,"nodeType":"CallExpression","endLine":134,"endColumn":40,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/resources/ui/uw.ui.DeedPreview.js","messages":[],"suppressedMessages":[{"ruleId":"no-jquery/no-global-selector","severity":2,"message":"Avoid queries which search the entire DOM. Keep DOM nodes in memory where possible.","line":34,"column":3,"nodeType":"CallExpression","endLine":34,"endColumn":41,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/resources/ui/uw.ui.Step.js","messages":[],"suppressedMessages":[{"ruleId":"no-jquery/no-global-selector","severity":2,"message":"Avoid queries which search the entire DOM. Keep DOM nodes in memory where possible.","line":39,"column":3,"nodeType":"CallExpression","endLine":39,"endColumn":32,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-global-selector","severity":2,"message":"Avoid queries which search the entire DOM. Keep DOM nodes in memory where possible.","line":56,"column":16,"nodeType":"CallExpression","endLine":56,"endColumn":25,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-global-selector","severity":2,"message":"Avoid queries which search the entire DOM. Keep DOM nodes in memory where possible.","line":64,"column":3,"nodeType":"CallExpression","endLine":64,"endColumn":20,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/resources/ui/uw.ui.Wizard.js","messages":[],"suppressedMessages":[{"ruleId":"no-jquery/no-global-selector","severity":2,"message":"Avoid queries which search the entire DOM. Keep DOM nodes in memory where possible.","line":49,"column":21,"nodeType":"CallExpression","endLine":49,"endColumn":39,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-sizzle","severity":2,"message":"Positional selector extensions are not allowed","line":66,"column":3,"nodeType":"CallExpression","endLine":66,"endColumn":51,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/resources/ui/uw.ui.base.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/resources/uw.ConcurrentQueue.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/resources/uw.CopyMetadataWidget.js","messages":[],"suppressedMessages":[{"ruleId":"no-jquery/no-fade","severity":2,"message":"Prefer CSS transitions to .fadeOut","line":126,"column":3,"nodeType":"CallExpression","endLine":129,"endColumn":30,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-fade","severity":2,"message":"Prefer CSS transitions to .fadeOut","line":143,"column":3,"nodeType":"CallExpression","endLine":146,"endColumn":30,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-loop-func","severity":2,"message":"Function declared in a loop contains unsafe references to variable(s) 'i'.","line":195,"column":6,"nodeType":"FunctionExpression","messageId":"unsafeRefs","endLine":199,"endColumn":7,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/resources/uw.DetailsWidget.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/resources/uw.FieldLayout.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/resources/uw.LicenseGroup.js","messages":[],"suppressedMessages":[{"ruleId":"mediawiki/msg-doc","severity":1,"message":"All possible message keys should be documented. See https://w.wiki/4r9a for details.","line":89,"column":35,"nodeType":"CallExpression","endLine":91,"endColumn":41,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"mediawiki/msg-doc","severity":1,"message":"All possible message keys should be documented. See https://w.wiki/4r9a for details.","line":92,"column":38,"nodeType":"CallExpression","endLine":94,"endColumn":44,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"mediawiki/class-doc","severity":1,"message":"All possible CSS classes should be documented. See https://w.wiki/PS2 for details.","line":147,"column":39,"nodeType":"ObjectExpression","endLine":147,"endColumn":75,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"mediawiki/class-doc","severity":1,"message":"All possible CSS classes should be documented. See https://w.wiki/PS2 for details.","line":182,"column":47,"nodeType":"ObjectExpression","endLine":182,"endColumn":83,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"mediawiki/class-doc","severity":1,"message":"All possible CSS classes should be documented. See https://w.wiki/PS2 for details.","line":337,"column":20,"nodeType":"CallExpression","endLine":337,"endColumn":106,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"mediawiki/msg-doc","severity":1,"message":"All possible message keys should be documented. See https://w.wiki/4r9a for details.","line":342,"column":12,"nodeType":"CallExpression","endLine":343,"endColumn":53,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/resources/uw.LicensePreviewDialog.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/resources/uw.ValidationMessageElement.js","messages":[],"suppressedMessages":[{"ruleId":"mediawiki/class-doc","severity":1,"message":"All possible CSS classes should be documented. See https://w.wiki/PS2 for details.","line":91,"column":15,"nodeType":"CallExpression","endLine":92,"endColumn":65,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/resources/uw.base.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/resources/uw.units.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/schemas/campaign.yaml","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/schemas/json-schema-draft-4.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/sql/tables.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/tests/qunit/.eslintrc.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/tests/qunit/controller/uw.controller.Deed.test.js","messages":[{"ruleId":"qunit/no-loose-assertions","severity":2,"message":"Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual.","line":23,"column":3,"nodeType":"CallExpression","messageId":"unexpectedLocalLooseAssertion","endLine":23,"endColumn":20},{"ruleId":"qunit/no-loose-assertions","severity":2,"message":"Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual.","line":24,"column":3,"nodeType":"CallExpression","messageId":"unexpectedLocalLooseAssertion","endLine":24,"endColumn":50},{"ruleId":"qunit/no-loose-assertions","severity":2,"message":"Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual.","line":25,"column":3,"nodeType":"CallExpression","messageId":"unexpectedLocalLooseAssertion","endLine":25,"endColumn":23}],"suppressedMessages":[],"errorCount":3,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/*\n * This file is part of the MediaWiki extension MediaUploader.\n *\n * MediaUploader is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 2 of the License, or\n * (at your option) any later version.\n *\n * MediaUploader is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with MediaUploader.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n( function ( uw ) {\n\tQUnit.module( 'uw.controller.Deed', QUnit.newMwEnvironment() );\n\n\tQUnit.test( 'Constructor sanity test', function ( assert ) {\n\t\tvar step = new uw.controller.Deed();\n\t\tassert.ok( step );\n\t\tassert.ok( step instanceof uw.controller.Step );\n\t\tassert.ok( step.ui );\n\t} );\n\n\tQUnit.test( 'load', function ( assert ) {\n\t\tvar step = new uw.controller.Deed(\n\t\t\t\tnew mw.Api(),\n\t\t\t\t{ licensing: {\n\t\t\t\t\tenabled: true,\n\t\t\t\t\tthirdParty: { type: 'radio', licenses: [] },\n\t\t\t\t\tshowTypes: [ 'thirdParty' ],\n\t\t\t\t\tdefaultType: 'thirdParty'\n\t\t\t\t} }\n\t\t\t),\n\t\t\tststub = this.sandbox.stub().returns( $.Deferred().promise() ),\n\t\t\tuploads = [\n\t\t\t\t{ file: {}, getThumbnail: ststub, on: function () {}, title: mw.Title.newFromText( 'Test2.jpg', 6 ) },\n\t\t\t\t{ file: {}, getThumbnail: ststub, on: function () {}, title: mw.Title.newFromText( 'Test4.jpg', 6 ) }\n\t\t\t];\n\n\t\tthis.sandbox.stub( step.ui, 'load' );\n\t\tstep.load( uploads );\n\n\t\tassert.strictEqual( ststub.callCount, 2 );\n\t} );\n}( mw.uploadWizard ) );\n","usedDeprecatedRules":[]},{"filePath":"/src/repo/tests/qunit/controller/uw.controller.Details.test.js","messages":[{"ruleId":"qunit/no-loose-assertions","severity":2,"message":"Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual.","line":51,"column":3,"nodeType":"CallExpression","messageId":"unexpectedLocalLooseAssertion","endLine":51,"endColumn":20},{"ruleId":"qunit/no-loose-assertions","severity":2,"message":"Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual.","line":52,"column":3,"nodeType":"CallExpression","messageId":"unexpectedLocalLooseAssertion","endLine":52,"endColumn":50},{"ruleId":"qunit/no-loose-assertions","severity":2,"message":"Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual.","line":53,"column":3,"nodeType":"CallExpression","messageId":"unexpectedLocalLooseAssertion","endLine":53,"endColumn":23},{"ruleId":"qunit/no-loose-assertions","severity":2,"message":"Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual.","line":71,"column":3,"nodeType":"CallExpression","messageId":"unexpectedLocalLooseAssertion","endLine":71,"endColumn":33},{"ruleId":"qunit/no-loose-assertions","severity":2,"message":"Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual.","line":76,"column":3,"nodeType":"CallExpression","messageId":"unexpectedLocalLooseAssertion","endLine":76,"endColumn":44},{"ruleId":"qunit/no-loose-assertions","severity":2,"message":"Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual.","line":78,"column":3,"nodeType":"CallExpression","messageId":"unexpectedLocalLooseAssertion","endLine":78,"endColumn":33},{"ruleId":"qunit/no-loose-assertions","severity":2,"message":"Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual.","line":85,"column":3,"nodeType":"CallExpression","messageId":"unexpectedLocalLooseAssertion","endLine":85,"endColumn":33},{"ruleId":"qunit/no-loose-assertions","severity":2,"message":"Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual.","line":92,"column":3,"nodeType":"CallExpression","messageId":"unexpectedLocalLooseAssertion","endLine":92,"endColumn":33},{"ruleId":"qunit/no-loose-assertions","severity":2,"message":"Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual.","line":149,"column":6,"nodeType":"CallExpression","messageId":"unexpectedLocalLooseAssertion","endLine":149,"endColumn":34}],"suppressedMessages":[],"errorCount":9,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/*\n * This file is part of the MediaWiki extension MediaUploader.\n *\n * MediaUploader is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 2 of the License, or\n * (at your option) any later version.\n *\n * MediaUploader is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with MediaUploader.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n( function ( uw ) {\n\tQUnit.module( 'uw.controller.Details', QUnit.newMwEnvironment() );\n\n\tfunction createTestUpload( sandbox, customDeedChooser, aborted ) {\n\t\tvar stubs = {\n\t\t\tucdc: sandbox.stub(),\n\t\t\tgetSerialized: sandbox.stub(),\n\t\t\tsetSerialized: sandbox.stub(),\n\t\t\tattach: sandbox.stub()\n\t\t};\n\n\t\treturn {\n\t\t\tdeedChooser: { deed: { name: customDeedChooser ? 'custom' : 'cc-by-sa-4.0' } },\n\n\t\t\ton: function () {},\n\n\t\t\tdetails: {\n\t\t\t\tuseCustomDeedChooser: stubs.ucdc,\n\t\t\t\tgetSerialized: stubs.getSerialized,\n\t\t\t\tsetSerialized: stubs.setSerialized,\n\t\t\t\tattach: stubs.attach\n\t\t\t},\n\n\t\t\tstate: aborted ? 'aborted' : 'stashed',\n\n\t\t\tstubs: stubs\n\t\t};\n\t}\n\n\tQUnit.test( 'Constructor sanity test', function ( assert ) {\n\t\tvar step = new uw.controller.Details( new mw.Api(), {\n\t\t\tmaxSimultaneousConnections: 1\n\t\t} );\n\t\tassert.ok( step );\n\t\tassert.ok( step instanceof uw.controller.Step );\n\t\tassert.ok( step.ui );\n\t} );\n\n\tQUnit.test( 'load', function ( assert ) {\n\t\tvar step = new uw.controller.Details( new mw.Api(), {\n\t\t\t\tmaxSimultaneousConnections: 1\n\t\t\t} ),\n\t\t\ttestUpload = createTestUpload( this.sandbox ),\n\t\t\tstepUiStub = this.sandbox.stub( step.ui, 'load' );\n\n\t\t// replace createDetails with a stub; UploadWizardDetails needs way too\n\t\t// much setup to actually be able to create it\n\t\tstep.createDetails = this.sandbox.stub();\n\n\t\tstep.load( [ testUpload ] );\n\n\t\tassert.strictEqual( testUpload.stubs.ucdc.called, false );\n\t\tassert.strictEqual( step.createDetails.callCount, 1 );\n\t\tassert.ok( stepUiStub.called );\n\n\t\ttestUpload = createTestUpload( this.sandbox, true );\n\t\tstep.load( [ testUpload ] );\n\n\t\tassert.ok( testUpload.stubs.ucdc.called );\n\t\tassert.strictEqual( step.createDetails.callCount, 2 );\n\t\tassert.ok( stepUiStub.called );\n\n\t\ttestUpload = createTestUpload( this.sandbox );\n\t\tstep.load( [ testUpload, createTestUpload( this.sandbox ) ] );\n\n\t\tassert.strictEqual( testUpload.stubs.ucdc.called, false );\n\t\tassert.strictEqual( step.createDetails.callCount, 4 );\n\t\tassert.ok( stepUiStub.called );\n\n\t\ttestUpload = createTestUpload( this.sandbox );\n\t\tstep.load( [ testUpload, createTestUpload( this.sandbox, false, true ) ] );\n\n\t\tassert.strictEqual( testUpload.stubs.ucdc.called, false );\n\t\tassert.strictEqual( step.createDetails.callCount, 6 );\n\t\tassert.ok( stepUiStub.called );\n\t} );\n\n\tQUnit.test( 'canTransition', function ( assert ) {\n\t\tvar upload = {},\n\t\t\tstep = new uw.controller.Details( new mw.Api(), {\n\t\t\t\tmaxSimultaneousConnections: 1\n\t\t\t} );\n\n\t\tassert.strictEqual( step.canTransition( upload ), false );\n\t\tupload.state = 'details';\n\t\tassert.strictEqual( step.canTransition( upload ), true );\n\t\tupload.state = 'complete';\n\t\tassert.strictEqual( step.canTransition( upload ), false );\n\t} );\n\n\tQUnit.test( 'transitionAll', function ( assert ) {\n\t\tvar tostub,\n\t\t\tdone = assert.async(),\n\t\t\tdonestub = this.sandbox.stub(),\n\t\t\tds = [ $.Deferred(), $.Deferred(), $.Deferred() ],\n\t\t\tps = [ ds[ 0 ].promise(), ds[ 1 ].promise(), ds[ 2 ].promise() ],\n\t\t\tcalls = [],\n\t\t\tstep;\n\n\t\ttostub = this.sandbox.stub( uw.controller.Details.prototype, 'transitionOne' );\n\t\ttostub.onFirstCall().returns( ps[ 0 ] );\n\t\ttostub.onSecondCall().returns( ps[ 1 ] );\n\t\ttostub.onThirdCall().returns( ps[ 2 ] );\n\n\t\tthis.sandbox.stub( uw.controller.Details.prototype, 'canTransition' ).returns( true );\n\n\t\tstep = new uw.controller.Details( new mw.Api(), {\n\t\t\tmaxSimultaneousConnections: 3\n\t\t} );\n\n\t\tstep.uploads = [\n\t\t\t{ id: 15 },\n\t\t\tundefined,\n\t\t\t{ id: 21 },\n\t\t\t{ id: 'aoeu' }\n\t\t];\n\n\t\tstep.transitionAll().done( donestub );\n\t\tsetTimeout( function () {\n\t\t\tcalls = [ tostub.getCall( 0 ), tostub.getCall( 1 ), tostub.getCall( 2 ) ];\n\n\t\t\tassert.strictEqual( calls[ 0 ].args[ 0 ].id, 15 );\n\t\t\tassert.strictEqual( calls[ 1 ].args[ 0 ].id, 21 );\n\n\t\t\tds[ 0 ].resolve();\n\t\t\tds[ 1 ].resolve();\n\t\t\tsetTimeout( function () {\n\t\t\t\tassert.strictEqual( donestub.called, false );\n\n\t\t\t\tds[ 2 ].resolve();\n\t\t\t\tsetTimeout( function () {\n\t\t\t\t\tassert.ok( donestub.called );\n\n\t\t\t\t\tdone();\n\t\t\t\t} );\n\t\t\t} );\n\t\t} );\n\t} );\n\n}( mw.uploadWizard ) );\n","usedDeprecatedRules":[]},{"filePath":"/src/repo/tests/qunit/controller/uw.controller.Step.test.js","messages":[{"ruleId":"qunit/no-loose-assertions","severity":2,"message":"Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual.","line":23,"column":3,"nodeType":"CallExpression","messageId":"unexpectedLocalLooseAssertion","endLine":23,"endColumn":20},{"ruleId":"qunit/no-loose-assertions","severity":2,"message":"Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual.","line":24,"column":3,"nodeType":"CallExpression","messageId":"unexpectedLocalLooseAssertion","endLine":24,"endColumn":23}],"suppressedMessages":[],"errorCount":2,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/*\n * This file is part of the MediaWiki extension MediaUploader.\n *\n * MediaUploader is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 2 of the License, or\n * (at your option) any later version.\n *\n * MediaUploader is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with MediaUploader.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n( function ( uw ) {\n\tQUnit.module( 'uw.controller.Step', QUnit.newMwEnvironment() );\n\n\tQUnit.test( 'Constructor sanity test', function ( assert ) {\n\t\tvar step = new uw.controller.Step( { on: function () {} }, new mw.Api(), {} );\n\t\tassert.ok( step );\n\t\tassert.ok( step.ui );\n\t} );\n\n}( mw.uploadWizard ) );\n","usedDeprecatedRules":[]},{"filePath":"/src/repo/tests/qunit/controller/uw.controller.Thanks.test.js","messages":[{"ruleId":"qunit/no-loose-assertions","severity":2,"message":"Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual.","line":23,"column":3,"nodeType":"CallExpression","messageId":"unexpectedLocalLooseAssertion","endLine":23,"endColumn":20},{"ruleId":"qunit/no-loose-assertions","severity":2,"message":"Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual.","line":24,"column":3,"nodeType":"CallExpression","messageId":"unexpectedLocalLooseAssertion","endLine":24,"endColumn":50},{"ruleId":"qunit/no-loose-assertions","severity":2,"message":"Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual.","line":25,"column":3,"nodeType":"CallExpression","messageId":"unexpectedLocalLooseAssertion","endLine":25,"endColumn":23}],"suppressedMessages":[],"errorCount":3,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/*\n * This file is part of the MediaWiki extension MediaUploader.\n *\n * MediaUploader is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 2 of the License, or\n * (at your option) any later version.\n *\n * MediaUploader is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with MediaUploader.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n( function ( uw ) {\n\tQUnit.module( 'uw.controller.Thanks', QUnit.newMwEnvironment() );\n\n\tQUnit.test( 'Constructor sanity test', function ( assert ) {\n\t\tvar step = new uw.controller.Thanks( new mw.Api(), { display: { thanksLabel: 'Thanks!' } } );\n\t\tassert.ok( step );\n\t\tassert.ok( step instanceof uw.controller.Step );\n\t\tassert.ok( step.ui );\n\t} );\n\n\tQUnit.test( 'load', function ( assert ) {\n\t\tvar step = new uw.controller.Thanks( new mw.Api(), {} ),\n\t\t\tauStub = this.sandbox.stub( step.ui, 'addUpload' );\n\n\t\tthis.sandbox.stub( step.ui, 'load' );\n\t\tstep.load( [\n\t\t\t{ on: function () {} },\n\t\t\t{ on: function () {} },\n\t\t\t{ on: function () {} }\n\t\t] );\n\n\t\tassert.strictEqual( auStub.callCount, 3 );\n\t} );\n\n\tQUnit.test( 'Custom button configuration', function ( assert ) {\n\t\tvar config = {\n\t\t\t\tdisplay: {\n\t\t\t\t\thomeButton: {\n\t\t\t\t\t\tlabel: 'This is just a test',\n\t\t\t\t\t\ttarget: 'https://wiki.example.com/wiki/Main_Page'\n\t\t\t\t\t},\n\t\t\t\t\tbeginButton: {\n\t\t\t\t\t\tlabel: 'Let me start again',\n\t\t\t\t\t\ttarget: 'https://commons.wikimedia.org/wiki/Special:UploadWizard'\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t\tuiThanks = new uw.ui.Thanks( config );\n\n\t\tassert.strictEqual(\n\t\t\tuiThanks.homeButton.getLabel(),\n\t\t\t'This is just a test',\n\t\t\t'The label of the home button matches the configured text.'\n\t\t);\n\n\t\tassert.strictEqual(\n\t\t\tuiThanks.homeButton.getHref(),\n\t\t\t'https://wiki.example.com/wiki/Main_Page',\n\t\t\t'The target of the home button matches the configured URL.'\n\t\t);\n\n\t\tassert.strictEqual(\n\t\t\tuiThanks.beginButton.getLabel(),\n\t\t\t'Let me start again',\n\t\t\t'The label of the begin button matches the configured text.'\n\t\t);\n\n\t\tassert.strictEqual(\n\t\t\tuiThanks.beginButton.getHref(),\n\t\t\t'https://commons.wikimedia.org/wiki/Special:UploadWizard',\n\t\t\t'The target of the begin button matches the configured URL.'\n\t\t);\n\n\t} );\n\n}( mw.uploadWizard ) );\n","usedDeprecatedRules":[]},{"filePath":"/src/repo/tests/qunit/controller/uw.controller.Tutorial.test.js","messages":[{"ruleId":"qunit/no-loose-assertions","severity":2,"message":"Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual.","line":23,"column":3,"nodeType":"CallExpression","messageId":"unexpectedLocalLooseAssertion","endLine":23,"endColumn":20},{"ruleId":"qunit/no-loose-assertions","severity":2,"message":"Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual.","line":24,"column":3,"nodeType":"CallExpression","messageId":"unexpectedLocalLooseAssertion","endLine":24,"endColumn":50},{"ruleId":"qunit/no-loose-assertions","severity":2,"message":"Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual.","line":25,"column":3,"nodeType":"CallExpression","messageId":"unexpectedLocalLooseAssertion","endLine":25,"endColumn":23},{"ruleId":"qunit/no-loose-assertions","severity":2,"message":"Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual.","line":26,"column":3,"nodeType":"CallExpression","messageId":"unexpectedLocalLooseAssertion","endLine":26,"endColumn":24},{"ruleId":"qunit/no-loose-assertions","severity":2,"message":"Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual.","line":41,"column":3,"nodeType":"CallExpression","messageId":"unexpectedLocalLooseAssertion","endLine":41,"endColumn":44},{"ruleId":"qunit/no-loose-assertions","severity":2,"message":"Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual.","line":42,"column":3,"nodeType":"CallExpression","messageId":"unexpectedLocalLooseAssertion","endLine":45,"endColumn":8},{"ruleId":"qunit/no-loose-assertions","severity":2,"message":"Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual.","line":48,"column":3,"nodeType":"CallExpression","messageId":"unexpectedLocalLooseAssertion","endLine":48,"endColumn":38},{"ruleId":"qunit/no-loose-assertions","severity":2,"message":"Unexpected assert.notOk. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual.","line":59,"column":3,"nodeType":"CallExpression","messageId":"unexpectedLocalLooseAssertion","endLine":59,"endColumn":41},{"ruleId":"qunit/no-loose-assertions","severity":2,"message":"Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual.","line":62,"column":3,"nodeType":"CallExpression","messageId":"unexpectedLocalLooseAssertion","endLine":62,"endColumn":46}],"suppressedMessages":[],"errorCount":9,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/*\n * This file is part of the MediaWiki extension MediaUploader.\n *\n * MediaUploader is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 2 of the License, or\n * (at your option) any later version.\n *\n * MediaUploader is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with MediaUploader.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n( function ( uw ) {\n\tQUnit.module( 'uw.controller.Tutorial', QUnit.newMwEnvironment() );\n\n\tQUnit.test( 'Constructor sanity test', function ( assert ) {\n\t\tvar step = new uw.controller.Tutorial( new mw.Api() );\n\t\tassert.ok( step );\n\t\tassert.ok( step instanceof uw.controller.Step );\n\t\tassert.ok( step.ui );\n\t\tassert.ok( step.api );\n\t} );\n\n\tQUnit.test( 'setSkipPreference', function ( assert ) {\n\t\tvar mnStub,\n\t\t\tapi = new mw.Api(),\n\t\t\tstep = new uw.controller.Tutorial( api ),\n\t\t\tacwStub = { release: this.sandbox.stub() },\n\t\t\tpwtd = $.Deferred();\n\n\t\tthis.sandbox.stub( mw, 'confirmCloseWindow' ).returns( acwStub );\n\t\tthis.sandbox.stub( api, 'postWithToken' ).returns( pwtd.promise() );\n\n\t\tstep.setSkipPreference( true );\n\n\t\tassert.ok( mw.confirmCloseWindow.called );\n\t\tassert.ok( api.postWithToken.calledWithExactly( 'options', {\n\t\t\taction: 'options',\n\t\t\tchange: 'upwiz_skiptutorial=1'\n\t\t} ) );\n\n\t\tpwtd.resolve();\n\t\tassert.ok( acwStub.release.called );\n\n\t\tapi = new mw.Api();\n\t\tstep = new uw.controller.Tutorial( api );\n\t\tacwStub.release.reset();\n\t\tpwtd = $.Deferred();\n\t\tmnStub = this.sandbox.stub( mw, 'notify' );\n\n\t\tthis.sandbox.stub( api, 'postWithToken' ).returns( pwtd.promise() );\n\n\t\tstep.setSkipPreference( true );\n\t\tassert.notOk( acwStub.release.called );\n\n\t\tpwtd.reject( 'http', { textStatus: 'Foo bar' } );\n\t\tassert.ok( mnStub.calledWith( 'Foo bar' ) );\n\t} );\n}( mw.uploadWizard ) );\n","usedDeprecatedRules":[]},{"filePath":"/src/repo/tests/qunit/controller/uw.controller.Upload.test.js","messages":[{"ruleId":"qunit/no-loose-assertions","severity":2,"message":"Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual.","line":26,"column":3,"nodeType":"CallExpression","messageId":"unexpectedLocalLooseAssertion","endLine":26,"endColumn":20},{"ruleId":"qunit/no-loose-assertions","severity":2,"message":"Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual.","line":27,"column":3,"nodeType":"CallExpression","messageId":"unexpectedLocalLooseAssertion","endLine":27,"endColumn":50},{"ruleId":"qunit/no-loose-assertions","severity":2,"message":"Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual.","line":28,"column":3,"nodeType":"CallExpression","messageId":"unexpectedLocalLooseAssertion","endLine":28,"endColumn":23},{"ruleId":"qunit/no-loose-assertions","severity":2,"message":"Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual.","line":40,"column":3,"nodeType":"CallExpression","messageId":"unexpectedLocalLooseAssertion","endLine":40,"endColumn":48},{"ruleId":"qunit/no-loose-assertions","severity":2,"message":"Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual.","line":45,"column":3,"nodeType":"CallExpression","messageId":"unexpectedLocalLooseAssertion","endLine":45,"endColumn":49},{"ruleId":"qunit/no-loose-assertions","severity":2,"message":"Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual.","line":50,"column":3,"nodeType":"CallExpression","messageId":"unexpectedLocalLooseAssertion","endLine":50,"endColumn":49},{"ruleId":"qunit/no-loose-assertions","severity":2,"message":"Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual.","line":77,"column":3,"nodeType":"CallExpression","messageId":"unexpectedLocalLooseAssertion","endLine":77,"endColumn":35}],"suppressedMessages":[],"errorCount":7,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/*\n * This file is part of the MediaWiki extension MediaUploader.\n *\n * MediaUploader is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 2 of the License, or\n * (at your option) any later version.\n *\n * MediaUploader is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with MediaUploader.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n( function ( uw ) {\n\tQUnit.module( 'uw.controller.Upload', QUnit.newMwEnvironment() );\n\n\tQUnit.test( 'Constructor sanity test', function ( assert ) {\n\t\tvar step = new uw.controller.Upload( new mw.Api(), {\n\t\t\tmaxUploads: 10,\n\t\t\tmaxSimultaneousConnections: 3\n\t\t} );\n\t\tassert.ok( step );\n\t\tassert.ok( step instanceof uw.controller.Step );\n\t\tassert.ok( step.ui );\n\t} );\n\n\tQUnit.test( 'updateFileCounts', function ( assert ) {\n\t\tvar step = new uw.controller.Upload( new mw.Api(), {\n\t\t\t\tmaxUploads: 5,\n\t\t\t\tmaxSimultaneousConnections: 3\n\t\t\t} ),\n\t\t\tufcStub = this.sandbox.stub( step.ui, 'updateFileCounts' );\n\n\t\tstep.uploads = [ 1, 2 ];\n\t\tstep.updateFileCounts();\n\t\tassert.ok( ufcStub.calledWith( true, true ) );\n\n\t\tufcStub.reset();\n\t\tstep.uploads = [];\n\t\tstep.updateFileCounts();\n\t\tassert.ok( ufcStub.calledWith( false, true ) );\n\n\t\tufcStub.reset();\n\t\tstep.uploads = [ 1, 2, 3, 4, 5, 6 ];\n\t\tstep.updateFileCounts();\n\t\tassert.ok( ufcStub.calledWith( true, false ) );\n\t} );\n\n\tQUnit.test( 'canTransition', function ( assert ) {\n\t\tvar upload = {},\n\t\t\tstep = new uw.controller.Upload( new mw.Api(), {\n\t\t\t\tmaxSimultaneousConnections: 1\n\t\t\t} );\n\n\t\tassert.strictEqual( step.canTransition( upload ), false );\n\t\tupload.state = 'new';\n\t\tassert.strictEqual( step.canTransition( upload ), true );\n\t\tupload.state = 'stashed';\n\t\tassert.strictEqual( step.canTransition( upload ), false );\n\t} );\n\n\tQUnit.test( 'transitionOne', function ( assert ) {\n\t\tvar upload = {\n\t\t\t\tstart: this.sandbox.stub()\n\t\t\t},\n\t\t\tstep = new uw.controller.Upload( new mw.Api(), {\n\t\t\t\tmaxSimultaneousConnections: 1\n\t\t\t} );\n\n\t\tthis.sandbox.stub( step, 'maybeStartProgressBar' );\n\t\tassert.strictEqual( upload.start.called, false );\n\t\tstep.transitionOne( upload );\n\t\tassert.ok( upload.start.called );\n\t} );\n}( mw.uploadWizard ) );\n","usedDeprecatedRules":[]},{"filePath":"/src/repo/tests/qunit/mw.UploadWizardLicenseInput.test.js","messages":[{"ruleId":"qunit/no-loose-assertions","severity":2,"message":"Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual.","line":23,"column":2,"nodeType":"CallExpression","messageId":"unexpectedLocalLooseAssertion","endLine":23,"endColumn":62}],"suppressedMessages":[],"errorCount":1,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"source":"QUnit.module( 'ext.uploadWizardLicenseInput', QUnit.newMwEnvironment( {\n\tbeforeEach: function () {\n\t\tmw.UploadWizard.config = {\n\t\t\tlicenses: {\n\t\t\t\t'cc-by-sa-3.0': {\n\t\t\t\t\tmsg: 'mediauploader-license-cc-by-sa-3.0',\n\t\t\t\t\ticons: [ 'cc-by', 'cc-sa' ],\n\t\t\t\t\turl: '//creativecommons.org/licenses/by-sa/3.0/',\n\t\t\t\t\tlanguageCodePrefix: 'deed.'\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t}\n} ) );\n\nQUnit.test( 'Smoke test', function ( assert ) {\n\tvar config = { type: 'radio', licenses: [] },\n\t\t$fixture = $( '<div>' ),\n\t\tuwLicenseInput;\n\n\tuwLicenseInput = new mw.UploadWizardLicenseInput( config );\n\t$fixture.append( uwLicenseInput.$element );\n\tassert.ok( uwLicenseInput, 'LicenseInput object created !' );\n} );\n\nQUnit.test( 'createInputs()', function ( assert ) {\n\tvar config = { type: 'radio', licenses: [ 'cc-by-sa-3.0' ] },\n\t\t$fixture = $( '<div>' ),\n\t\tuwLicenseInput,\n\t\t$input,\n\t\t$label;\n\n\tuwLicenseInput = new mw.UploadWizardLicenseInput( config );\n\t$fixture.append( uwLicenseInput.$element );\n\n\t// Check radio button is there\n\t$input = $fixture.find( '.oo-ui-radioInputWidget .oo-ui-inputWidget-input[value=\"cc-by-sa-3.0\"]' );\n\tassert.strictEqual( $input.length, 1, 'Radio button created.' );\n\n\t// Check label is there\n\t$label = $input.closest( '.oo-ui-radioOptionWidget' ).find( '.oo-ui-labelElement-label' );\n\tassert.strictEqual( $label.length, 1, 'Label created.' );\n} );\n\nQUnit.test( 'createGroupedInputs()', function ( assert ) {\n\tvar config = {\n\t\t\ttype: 'checkbox',\n\t\t\tlicenseGroups: [\n\t\t\t\t{\n\t\t\t\t\thead: 'mediauploader-license-cc-head',\n\t\t\t\t\tsubhead: 'mediauploader-license-cc-subhead',\n\t\t\t\t\tlicenses: [ 'cc-by-sa-3.0' ]\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t$fixture = $( '<div>' ),\n\t\tuwLicenseInput;\n\n\tuwLicenseInput = new mw.UploadWizardLicenseInput( config );\n\t$fixture.append( uwLicenseInput.$element );\n\n\t// Check license group is there\n\tassert.strictEqual( $fixture.find( '.mediauploader-deed-license-group' ).length, 1, 'License group created.' );\n\n\t// Check subheader is there\n\tassert.strictEqual( $fixture.find( '.mediauploader-deed-license-group-subhead' ).length, 1, 'License subheader created.' );\n\n\t// Check license is there\n\tassert.strictEqual( $fixture.find( '.mediauploader-deed-license-group .oo-ui-fieldsetLayout-group' ).length, 1, 'License created.' );\n} );\n","usedDeprecatedRules":[]},{"filePath":"/src/repo/tests/qunit/mw.UploadWizardUpload.test.js","messages":[{"ruleId":"qunit/no-loose-assertions","severity":2,"message":"Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual.","line":45,"column":3,"nodeType":"CallExpression","messageId":"unexpectedLocalLooseAssertion","endLine":45,"endColumn":22}],"suppressedMessages":[],"errorCount":1,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/*\n * This file is part of the MediaWiki extension MediaUploader.\n *\n * MediaUploader is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 2 of the License, or\n * (at your option) any later version.\n *\n * MediaUploader is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with MediaUploader.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n( function () {\n\tQUnit.module( 'mw.UploadWizardUpload', QUnit.newMwEnvironment() );\n\n\tfunction createUpload( filename ) {\n\t\tvar upload,\n\t\t\toldconf = mw.UploadWizard.config;\n\n\t\tmw.UploadWizard.config = {};\n\n\t\tupload = new mw.UploadWizardUpload( {\n\t\t\tapi: {\n\t\t\t\tdefaults: {\n\t\t\t\t\tajax: {}\n\t\t\t\t}\n\t\t\t}\n\t\t}, {\n\t\t\tname: filename\n\t\t} );\n\n\t\tmw.UploadWizard.config = oldconf;\n\n\t\treturn upload;\n\t}\n\n\tQUnit.test( 'constructor sanity test', function ( assert ) {\n\t\tvar upload = createUpload();\n\n\t\tassert.ok( upload );\n\t} );\n\n\tQUnit.test( 'getBasename', function ( assert ) {\n\t\tvar upload;\n\n\t\tupload = createUpload( 'path/to/filename.png' );\n\t\tassert.strictEqual( upload.getBasename(), 'filename.png', 'Path is stripped' );\n\n\t\tupload = createUpload( 'filename.png' );\n\t\tassert.strictEqual( upload.getBasename(), 'filename.png', 'Only filename is left alone' );\n\n\t\tupload = createUpload( '///////////' );\n\t\tassert.strictEqual( upload.getBasename(), '', 'Nonsensical path is just removed' );\n\t} );\n}() );\n","usedDeprecatedRules":[]},{"filePath":"/src/repo/tests/qunit/mw.fileApi.test.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/tests/qunit/transports/mw.FormDataTransport.test.js","messages":[{"ruleId":"qunit/no-loose-assertions","severity":2,"message":"Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual.","line":39,"column":3,"nodeType":"CallExpression","messageId":"unexpectedLocalLooseAssertion","endLine":39,"endColumn":25},{"ruleId":"qunit/no-loose-assertions","severity":2,"message":"Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual.","line":48,"column":3,"nodeType":"CallExpression","messageId":"unexpectedLocalLooseAssertion","endLine":48,"endColumn":39},{"ruleId":"qunit/no-loose-assertions","severity":2,"message":"Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual.","line":52,"column":3,"nodeType":"CallExpression","messageId":"unexpectedLocalLooseAssertion","endLine":52,"endColumn":36},{"ruleId":"qunit/no-loose-assertions","severity":2,"message":"Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual.","line":53,"column":3,"nodeType":"CallExpression","messageId":"unexpectedLocalLooseAssertion","endLine":53,"endColumn":33},{"ruleId":"qunit/no-loose-assertions","severity":2,"message":"Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual.","line":60,"column":3,"nodeType":"CallExpression","messageId":"unexpectedLocalLooseAssertion","endLine":60,"endColumn":22},{"ruleId":"qunit/no-loose-assertions","severity":2,"message":"Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual.","line":78,"column":3,"nodeType":"CallExpression","messageId":"unexpectedLocalLooseAssertion","endLine":78,"endColumn":30},{"ruleId":"qunit/no-loose-assertions","severity":2,"message":"Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual.","line":82,"column":3,"nodeType":"CallExpression","messageId":"unexpectedLocalLooseAssertion","endLine":82,"endColumn":27},{"ruleId":"qunit/no-loose-assertions","severity":2,"message":"Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual.","line":102,"column":3,"nodeType":"CallExpression","messageId":"unexpectedLocalLooseAssertion","endLine":102,"endColumn":29},{"ruleId":"qunit/no-loose-assertions","severity":2,"message":"Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual.","line":131,"column":3,"nodeType":"CallExpression","messageId":"unexpectedLocalLooseAssertion","endLine":131,"endColumn":29},{"ruleId":"qunit/no-loose-assertions","severity":2,"message":"Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual.","line":150,"column":4,"nodeType":"CallExpression","messageId":"unexpectedLocalLooseAssertion","endLine":153,"endColumn":13},{"ruleId":"qunit/no-loose-assertions","severity":2,"message":"Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual.","line":183,"column":4,"nodeType":"CallExpression","messageId":"unexpectedLocalLooseAssertion","endLine":183,"endColumn":37},{"ruleId":"qunit/no-loose-assertions","severity":2,"message":"Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual.","line":184,"column":4,"nodeType":"CallExpression","messageId":"unexpectedLocalLooseAssertion","endLine":184,"endColumn":56},{"ruleId":"qunit/no-loose-assertions","severity":2,"message":"Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual.","line":202,"column":4,"nodeType":"CallExpression","messageId":"unexpectedLocalLooseAssertion","endLine":202,"endColumn":46},{"ruleId":"qunit/no-loose-assertions","severity":2,"message":"Unexpected assert.notOk. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual.","line":203,"column":4,"nodeType":"CallExpression","messageId":"unexpectedLocalLooseAssertion","endLine":203,"endColumn":33},{"ruleId":"qunit/no-loose-assertions","severity":2,"message":"Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual.","line":222,"column":4,"nodeType":"CallExpression","messageId":"unexpectedLocalLooseAssertion","endLine":222,"endColumn":68},{"ruleId":"qunit/no-loose-assertions","severity":2,"message":"Unexpected assert.notOk. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual.","line":223,"column":4,"nodeType":"CallExpression","messageId":"unexpectedLocalLooseAssertion","endLine":223,"endColumn":33}],"suppressedMessages":[],"errorCount":16,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/*\n * This file is part of the MediaWiki extension MediaUploader.\n *\n * MediaUploader is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 2 of the License, or\n * (at your option) any later version.\n *\n * MediaUploader is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with MediaUploader.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n( function () {\n\tQUnit.module( 'mw.FormDataTransport', QUnit.newMwEnvironment() );\n\n\tfunction createTransport( chunkSize, api ) {\n\t\tvar config;\n\n\t\tchunkSize = chunkSize || 0;\n\t\tapi = api || {};\n\n\t\tconfig = {\n\t\t\tuseRetryTimeout: false,\n\t\t\tchunkSize: chunkSize,\n\t\t\tmaxPhpUploadSize: chunkSize\n\t\t};\n\n\t\treturn new mw.FormDataTransport( api, {}, config );\n\t}\n\n\tQUnit.test( 'Constructor sanity test', function ( assert ) {\n\t\tvar transport = createTransport();\n\n\t\tassert.ok( transport );\n\t} );\n\n\tQUnit.test( 'abort', function ( assert ) {\n\t\tvar transport = createTransport( 0 ),\n\t\t\trequest = $.Deferred().promise( { abort: this.sandbox.stub() } );\n\n\t\ttransport.request = request;\n\n\t\tassert.ok( request.abort.notCalled );\n\n\t\ttransport.abort();\n\n\t\tassert.ok( request.abort.called );\n\t\tassert.ok( transport.aborted );\n\t} );\n\n\tQUnit.test( 'createParams', function ( assert ) {\n\t\tvar transport = createTransport( 10 ),\n\t\t\tparams = transport.createParams( 'foobar.jpg', 0 );\n\n\t\tassert.ok( params );\n\n\t\tassert.strictEqual( params.filename, 'foobar.jpg' );\n\t\tassert.strictEqual( params.offset, 0 );\n\t} );\n\n\tQUnit.test( 'post', function ( assert ) {\n\t\tvar stub = this.sandbox.stub(),\n\t\t\t// post() works on a promise and binds .then, so we have to make\n\t\t\t// sure it actually is a promise, but also that it calls our stub\n\t\t\ttransport = createTransport( 10, { post: function () {\n\t\t\t\tstub();\n\t\t\t\treturn $.Deferred().resolve();\n\t\t\t} } );\n\n\t\tthis.sandbox.useFakeXMLHttpRequest();\n\t\tthis.sandbox.useFakeServer();\n\n\t\tassert.ok( stub.notCalled );\n\n\t\ttransport.post( {} );\n\n\t\tassert.ok( stub.called );\n\t} );\n\n\tQUnit.test( 'upload', function ( assert ) {\n\t\tvar request,\n\t\t\ttransport = createTransport( 10, new mw.Api() ),\n\t\t\tfakeFile = {\n\t\t\t\tname: 'test file for fdt.jpg',\n\t\t\t\tsize: 5\n\t\t\t};\n\n\t\tthis.sandbox.useFakeXMLHttpRequest();\n\t\tthis.sandbox.useFakeServer();\n\n\t\ttransport.upload( fakeFile, 'test file for fdt.jpg' );\n\n\t\tassert.strictEqual( this.sandbox.server.requests.length, 1 );\n\t\trequest = this.sandbox.server.requests[ 0 ];\n\t\tassert.strictEqual( request.method, 'POST' );\n\t\tassert.strictEqual( request.url, mw.util.wikiScript( 'api' ) );\n\t\tassert.ok( request.async );\n\n\t\ttransport.abort();\n\t} );\n\n\tQUnit.test( 'uploadChunk', function ( assert ) {\n\t\tvar request,\n\t\t\ttransport = createTransport( 10, new mw.Api() ),\n\t\t\tfakeFile = {\n\t\t\t\tname: 'test file for fdt.jpg',\n\t\t\t\tsize: 20,\n\t\t\t\tslice: function ( offset ) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tname: 'test file for fdt.jpg',\n\t\t\t\t\t\toffset: offset,\n\t\t\t\t\t\tsize: 10\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t};\n\n\t\tthis.sandbox.useFakeXMLHttpRequest();\n\t\tthis.sandbox.useFakeServer();\n\n\t\ttransport.uploadChunk( fakeFile, 0 );\n\n\t\tassert.strictEqual( this.sandbox.server.requests.length, 1 );\n\t\trequest = this.sandbox.server.requests[ 0 ];\n\t\tassert.strictEqual( request.method, 'POST' );\n\t\tassert.strictEqual( request.url, mw.util.wikiScript( 'api' ) );\n\t\tassert.ok( request.async );\n\n\t\ttransport.abort();\n\t} );\n\n\t// test invalid server response (in missing 'stage' param)\n\tQUnit.test( 'checkStatus invalid API response', function ( assert ) {\n\t\tvar done = assert.async(),\n\t\t\ttransport = createTransport( 10, new mw.Api() ),\n\t\t\ttstub = this.sandbox.stub(),\n\t\t\tpoststub = this.sandbox.stub( transport.api, 'post' ),\n\t\t\tpostd = $.Deferred();\n\n\t\t// prepare a bogus invalid API result\n\t\tpoststub.returns( postd.promise() );\n\t\tpostd.resolve( { upload: { result: 'Poll' } } );\n\n\t\t// call tstub upon checkStatus failure, and verify it got called correctly\n\t\ttransport.checkStatus().fail( tstub, function () {\n\t\t\tassert.ok( tstub.calledWith( 'server-error', { errors: [ {\n\t\t\t\tcode: 'server-error',\n\t\t\t\thtml: mw.message( 'api-clientside-error-invalidresponse' ).parse()\n\t\t\t} ] } ) );\n\t\t\tdone();\n\t\t} );\n\t} );\n\n\t// test retry after server responds upload is still incomplete\n\tQUnit.test( 'checkStatus retry', function ( assert ) {\n\t\tvar transport = createTransport( 10, new mw.Api() ),\n\t\t\tusstub = this.sandbox.stub(),\n\t\t\tpoststub = this.sandbox.stub( transport.api, 'post' ),\n\t\t\tpostd = $.Deferred(),\n\t\t\tpostd2 = $.Deferred();\n\n\t\ttransport.on( 'update-stage', usstub );\n\n\t\t// prepare a first API call that responds with 'Poll' (upload\n\t\t// concatenation is not yet complete) followed by a second call that\n\t\t// marks the upload successful\n\t\tpoststub\n\t\t\t.onFirstCall().returns( postd.promise() )\n\t\t\t.onSecondCall().returns( postd2.promise() );\n\n\t\t// resolve 3 API calls, where server first responds upload is not yet\n\t\t// assembled, and second says it's published\n\t\tpostd.resolve( { upload: { result: 'Poll', stage: 'queued' } } );\n\t\tpostd2.resolve( { upload: { result: 'Success' } } );\n\n\t\t// confirm that, once second API call was successful, status resolves,\n\t\t// 2 API calls have gone out & the failed call updates stage accordingly\n\t\treturn transport.checkStatus().done( function () {\n\t\t\tassert.ok( poststub.calledTwice );\n\t\t\tassert.ok( usstub.firstCall.calledWith( 'queued' ) );\n\t\t} );\n\t} );\n\n\tQUnit.test( 'checkStatus success', function ( assert ) {\n\t\tvar transport = createTransport( 10, new mw.Api() ),\n\t\t\ttstub = this.sandbox.stub(),\n\t\t\tusstub = this.sandbox.stub(),\n\t\t\tpoststub = this.sandbox.stub( transport.api, 'post' ),\n\t\t\tpostd = $.Deferred();\n\n\t\ttransport.on( 'update-stage', usstub );\n\n\t\t// prepare a bogus valid API result\n\t\tpoststub.returns( postd.promise() );\n\t\tpostd.resolve( 'testing' );\n\n\t\treturn transport.checkStatus().done( tstub, function () {\n\t\t\tassert.ok( tstub.calledWith( 'testing' ) );\n\t\t\tassert.notOk( usstub.called );\n\t\t} );\n\t} );\n\n\tQUnit.test( 'checkStatus error API response', function ( assert ) {\n\t\tvar done = assert.async(),\n\t\t\ttransport = createTransport( 10, new mw.Api() ),\n\t\t\ttstub = this.sandbox.stub(),\n\t\t\tusstub = this.sandbox.stub(),\n\t\t\tpoststub = this.sandbox.stub( transport.api, 'post' ),\n\t\t\tpostd = $.Deferred();\n\n\t\ttransport.on( 'update-stage', usstub );\n\n\t\t// prepare an error API response\n\t\tpoststub.returns( postd.promise() );\n\t\tpostd.reject( 'testing', { error: 'testing' } );\n\n\t\ttransport.checkStatus().fail( tstub, function () {\n\t\t\tassert.ok( tstub.calledWith( 'testing', { error: 'testing' } ) );\n\t\t\tassert.notOk( usstub.called );\n\t\t\tdone();\n\t\t} );\n\t} );\n\n}() );\n","usedDeprecatedRules":[]},{"filePath":"/src/repo/tests/qunit/uw.ConcurrentQueue.test.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/tests/qunit/uw.TitleDetailsWidget.test.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]}]

--- end ---
$ ./node_modules/.bin/grunt stylelint
--- stdout ---
Running "stylelint:all" (stylelint) task

resources/jquery.arrowSteps/jquery.arrowSteps.less
 25:3   ✖  Expected double colon pseudo-element notation  selector-pseudo-element-colon-notation
 26:3   ✖  Expected double colon pseudo-element notation  selector-pseudo-element-colon-notation
 64:3   ✖  Expected double colon pseudo-element notation  selector-pseudo-element-colon-notation
 72:3   ✖  Expected double colon pseudo-element notation  selector-pseudo-element-colon-notation
 80:3   ✖  Expected double colon pseudo-element notation  selector-pseudo-element-colon-notation
 81:3   ✖  Expected double colon pseudo-element notation  selector-pseudo-element-colon-notation
 88:29  ✖  Expected double colon pseudo-element notation  selector-pseudo-element-colon-notation

resources/uploadWizard.less
 612:3   ✖  Expected double colon pseudo-element notation  selector-pseudo-element-colon-notation
 613:3   ✖  Expected double colon pseudo-element notation  selector-pseudo-element-colon-notation
 620:20  ✖  Expected double colon pseudo-element notation  selector-pseudo-element-colon-notation

10 problems (10 errors, 0 warnings)


⚠ 10 warnings

Warning: Task "stylelint:all" failed. Use --force to continue.

Aborted due to warnings.

--- end ---
$ ./node_modules/.bin/stylelint resources/uploadWizard.noWizard.less resources/variables.less resources/ext.uploadWizard.uploadCampaign.display.less resources/uw.CopyMetadataWidget.less resources/jquery.arrowSteps/jquery.arrowSteps.less resources/ui/steps/uw.ui.Thanks.less resources/uw.FieldLayout.less resources/ext.uploadWizard.uploadCampaign.list.css resources/details/uw.MultipleLanguageInputWidget.less resources/details/uw.LocationDetailsWidget.less resources/ui/steps/uw.ui.Tutorial.less resources/uploadWizard.less resources/details/uw.DateDetailsWidget.less resources/details/uw.SingleLanguageInputWidget.less -f json
--- stdout ---
[{"source":"/src/repo/resources/uploadWizard.noWizard.less","deprecations":[],"invalidOptionWarnings":[],"parseErrors":[],"errored":false,"warnings":[]},{"source":"/src/repo/resources/variables.less","deprecations":[],"invalidOptionWarnings":[],"parseErrors":[],"errored":false,"warnings":[]},{"source":"/src/repo/resources/ext.uploadWizard.uploadCampaign.display.less","deprecations":[],"invalidOptionWarnings":[],"parseErrors":[],"errored":false,"warnings":[]},{"source":"/src/repo/resources/uw.CopyMetadataWidget.less","deprecations":[],"invalidOptionWarnings":[],"parseErrors":[],"errored":false,"warnings":[]},{"source":"/src/repo/resources/jquery.arrowSteps/jquery.arrowSteps.less","deprecations":[],"invalidOptionWarnings":[],"parseErrors":[],"errored":true,"warnings":[{"line":25,"column":3,"endLine":25,"endColumn":4,"rule":"selector-pseudo-element-colon-notation","severity":"error","text":"Expected double colon pseudo-element notation (selector-pseudo-element-colon-notation)"},{"line":26,"column":3,"endLine":26,"endColumn":4,"rule":"selector-pseudo-element-colon-notation","severity":"error","text":"Expected double colon pseudo-element notation (selector-pseudo-element-colon-notation)"},{"line":64,"column":3,"endLine":64,"endColumn":4,"rule":"selector-pseudo-element-colon-notation","severity":"error","text":"Expected double colon pseudo-element notation (selector-pseudo-element-colon-notation)"},{"line":72,"column":3,"endLine":72,"endColumn":4,"rule":"selector-pseudo-element-colon-notation","severity":"error","text":"Expected double colon pseudo-element notation (selector-pseudo-element-colon-notation)"},{"line":80,"column":3,"endLine":80,"endColumn":4,"rule":"selector-pseudo-element-colon-notation","severity":"error","text":"Expected double colon pseudo-element notation (selector-pseudo-element-colon-notation)"},{"line":81,"column":3,"endLine":81,"endColumn":4,"rule":"selector-pseudo-element-colon-notation","severity":"error","text":"Expected double colon pseudo-element notation (selector-pseudo-element-colon-notation)"},{"line":88,"column":29,"endLine":88,"endColumn":30,"rule":"selector-pseudo-element-colon-notation","severity":"error","text":"Expected double colon pseudo-element notation (selector-pseudo-element-colon-notation)"}]},{"source":"/src/repo/resources/ui/steps/uw.ui.Thanks.less","deprecations":[],"invalidOptionWarnings":[],"parseErrors":[],"errored":false,"warnings":[]},{"source":"/src/repo/resources/uw.FieldLayout.less","deprecations":[],"invalidOptionWarnings":[],"parseErrors":[],"errored":false,"warnings":[]},{"source":"/src/repo/resources/ext.uploadWizard.uploadCampaign.list.css","deprecations":[],"invalidOptionWarnings":[],"parseErrors":[],"errored":false,"warnings":[]},{"source":"/src/repo/resources/details/uw.MultipleLanguageInputWidget.less","deprecations":[],"invalidOptionWarnings":[],"parseErrors":[],"errored":false,"warnings":[]},{"source":"/src/repo/resources/details/uw.LocationDetailsWidget.less","deprecations":[],"invalidOptionWarnings":[],"parseErrors":[],"errored":false,"warnings":[]},{"source":"/src/repo/resources/ui/steps/uw.ui.Tutorial.less","deprecations":[],"invalidOptionWarnings":[],"parseErrors":[],"errored":false,"warnings":[]},{"source":"/src/repo/resources/uploadWizard.less","deprecations":[],"invalidOptionWarnings":[],"parseErrors":[],"errored":true,"warnings":[{"line":612,"column":3,"endLine":612,"endColumn":4,"rule":"selector-pseudo-element-colon-notation","severity":"error","text":"Expected double colon pseudo-element notation (selector-pseudo-element-colon-notation)"},{"line":613,"column":3,"endLine":613,"endColumn":4,"rule":"selector-pseudo-element-colon-notation","severity":"error","text":"Expected double colon pseudo-element notation (selector-pseudo-element-colon-notation)"},{"line":620,"column":20,"endLine":620,"endColumn":21,"rule":"selector-pseudo-element-colon-notation","severity":"error","text":"Expected double colon pseudo-element notation (selector-pseudo-element-colon-notation)"}]},{"source":"/src/repo/resources/details/uw.DateDetailsWidget.less","deprecations":[],"invalidOptionWarnings":[],"parseErrors":[],"errored":false,"warnings":[]},{"source":"/src/repo/resources/details/uw.SingleLanguageInputWidget.less","deprecations":[],"invalidOptionWarnings":[],"parseErrors":[],"errored":false,"warnings":[]}]
--- end ---
$ /usr/bin/npm ci --legacy-peer-deps
--- stdout ---

added 437 packages, and audited 438 packages in 4s

66 packages are looking for funding
  run `npm fund` for details

1 high severity vulnerability

To address all issues, run:
  npm audit fix

Run `npm audit` for details.

--- end ---
$ /usr/bin/npm test
--- stdout ---

> test
> grunt test

Running "eslint:all" (eslint) task

/src/repo/.eslintrc.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/.stylelintrc.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/Gruntfile.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/composer.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/docs/external.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/docs/jsduck-config.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/extension.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/i18n/api/en.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/i18n/api/qqq.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/i18n/ar.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/i18n/bg.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/i18n/cs.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/i18n/de.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/i18n/en.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/i18n/es.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/i18n/fa.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/i18n/fi.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/i18n/fr.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/i18n/gu.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/i18n/he.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/i18n/hi.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/i18n/hy.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/i18n/ia.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/i18n/id.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/i18n/it.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/i18n/ja.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/i18n/kaa.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/i18n/krc.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/i18n/ks-arab.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/i18n/lb.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/i18n/license/en.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/i18n/license/qqq.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/i18n/mk.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/i18n/ms.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/i18n/nb.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/i18n/pl.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/i18n/pnb.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/i18n/pt-br.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/i18n/pt.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/i18n/qqq.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/i18n/ru.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/i18n/rw.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/i18n/se.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/i18n/sh.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/i18n/shn.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/i18n/si.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/i18n/skr-arab.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/i18n/sl.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/i18n/smn.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/i18n/sms.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/i18n/sr-ec.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/i18n/sr-el.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/i18n/sv.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/i18n/tly.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/i18n/tr.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/i18n/uk.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/i18n/vi.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/i18n/zh-hans.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/package-lock.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/package.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/resources/controller/uw.controller.Deed.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/resources/controller/uw.controller.Details.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/resources/controller/uw.controller.Step.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/resources/controller/uw.controller.Thanks.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/resources/controller/uw.controller.Tutorial.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/resources/controller/uw.controller.Upload.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/resources/controller/uw.controller.base.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/resources/deed/uw.deed.Abstract.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/resources/deed/uw.deed.Custom.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/resources/deed/uw.deed.External.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/resources/deed/uw.deed.None.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/resources/deed/uw.deed.OwnWork.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/resources/deed/uw.deed.ThirdParty.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/resources/deed/uw.deed.base.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/resources/details/uw.CategoriesDetailsWidget.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/resources/details/uw.DateDetailsWidget.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/resources/details/uw.DeedChooserDetailsWidget.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/resources/details/uw.DropdownWidget.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/resources/details/uw.LanguageDropdownWidget.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/resources/details/uw.LocationDetailsWidget.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/resources/details/uw.MultipleLanguageInputWidget.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/resources/details/uw.SingleLanguageInputWidget.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/resources/details/uw.TextWidget.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/resources/details/uw.TitleDetailsWidget.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/resources/details/uw.UlsWidget.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/resources/ext.mediaUploader.campaignEditor.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/resources/handlers/mw.ApiUploadFormDataHandler.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/resources/handlers/mw.ApiUploadHandler.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/resources/jquery.arrowSteps/jquery.arrowSteps.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/resources/jquery/jquery.morphCrossfade.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/resources/mw.DestinationChecker.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/resources/mw.Escaper.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/resources/mw.GroupProgressBar.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/resources/mw.QuickTitleChecker.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/resources/mw.UploadWizard.js
  1:1  error    Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions
  4:0  warning  Missing JSDoc @param "uw" type                                 jsdoc/require-param-type

/src/repo/resources/mw.UploadWizardDeedChooser.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/resources/mw.UploadWizardDetails.js
    1:1   error    Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions
  796:10  warning  ES2015 RegExp 'u' flag is forbidden                            es-x/no-regexp-u-flag

/src/repo/resources/mw.UploadWizardLicenseInput.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/resources/mw.UploadWizardPage.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/resources/mw.UploadWizardUpload.js
    1:1   error    Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions
    8:0   warning  Missing JSDoc @param "uw" type                                 jsdoc/require-param-type
  222:16  warning  ES2015 'Uint8Array' is forbidden                               es-x/no-typed-arrays

/src/repo/resources/mw.UploadWizardUploadInterface.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/resources/mw.canvas.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/resources/mw.errorDialog.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/resources/mw.fileApi.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/resources/transports/mw.FormDataTransport.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/resources/ui/steps/uw.ui.Deed.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/resources/ui/steps/uw.ui.Details.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/resources/ui/steps/uw.ui.Thanks.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/resources/ui/steps/uw.ui.Tutorial.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/resources/ui/steps/uw.ui.Upload.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/resources/ui/uw.ui.DeedPreview.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/resources/ui/uw.ui.Step.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/resources/ui/uw.ui.Wizard.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/resources/ui/uw.ui.base.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/resources/uw.ConcurrentQueue.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/resources/uw.CopyMetadataWidget.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/resources/uw.DetailsWidget.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/resources/uw.FieldLayout.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/resources/uw.LicenseGroup.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/resources/uw.LicensePreviewDialog.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/resources/uw.ValidationMessageElement.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/resources/uw.base.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/resources/uw.units.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/schemas/campaign.yaml
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/schemas/json-schema-draft-4.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/sql/tables.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/tests/qunit/controller/uw.controller.Deed.test.js
  23:3  warning  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual  qunit/no-loose-assertions
  24:3  warning  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual  qunit/no-loose-assertions
  25:3  warning  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual  qunit/no-loose-assertions

/src/repo/tests/qunit/controller/uw.controller.Details.test.js
   51:3  warning  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual  qunit/no-loose-assertions
   52:3  warning  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual  qunit/no-loose-assertions
   53:3  warning  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual  qunit/no-loose-assertions
   71:3  warning  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual  qunit/no-loose-assertions
   76:3  warning  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual  qunit/no-loose-assertions
   78:3  warning  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual  qunit/no-loose-assertions
   85:3  warning  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual  qunit/no-loose-assertions
   92:3  warning  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual  qunit/no-loose-assertions
  149:6  warning  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual  qunit/no-loose-assertions

/src/repo/tests/qunit/controller/uw.controller.Step.test.js
  23:3  warning  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual  qunit/no-loose-assertions
  24:3  warning  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual  qunit/no-loose-assertions

/src/repo/tests/qunit/controller/uw.controller.Thanks.test.js
  23:3  warning  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual  qunit/no-loose-assertions
  24:3  warning  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual  qunit/no-loose-assertions
  25:3  warning  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual  qunit/no-loose-assertions

/src/repo/tests/qunit/controller/uw.controller.Tutorial.test.js
  23:3  warning  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual     qunit/no-loose-assertions
  24:3  warning  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual     qunit/no-loose-assertions
  25:3  warning  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual     qunit/no-loose-assertions
  26:3  warning  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual     qunit/no-loose-assertions
  41:3  warning  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual     qunit/no-loose-assertions
  42:3  warning  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual     qunit/no-loose-assertions
  48:3  warning  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual     qunit/no-loose-assertions
  59:3  warning  Unexpected assert.notOk. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual  qunit/no-loose-assertions
  62:3  warning  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual     qunit/no-loose-assertions

/src/repo/tests/qunit/controller/uw.controller.Upload.test.js
  26:3  warning  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual  qunit/no-loose-assertions
  27:3  warning  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual  qunit/no-loose-assertions
  28:3  warning  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual  qunit/no-loose-assertions
  40:3  warning  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual  qunit/no-loose-assertions
  45:3  warning  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual  qunit/no-loose-assertions
  50:3  warning  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual  qunit/no-loose-assertions
  77:3  warning  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual  qunit/no-loose-assertions

/src/repo/tests/qunit/mw.UploadWizardLicenseInput.test.js
  23:2  warning  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual  qunit/no-loose-assertions

/src/repo/tests/qunit/mw.UploadWizardUpload.test.js
  45:3  warning  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual  qunit/no-loose-assertions

/src/repo/tests/qunit/transports/mw.FormDataTransport.test.js
   39:3  warning  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual     qunit/no-loose-assertions
   48:3  warning  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual     qunit/no-loose-assertions
   52:3  warning  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual     qunit/no-loose-assertions
   53:3  warning  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual     qunit/no-loose-assertions
   60:3  warning  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual     qunit/no-loose-assertions
   78:3  warning  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual     qunit/no-loose-assertions
   82:3  warning  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual     qunit/no-loose-assertions
  102:3  warning  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual     qunit/no-loose-assertions
  131:3  warning  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual     qunit/no-loose-assertions
  150:4  warning  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual     qunit/no-loose-assertions
  183:4  warning  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual     qunit/no-loose-assertions
  184:4  warning  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual     qunit/no-loose-assertions
  202:4  warning  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual     qunit/no-loose-assertions
  203:4  warning  Unexpected assert.notOk. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual  qunit/no-loose-assertions
  222:4  warning  Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual     qunit/no-loose-assertions
  223:4  warning  Unexpected assert.notOk. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual  qunit/no-loose-assertions

✖ 181 problems (126 errors, 55 warnings)

Warning: Task "eslint:all" failed. Use --force to continue.

Aborted due to warnings.

--- end ---
Traceback (most recent call last):
  File "/venv/lib/python3.9/site-packages/runner-0.1.0-py3.9.egg/runner/__init__.py", line 1400, in main
    libup.run(args.repo, args.output, args.branch)
  File "/venv/lib/python3.9/site-packages/runner-0.1.0-py3.9.egg/runner/__init__.py", line 1338, in run
    self.npm_upgrade(plan)
  File "/venv/lib/python3.9/site-packages/runner-0.1.0-py3.9.egg/runner/__init__.py", line 1049, in npm_upgrade
    self.npm_test()
  File "/venv/lib/python3.9/site-packages/runner-0.1.0-py3.9.egg/runner/__init__.py", line 287, in npm_test
    self.check_call(['npm', 'test'])
  File "/venv/lib/python3.9/site-packages/runner-0.1.0-py3.9.egg/runner/shell2.py", line 54, in check_call
    res.check_returncode()
  File "/usr/lib/python3.9/subprocess.py", line 460, in check_returncode
    raise CalledProcessError(self.returncode, self.args, self.stdout,
subprocess.CalledProcessError: Command '['/usr/bin/npm', 'test']' returned non-zero exit status 3.
Source code is licensed under the AGPL.