mediawiki/extensions/GuidedTour: main (log #960900)

sourcepatches

This run took 41 seconds.

$ date
--- stdout ---
Sun Mar 19 23:34:48 UTC 2023

--- end ---
$ git clone file:///srv/git/mediawiki-extensions-GuidedTour.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 ---
f4f76c42c1f2caedb739d0903d3bc8718986189b refs/heads/master

--- end ---
$ /usr/bin/npm audit --json --legacy-peer-deps
--- stdout ---
{
  "auditReportVersion": 2,
  "vulnerabilities": {},
  "metadata": {
    "vulnerabilities": {
      "info": 0,
      "low": 0,
      "moderate": 0,
      "high": 0,
      "critical": 0,
      "total": 0
    },
    "dependencies": {
      "prod": 1,
      "dev": 428,
      "optional": 0,
      "peer": 7,
      "peerOptional": 0,
      "total": 428
    }
  }
}

--- 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: 36 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 mediawiki/mediawiki-codesniffer (v41.0.0)
  - 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.2)
  - 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 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: 36 installs, 0 updates, 0 removals
    0 [>---------------------------]    0 [->--------------------------]    0 [--->------------------------]  - Installing composer/pcre (3.1.0): Extracting archive
  - Installing symfony/polyfill-php80 (v1.27.0): Extracting archive
  - Installing squizlabs/php_codesniffer (3.7.2): 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 (v41.0.0): 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
  0/27 [>---------------------------]   0%
 10/27 [==========>-----------------]  37%
 19/27 [===================>--------]  70%
 26/27 [==========================>-]  96%
 27/27 [============================] 100%4 package suggestions were added by new dependencies, use `composer suggest` to see details.
Generating autoload files
14 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 409 packages, and audited 410 packages in 5s

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

found 0 vulnerabilities

--- 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 410 packages in 753ms

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

found 0 vulnerabilities

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

--- end ---
$ ./node_modules/.bin/eslint i18n/da.json modules/ext.guidedTour.lib/ext.guidedTour.lib.TransitionAction.js modules/ext.guidedTour.lib/ext.guidedTour.lib.WikitextDescription.js i18n/fa.json i18n/km.json i18n/vi.json i18n/hy.json i18n/nb.json i18n/ne.json i18n/eu.json modules/ext.guidedTour.lib/ext.guidedTour.lib.main.js i18n/lki.json i18n/is.json i18n/fo.json i18n/he.json i18n/en.json i18n/ce.json i18n/sq.json i18n/ca.json i18n/hyw.json i18n/ksh.json i18n/eo.json package.json modules/ext.guidedTour.autolauncher.js i18n/rmc.json modules/tours/test.js i18n/bg.json i18n/sd.json i18n/ar.json i18n/ps.json i18n/tt-cyrl.json i18n/pt-br.json i18n/el.json i18n/kn.json modules/ext.guidedTour.lib/ext.guidedTour.lib.StepBuilder.js i18n/fi.json i18n/gu.json i18n/gl.json i18n/ka.json i18n/ckb.json i18n/uk.json i18n/hu.json i18n/se.json i18n/yi.json i18n/ko.json modules/tours/firsteditve.js i18n/szy.json i18n/pt.json i18n/br.json i18n/ia.json i18n/sv.json extension.json i18n/sco.json i18n/diq.json i18n/fy.json composer.json modules/ext.guidedTour.launcher.js i18n/sje.json i18n/hi.json i18n/cs.json modules/ext.guidedTour.lib/ext.guidedTour.lib.Step.js i18n/su.json i18n/de.json i18n/my.json i18n/be-tarask.json i18n/sa.json modules/ext.guidedTour.lib/ext.guidedTour.lib.Tour.js i18n/sr-ec.json i18n/es.json i18n/wuu.json i18n/bn.json i18n/smn.json i18n/pl.json i18n/it.json i18n/gd.json package-lock.json i18n/pms.json i18n/qqq.json i18n/bs.json Gruntfile.js modules/tours/firstedit.js i18n/lb.json i18n/ja.json i18n/ru.json i18n/mk.json i18n/fr.json i18n/sr-el.json i18n/kk-cyrl.json i18n/ilo.json i18n/scn.json i18n/lt.json modules/tours/onshow.js i18n/hsb.json i18n/zh-hans.json jsduck.json i18n/te.json i18n/sk.json modules/tours/uprightdownleft.js i18n/tr.json i18n/bcl.json i18n/sl.json tests/qunit/ext.guidedTour.lib.tests.js modules/ext.guidedTour.lib/ext.guidedTour.lib.TourBuilder.js i18n/ms.json i18n/lv.json modules/ext.guidedTour.lib/ext.guidedTour.lib.TransitionEvent.js i18n/id.json i18n/ku-latn.json modules/ext.guidedTour.lib.internal.js i18n/io.json i18n/ast.json modules/mediawiki.libs.guiders/mediawiki.libs.guiders.js i18n/roa-tara.json i18n/oc.json i18n/be.json i18n/nl.json i18n/ml.json i18n/zh-hant.json --fix
--- stdout ---

/src/repo/modules/ext.guidedTour.lib/ext.guidedTour.lib.Tour.js
  188:20  warning  Where possible, maintain application state in JS to avoid slower DOM queries  no-jquery/no-class-state

/src/repo/modules/ext.guidedTour.lib/ext.guidedTour.lib.main.js
  121:9  warning  'tourName' is already declared in the upper scope on line 105 column 7  no-shadow

/src/repo/modules/mediawiki.libs.guiders/mediawiki.libs.guiders.js
  388:15  warning  Positional selector extensions are not allowed  no-jquery/no-sizzle
  448:15  warning  Positional selector extensions are not allowed  no-jquery/no-sizzle
  732:3   warning  Selector extensions are not allowed             no-jquery/no-sizzle

/src/repo/tests/qunit/ext.guidedTour.lib.tests.js
  986:7  warning  'firstStepBuilder' is already declared in the upper scope on line 23 column 35                              no-shadow
  987:7  warning  'firstStep' is already declared in the upper scope on line 23 column 53                                     no-shadow
  991:3  error    Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual  qunit/no-loose-assertions
  996:3  error    Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual  qunit/no-loose-assertions

✖ 9 problems (2 errors, 7 warnings)


--- end ---
$ ./node_modules/.bin/eslint i18n/da.json modules/ext.guidedTour.lib/ext.guidedTour.lib.TransitionAction.js modules/ext.guidedTour.lib/ext.guidedTour.lib.WikitextDescription.js i18n/fa.json i18n/km.json i18n/vi.json i18n/hy.json i18n/nb.json i18n/ne.json i18n/eu.json modules/ext.guidedTour.lib/ext.guidedTour.lib.main.js i18n/lki.json i18n/is.json i18n/fo.json i18n/he.json i18n/en.json i18n/ce.json i18n/sq.json i18n/ca.json i18n/hyw.json i18n/ksh.json i18n/eo.json package.json modules/ext.guidedTour.autolauncher.js i18n/rmc.json modules/tours/test.js i18n/bg.json i18n/sd.json i18n/ar.json i18n/ps.json i18n/tt-cyrl.json i18n/pt-br.json i18n/el.json i18n/kn.json modules/ext.guidedTour.lib/ext.guidedTour.lib.StepBuilder.js i18n/fi.json i18n/gu.json i18n/gl.json i18n/ka.json i18n/ckb.json i18n/uk.json i18n/hu.json i18n/se.json i18n/yi.json i18n/ko.json modules/tours/firsteditve.js i18n/szy.json i18n/pt.json i18n/br.json i18n/ia.json i18n/sv.json extension.json i18n/sco.json i18n/diq.json i18n/fy.json composer.json modules/ext.guidedTour.launcher.js i18n/sje.json i18n/hi.json i18n/cs.json modules/ext.guidedTour.lib/ext.guidedTour.lib.Step.js i18n/su.json i18n/de.json i18n/my.json i18n/be-tarask.json i18n/sa.json modules/ext.guidedTour.lib/ext.guidedTour.lib.Tour.js i18n/sr-ec.json i18n/es.json i18n/wuu.json i18n/bn.json i18n/smn.json i18n/pl.json i18n/it.json i18n/gd.json package-lock.json i18n/pms.json i18n/qqq.json i18n/bs.json Gruntfile.js modules/tours/firstedit.js i18n/lb.json i18n/ja.json i18n/ru.json i18n/mk.json i18n/fr.json i18n/sr-el.json i18n/kk-cyrl.json i18n/ilo.json i18n/scn.json i18n/lt.json modules/tours/onshow.js i18n/hsb.json i18n/zh-hans.json jsduck.json i18n/te.json i18n/sk.json modules/tours/uprightdownleft.js i18n/tr.json i18n/bcl.json i18n/sl.json tests/qunit/ext.guidedTour.lib.tests.js modules/ext.guidedTour.lib/ext.guidedTour.lib.TourBuilder.js i18n/ms.json i18n/lv.json modules/ext.guidedTour.lib/ext.guidedTour.lib.TransitionEvent.js i18n/id.json i18n/ku-latn.json modules/ext.guidedTour.lib.internal.js i18n/io.json i18n/ast.json modules/mediawiki.libs.guiders/mediawiki.libs.guiders.js i18n/roa-tara.json i18n/oc.json i18n/be.json i18n/nl.json i18n/ml.json i18n/zh-hant.json -f json
--- stdout ---
[{"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/extension.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/ast.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/bcl.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/be-tarask.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/be.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/bn.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/br.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/bs.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/ca.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/ce.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/ckb.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/da.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/diq.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/el.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/eo.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/eu.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/fo.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/fy.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/gd.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/gl.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/hsb.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/hu.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/hyw.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/ilo.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/io.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/is.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/ka.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/kk-cyrl.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/km.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/kn.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/ko.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/ksh.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/ku-latn.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/lki.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/lt.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/lv.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/ml.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/my.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/ne.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/nl.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/oc.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/pms.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/ps.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/rmc.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/roa-tara.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/sa.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/scn.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/sco.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/sd.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/sje.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/sk.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/sq.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/su.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/szy.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/te.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/tt-cyrl.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/wuu.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/i18n/yi.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/i18n/zh-hant.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/jsduck.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/modules/ext.guidedTour.autolauncher.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/modules/ext.guidedTour.launcher.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/modules/ext.guidedTour.lib.internal.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/modules/ext.guidedTour.lib/ext.guidedTour.lib.Step.js","messages":[],"suppressedMessages":[{"ruleId":"mediawiki/msg-doc","severity":2,"message":"All possible message keys should be documented. See https://w.wiki/4r9a for details.","line":213,"column":18,"nodeType":"CallExpression","endLine":213,"endColumn":46,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"mediawiki/msg-doc","severity":2,"message":"All possible message keys should be documented. See https://w.wiki/4r9a for details.","line":222,"column":18,"nodeType":"CallExpression","endLine":222,"endColumn":42,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"mediawiki/msg-doc","severity":2,"message":"All possible message keys should be documented. See https://w.wiki/4r9a for details.","line":319,"column":18,"nodeType":"CallExpression","endLine":319,"endColumn":46,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"mediawiki/msg-doc","severity":2,"message":"All possible message keys should be documented. See https://w.wiki/4r9a for details.","line":340,"column":18,"nodeType":"CallExpression","endLine":340,"endColumn":46,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"mediawiki/msg-doc","severity":2,"message":"All possible message keys should be documented. See https://w.wiki/4r9a for details.","line":731,"column":20,"nodeType":"CallExpression","endLine":731,"endColumn":50,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"mediawiki/msg-doc","severity":2,"message":"All possible message keys should be documented. See https://w.wiki/4r9a for details.","line":737,"column":26,"nodeType":"CallExpression","endLine":737,"endColumn":62,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/modules/ext.guidedTour.lib/ext.guidedTour.lib.StepBuilder.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/modules/ext.guidedTour.lib/ext.guidedTour.lib.Tour.js","messages":[{"ruleId":"no-jquery/no-class-state","severity":1,"message":"Where possible, maintain application state in JS to avoid slower DOM queries","line":188,"column":20,"nodeType":"CallExpression","endLine":188,"endColumn":51}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"( function () {\n\tvar gt = mw.guidedTour,\n\t\tinternal = gt.internal,\n\t\tguiders = mw.libs.guiders;\n\n\t// Any public member used to define the tour belongs in TourBuilder or\n\t// StepBuilder.\n\t/**\n\t * @class mw.guidedTour.Tour\n\t *\n\t * @private\n\t *\n\t * A guided tour\n\t */\n\n\t/**\n\t * See mw.guidedTour.TourBuilder#constructor, which passes through to this.\n\t *\n\t * @constructor\n\t * @param {Object} tourSpec Specification of tour\n\t * @private\n\t */\n\tfunction Tour( tourSpec ) {\n\t\tvar moduleName;\n\n\t\t/**\n\t\t * Name of tour\n\t\t *\n\t\t * @property {string}\n\t\t * @private\n\t\t * @readonly\n\t\t */\n\t\tthis.name = tourSpec.name;\n\n\t\t/**\n\t\t * Whether tour is limited to one page\n\t\t *\n\t\t * @property {boolean}\n\t\t * @private\n\t\t * @readonly\n\t\t */\n\t\tthis.isSinglePage = tourSpec.isSinglePage;\n\n\t\t/**\n\t\t * Condition for showing the tour.\n\t\t *\n\t\t * See mw.guidedTour.Tour#constructor for details on possible values.\n\t\t *\n\t\t * @property {string}\n\t\t * @private\n\t\t * @readonly\n\t\t */\n\t\tthis.showConditionally = tourSpec.showConditionally;\n\n\t\tinternal.definedTours[ this.name ] = this;\n\n\t\t/**\n\t\t * Object mapping step names to mw.guidedTour.Step objects\n\t\t *\n\t\t * @property {Object}\n\t\t * @private\n\t\t */\n\t\tthis.steps = {};\n\n\t\t/**\n\t\t * First step of tour\n\t\t *\n\t\t * @property {mw.guidedTour.Step}\n\t\t * @private\n\t\t */\n\t\tthis.firstStep = null;\n\n\t\t// TODO (mattflaschen, 2014-04-04): Consider refactoring this in\n\t\t// conjunction with the user state work.\n\t\t/**\n\t\t * Current step\n\t\t *\n\t\t * This step is the most recently displayed for the user.  It is not\n\t\t * necessarily currently displayed.  It indicates the user's progress\n\t\t * through the tour.  It corresponds to the step saved to the user's\n\t\t * state (cookie), except for single-page tours (which use this, but not\n\t\t * the cookie).\n\t\t *\n\t\t * @property {mw.guidedTour.Step}\n\t\t * @private\n\t\t */\n\t\tthis.currentStep = null;\n\n\t\t// Manually updated by the TourBuilder since JavaScript does not have a\n\t\t// performant way to get the length of an object/associative array.\n\t\t/**\n\t\t * Step count\n\t\t *\n\t\t * @property {number}\n\t\t * @private\n\t\t * @readonly\n\t\t */\n\t\tthis.stepCount = 0;\n\n\t\t/**\n\t\t * CSS class\n\t\t *\n\t\t * @property {string}\n\t\t * @private\n\t\t * @readonly\n\t\t */\n\t\tthis.cssClass = 'mw-guidedtour-tour-' + this.name;\n\n\t\t/**\n\t\t * Whether the tour should be flipped; see getShouldFlipHorizontally.\n\t\t *\n\t\t * Initialized in initialize()\n\t\t */\n\t\tthis.flipRTL = null;\n\n\t\tmoduleName = internal.getTourModuleName( this.name );\n\n\t\t/**\n\t\t * Whether this is defined through a ResourceLoader module in an extension\n\t\t *\n\t\t * @property {boolean}\n\t\t * @private\n\t\t * @readonly\n\t\t */\n\t\tthis.isExtensionDefined = ( mw.loader.getState( moduleName ) !== null );\n\n\t\t/**\n\t\t * Promise tracking when this tour is initialized (guiders have been created)\n\t\t *\n\t\t * @property {null|jQuery.Deferred}\n\t\t * @private\n\t\t */\n\t\tthis.initialized = null;\n\t}\n\n\t// TODO: Change this to use before/after (T142267)\n\t/**\n\t * Determines whether guiders in this tour should be horizontally flipped due to LTR/RTL\n\t *\n\t * Considers the HTML element's dir attribute and body LTR/RTL classes in addition\n\t * to parameter.\n\t *\n\t * We assume that all tours defined in extensions use LTR, as with CSS/LESS.\n\t *\n\t * We assume that tours defined on-wiki use their site's directionality.\n\t *\n\t * Examples:\n\t *\n\t * * A user on Arabic Wikipedia views an extension-defined tour in the default\n\t * language for their wiki (Arabic).  The tour is flipped.\n\t *\n\t * * A user on Hebrew Wikipedia writes a tour in the MediaWiki namespace.  They\n\t * view the tour in the default language (Hebrew).  The tour is not flipped.\n\t *\n\t * * A user on English Wikipedia is browsing with the user language set to Farsi.\n\t * They view an extension-defined tour.  The tour is flipped.\n\t *\n\t * @private\n\t *\n\t * @param {'ltr'|'rtl'} interfaceDirection Direction the interface is being viewed\n\t *   in; can be changed by user preferences or uselang\n\t * @param {'ltr'|'rtl'} siteDirection Main direction of site\n\t *\n\t * @return {boolean} true if steps should be flipped, false otherwise\n\t */\n\tTour.prototype.getShouldFlipHorizontally = function ( interfaceDirection, siteDirection ) {\n\t\tvar tourDirection;\n\n\t\t// Direction the tour is assumed to be written for\n\t\ttourDirection = this.isExtensionDefined ? 'ltr' : siteDirection;\n\n\t\t// We flip if needed to match the interface direction\n\t\treturn tourDirection !== interfaceDirection;\n\t};\n\n\t/**\n\t * Initializes a tour to prepare for showing it.  If it's already initialized,\n\t * do nothing.\n\t *\n\t * @private\n\t *\n\t * @return {jQuery.Promise} Promise that waits on all steps to initialize (or one to fail)\n\t */\n\tTour.prototype.initialize = function () {\n\t\tvar stepName, promises = [],\n\t\t\t$body = $( document.body ),\n\t\t\tinterfaceDirection = $( 'html' ).attr( 'dir' ),\n\t\t\tsiteDirection = $body.hasClass( 'sitedir-ltr' ) ? 'ltr' : 'rtl';\n\n\t\tif ( !this.initialized ) {\n\t\t\tthis.flipRTL = this.getShouldFlipHorizontally( interfaceDirection, siteDirection );\n\t\t\tfor ( stepName in this.steps ) {\n\t\t\t\tpromises.push( this.steps[ stepName ].initialize() );\n\t\t\t}\n\t\t\tthis.initialized = $.when.apply( $, promises );\n\t\t}\n\n\t\treturn this.initialized;\n\t};\n\n\t/**\n\t * Checks whether any of the guiders in this tour are visible\n\t *\n\t * @private\n\t *\n\t * @return {boolean} Whether part of this tour is visible\n\t */\n\tTour.prototype.isVisible = function () {\n\t\tvar tourVisibleSelector = '.' + this.cssClass + ':visible';\n\n\t\treturn $( tourVisibleSelector ).length > 0;\n\t};\n\n\t/**\n\t * Gets a step object, given a step name or step object.\n\t *\n\t * In either case, it checks that the step belongs to the tour, and throws an\n\t * exception if it does not.\n\t *\n\t * @private\n\t *\n\t * @param {string|mw.guidedTour.Step} step Step name or step\n\t *\n\t * @return {mw.guidedTour.Step} step, validated to exist in this tour\n\t * @throws {mw.guidedTour.IllegalArgumentError} If the step, or step name, is not\n\t *   part of this tour\n\t */\n\tTour.prototype.getStep = function ( step ) {\n\t\tvar stepName;\n\n\t\tif ( typeof step === 'string' ) {\n\t\t\tstepName = step;\n\t\t\tstep = this.steps[ stepName ];\n\t\t\tif ( !step ) {\n\t\t\t\tthrow new gt.IllegalArgumentError( 'Step \"' + stepName + '\" not found in the \"' + this.name + '\" tour.' );\n\t\t\t}\n\t\t} else {\n\t\t\tif ( step.tour !== this ) {\n\t\t\t\tthrow new gt.IllegalArgumentError( 'Step object must belong to this tour (\"' + this.name + '\")' );\n\t\t\t}\n\t\t}\n\n\t\treturn step;\n\t};\n\n\t/**\n\t * Shows a step\n\t *\n\t * It can be requested by name (string) or by Step (mw.guidedTour.Step).\n\t *\n\t * It will first check to see if the tour should transition.\n\t *\n\t * @private\n\t *\n\t * @param {mw.guidedTour.Step|string} step Step name or object\n\t *\n\t * @throws {Error} If initialize fails\n\t *\n\t * @return {void}\n\t */\n\tTour.prototype.showStep = function ( step ) {\n\t\tvar guider, transitionEvent, tour = this;\n\n\t\tstep = tour.getStep( step );\n\n\t\tthis.initialize().done( function () {\n\t\t\ttransitionEvent = new gt.TransitionEvent();\n\t\t\ttransitionEvent.type = gt.TransitionEvent.BUILTIN;\n\t\t\ttransitionEvent.subtype = gt.TransitionEvent.TRANSITION_BEFORE_SHOW;\n\t\t\tstep = step.checkTransition( transitionEvent );\n\n\t\t\t// null means a TransitionAction (hide/end)\n\t\t\tif ( step !== null ) {\n\t\t\t\tguider = guiders._guiderById( step.specification.id );\n\t\t\t\tif ( guider !== undefined && guider.elem.is( ':visible' ) ) {\n\t\t\t\t\t// Already showing the same one\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// A guider from the same tour is visible\n\t\t\t\tif ( tour.isVisible() ) {\n\t\t\t\t\tguiders.hideAll();\n\t\t\t\t}\n\n\t\t\t\tguiders.show( step.specification.id );\n\t\t\t}\n\t\t} ).fail( function ( e ) {\n\t\t\tthrow new Error( 'Could not show step \\'' + step.name + '\\' because this.initialize() failed.  Underlying error: ' + e );\n\t\t} );\n\t};\n\n\t/**\n\t * Starts tour by showing the first step\n\t *\n\t * @private\n\t *\n\t * @return {void}\n\t * @throws {mw.guidedTour.TourDefinitionError} If firstStep was never called on the\n\t *  TourBuilder\n\t */\n\tTour.prototype.start = function () {\n\t\tif ( this.firstStep === null ) {\n\t\t\tthrow new gt.TourDefinitionError(\n\t\t\t\t'The .firstStep() method must be called for all tours.'\n\t\t\t);\n\t\t}\n\n\t\tthis.showStep( this.firstStep );\n\t};\n\n\tmw.guidedTour.Tour = Tour;\n}() );\n","usedDeprecatedRules":[]},{"filePath":"/src/repo/modules/ext.guidedTour.lib/ext.guidedTour.lib.TourBuilder.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/modules/ext.guidedTour.lib/ext.guidedTour.lib.TransitionAction.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/modules/ext.guidedTour.lib/ext.guidedTour.lib.TransitionEvent.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/modules/ext.guidedTour.lib/ext.guidedTour.lib.WikitextDescription.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/modules/ext.guidedTour.lib/ext.guidedTour.lib.main.js","messages":[{"ruleId":"no-shadow","severity":1,"message":"'tourName' is already declared in the upper scope on line 105 column 7.","line":121,"column":9,"nodeType":"Identifier","messageId":"noShadow","endLine":121,"endColumn":17}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/* global ve */\n/**\n * GuidedTour public API\n *\n * Set as mw.guidedTour and often aliased to gt locally\n *\n * @author Terry Chay <tchay@wikimedia.org>\n * @author Matt Flaschen <mflaschen@wikimedia.org>\n * @author Ori Livneh <olivneh@wikimedia.org>\n * @author Rob Moen <rmoen@wikimedia.org>\n * @author S Page <spage@wikimedia.org>\n * @author Sam Smith <git@samsmith.io>\n * @author Luke Welling <lwelling@wikimedia.org>\n *\n * @class mw.guidedTour\n * @singleton\n */\n/*\n  * Part of GuidedTour, the MediaWiki extension for guided tours.\n  *\n  * Uses Optimize.ly's Guiders library (with customizations developed at WordPress\n  * and MediaWiki).\n  */\n( function ( guiders ) {\n\t'use strict';\n\n\tvar gt = mw.guidedTour,\n\t\tinternal = gt.internal,\n\t\tcookieName, cookieParams,\n\t\t// Initialized to false at page load\n\t\t// Will be set true any time postEdit fires, including right after\n\t\t// legacy wgPostEdit variable is set to true.\n\t\tisPostEdit = false;\n\n\t/**\n\t * Returns the current user state, initalizing it if needed\n\t *\n\t * @private\n\t *\n\t * @return {Object} user state object.  If there is none, or the format was\n\t *  invalid, returns a skeleton state object from\n\t *  mw.guidedTour.internal#getInitialUserStateObject\n\t */\n\tfunction getCookieState() {\n\t\tvar cookieValue, parsed;\n\t\tcookieValue = mw.cookie.get( cookieName );\n\t\tparsed = internal.parseUserState( cookieValue );\n\t\tif ( parsed !== null ) {\n\t\t\treturn parsed;\n\t\t} else {\n\t\t\treturn internal.getInitialUserStateObject();\n\t\t}\n\t}\n\n\t/**\n\t * Returns the current combined user state (cookie state and server-launched state)\n\t * Basically, this acts like the cookie state, except the server can specify\n\t * tours that take precedence via a $wg.\n\t *\n\t * @private\n\t *\n\t * @return {Object} combined state object.  If there is none, or the format was\n\t * invalid, returns a skeleton state object from\n\t * mw.guidedTour.internal#getInitialUserStateObject\n\t */\n\tfunction getUserState() {\n\t\tvar\n\t\t\tcookieState = getCookieState(),\n\t\t\tserverState = mw.config.get( 'wgGuidedTourLaunchState' ),\n\t\t\tstate = cookieState;\n\n\t\tif ( serverState !== null ) {\n\t\t\tstate = $.extend( true, state, serverState );\n\t\t}\n\n\t\treturn state;\n\n\t}\n\n\t/**\n\t * Removes a tour from the cookie\n\t *\n\t * @private\n\t *\n\t * @param {string} tourName name of tour to remove\n\t *\n\t * @return {void}\n\t */\n\tfunction removeTourFromUserStateByName( tourName ) {\n\t\tvar parsedCookie = getCookieState();\n\t\tdelete parsedCookie.tours[ tourName ];\n\t\tmw.cookie.set( cookieName, JSON.stringify( parsedCookie ), cookieParams );\n\t}\n\n\t/**\n\t * Launch tour from given user state\n\t *\n\t * @private\n\t *\n\t * @param {Object} state State that specifies the tour progress\n\t *\n\t * // @return {boolean} Whether a tour was launched\n\t */\n\tfunction launchTourFromState( state ) {\n\t\tvar tourName, tourNames,\n\t\t\tcandidateTours = [];\n\n\t\tfor ( tourName in state.tours ) {\n\t\t\tcandidateTours.push( {\n\t\t\t\tname: tourName,\n\t\t\t\tstep: state.tours[ tourName ].step\n\t\t\t} );\n\t\t}\n\n\t\ttourNames = candidateTours.map( function ( el ) {\n\t\t\treturn el.name;\n\t\t} );\n\n\t\tinternal.loadMultipleTours( tourNames )\n\t\t\t.always( function () {\n\t\t\t\tvar tourName, max, currentStart;\n\n\t\t\t\t// This value is before 1970, but is a simple way\n\t\t\t\t// to ensure the comparison below always works.\n\t\t\t\tmax = {\n\t\t\t\t\tstartTime: -1\n\t\t\t\t};\n\n\t\t\t\t// Not all the tours in the cookie necessarily\n\t\t\t\t// loaded successfully, but the defined tours did.\n\t\t\t\t// So we make sure it is defined and in the user\n\t\t\t\t// state.\n\t\t\t\tfor ( tourName in internal.definedTours ) {\n\t\t\t\t\tif (\n\t\t\t\t\t\tstate.tours[ tourName ] !== undefined &&\n\t\t\t\t\t\tgt.shouldShowTour( {\n\t\t\t\t\t\t\ttourName: tourName,\n\t\t\t\t\t\t\tuserState: state,\n\t\t\t\t\t\t\tpageName: mw.config.get( 'wgPageName' ),\n\t\t\t\t\t\t\tarticleId: mw.config.get( 'wgArticleId' ),\n\t\t\t\t\t\t\tcondition: internal.definedTours[ tourName ].showConditionally\n\t\t\t\t\t\t} )\n\t\t\t\t\t) {\n\t\t\t\t\t\tcurrentStart = state.tours[ tourName ].startTime || 0;\n\t\t\t\t\t\tif ( currentStart > max.startTime ) {\n\t\t\t\t\t\t\tmax = {\n\t\t\t\t\t\t\t\tname: tourName,\n\t\t\t\t\t\t\t\tstep: state.tours[ tourName ].step,\n\t\t\t\t\t\t\t\tstartTime: currentStart\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif ( max.name !== undefined ) {\n\t\t\t\t\t// Launch the most recently started tour\n\t\t\t\t\t// that meets the conditions.\n\t\t\t\t\tgt.launchTour( max.name, gt.makeTourId( max ) );\n\t\t\t\t}\n\t\t\t} );\n\t}\n\n\t// TODO (mattflaschen, 2013-07-10): Known issue: This runs too early on a direct\n\t// visit to a veaction=edit page.  This probably affects other JS-generated\n\t// interfaces too.\n\t/**\n\t * Initializes guiders and shows tour, starting at the specified step.\n\t * Does not check conditions, so that should already be done\n\t *\n\t * @private\n\t *\n\t * @param {string} tourName name of tour\n\t * @param {string} tourId id to start at\n\t *\n\t * @return {void}\n\t * @throws {mw.guidedTour.IllegalArgumentError} If the tour ID is not consistent\n\t *   with the tour name, or does not refer to a valid step\n\t */\n\tfunction showTour( tourName, tourId ) {\n\t\tvar tour, tourInfo;\n\t\ttour = internal.definedTours[ tourName ];\n\n\t\ttourInfo = gt.parseTourId( tourId );\n\t\tif ( tourInfo.name !== tourName ) {\n\t\t\tthrow new gt.IllegalArgumentError( 'The tour ID \"' + tourId + '\" is not part of the tour \"' + tourName + '\".' );\n\t\t}\n\n\t\ttour.showStep( tourInfo.step );\n\t}\n\n\t/**\n\t * Guiders has a window resize and document ready listener.\n\t *\n\t * However, we're adding some MW-specific code. Currently, this listens for a\n\t * custom event from the WikiEditor extension, which fires after the extension's\n\t * async loop finishes. If WikiEditor is not running this event just won't fire.\n\t *\n\t * @private\n\t *\n\t * @return {void}\n\t */\n\tfunction setupRepositionListeners() {\n\t\t$( '#wpTextbox1' ).on( 'wikiEditor-toolbar-doneInitialSections', guiders.reposition );\n\t\tmw.hook( 've.skinTabSetupComplete' ).add( guiders.reposition );\n\t}\n\n\t/**\n\t * Listen for events that may mean a tour should transition.\n\t * Currently this listens for some custom events from VisualEditor.\n\t *\n\t * @private\n\t */\n\tfunction setupStepTransitionListeners() {\n\t\t// TODO (mattflaschen, 2014-03-17): Temporary hack, until\n\t\t// there are tour-level transition listeners.\n\t\t// Will also change as mediawiki.libs.guiders module is refactored.\n\t\t/**\n\t\t * Checks for a transition after a minimal timeout\n\t\t *\n\t\t * @param {mw.guidedTour.TransitionEvent} transitionEvent event that triggered\n\t\t *  the check\n\t\t */\n\t\tfunction transition( transitionEvent ) {\n\t\t\t// I found this timeout necessary when testing, probably to give the\n\t\t\t// browser queue a chance to do pending DOM rendering.\n\t\t\tsetTimeout( function () {\n\t\t\t\tvar currentStepInfo, currentStep, nextStep, tour;\n\n\t\t\t\tif ( guiders._currentGuiderID === null ) {\n\t\t\t\t\t// Ignore transitions if there is no active tour.\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tcurrentStepInfo = gt.parseTourId( guiders._currentGuiderID );\n\t\t\t\tif ( currentStepInfo === null ) {\n\t\t\t\t\tmw.log.warn( 'Invalid _currentGuiderID.  Returning early' );\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\ttour = internal.definedTours[ currentStepInfo.name ];\n\t\t\t\tcurrentStep = tour.getStep( currentStepInfo.step );\n\t\t\t\tnextStep = currentStep.checkTransition( transitionEvent );\n\t\t\t\tif ( nextStep !== currentStep && nextStep !== null ) {\n\t\t\t\t\ttour.showStep( nextStep );\n\t\t\t\t}\n\n\t\t\t}, 0 );\n\t\t}\n\n\t\t// The next two are handled differently since they also require\n\t\t// settings an internal boolean.\n\t\t// TODO (mattflaschen, 2014-04-01): Hack pending tour-level listeners.\n\t\tmw.hook( 'postEdit' ).add( function () {\n\t\t\tvar transitionEvent = new gt.TransitionEvent();\n\t\t\ttransitionEvent.type = gt.TransitionEvent.MW_HOOK;\n\t\t\ttransitionEvent.hookName = 'postEdit';\n\t\t\ttransitionEvent.hookArguments = [];\n\n\t\t\tisPostEdit = true;\n\t\t\ttransition( transitionEvent );\n\t\t} );\n\t}\n\n\t/**\n\t * Internal initialization of guiders and guidedtour, called once after singleton\n\t * is built.\n\t *\n\t * @private\n\t *\n\t * @return {void}\n\t */\n\tfunction initialize() {\n\t\t// GuidedTour uses cookies to keep the user's progress when they are in the\n\t\t// tour, unless it's single-page.\n\t\tcookieName = '-mw-tour';\n\t\tcookieParams = { expires: null }; // null means to use a session cookie.\n\n\t\t// Show X button\n\t\tguiders._defaultSettings.xButton = true;\n\n\t\tguiders._defaultSettings.autoFocus = true;\n\t\tguiders._defaultSettings.closeOnEscape = true;\n\t\tguiders._defaultSettings.closeOnClickOutside = true;\n\t\tguiders._defaultSettings.flipToKeepOnScreen = true;\n\n\t\t$( function () {\n\t\t\tsetupRepositionListeners();\n\t\t\tsetupStepTransitionListeners();\n\t\t} );\n\t}\n\n\t// Add external API (internal API is at gt.internal)\n\t// Most, but not all, of this is public (non-public ones use standard\n\t// @private marking).\n\t$.extend( gt, {\n\t\t/**\n\t\t * Parses tour ID into an object with name and step keys.\n\t\t *\n\t\t * @param {string} tourId ID of tour/step combination\n\t\t *\n\t\t * @return {Object|null} Tour info object, or null if invalid input\n\t\t * @return {string} return.name Tour name\n\t\t * @return {string} return.step Tour step, always a string, but\n\t\t *   either textual (e.g. 'preview') or numeric (e.g. '5')\n\t\t */\n\t\tparseTourId: function ( tourId ) {\n\t\t\t// Keep in sync with regex in GuidedTourHooks.php\n\t\t\tvar TOUR_ID_REGEX = /^gt-([^.-]+)-([^.-]+)$/,\n\t\t\t\ttourMatch, tourName, tourStep;\n\n\t\t\tif ( typeof tourId !== 'string' ) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\ttourMatch = tourId.match( TOUR_ID_REGEX );\n\t\t\tif ( !tourMatch ) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\ttourName = tourMatch[ 1 ];\n\t\t\ttourStep = tourMatch[ 2 ];\n\n\t\t\tif ( tourName.length === 0 ) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tname: tourName,\n\t\t\t\tstep: tourStep\n\t\t\t};\n\t\t},\n\n\t\t/**\n\t\t * Serializes tour information into a string\n\t\t *\n\t\t * @param {Object} tourInfo\n\t\t * @param {string} tourInfo.name Tour name\n\t\t * @param {number|string} tourInfo.step Tour step, which can be a string,\n\t\t *   such as 'preview', or numeric, as either a string ('5') or a\n\t\t *   number (5).\n\t\t *\n\t\t * @return {string|null} ID of tour, or null if invalid input\n\t\t */\n\t\tmakeTourId: function ( tourInfo ) {\n\t\t\tif ( !$.isPlainObject( tourInfo ) ) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\treturn 'gt-' + tourInfo.name + '-' + tourInfo.step;\n\t\t},\n\n\t\t/**\n\t\t * Launch a tour.  Tours start automatically if the environment is present\n\t\t * (user string or cookie).\n\t\t *\n\t\t * However, this method allows one tour to launch another.  It also allows\n\t\t * callers to launch a tour on demand.\n\t\t *\n\t\t * The tour will only be shown if allowed by the specification (see defineTour).\n\t\t *\n\t\t * It will first try loading a tour module, then fall back on an on-wiki tour.\n\t\t * This means the caller doesn't need to know how it's implemented (which could\n\t\t * change).\n\t\t *\n\t\t * launchTour is used to load the tour specified in the URL too.  This case\n\t\t * does not require an extra request for an extension-defined tour since it\n\t\t * is already loaded.\n\t\t *\n\t\t * `mw.guidedTour.launcher.launchTour` should always be used over this method.\n\t\t *\n\t\t * @private\n\t\t *\n\t\t * @param {string} tourName Name of tour\n\t\t * @param {string|null} [tourId='gt-' + tourName + '-' + step] ID of tour\n\t\t *   and step.  Omitted or null means to start the tour from the beginning.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tlaunchTour: function ( tourName, tourId ) {\n\t\t\tinternal.loadTour( tourName ).done( function () {\n\t\t\t\tvar tour = internal.definedTours[ tourName ];\n\n\t\t\t\tif ( tour && gt.shouldShowTour( {\n\t\t\t\t\ttourName: tourName,\n\t\t\t\t\tuserState: getUserState(),\n\t\t\t\t\tpageName: mw.config.get( 'wgPageName' ),\n\t\t\t\t\tarticleId: mw.config.get( 'wgArticleId' ),\n\t\t\t\t\tcondition: tour.showConditionally\n\t\t\t\t} ) ) {\n\t\t\t\t\tif ( tourId ) {\n\t\t\t\t\t\tshowTour( tourName, tourId );\n\t\t\t\t\t} else {\n\t\t\t\t\t\ttour.start();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} );\n\t\t},\n\n\t\t/**\n\t\t * Attempts to launch a tour from the query string (tour parameter)\n\t\t *\n\t\t * @return {boolean} Whether a tour was launched\n\t\t */\n\t\tlaunchTourFromQueryString: function () {\n\t\t\tvar step, tourId, tourName = mw.util.getParamValue( 'tour' );\n\n\t\t\tif ( tourName !== null && tourName.length !== 0 ) {\n\t\t\t\tstep = gt.getStepFromQuery();\n\t\t\t\tif ( step !== null && step !== '' ) {\n\t\t\t\t\ttourId = gt.makeTourId( {\n\t\t\t\t\t\tname: tourName,\n\t\t\t\t\t\tstep: step\n\t\t\t\t\t} );\n\t\t\t\t} else {\n\t\t\t\t\ttourId = null;\n\t\t\t\t}\n\n\t\t\t\tgt.launchTour( tourName, tourId );\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 * Attempts to launch a tour from combined user state (cookie + tours launched\n\t\t * directly by server)\n\t\t *\n\t\t * @return {boolean} Whether a tour was launched\n\t\t */\n\t\tlaunchTourFromUserState: function () {\n\t\t\tvar state = getUserState();\n\t\t\treturn launchTourFromState( state );\n\t\t},\n\n\t\t/**\n\t\t * Attempts to automatically launch a tour based on the environment\n\t\t *\n\t\t * If the query string has a tour parameter, the method attempts to use that.\n\t\t *\n\t\t * Otherwise, the method tries to use the GuidedTour cookie.  It checks which tours\n\t\t * are applicable to the current page.  If more than one is, this method\n\t\t * loads the most recently started tour.\n\t\t *\n\t\t * If both fail, it does nothing.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tlaunchTourFromEnvironment: function () {\n\t\t\t// Tour is either in the query string or cookie (prefer query string)\n\n\t\t\tif ( this.launchTourFromQueryString() ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tthis.launchTourFromUserState();\n\t\t},\n\n\t\t/**\n\t\t * Sets the tour cookie, given a tour name and optionally, a step.\n\t\t *\n\t\t * You can use this when you want the tour to be displayed on a future page.\n\t\t * If there is currently no cookie, it will set the start time.  This\n\t\t * will not be done if only the step is changing.\n\t\t *\n\t\t * This does not take into account isSinglePage.\n\t\t *\n\t\t * @param {string} name Tour name\n\t\t * @param {number|string} [step=1] Tour step\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tsetTourCookie: function ( name, step ) {\n\t\t\tstep = step || 1;\n\n\t\t\tgt.updateUserStateForTour( {\n\t\t\t\ttourInfo: {\n\t\t\t\t\tname: name,\n\t\t\t\t\tstep: step\n\t\t\t\t},\n\t\t\t\twasShown: false\n\t\t\t} );\n\t\t},\n\n\t\t// TODO (mattflaschen, 2014-04-04): Cleanup and move into Tour\n\t\t/**\n\t\t * Ends the tour, removing user's state\n\t\t *\n\t\t * @param {string} [tourName] tour to end, defaulting to most recent one\n\t\t *  that showed a guider\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tendTour: function ( tourName ) {\n\t\t\tvar guider, tourId, tourInfo, tour;\n\t\t\tif ( tourName !== undefined ) {\n\t\t\t\tremoveTourFromUserStateByName( tourName );\n\t\t\t} else {\n\t\t\t\ttourId = guiders._currentGuiderID;\n\t\t\t\ttourInfo = gt.parseTourId( tourId );\n\t\t\t\ttourName = tourInfo.name;\n\t\t\t\tguider = guiders._guiderById( tourId );\n\t\t\t\tgt.removeTourFromUserStateByGuider( guider );\n\t\t\t}\n\n\t\t\ttour = internal.definedTours[ tourName ];\n\t\t\tif ( tour.currentStep !== null ) {\n\t\t\t\ttour.currentStep.unregisterMwHooks();\n\t\t\t}\n\n\t\t\tguiders.hideAll();\n\t\t},\n\n\t\t/**\n\t\t * Hides the guider(s)\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\thideAll: function () {\n\t\t\tguiders.hideAll();\n\t\t},\n\n\t\t// Begin onShow bindings section\n\t\t//\n\t\t// These are used as the value of the onShow field of a step.\n\t\t// These are deprecated.  To allow async API calls, they are now\n\t\t// implemented another way in mw.GuidedTour.Step, but this is a temporary\n\t\t// backwards compatibility shim.\n\t\t/**\n\t\t * Parses description as wikitext\n\t\t *\n\t\t * Add this to onShow.\n\t\t *\n\t\t * @deprecated\n\t\t *\n\t\t * @param {Object} guider Guider object to set description on\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tparseDescription: 'parseDescription is not a real function',\n\n\t\t// Do not use mw.log.deprecate for this, since there is some magic\n\t\t// in StepBuilder that accesses it to check equality.\n\t\t/**\n\t\t * Parses a wiki page and uses the HTML as the description.\n\t\t *\n\t\t * To use this, put the page name as the description, and use this as the\n\t\t * value of onShow.\n\t\t *\n\t\t * @deprecated\n\t\t *\n\t\t * @param {Object} guider Guider object to set description on\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tgetPageAsDescription: 'getPageAsDescription is not a real function',\n\n\t\t// End onShow bindings section\n\n\t\t//\n\t\t// Begin transition helpers\n\t\t//\n\t\t// These are utility functions useful in determining whether a step should\n\t\t// transition (e.g. move to a new step or hide the guider), and if so what\n\t\t// to do.\n\t\t/**\n\t\t * Checks whether user is on a particular wiki page.\n\t\t *\n\t\t * @param {string} pageName Expected page name\n\t\t *\n\t\t * @return {boolean} true if the page name is a strict match, false otherwise\n\t\t */\n\t\tisPage: function ( pageName ) {\n\t\t\treturn mw.config.get( 'wgPageName' ) === pageName;\n\t\t},\n\n\t\t/**\n\t\t * Checks whether the query and pageName match the provided ones.\n\t\t *\n\t\t * It will return true if and only if the actual query string has all of the\n\t\t * mappings from queryParts (the actual query string may be a superset of the\n\t\t * expected), and pageName (optional) is exactly equal to wgPageName.\n\t\t *\n\t\t * If pageName is falsy, the page name will not be considered in any way.\n\t\t *\n\t\t * @param {Object} queryParts Object mapping expected query\n\t\t *  parameter names (string) to expected values (string)\n\t\t * @param {string} [pageName] Page name\n\t\t *\n\t\t * @return {boolean} true if and only if there is a match per above\n\t\t */\n\t\thasQuery: function ( queryParts, pageName ) {\n\t\t\tvar qname;\n\n\t\t\tif ( pageName && mw.config.get( 'wgPageName' ) !== pageName ) {\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tfor ( qname in queryParts ) {\n\t\t\t\tif ( mw.util.getParamValue( qname ) !== queryParts[ qname ] ) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true;\n\t\t},\n\n\t\t/**\n\t\t * Checks if the user is editing, with either wikitext or the\n\t\t * VisualEditor.  Does not include previewing.\n\t\t *\n\t\t * @return {boolean} true if and only if they are actively editing\n\t\t */\n\t\tisEditing: function () {\n\t\t\treturn gt.isEditingWithWikitext() || gt.isEditingWithVisualEditor();\n\t\t},\n\n\t\t/**\n\t\t * Checks if the user is editing with wikitext.  Does not include previewing.\n\t\t *\n\t\t * @return {boolean} true if and only if they are on the edit action\n\t\t */\n\t\tisEditingWithWikitext: function () {\n\t\t\treturn mw.config.get( 'wgAction' ) === 'edit';\n\t\t},\n\n\t\t/**\n\t\t * Checks if the user is editing with VisualEditor.  This is only true if\n\t\t * the surface is actually open for edits.\n\t\t *\n\t\t * Use isVisualEditorOpen instead if you want to check if there is a\n\t\t * VisualEditor instance on the page.\n\t\t *\n\t\t * @see mw.guidedTour#isVisualEditorOpen\n\t\t *\n\t\t * @return {boolean} true if and only if they are actively editing with VisualEditor\n\t\t */\n\t\tisEditingWithVisualEditor: function () {\n\t\t\treturn $( '.ve-ce-documentNode[contenteditable=\"true\"]' ).length > 0;\n\t\t},\n\n\t\t/**\n\t\t * Checks whether VisualEditor is open\n\t\t *\n\t\t * @return {boolean} true if and only if there is a VisualEditor instance\n\t\t * on the page\n\t\t */\n\t\tisVisualEditorOpen: function () {\n\t\t\treturn typeof ve !== 'undefined' && ve.instances && ve.instances.length > 0;\n\t\t},\n\n\t\t// TODO: Doesn't currently detect reviewing with VE\n\t\t/**\n\t\t * Checks whether the user is previewing or reviewing changes\n\t\t * (after clicking \"Show changes\")\n\t\t *\n\t\t * @return {boolean} true if and only if they are reviewing\n\t\t */\n\t\tisReviewing: function () {\n\t\t\treturn gt.isReviewingWithWikitext();\n\t\t},\n\n\t\t/**\n\t\t * Checks whether the user is previewing or reviewing wikitext changes\n\t\t * (the latter meaning the screen after clicking \"Show changes\")\n\t\t *\n\t\t * @return {boolean} true if and only if they are reviewing wikitext\n\t\t */\n\t\tisReviewingWithWikitext: function () {\n\t\t\treturn mw.config.get( 'wgAction' ) === 'submit';\n\t\t},\n\n\t\t/**\n\t\t * Checks whether the user just saved an edit.\n\t\t *\n\t\t * You can also handle the 'postEdit' mw.hook in a\n\t\t * mw.guidedTour.StepBuilder#transition handler.\n\t\t *\n\t\t * This method is not necessary if post-edit is the only\n\t\t * criterion for the transition.\n\t\t *\n\t\t * @return {boolean} true if they just saved an edit, false otherwise\n\t\t */\n\t\tisPostEdit: function () {\n\t\t\treturn isPostEdit;\n\t\t},\n\n\t\t// End transition helpers\n\n\t\t/**\n\t\t * Gets step of tour from querystring\n\t\t *\n\t\t * @private\n\t\t *\n\t\t * @return {string} Step\n\t\t */\n\t\tgetStepFromQuery: function () {\n\t\t\treturn mw.util.getParamValue( 'step' );\n\t\t},\n\n\t\t/**\n\t\t * Resumes a loaded tour, specifying a tour and (optionally) a step.\n\t\t *\n\t\t * If no step is provided, it will first try to get a step from the URL.\n\t\t *\n\t\t * If that fails, it will try to resume from the cookie.\n\t\t *\n\t\t * Finally, it will default to step 1.\n\t\t *\n\t\t * @param {string} tourName Tour name\n\t\t * @param {number|string} [step] Step, defaulting to the cookie or first step of tour.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tresumeTour: function ( tourName, step ) {\n\t\t\tvar userState;\n\n\t\t\tif ( step === undefined ) {\n\t\t\t\tstep = gt.getStepFromQuery() || 0;\n\t\t\t}\n\n\t\t\tuserState = getUserState();\n\t\t\tif ( ( step === 0 ) && userState.tours[ tourName ] !== undefined ) {\n\t\t\t\t// start from user state position\n\t\t\t\tshowTour( tourName, gt.makeTourId( {\n\t\t\t\t\tname: tourName,\n\t\t\t\t\tstep: userState.tours[ tourName ].step\n\t\t\t\t} ) );\n\t\t\t}\n\n\t\t\tif ( step === 0 ) {\n\t\t\t\tstep = 1;\n\t\t\t}\n\t\t\t// start from step specified\n\t\t\tshowTour( tourName, gt.makeTourId( {\n\t\t\t\tname: tourName,\n\t\t\t\tstep: step\n\t\t\t} ) );\n\t\t},\n\n\t\t/**\n\t\t * Removes the tour cookie for a given guider.\n\t\t *\n\t\t * @private\n\t\t *\n\t\t * @param {Object} guider any guider from the tour\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tremoveTourFromUserStateByGuider: function ( guider ) {\n\t\t\tvar tourInfo = gt.parseTourId( guider.id );\n\t\t\tif ( tourInfo !== null ) {\n\t\t\t\tremoveTourFromUserStateByName( tourInfo.name );\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Updates a single tour in the user cookie state.  The tour must already be loaded.\n\t\t *\n\t\t * @private\n\t\t *\n\t\t * @param {Object} args keyword arguments\n\t\t * @param {Object} args.tourInfo tour info object with name and step\n\t\t * @param {boolean} args.wasShown true if the guider was actually just shown on the\n\t\t *   current page, false otherwise.  Certain fields can only be initialized on a\n\t\t *   page where it was shown.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tupdateUserStateForTour: function ( args ) {\n\t\t\tvar cookieState = getCookieState(), tourName, tourSpec, articleId, pageName,\n\t\t\t\tcookieValue;\n\n\t\t\ttourName = args.tourInfo.name;\n\t\t\t// It should be defined, except when wasShown is false.\n\t\t\ttourSpec = internal.definedTours[ tourName ] || {};\n\n\t\t\t// Ensure there's a sub-object for this tour\n\t\t\tif ( cookieState.tours[ tourName ] === undefined ) {\n\t\t\t\tcookieState.tours[ tourName ] = {};\n\n\t\t\t\tcookieState.tours[ tourName ].startTime = Date.now();\n\t\t\t}\n\n\t\t\tif (\n\t\t\t\targs.wasShown && tourSpec.showConditionally === 'stickToFirstPage' &&\n\t\t\t\tcookieState.tours[ tourName ].firstArticleId === undefined &&\n\t\t\t\tcookieState.tours[ tourName ].firstSpecialPageName === undefined\n\t\t\t) {\n\t\t\t\tarticleId = mw.config.get( 'wgArticleId' );\n\t\t\t\tif ( articleId !== 0 ) {\n\t\t\t\t\tcookieState.tours[ tourName ].firstArticleId = articleId;\n\t\t\t\t} else {\n\t\t\t\t\tpageName = mw.config.get( 'wgPageName' );\n\t\t\t\t\tcookieState.tours[ tourName ].firstSpecialPageName = pageName;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tcookieState.tours[ tourName ].step = String( args.tourInfo.step );\n\t\t\tcookieValue = JSON.stringify( cookieState );\n\t\t\tmw.cookie.set( cookieName, cookieValue, cookieParams );\n\t\t},\n\n\t\t// Below are exposed for unit testing only, and should be considered\n\t\t// private\n\t\t/**\n\t\t * Returns cookie configuration, for testing only.\n\t\t *\n\t\t * @private\n\t\t *\n\t\t * @return {Object} cookie configuration\n\t\t */\n\t\tgetCookieConfiguration: function () {\n\t\t\treturn {\n\t\t\t\tname: cookieName,\n\t\t\t\tparameters: cookieParams\n\t\t\t};\n\t\t},\n\n\t\t/**\n\t\t * Determines whether to show a given tour, given the name, full cookie\n\t\t * value, and condition specified in the tour definition.\n\t\t *\n\t\t * Exposed only for testing.\n\t\t *\n\t\t * @private\n\t\t *\n\t\t * @param {Object} args arguments\n\t\t * @param {string} args.tourName name of tour\n\t\t * @param {Object} args.userState full value of tour cookie, not null\n\t\t * @param {string} args.pageName current full page name (wgPageName format)\n\t\t * @param {string} args.articleId current article ID\n\t\t * @param {string} [args.condition] showIf condition specified in tour definition, if any\n\t\t *   See mw.guidedTour.TourBuilder#constructor for usage\n\t\t *\n\t\t * @return {boolean} true to show, false otherwise\n\t\t * @throws {mw.guidedTour.TourDefinitionError} On invalid conditions\n\t\t */\n\t\tshouldShowTour: function ( args ) {\n\t\t\tvar subState = args.userState.tours[ args.tourName ];\n\t\t\tif ( args.condition !== undefined ) {\n\t\t\t\t// TODO (mattflaschen, 2013-07-09): Allow having multiple\n\t\t\t\t// conditions ANDed together in an array.\n\t\t\t\tswitch ( args.condition ) {\n\t\t\t\t\tcase 'stickToFirstPage':\n\t\t\t\t\t\tif ( subState === undefined ) {\n\t\t\t\t\t\t\t// Not yet shown\n\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif ( subState.firstArticleId !== undefined ) {\n\t\t\t\t\t\t\treturn subState.firstArticleId === args.articleId;\n\t\t\t\t\t\t} else if ( subState.firstSpecialPageName !== undefined ) {\n\t\t\t\t\t\t\treturn subState.firstSpecialPageName === args.pageName;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 'wikitext':\n\t\t\t\t\t\t// Any screen that is *not* VisualEditor-specific\n\t\t\t\t\t\t// Reading, history, wikitext-specific screens, etc.\n\t\t\t\t\t\treturn !gt.isVisualEditorOpen();\n\t\t\t\t\tcase 'VisualEditor':\n\t\t\t\t\t\t// Any screen that is *not* wikitext-specific\n\t\t\t\t\t\t// Reading, history, VisualEditor screen, etc.\n\t\t\t\t\t\treturn !gt.isEditingWithWikitext() && !gt.isReviewingWithWikitext();\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tthrow new gt.TourDefinitionError( '\\'' + args.condition + '\\' is not a supported condition' );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// No conditions or inconsistent cookie data\n\t\t\treturn true;\n\t\t}\n\t} );\n\n\t/**\n\t * Creates a tour based on an object specifying it, but does not show\n\t * it immediately\n\t *\n\t * mw.guidedTour.Tour#constructor has details on tourSpec.name,\n\t * tourSpec.isSinglePage, and tourSpec.showConditionally.\n\t *\n\t * @method defineTour\n\t * @deprecated\n\t *\n\t * @param {Object} tourSpec object specifying tour\n\t * @param {Array} tourSpec.steps Array of steps; see\n\t * mw.guidedTour.TourBuilder#step.  In addition, the following deprecated\n\t * option is supported only through defineTour.\n\t * @param {Function} [tourSpec.steps.shouldSkip] Function returning a\n\t *  boolean, which specifies whether to skip the current step based on the\n\t *  page state\n\t * @param {boolean} tourSpec.steps.shouldSkip.return true to skip, false\n\t *  otherwise\n\t *\n\t * @return {boolean} true, on success; throws otherwise\n\t * @throws {mw.guidedTour.TourDefinitionError} On invalid input\n\t */\n\tmw.log.deprecate( gt, 'defineTour', function ( tourSpec ) {\n\t\tvar tourBuilder, stepBuilders = [], steps, i, j, stepCount;\n\n\t\t/**\n\t\t * Prepares a stepSpec for being passed to firstStep or step\n\t\t *\n\t\t * @private\n\t\t *\n\t\t * @param {number} index 0-based index in step array for step to convert\n\t\t * @param {Object} stepSpec Specification\n\t\t *\n\t\t * @return {Object} Augmented object\n\t\t */\n\t\tfunction convertStepSpec( index, stepSpec ) {\n\t\t\treturn $.extend( true, {\n\t\t\t\tname: ( index + 1 ).toString(),\n\t\t\t\tallowAutomaticNext: false\n\t\t\t}, stepSpec );\n\t\t}\n\n\t\t/**\n\t\t * Follows the chain of shouldSkip through the steps, and returns the\n\t\t * resulting StepBuilder, mw.guidedTour.TransitionAction#hide (if the last\n\t\t * shouldSkip returns true), or undefined\n\t\t *\n\t\t * @private\n\t\t *\n\t\t * @param {number} skipStartIndex 0-based index of the step to start at\n\t\t *\n\t\t * @return {mw.guidedTour.StepBuilder|mw.guidedTour.TransitionAction|undefined}\n\t\t *  next step, hide (if the tour should be hidden for now), or undefined\n\t\t *  for no change\n\t\t */\n\t\tfunction followShouldSkip( skipStartIndex ) {\n\t\t\tvar skipIndex = skipStartIndex;\n\n\t\t\twhile ( skipIndex < stepCount &&\n\t\t\t\tsteps[ skipIndex ].shouldSkip &&\n\t\t\t\tsteps[ skipIndex ].shouldSkip() ) {\n\n\t\t\t\tskipIndex++;\n\t\t\t}\n\n\t\t\tif ( skipIndex === skipStartIndex ) {\n\t\t\t\t// No change, so don't skip\n\t\t\t\treturn undefined;\n\t\t\t} else if ( skipIndex < stepCount ) {\n\t\t\t\treturn stepBuilders[ skipIndex ];\n\t\t\t} else {\n\t\t\t\t// Skipped past the end\n\t\t\t\treturn gt.TransitionAction.HIDE;\n\t\t\t}\n\t\t}\n\n\t\t/**\n\t\t * Gets a transition callback for the given start index\n\t\t *\n\t\t * @private\n\t\t *\n\t\t * @param {number} startIndex 0-based index of step to convert\n\t\t *\n\t\t * @return {Function} Handler that returns the target after skipping\n\t\t */\n\t\tfunction getTransitionHandler( startIndex ) {\n\t\t\treturn function () {\n\t\t\t\treturn followShouldSkip( startIndex );\n\t\t\t};\n\t\t}\n\n\t\tif ( arguments.length !== 1 ) {\n\t\t\t// Object itself is checked in TourBuilder.\n\t\t\tthrow new gt.TourDefinitionError( 'Check your syntax. There must be exactly one argument, \\'tourSpec\\', which must be an object.' );\n\t\t}\n\n\t\ttourBuilder = new gt.TourBuilder( tourSpec );\n\n\t\tsteps = tourSpec.steps;\n\t\tif ( !Array.isArray( steps ) || steps.length < 1 ) {\n\t\t\tthrow new gt.TourDefinitionError( '\\'tourSpec.steps\\' must be an array, a list of one or more steps.' );\n\t\t}\n\n\t\tstepCount = steps.length;\n\n\t\tstepBuilders[ 0 ] = tourBuilder.firstStep(\n\t\t\tconvertStepSpec( 0, tourSpec.steps[ 0 ] )\n\t\t);\n\n\t\tfor ( i = 1; i < stepCount; i++ ) {\n\t\t\tstepBuilders[ i ] = tourBuilder.step(\n\t\t\t\tconvertStepSpec( i, steps[ i ] )\n\t\t\t);\n\t\t}\n\n\t\tfor ( j = 0; j < stepCount; j++ ) {\n\t\t\tif ( j < stepCount - 1 ) {\n\t\t\t\tstepBuilders[ j ].next( stepBuilders[ j + 1 ] );\n\t\t\t}\n\n\t\t\t// Don't register a custom skip handler if it can never skip.\n\t\t\tif ( steps[ j ].shouldSkip ) {\n\t\t\t\tstepBuilders[ j ].transition( getTransitionHandler( j ) );\n\t\t\t}\n\t\t}\n\n\t\treturn true;\n\t} );\n\n\t// Keep after main mw.guidedTour methods.\n\t// jsduck assumes methods belong to the classes they follow in source\n\t// code order.\n\t/**\n\t * Error subclass for errors that occur during tour definition\n\t *\n\t * @class mw.guidedTour.TourDefinitionError\n\t * @extends Error\n\t */\n\n\t/**\n\t * @constructor\n\t *\n\t * @param {string} message Error message text\n\t */\n\tgt.TourDefinitionError = function ( message ) {\n\t\tthis.message = message;\n\t};\n\n\tgt.TourDefinitionError.prototype.toString = function () {\n\t\treturn 'TourDefinitionError: ' + this.message;\n\t};\n\tgt.TourDefinitionError.prototype.constructor = gt.TourDefinitionError;\n\n\t/**\n\t * Error subclass for invalid arguments (that are not part of tour definition)\n\t *\n\t * @class mw.guidedTour.IllegalArgumentError\n\t * @extends Error\n\t */\n\n\t/**\n\t * @constructor\n\t *\n\t * @param {string} message Error message text\n\t */\n\tgt.IllegalArgumentError = function ( message ) {\n\t\tthis.message = message;\n\t};\n\n\tgt.IllegalArgumentError.prototype.toString = function () {\n\t\treturn 'IllegalArgumentError: ' + this.message;\n\t};\n\tgt.IllegalArgumentError.prototype.constructor = gt.IllegalArgumentError;\n\n\tinitialize();\n}( mw.libs.guiders ) );\n","usedDeprecatedRules":[]},{"filePath":"/src/repo/modules/mediawiki.libs.guiders/mediawiki.libs.guiders.js","messages":[{"ruleId":"no-jquery/no-sizzle","severity":1,"message":"Positional selector extensions are not allowed","line":388,"column":15,"nodeType":"CallExpression","endLine":388,"endColumn":62},{"ruleId":"no-jquery/no-sizzle","severity":1,"message":"Positional selector extensions are not allowed","line":448,"column":15,"nodeType":"CallExpression","endLine":448,"endColumn":63},{"ruleId":"no-jquery/no-sizzle","severity":1,"message":"Selector extensions are not allowed","line":732,"column":3,"nodeType":"CallExpression","endLine":732,"endColumn":25}],"suppressedMessages":[{"ruleId":"no-script-url","severity":2,"message":"Script URL is a form of eval.","line":103,"column":38,"nodeType":"Literal","messageId":"unexpectedScriptURL","endLine":103,"endColumn":59,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/variable-pattern","severity":2,"message":"jQuery collection names must match the variablePattern","line":161,"column":5,"nodeType":"AssignmentExpression","endLine":163,"endColumn":44,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"mediawiki/class-doc","severity":2,"message":"All possible CSS classes should be documented. See https://w.wiki/PS2 for details.","line":181,"column":5,"nodeType":"CallExpression","endLine":181,"endColumn":55,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-fade","severity":2,"message":"Prefer CSS transitions to .fadeIn","line":479,"column":3,"nodeType":"CallExpression","endLine":479,"endColumn":42,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"mediawiki/class-doc","severity":2,"message":"All possible CSS classes should be documented. See https://w.wiki/PS2 for details.","line":482,"column":5,"nodeType":"CallExpression","endLine":482,"endColumn":39,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-fade","severity":2,"message":"Prefer CSS transitions to .fadeOut","line":490,"column":3,"nodeType":"CallExpression","endLine":490,"endColumn":43,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-parse-html-literal","severity":2,"message":"Prefer DOM building to parsing HTML literals","line":497,"column":4,"nodeType":"CallExpression","endLine":497,"endColumn":36,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"mediawiki/class-doc","severity":2,"message":"All possible CSS classes should be documented. See https://w.wiki/PS2 for details.","line":537,"column":3,"nodeType":"CallExpression","endLine":537,"endColumn":50,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"mediawiki/class-doc","severity":2,"message":"All possible CSS classes should be documented. See https://w.wiki/PS2 for details.","line":595,"column":4,"nodeType":"CallExpression","endLine":595,"endColumn":50,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/variable-pattern","severity":2,"message":"jQuery collection names must match the variablePattern","line":684,"column":3,"nodeType":"AssignmentExpression","endLine":684,"endColumn":33,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"mediawiki/class-doc","severity":2,"message":"All possible CSS classes should be documented. See https://w.wiki/PS2 for details.","line":687,"column":4,"nodeType":"CallExpression","endLine":687,"endColumn":50,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-fade","severity":2,"message":"Prefer CSS transitions to .fadeOut","line":741,"column":3,"nodeType":"CallExpression","endLine":741,"endColumn":35,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":3,"fixableErrorCount":0,"fixableWarningCount":0,"source":"// TODO (mattflaschen, 2013-07-30): Remove these after the following are resolved:\n// * Script URL, we need to determine the replacement, either a wrapper of the onclick that\n// calls preventDefault, or another element with no default.\n/*!\n * guiders.js\n *\n * Developed by Jeff Pickhardt (jeff+pickhardt@optimizely.com) at Optimizely. (www.optimizely.com)\n * We make A/B testing you'll actually use.\n *\n * Released under the Apache License 2.0.\n * www.apache.org/licenses/LICENSE-2.0.html\n *\n * Questions about Optimizely should be sent to:\n * sales@optimizely.com or support@optimizely.com\n *\n * Further developed by the Growth Team at Wikimedia.\n *\n * Enjoy!\n *\n * Changes:\n *\n * - initGuider(): Allows for initializing Guiders without actually creating them (useful when guider is not in the DOM yet. Avoids error: base is null [Break On This Error] var top = base.top;\n *\n * - overlay \"error\": If not set to true, this defines the class of the overlay. (This is useful for coloring the background of the overlay red on error.\n * - onShow: If this returns a guider object, then it can shunt (skip) the rest of show()\n *\n * See https://www.mediawiki.org/wiki/Extension:GuidedTour and https://phabricator.wikimedia.org/diffusion/EGTO/\n *\n * Previously, there was a MediaWiki-specific repository for\n * Guiders (based on the upstream one).  For earlier version control history, see\n * https://phabricator.wikimedia.org/diffusion/EGTG/history/\n */\n\n/**\n * Code for rendering and low-level code of moving between steps.\n *\n * You should use the public mw.guidedTour API when possible, rather then calling methods\n * from this file directly.  The API of this file will change.\n *\n * @author jeff+pickhardt@optimizely.com\n * @author mflaschen@wikimedia.org\n * @author tychay@php.net\n *\n * @class mw.libs.guiders\n * @singleton\n */\nmw.libs.guiders = ( function () {\n\tvar guiders = {},\n\t\t_resizing;\n\n\tguiders._defaultSettings = {\n\t\tattachTo: null, // Selector of the element to attach to.\n\t\tautoFocus: false, // Determines whether or not the browser scrolls to the element.\n\t\tbuttons: [],\n\t\tbuttonCustomHTML: '',\n\t\tclassString: null,\n\t\tcloseOnEscape: false,\n\t\tcloseOnClickOutside: false,\n\t\tdescription: '',\n\t\t// If guider would go off screen to the left or right, flip horizontally.\n\t\t// If guider would go off the top of the screen, flip vertically. If it would go off the bottom of the screen do nothing, since most pages scroll in the vertical direction.\n\t\t// It will be flipped both ways if it would be off-screen on two sides.\n\t\tflipToKeepOnScreen: false,\n\t\toffset: {\n\t\t\ttop: null,\n\t\t\tleft: null\n\t\t},\n\t\t// Function taking three arguments, the guider, a legacy boolean for close\n\t\t// type (false for text close button, true for everything else), and a text\n\t\t// string for closeMethod ('xButton', 'escapeKey', 'clickOutside')\n\t\tonClose: null,\n\t\tonHide: null,\n\t\tonShow: null,\n\t\toverlay: false,\n\n\t\t// 1-12 follows an analog clock, 0 means centered. You can also use the string positions\n\t\t// listed below at guiders._offsetNameMapping, such as \"topRight\".\n\t\tposition: 0,\n\t\ttitle: '',\n\t\twidth: 400,\n\t\txButton: false // this places a closer \"x\" button in the top right of the guider\n\t};\n\n\tguiders._htmlSkeleton = [\n\t\t'<div class=\"guider\">',\n\t\t'  <div class=\"guider_content\">',\n\t\t'    <h1 class=\"guider_title\"></h1>',\n\t\t'    <div class=\"guider_close\"></div>',\n\t\t'    <p class=\"guider_description\"></p>',\n\t\t'    <div class=\"guider_buttons\"></div>',\n\t\t'  </div>',\n\t\t'  <div class=\"guider_arrow\">',\n\t\t'    <div class=\"guider_arrow_inner_container\">',\n\t\t'      <div class=\"guider_arrow_inner\"></div>',\n\t\t'    </div>',\n\t\t'  </div>',\n\t\t'</div>'\n\t].join( '' );\n\n\tguiders._arrowSize = 42; // This is the arrow's width and height.\n\tguiders._buttonElement = '<a></a>';\n\t// eslint-disable-next-line no-script-url\n\tguiders._buttonAttributes = { href: 'javascript:void(0);' };\n\tguiders._buttonClass = 'mw-ui-button';\n\tguiders._currentGuiderID = null;\n\tguiders._guiderInits = {}; // stores uncreated guiders indexed by id\n\tguiders._guiders = {}; // stores created guiders indexed by id\n\tguiders._lastCreatedGuiderID = null;\n\tguiders._scrollDuration = 750; // In milliseconds\n\n\t// See position above in guiders._defaultSettings\n\tguiders._offsetNameMapping = {\n\t\ttopLeft: 11,\n\t\ttop: 12,\n\t\ttopRight: 1,\n\t\trightTop: 2,\n\t\tright: 3,\n\t\trightBottom: 4,\n\t\tbottomRight: 5,\n\t\tbottom: 6,\n\t\tbottomLeft: 7,\n\t\tleftBottom: 8,\n\t\tleft: 9,\n\t\tleftTop: 10\n\t};\n\tguiders._windowHeight = 0;\n\n\t// Handles a user-initiated close action (e.g. clicking close or hitting ESC)\n\t// isAlternativeClose is false for the text Close button, and true for everything else.\n\tguiders.handleOnClose = function ( myGuider, isAlternativeClose, closeMethod ) {\n\t\tif ( myGuider.onClose ) {\n\t\t\tmyGuider.onClose( myGuider, isAlternativeClose, closeMethod );\n\t\t}\n\n\t\tguiders.hideAll();\n\t};\n\n\tguiders._makeButtonListener = function ( onclickCallback ) {\n\t\treturn function ( evt ) {\n\t\t\tevt.preventDefault();\n\t\t\tonclickCallback.call( this, evt );\n\t\t};\n\t};\n\n\tguiders._addButtons = function ( myGuider ) {\n\t\tvar guiderButtonsContainer, i, thisButton, $thisButtonElem,\n\t\t\tthisButtonHtml, $myCustomHTML;\n\n\t\t// Add buttons\n\t\tguiderButtonsContainer = myGuider.elem.find( '.guider_buttons' );\n\n\t\tif ( myGuider.buttons === null || myGuider.buttons.length === 0 ) {\n\t\t\tguiderButtonsContainer.remove();\n\t\t\treturn;\n\t\t}\n\n\t\tfor ( i = myGuider.buttons.length - 1; i >= 0; i-- ) {\n\t\t\tthisButton = myGuider.buttons[ i ];\n\t\t\tif ( thisButton.hasIcon ) {\n\t\t\t\t// eslint-disable-next-line no-jquery/variable-pattern\n\t\t\t\tthisButtonHtml = $( '<span>' )\n\t\t\t\t\t.addClass( 'guider_button_icon' )\n\t\t\t\t\t.attr( 'aria-label', thisButton.name );\n\t\t\t} else {\n\t\t\t\tthisButtonHtml = thisButton.name;\n\t\t\t}\n\t\t\t$thisButtonElem = $(\n\t\t\t\tguiders._buttonElement,\n\t\t\t\t$.extend(\n\t\t\t\t\t{\n\t\t\t\t\t\tclass: guiders._buttonClass,\n\t\t\t\t\t\thtml: thisButtonHtml\n\t\t\t\t\t},\n\t\t\t\t\tguiders._buttonAttributes,\n\t\t\t\t\tthisButton.html || {}\n\t\t\t\t)\n\t\t\t);\n\n\t\t\tif ( typeof thisButton.classString !== 'undefined' && thisButton.classString !== null ) {\n\t\t\t\t// eslint-disable-next-line mediawiki/class-doc\n\t\t\t\t$thisButtonElem.addClass( thisButton.classString );\n\t\t\t}\n\n\t\t\tguiderButtonsContainer.append( $thisButtonElem );\n\n\t\t\tif ( thisButton.onclick ) {\n\t\t\t\t$thisButtonElem.on( 'click', guiders._makeButtonListener( thisButton.onclick ) );\n\t\t\t}\n\t\t}\n\n\t\tif ( myGuider.buttonCustomHTML !== '' ) {\n\t\t\t$myCustomHTML = $( myGuider.buttonCustomHTML );\n\t\t\tmyGuider.elem.find( '.guider_buttons' ).append( $myCustomHTML );\n\t\t}\n\n\t\tif ( myGuider.buttons.length === 0 ) {\n\t\t\tguiderButtonsContainer.remove();\n\t\t}\n\t};\n\n\tguiders._addXButton = function ( myGuider ) {\n\t\tvar xButtonContainer, $xButton;\n\n\t\txButtonContainer = myGuider.elem.find( '.guider_close' );\n\t\t$xButton = $( '<a>',\n\t\t\t$.extend( { class: 'x_button' }, guiders._buttonAttributes )\n\t\t);\n\t\txButtonContainer.append( $xButton );\n\t\t$xButton.on( {\n\t\t\tclick: function () {\n\t\t\t\tguiders.handleOnClose( myGuider, true, 'xButton' );\n\t\t\t}\n\t\t} );\n\t};\n\n\tguiders._wireEscape = function ( myGuider ) {\n\t\t$( document ).on( 'keydown', function ( event ) {\n\t\t\tif ( event.keyCode === 27 || event.which === 27 ) {\n\t\t\t\tguiders.handleOnClose( myGuider, true, 'escapeKey' /* close by escape key */ );\n\t\t\t\treturn false;\n\t\t\t}\n\t\t} );\n\t};\n\n\t// myGuider is passed though it's not currently used.\n\tguiders._unWireEscape = function ( /* myGuider */ ) {\n\t\t$( document ).off( 'keydown' );\n\t};\n\n\tguiders._wireClickOutside = function ( myGuider ) {\n\t\t$( document ).on( 'click.guiders', function ( event ) {\n\t\t\tif ( $( event.target ).closest( '.guider' ).length === 0 ) {\n\t\t\t\tguiders.handleOnClose( myGuider, true, 'clickOutside' /* close by clicking outside */ );\n\t\t\t\tif ( event.target.id === 'guider_overlay' ) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t} );\n\t};\n\n\tguiders._unWireClickOutside = function () {\n\t\t$( document ).off( 'click.guiders' );\n\t};\n\n\t/**\n\t * Flips a position horizontally, vertically, both, or not all.  This can be used\n\t * for various scenarios, such as handling right-to-left languages and flipping a position\n\t * if the original one would go off screen.\n\t *\n\t * It accepts both string (e.g. \"top\") and numeric (e.g. 12) positions.\n\t *\n\t * @param {string|number} position position as in guider settings object\n\t * @param {Object} options how to flip\n\t * @param {boolean} options.vertical true to flip vertical (optional, defaults false)\n\t * @param {boolean} options.horizontal true to flip vertical (optional, defaults false)\n\t * @return {number} position with requested flippings in numeric form\n\t */\n\tguiders.getFlippedPosition = function ( position, options ) {\n\t\tvar TOP_CLOCK = 12, HALF_CLOCK = 6;\n\n\t\tif ( !options.horizontal && !options.vertical ) {\n\t\t\treturn position;\n\t\t}\n\n\t\t// Convert to numeric if needed\n\t\tif ( guiders._offsetNameMapping[ position ] !== undefined ) {\n\t\t\tposition = guiders._offsetNameMapping[ position ];\n\t\t}\n\n\t\tposition = Number( position );\n\n\t\tif ( position === 0 ) {\n\t\t\t// Don't change center position.\n\t\t\treturn position;\n\t\t}\n\n\t\t// This math is all based on the analog clock model used for guiders positioning.\n\t\tif ( options.horizontal && !options.vertical ) {\n\t\t\tposition = TOP_CLOCK - position;\n\t\t} else if ( options.vertical && !options.horizontal ) {\n\t\t\tposition = HALF_CLOCK - position;\n\t\t} else if ( options.vertical && options.horizontal ) {\n\t\t\tposition = position + HALF_CLOCK;\n\t\t}\n\n\t\tif ( position < 1 ) {\n\t\t\tposition += TOP_CLOCK;\n\t\t} else if ( position > TOP_CLOCK ) {\n\t\t\tposition -= TOP_CLOCK;\n\t\t}\n\n\t\treturn position;\n\t};\n\n\t/**\n\t * Returns CSS for attaching a guider to its associated element\n\t *\n\t * @param {jQuery} attachTo element to attach to\n\t * @param {Object} guider guider object\n\t * @param {number} position position for guider, using clock\n\t *   model (0-12).\n\t * @return {Object} CSS properties for the attachment\n\t */\n\tguiders._getAttachCss = function ( attachTo, guider, position ) {\n\t\tvar myHeight, myWidth, base, top, left, topMarginOfBody, attachToHeight,\n\t\t\tattachToWidth, bufferOffset, offsetMap, offset, positionType;\n\n\t\tmyHeight = guider.elem.innerHeight();\n\t\tmyWidth = guider.elem.innerWidth();\n\n\t\tif ( position === 0 ) {\n\t\t\t// The guider is positioned in the center of the screen.\n\t\t\treturn {\n\t\t\t\tposition: 'fixed',\n\t\t\t\ttop: ( $( window ).height() - myHeight ) / 3 + 'px',\n\t\t\t\tleft: ( $( window ).width() - myWidth ) / 2 + 'px'\n\t\t\t};\n\t\t}\n\n\t\t// Otherwise, the guider is positioned relative to the attachTo element.\n\t\tbase = attachTo.offset();\n\t\ttop = base.top;\n\t\tleft = base.left;\n\n\t\t// topMarginOfBody corrects positioning if body has a top margin set on it.\n\t\ttopMarginOfBody = $( 'body' ).outerHeight( true ) - $( 'body' ).outerHeight( false );\n\t\ttop -= topMarginOfBody;\n\n\t\tattachToHeight = attachTo.innerHeight();\n\t\tattachToWidth = attachTo.innerWidth();\n\n\t\tbufferOffset = 0.9 * guiders._arrowSize - 10;\n\n\t\t// offsetMap follows the form: [height, width]\n\t\toffsetMap = {\n\t\t\t1: [ -bufferOffset - myHeight, attachToWidth - myWidth ],\n\t\t\t2: [ 0, bufferOffset + attachToWidth ],\n\t\t\t3: [ attachToHeight / 2 - myHeight / 2, bufferOffset + attachToWidth ],\n\t\t\t4: [ attachToHeight - myHeight, bufferOffset + attachToWidth ],\n\t\t\t5: [ bufferOffset + attachToHeight, attachToWidth - myWidth ],\n\t\t\t6: [ bufferOffset + attachToHeight, attachToWidth / 2 - myWidth / 2 ],\n\t\t\t7: [ bufferOffset + attachToHeight, 0 ],\n\t\t\t8: [ attachToHeight - myHeight, -myWidth - bufferOffset ],\n\t\t\t9: [ attachToHeight / 2 - myHeight / 2, -myWidth - bufferOffset ],\n\t\t\t10: [ 0, -myWidth - bufferOffset ],\n\t\t\t11: [ -bufferOffset - myHeight, 0 ],\n\t\t\t12: [ -bufferOffset - myHeight, attachToWidth / 2 - myWidth / 2 ]\n\t\t};\n\t\toffset = offsetMap[ position ];\n\t\ttop += offset[ 0 ];\n\t\tleft += offset[ 1 ];\n\n\t\tpositionType = 'absolute';\n\t\t// If the element you are attaching to is position: fixed, then we will make the guider\n\t\t// position: fixed as well.\n\t\tif ( attachTo.css( 'position' ) === 'fixed' ) {\n\t\t\tpositionType = 'fixed';\n\t\t\ttop -= $( window ).scrollTop();\n\t\t\tleft -= $( window ).scrollLeft();\n\t\t}\n\n\t\t// If you specify an additional offset parameter when you create the guider, it gets added here.\n\t\tif ( guider.offset.top !== null ) {\n\t\t\ttop += guider.offset.top;\n\t\t}\n\t\tif ( guider.offset.left !== null ) {\n\t\t\tleft += guider.offset.left;\n\t\t}\n\n\t\treturn {\n\t\t\tposition: positionType,\n\t\t\ttop: parseInt( top, 10 ),\n\t\t\tleft: parseInt( left, 10 )\n\t\t};\n\t};\n\n\t/**\n\t * Gets element to attach to, wrapped by jQuery.  Filters out elements that are not\n\t * :visible, such as (such as those with display: none).\n\t *\n\t * @private\n\t *\n\t * @param {Object} guider guider being attached\n\t *\n\t * @return {jQuery|null} jQuery node for element, or null for no match\n\t */\n\tguiders._getAttachTarget = function ( guider ) {\n\t\tvar $node = $( guider.attachTo ).filter( ':visible:first' );\n\n\t\treturn $node.length > 0 ? $node : null;\n\t};\n\n\t/**\n\t * Attaches a guider\n\t *\n\t * @param {Object} myGuider guider to attach\n\t * @return {jQuery|undefined} jQuery node for guider's element if successful,\n\t *   or undefined for invalid input.\n\t */\n\tguiders._attach = function ( myGuider ) {\n\t\tvar position, $attachTarget, css, rightOfGuider, flipVertically,\n\t\t\tflipHorizontally;\n\n\t\tif ( typeof myGuider !== 'object' ) {\n\t\t\treturn;\n\t\t}\n\n\t\t$attachTarget = guiders._getAttachTarget( myGuider );\n\n\t\t// We keep a local position, separate from the originally requested one.\n\t\t// We alter this locally for auto-flip and missing elements.\n\t\t//\n\t\t// However, the DOM or window size may change later, and on each attach we want to start\n\t\t// with the originally requested position as the baseline.\n\t\tposition = $attachTarget !== null ? myGuider.position : 0;\n\n\t\tcss = guiders._getAttachCss( $attachTarget, myGuider, position );\n\n\t\tif ( myGuider.flipToKeepOnScreen ) {\n\t\t\trightOfGuider = css.left + myGuider.width;\n\t\t\tflipVertically = css.top < 0;\n\t\t\tflipHorizontally = css.left < 0 || rightOfGuider > $( 'body' ).innerWidth();\n\t\t\tif ( flipVertically || flipHorizontally ) {\n\t\t\t\tposition = guiders.getFlippedPosition( position, {\n\t\t\t\t\tvertical: flipVertically,\n\t\t\t\t\thorizontal: flipHorizontally\n\t\t\t\t} );\n\t\t\t\tcss = guiders._getAttachCss( $attachTarget, myGuider, position );\n\t\t\t}\n\t\t}\n\n\t\tguiders._styleArrow( myGuider, position );\n\t\tguiders._setupAnimations( myGuider, position );\n\t\treturn myGuider.elem.css( css );\n\t};\n\n\t/**\n\t * Gets action button element, wrapped by jQuery.  Filters out elements that are not\n\t * :visible, such as (such as those with display: none).\n\t *\n\t * @private\n\t *\n\t * @param {Object} guider guider being attached\n\t *\n\t * @return {jQuery|null} jQuery node for element, or null for no match\n\t */\n\tguiders._getActionBtnTarget = function ( guider ) {\n\t\tvar $node = $( guider.actionBtn ).filter( ':visible:first' );\n\n\t\treturn $node.length > 0 ? $node : null;\n\t};\n\n\t/**\n\t * Returns the guider by ID.\n\t *\n\t * Add check to create and grab guider from inits if it exists there.\n\t *\n\t * @param {string} id id of guider\n\t * @return {Object} guider object\n\t */\n\tguiders._guiderById = function ( id ) {\n\t\tvar myGuider;\n\n\t\tif ( typeof guiders._guiders[ id ] === 'undefined' ) {\n\t\t\tif ( typeof guiders._guiderInits[ id ] === 'undefined' ) {\n\t\t\t\tthrow new Error( 'Cannot find guider with id ' + id );\n\t\t\t}\n\t\t\tmyGuider = guiders._guiderInits[ id ];\n\t\t\tguiders.createGuider( myGuider );\n\t\t\tdelete guiders._guiderInits[ id ]; // prevents recursion\n\t\t\t// fall through ...\n\t\t}\n\t\treturn guiders._guiders[ id ];\n\t};\n\n\tguiders._showOverlay = function ( overlayClass ) {\n\t\t// FIXME: Use CSS transition\n\t\t// eslint-disable-next-line no-jquery/no-fade\n\t\t$( '#guider_overlay' ).fadeIn( 'fast' ).each( function () {\n\t\t\tif ( overlayClass ) {\n\t\t\t\t// eslint-disable-next-line mediawiki/class-doc\n\t\t\t\t$( this ).addClass( overlayClass );\n\t\t\t}\n\t\t} );\n\t};\n\n\tguiders._hideOverlay = function () {\n\t\t// FIXME: Use CSS transition\n\t\t// eslint-disable-next-line no-jquery/no-fade\n\t\t$( '#guider_overlay' ).fadeOut( 'fast' ).removeClass();\n\t};\n\n\tguiders._initializeOverlay = function () {\n\t\tif ( $( '#guider_overlay' ).length === 0 ) {\n\t\t\t// FIXME: Stop needing this to have an ID alongside the class.\n\t\t\t// eslint-disable-next-line no-jquery/no-parse-html-literal\n\t\t\t$( '<div id=\"guider_overlay\">' ).addClass( 'guider_overlay' ).hide().appendTo( 'body' );\n\t\t}\n\t};\n\n\tguiders._styleArrow = function ( myGuider, position ) {\n\t\tvar $myGuiderArrow, newClass, myHeight, myWidth, arrowOffset, positionMap,\n\t\t\tarrowPosition;\n\n\t\t$myGuiderArrow = $( myGuider.elem.find( '.guider_arrow' ) );\n\n\t\tposition = position || 0;\n\n\t\t// Remove possible old direction.\n\t\t// Position, and thus arrow, can change on resize due to flipToKeepOnScreen\n\t\t// Also, if an element is added to or removed from the DOM, the arrow may need to change on reposition.\n\t\t//\n\t\t// If there should be an arrow, the new one will be added below.\n\t\t$myGuiderArrow.removeClass( 'guider_arrow_down guider_arrow_left guider_arrow_up guider_arrow_right' );\n\n\t\t// No arrow for center position\n\t\tif ( position === 0 ) {\n\t\t\treturn;\n\t\t}\n\t\tnewClass = {\n\t\t\t1: 'guider_arrow_down',\n\t\t\t2: 'guider_arrow_left',\n\t\t\t3: 'guider_arrow_left',\n\t\t\t4: 'guider_arrow_left',\n\t\t\t5: 'guider_arrow_up',\n\t\t\t6: 'guider_arrow_up',\n\t\t\t7: 'guider_arrow_up',\n\t\t\t8: 'guider_arrow_right',\n\t\t\t9: 'guider_arrow_right',\n\t\t\t10: 'guider_arrow_right',\n\t\t\t11: 'guider_arrow_down',\n\t\t\t12: 'guider_arrow_down'\n\t\t};\n\n\t\t// Classes documented above\n\t\t// eslint-disable-next-line mediawiki/class-doc\n\t\t$myGuiderArrow.addClass( newClass[ position ] );\n\n\t\tmyHeight = myGuider.elem.innerHeight();\n\t\tmyWidth = myGuider.elem.innerWidth();\n\t\tarrowOffset = guiders._arrowSize / 2;\n\t\tpositionMap = {\n\t\t\t1: [ 'right', arrowOffset ],\n\t\t\t2: [ 'top', arrowOffset ],\n\t\t\t3: [ 'top', myHeight / 2 - arrowOffset ],\n\t\t\t4: [ 'bottom', arrowOffset ],\n\t\t\t5: [ 'right', arrowOffset ],\n\t\t\t6: [ 'left', myWidth / 2 - arrowOffset ],\n\t\t\t7: [ 'left', arrowOffset ],\n\t\t\t8: [ 'bottom', arrowOffset ],\n\t\t\t9: [ 'top', myHeight / 2 - arrowOffset ],\n\t\t\t10: [ 'top', arrowOffset ],\n\t\t\t11: [ 'left', arrowOffset ],\n\t\t\t12: [ 'left', myWidth / 2 - arrowOffset ]\n\t\t};\n\t\tarrowPosition = positionMap[ position ];\n\t\t$myGuiderArrow.css( arrowPosition[ 0 ], arrowPosition[ 1 ] + 'px' );\n\t};\n\n\t/**\n\t * Remove all animation classes\n\t *\n\t * @param {Object} myGuider guider to remove animations from\n\t */\n\tguiders._removeAnimations = function ( myGuider ) {\n\t\tmyGuider.elem.removeClass( 'mwe-gt-fade-in-down mwe-gt-fade-in-up mwe-gt-fade-in-left mwe-gt-fade-in-right' );\n\t};\n\n\t/**\n\t * Add appropriate animation class relative to guider position\n\t *\n\t * @param {Object} myGuider guider to add animation class to\n\t * @param {number} position guider attachment position\n\t */\n\tguiders._setupAnimations = function ( myGuider, position ) {\n\t\tvar classMap = {\n\t\t\t1: 'mwe-gt-fade-in-down',\n\t\t\t2: 'mwe-gt-fade-in-left',\n\t\t\t3: 'mwe-gt-fade-in-left',\n\t\t\t4: 'mwe-gt-fade-in-left',\n\t\t\t5: 'mwe-gt-fade-in-up',\n\t\t\t6: 'mwe-gt-fade-in-up',\n\t\t\t7: 'mwe-gt-fade-in-up',\n\t\t\t8: 'mwe-gt-fade-in-right',\n\t\t\t9: 'mwe-gt-fade-in-right',\n\t\t\t10: 'mwe-gt-fade-in-right',\n\t\t\t11: 'mwe-gt-fade-in-down',\n\t\t\t12: 'mwe-gt-fade-in-down'\n\t\t};\n\t\tguiders._removeAnimations( myGuider );\n\t\t// Assign animation class for myGuider\n\t\tif ( position !== 0 ) {\n\t\t\t// Classes documented above\n\t\t\t// eslint-disable-next-line mediawiki/class-doc\n\t\t\tmyGuider.elem.addClass( classMap[ position ] );\n\t\t}\n\t};\n\n\tguiders.reposition = function () {\n\t\tvar currentGuider = guiders._guiders[ guiders._currentGuiderID ];\n\t\tguiders._attach( currentGuider );\n\t};\n\n\t/**\n\t * Shows the 'next' step\n\t */\n\tguiders.next = function () {\n\t\tguiders.doStep( 'next' );\n\t};\n\n\t/**\n\t * Shows the 'back' step\n\t */\n\tguiders.back = function () {\n\t\tguiders.doStep( 'back' );\n\t};\n\n\t/**\n\t * Move the guider directionally to the corresponding step. eg. next, back\n\t *\n\t * @param {string} direction next or back\n\t */\n\tguiders.doStep = function ( direction ) {\n\t\tvar currentGuider, moveToGuiderId, myGuider, omitHidingOverlay, actionBtn;\n\t\ttry {\n\t\t\tcurrentGuider = guiders._guiderById( guiders._currentGuiderID ); // has check to make sure guider is initialized\n\t\t} catch ( err ) {\n\t\t\treturn;\n\t\t}\n\t\tcurrentGuider.elem.data( 'locked', true );\n\n\t\tif ( currentGuider[ direction ] ) {\n\t\t\tmoveToGuiderId = currentGuider[ direction ]();\n\t\t}\n\t\tmoveToGuiderId = moveToGuiderId || null;\n\n\t\tif ( moveToGuiderId !== null && moveToGuiderId !== '' ) {\n\t\t\tmyGuider = guiders._guiderById( moveToGuiderId );\n\t\t\tomitHidingOverlay = !!myGuider.overlay;\n\t\t\tguiders.hideAll( omitHidingOverlay, true );\n\t\t\tactionBtn = guiders._getActionBtnTarget( currentGuider );\n\t\t\tif ( actionBtn !== null && actionBtn !== '' ) {\n\t\t\t\tactionBtn.click();\n\t\t\t}\n\t\t\tguiders.show( moveToGuiderId );\n\t\t}\n\t};\n\n\t/**\n\t * This stores the guider but does no work on it.\n\t * It is an alternative to createGuider() that defers the actual setup work.\n\t *\n\t * @param {Object} passedSettings Settings\n\t */\n\tguiders.initGuider = function ( passedSettings ) {\n\t\tif ( passedSettings === null || passedSettings === undefined ) {\n\t\t\treturn;\n\t\t}\n\t\tif ( !passedSettings.id ) {\n\t\t\treturn;\n\t\t}\n\t\tthis._guiderInits[ passedSettings.id ] = passedSettings;\n\t};\n\n\t/**\n\t * Creates a guider\n\t *\n\t * @param {Object} passedSettings settings for the guider\n\t * @return {Object} guiders singleton\n\t */\n\tguiders.createGuider = function ( passedSettings ) {\n\t\tvar $guiderElement, myGuider, $guiderTitleContainer;\n\n\t\tif ( passedSettings === null || passedSettings === undefined ) {\n\t\t\tpassedSettings = {};\n\t\t}\n\n\t\t// Extend those settings with passedSettings\n\t\tmyGuider = $.extend( {}, guiders._defaultSettings, passedSettings );\n\t\tmyGuider.id = myGuider.id || String( Math.floor( Math.random() * 1000 ) );\n\n\t\t$guiderElement = $( guiders._htmlSkeleton );\n\t\t// eslint-disable-next-line no-jquery/variable-pattern\n\t\tmyGuider.elem = $guiderElement;\n\t\tif ( typeof myGuider.classString !== 'undefined' && myGuider.classString !== null ) {\n\t\t\t// eslint-disable-next-line mediawiki/class-doc\n\t\t\tmyGuider.elem.addClass( myGuider.classString );\n\t\t}\n\t\tmyGuider.elem.css( 'width', myGuider.width + 'px' );\n\n\t\t$guiderTitleContainer = $guiderElement.find( '.guider_title' );\n\t\t$guiderTitleContainer.html( myGuider.title );\n\n\t\t$guiderElement.find( '.guider_description' ).html( myGuider.description );\n\n\t\tguiders._addButtons( myGuider );\n\n\t\tif ( myGuider.xButton ) {\n\t\t\tguiders._addXButton( myGuider );\n\t\t}\n\n\t\t$guiderElement.hide();\n\t\t$guiderElement.appendTo( 'body' );\n\t\t$guiderElement.attr( 'id', myGuider.id );\n\n\t\t// If a string form (e.g. 'top') was passed, convert it to numeric (e.g. 12)\n\t\t// As an alternative to the clock model, you can also use keywords to position the myGuider.\n\t\tif ( guiders._offsetNameMapping[ myGuider.position ] ) {\n\t\t\tmyGuider.position = guiders._offsetNameMapping[ myGuider.position ];\n\t\t}\n\n\t\tguiders._initializeOverlay();\n\n\t\tguiders._guiders[ myGuider.id ] = myGuider;\n\t\tguiders._lastCreatedGuiderID = myGuider.id;\n\n\t\treturn guiders;\n\t};\n\n\t/**\n\t * Hides all guiders\n\t *\n\t * @param {boolean|undefined} omitHidingOverlay falsy to hide overlay,\n\t *   true not to change it\n\t * @param {boolean} next true if caller will immediately show another guider\n\t *   in place of the one being hidden (optional, defaults false)\n\t * @return {Object} guiders singleton\n\t */\n\tguiders.hideAll = function ( omitHidingOverlay, next ) {\n\t\tnext = next || false;\n\n\t\t$( '.guider:visible' ).each( function ( index, elem ) {\n\t\t\tvar myGuider = guiders._guiderById( $( elem ).attr( 'id' ) );\n\t\t\tif ( myGuider.onHide ) {\n\t\t\t\tmyGuider.onHide( myGuider, next );\n\t\t\t}\n\t\t} );\n\t\tguiders._unWireClickOutside();\n\t\t// FIXME: Use CSS transition\n\t\t// eslint-disable-next-line no-jquery/no-fade\n\t\t$( '.guider' ).fadeOut( 'fast' );\n\t\tif ( omitHidingOverlay !== true ) {\n\t\t\tguiders._hideOverlay();\n\t\t}\n\t\treturn guiders;\n\t};\n\n\t/**\n\t * Show a guider\n\t *\n\t * @param {string} id id of guider to show.  The default is the last guider created.\n\t * @return {undefined|boolean|Object} Undefined in case of error, return value\n\t *   from the guider's onShow, if that is truthy, otherwise the guiders\n\t *   singleton.\n\t */\n\tguiders.show = function ( id ) {\n\t\tvar myGuider, showReturn, windowHeight, scrollHeight, guiderOffsetTop,\n\t\t\tguiderElemHeight, isGuiderBelow, isGuiderAbove, nextGuiderId,\n\t\t\tnextGuiderData, testInDom, stylePosition;\n\n\t\tif ( !id && guiders._lastCreatedGuiderID ) {\n\t\t\tid = guiders._lastCreatedGuiderID;\n\t\t}\n\n\t\ttry {\n\t\t\tmyGuider = guiders._guiderById( id );\n\t\t} catch ( err ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// You can use an onShow function to take some action before the guider is shown.\n\t\tif ( myGuider.onShow ) {\n\t\t\t// if onShow returns something, assume this means you want to bypass the\n\t\t\t//  rest of onShow.\n\t\t\tshowReturn = myGuider.onShow( myGuider );\n\t\t\tif ( showReturn ) {\n\t\t\t\treturn showReturn;\n\t\t\t}\n\t\t}\n\t\t// handle overlay\n\t\tif ( myGuider.overlay ) {\n\t\t\tguiders._showOverlay( myGuider.overlay );\n\t\t}\n\t\t// bind esc = close action\n\t\tif ( myGuider.closeOnEscape ) {\n\t\t\tguiders._wireEscape( myGuider );\n\t\t} else {\n\t\t\tguiders._unWireEscape( myGuider );\n\t\t}\n\n\t\tif ( myGuider.closeOnClickOutside ) {\n\t\t\tguiders._wireClickOutside( myGuider );\n\t\t}\n\n\t\tguiders._attach( myGuider );\n\t\tmyGuider.elem.fadeIn( 'fast' ).data( 'locked', false );\n\t\tguiders._currentGuiderID = id;\n\n\t\twindowHeight = guiders._windowHeight = $( window ).height();\n\t\tscrollHeight = $( window ).scrollTop();\n\n\t\t// .offset().top returns invalid value (0) when position: absolute\n\t\tstylePosition = myGuider.elem.css( 'position' ) ? myGuider.elem.css( 'position' ).toLowerCase() : '';\n\t\tguiderOffsetTop = stylePosition === 'absolute' ?\n\t\t\tparseFloat( myGuider.elem.css( 'top' ) || 0 ) : myGuider.elem.offset().top;\n\n\t\tguiderElemHeight = myGuider.elem.height();\n\t\tisGuiderBelow = ( scrollHeight + windowHeight < guiderOffsetTop + guiderElemHeight ); /* we will need to scroll down */\n\t\tisGuiderAbove = ( guiderOffsetTop < scrollHeight ); /* we will need to scroll up */\n\t\tif ( myGuider.autoFocus && ( isGuiderBelow || isGuiderAbove ) ) {\n\t\t\t// Sometimes the browser won't scroll if the person just clicked,\n\t\t\t// so let's do this in a setTimeout.\n\t\t\tguiders._removeAnimations( myGuider );\n\t\t\tsetTimeout( guiders.scrollToCurrent, 10 );\n\t\t}\n\n\t\t$( myGuider.elem ).trigger( 'guiders.show' );\n\t\t$( myGuider.elem ).find( '.mw-ui-progressive:first-child' ).trigger( 'focus' );\n\n\t\t// Create (preload) next guider if it hasn't been created\n\t\tnextGuiderId = myGuider.next || null;\n\t\tif ( nextGuiderId !== null && nextGuiderId !== '' ) {\n\t\t\tif ( ( nextGuiderData = guiders._guiderInits[ nextGuiderId ] ) ) {\n\t\t\t\t// Only attach if it exists and is :visible\n\t\t\t\ttestInDom = guiders._getAttachTarget( nextGuiderData );\n\t\t\t\tif ( testInDom !== null ) {\n\t\t\t\t\tguiders.createGuider( nextGuiderData );\n\t\t\t\t\tnextGuiderData = undefined;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn guiders;\n\t};\n\n\t/**\n\t * Scroll to the current guider\n\t */\n\tguiders.scrollToCurrent = function () {\n\t\tvar currentGuider, windowHeight, guiderOffset,\n\t\t\tguiderElemHeight, scrollToHeight;\n\n\t\tcurrentGuider = guiders._guiders[ guiders._currentGuiderID ];\n\t\tif ( typeof currentGuider === 'undefined' ) {\n\t\t\treturn;\n\t\t}\n\t\twindowHeight = guiders._windowHeight;\n\t\t// scrollHeight = $( window ).scrollTop();\n\t\tguiderOffset = currentGuider.elem.offset();\n\t\tguiderElemHeight = currentGuider.elem.height();\n\n\t\t// Scroll to the guider's position.\n\t\tscrollToHeight = Math.round( Math.max( guiderOffset.top + ( guiderElemHeight / 2 ) - ( windowHeight / 2 ), 0 ) );\n\t\t// Basic concept from https://github.com/yckart/jquery.scrollto.js/blob/master/jquery.scrollto.js\n\t\t$( 'html, body' ).animate( {\n\t\t\tscrollTop: scrollToHeight\n\t\t}, guiders._scrollDuration );\n\t};\n\n\t// Change the bubble position after browser gets resized\n\t_resizing = undefined;\n\t$( window ).on( 'resize', function () {\n\t\tif ( typeof ( _resizing ) !== 'undefined' ) {\n\t\t\tclearTimeout( _resizing ); // Prevents seizures\n\t\t}\n\t\t_resizing = setTimeout( function () {\n\t\t\t_resizing = undefined;\n\t\t\tif ( typeof ( guiders ) !== 'undefined' ) {\n\t\t\t\tguiders.reposition();\n\t\t\t}\n\t\t}, 20 );\n\t} );\n\n\t$( function () {\n\t\tguiders.reposition();\n\t} );\n\n\treturn guiders;\n} ).call( this );\n","usedDeprecatedRules":[]},{"filePath":"/src/repo/modules/tours/firstedit.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/modules/tours/firsteditve.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/modules/tours/onshow.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/modules/tours/test.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/src/repo/modules/tours/uprightdownleft.js","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/tests/qunit/ext.guidedTour.lib.tests.js","messages":[{"ruleId":"no-shadow","severity":1,"message":"'firstStepBuilder' is already declared in the upper scope on line 23 column 35.","line":986,"column":7,"nodeType":"Identifier","messageId":"noShadow","endLine":986,"endColumn":23},{"ruleId":"no-shadow","severity":1,"message":"'firstStep' is already declared in the upper scope on line 23 column 53.","line":987,"column":7,"nodeType":"Identifier","messageId":"noShadow","endLine":987,"endColumn":16},{"ruleId":"qunit/no-loose-assertions","severity":2,"message":"Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual.","line":991,"column":3,"nodeType":"CallExpression","messageId":"unexpectedLocalLooseAssertion","endLine":995,"endColumn":4},{"ruleId":"qunit/no-loose-assertions","severity":2,"message":"Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual.","line":996,"column":3,"nodeType":"CallExpression","messageId":"unexpectedLocalLooseAssertion","endLine":1001,"endColumn":4}],"suppressedMessages":[{"ruleId":"no-unused-vars","severity":2,"message":"'missingNameBuilder' is assigned a value but never used.","line":639,"column":9,"nodeType":"Identifier","messageId":"unusedVar","endLine":639,"endColumn":27,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'numericNameBuilder' is assigned a value but never used.","line":651,"column":9,"nodeType":"Identifier","messageId":"unusedVar","endLine":651,"endColumn":27,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'tour' is assigned a value but never used.","line":1160,"column":9,"nodeType":"Identifier","messageId":"unusedVar","endLine":1160,"endColumn":13,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'tour' is assigned a value but never used.","line":1169,"column":9,"nodeType":"Identifier","messageId":"unusedVar","endLine":1169,"endColumn":13,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'tour' is assigned a value but never used.","line":1178,"column":9,"nodeType":"Identifier","messageId":"unusedVar","endLine":1178,"endColumn":13,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":2,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"( function () {\n\t'use strict';\n\n\tvar originalVE;\n\t// Step specification as passed to the legacy defineTour method\n\tvar VALID_DEFINE_TOUR_STEP_SPEC = {\n\t\ttitlemsg: 'guidedtour-tour-test-callouts',\n\t\tdescriptionmsg: 'guidedtour-tour-test-portal-description',\n\t\tattachTo: '#n-portal a',\n\t\tposition: '3',\n\t\tbuttons: [ {\n\t\t\taction: 'next'\n\t\t} ]\n\t};\n\t// Step specification as used with the current builder API\n\tvar VALID_BUILDER_STEP_SPEC = {\n\t\tname: 'intro',\n\t\ttitlemsg: 'guidedtour-tour-test-intro-title',\n\t\tdescriptionmsg: 'guidedtour-tour-test-intro-description',\n\t\tposition: 'bottom',\n\t\tattachTo: '#ca-edit'\n\t};\n\tvar validTourBuilder, validTour, firstStepBuilder, firstStep, otherTourBuilder, otherTourStepBuilder;\n\n\tvar gt = mw.guidedTour;\n\tvar cookieConfig = gt.getCookieConfiguration();\n\tvar cookieName = cookieConfig.name;\n\tvar cookieParams = cookieConfig.parameters;\n\n\tfunction compareTypeAndMessage( errorConstructor, regexErrorMessage ) {\n\t\treturn function ( actualException ) {\n\t\t\treturn actualException instanceof errorConstructor &&\n\t\t\t\tregexErrorMessage.test( actualException );\n\t\t};\n\t}\n\n\tQUnit.module( 'ext.guidedTour.lib', QUnit.newMwEnvironment( {\n\t\tbeforeEach: function () {\n\t\t\toriginalVE = window.ve;\n\n\t\t\tvalidTourBuilder = new gt.TourBuilder( { name: 'placeholder' } );\n\t\t\tvalidTour = validTourBuilder.tour;\n\t\t\tfirstStepBuilder = validTourBuilder.firstStep( VALID_BUILDER_STEP_SPEC );\n\t\t\tfirstStep = firstStepBuilder.step;\n\n\t\t\totherTourBuilder = new gt.TourBuilder( {\n\t\t\t\tname: 'upload'\n\t\t\t} );\n\t\t\totherTourStepBuilder = otherTourBuilder.step( {\n\t\t\t\tname: 'filename',\n\t\t\t\tdescription: 'filename description'\n\t\t\t} );\n\n\t\t\tthis.stub( mw.libs.guiders, 'show' );\n\t\t},\n\t\tafterEach: function () {\n\t\t\twindow.ve = originalVE;\n\t\t}\n\t} ) );\n\n\tQUnit.test( 'makeTourId', function ( assert ) {\n\t\tassert.strictEqual(\n\t\t\tgt.makeTourId( {\n\t\t\t\tname: 'test',\n\t\t\t\tstep: 3\n\t\t\t} ),\n\t\t\t'gt-test-3',\n\t\t\t'Successful makeTourId call'\n\t\t);\n\n\t\tassert.strictEqual(\n\t\t\tgt.makeTourId( 'test' ),\n\t\t\tnull,\n\t\t\t'String input returns null'\n\t\t);\n\n\t\tassert.strictEqual(\n\t\t\tgt.makeTourId( null ),\n\t\t\tnull,\n\t\t\t'null input returns null'\n\t\t);\n\n\t\tassert.strictEqual(\n\t\t\tgt.makeTourId(),\n\t\t\tnull,\n\t\t\t'Missing parameter returns null'\n\t\t);\n\t} );\n\n\tQUnit.test( 'parseTourId', function ( assert ) {\n\t\tvar tourId = 'gt-test-2', expectedTourInfo;\n\t\texpectedTourInfo = {\n\t\t\tname: 'test',\n\t\t\tstep: '2'\n\t\t};\n\t\tassert.deepEqual(\n\t\t\tgt.parseTourId( tourId ),\n\t\t\texpectedTourInfo,\n\t\t\t'Simple tourId'\n\t\t);\n\t} );\n\n\tQUnit.test( 'isPage', function ( assert ) {\n\t\tvar PAGE_NAME_TO_SKIP = 'TestPage',\n\t\t\tOTHER_PAGE_NAME = 'WrongPage';\n\n\t\tmw.config.set( 'wgPageName', PAGE_NAME_TO_SKIP );\n\t\tassert.strictEqual(\n\t\t\tgt.isPage( PAGE_NAME_TO_SKIP ),\n\t\t\ttrue,\n\t\t\t'Page matches'\n\t\t);\n\n\t\tmw.config.set( 'wgPageName', OTHER_PAGE_NAME );\n\t\tassert.strictEqual(\n\t\t\tgt.isPage( PAGE_NAME_TO_SKIP ),\n\t\t\tfalse,\n\t\t\t'Page does match'\n\t\t);\n\t} );\n\n\tQUnit.test( 'hasQuery', function ( assert ) {\n\t\tvar paramMap,\n\t\t\tPAGE_NAME_TO_SKIP = 'RightPage',\n\t\t\tOTHER_PAGE_NAME = 'OtherPage';\n\n\t\tthis.sandbox.stub( mw.util, 'getParamValue', function ( param ) {\n\t\t\treturn paramMap[ param ];\n\t\t} );\n\n\t\tparamMap = { action: 'edit', debug: 'true' };\n\n\t\tassert.strictEqual(\n\t\t\tgt.hasQuery( { action: 'edit' } ),\n\t\t\ttrue,\n\t\t\t'Query matches, page name is undefined'\n\t\t);\n\t\tassert.strictEqual(\n\t\t\tgt.hasQuery( { action: 'edit' }, null ),\n\t\t\ttrue,\n\t\t\t'Query matches, page name is null'\n\t\t);\n\n\t\tmw.config.set( 'wgPageName', PAGE_NAME_TO_SKIP );\n\t\tassert.strictEqual(\n\t\t\tgt.hasQuery( { action: 'edit' }, PAGE_NAME_TO_SKIP ),\n\t\t\ttrue,\n\t\t\t'Query and page both match'\n\t\t);\n\n\t\tmw.config.set( 'wgPageName', OTHER_PAGE_NAME );\n\t\tassert.strictEqual(\n\t\t\tgt.hasQuery( { action: 'edit' }, PAGE_NAME_TO_SKIP ),\n\t\t\tfalse,\n\t\t\t'Query matches, but page does not' );\n\n\t\tparamMap = { debug: 'true', somethingElse: 'medium' };\n\n\t\tassert.strictEqual(\n\t\t\tgt.hasQuery( { action: 'edit' } ),\n\t\t\tfalse,\n\t\t\t'Query does not match, page is undefined'\n\t\t);\n\n\t\tmw.config.set( 'wgPageName', PAGE_NAME_TO_SKIP );\n\t\tassert.strictEqual(\n\t\t\tgt.hasQuery( { action: 'edit' }, PAGE_NAME_TO_SKIP ),\n\t\t\tfalse,\n\t\t\t'Query does not match, although page does'\n\t\t);\n\n\t\tmw.config.set( 'wgPageName', OTHER_PAGE_NAME );\n\t\tassert.strictEqual(\n\t\t\tgt.hasQuery( { action: 'edit' }, PAGE_NAME_TO_SKIP ),\n\t\t\tfalse,\n\t\t\t'Neither query nor page match'\n\t\t);\n\t} );\n\n\tQUnit.test( 'getStepFromQuery', function ( assert ) {\n\t\tvar step;\n\t\tthis.sandbox.stub( mw.util, 'getParamValue', function () {\n\t\t\treturn step;\n\t\t} );\n\n\t\tstep = 6;\n\t\tassert.strictEqual(\n\t\t\tgt.getStepFromQuery(),\n\t\t\tstep,\n\t\t\t'Step is returned correctly when present'\n\t\t);\n\n\t\tstep = null;\n\t\tassert.strictEqual(\n\t\t\tgt.getStepFromQuery(),\n\t\t\tstep,\n\t\t\t'Step is returned as null when not present'\n\t\t);\n\t} );\n\n\tQUnit.test( 'setTourCookie', function ( assert ) {\n\t\tvar firstTourName = 'foo';\n\t\tvar secondTourName = 'bar';\n\t\tvar numberStep = 5;\n\t\tvar stringStep = '3';\n\t\tvar oldCookieValue = mw.cookie.get( cookieName );\n\n\t\tfunction assertValidCookie( expectedName, expectedStep, message ) {\n\t\t\tvar cookieValue = mw.cookie.get( cookieName );\n\t\t\tvar userState = gt.internal.parseUserState( cookieValue );\n\n\t\t\tassert.strictEqual(\n\t\t\t\tuserState.tours[ expectedName ].step,\n\t\t\t\texpectedStep,\n\t\t\t\tmessage\n\t\t\t);\n\t\t}\n\n\t\tfunction clearCookie() {\n\t\t\tmw.cookie.set( cookieName, null, cookieParams );\n\t\t}\n\n\t\tgt.setTourCookie( firstTourName );\n\t\tassertValidCookie( firstTourName, '1', 'Step defaults to 1' );\n\t\tclearCookie();\n\n\t\tgt.setTourCookie( firstTourName, numberStep );\n\t\tassertValidCookie( firstTourName, String( numberStep ), 'setTourCookie accepts numeric step, which is converted to string' );\n\t\tclearCookie();\n\n\t\tgt.setTourCookie( firstTourName, stringStep );\n\t\tassertValidCookie( firstTourName, stringStep, 'setTourCookie accepts string step' );\n\n\t\tgt.setTourCookie( secondTourName, numberStep );\n\t\tassertValidCookie( firstTourName, stringStep, 'First tour is still remembered after second is stored' );\n\t\tassertValidCookie( secondTourName, String( numberStep ), 'Second tour is also remembered' );\n\n\t\tmw.cookie.set( cookieName, oldCookieValue, cookieParams );\n\t} );\n\n\tQUnit.test( 'shouldShow', function ( assert ) {\n\t\tvar visualEditorArgs = {\n\t\t\ttourName: 'visualeditorintro',\n\t\t\tuserState: {\n\t\t\t\tversion: 1,\n\t\t\t\ttours: {}\n\t\t\t},\n\t\t\tpageName: 'Page',\n\t\t\tarticleId: 123,\n\t\t\tcondition: 'VisualEditor'\n\t\t};\n\n\t\tvar wikitextArgs = {\n\t\t\ttourName: 'wikitextintro',\n\t\t\tuserState: {\n\t\t\t\tversion: 1,\n\t\t\t\ttours: {}\n\t\t\t},\n\t\t\tpageName: 'Page',\n\t\t\tarticleId: 123,\n\t\t\tcondition: 'wikitext'\n\t\t};\n\n\t\tvar mockOpenVE = {\n\t\t\tinstances: [ {} ]\n\t\t};\n\n\t\tassert.throws(\n\t\t\tfunction () {\n\t\t\t\treturn gt.shouldShowTour( {\n\t\t\t\t\ttourName: 'test',\n\t\t\t\t\tuserState: {\n\t\t\t\t\t\tversion: 1,\n\t\t\t\t\t\ttours: {\n\t\t\t\t\t\t\ttest: {\n\t\t\t\t\t\t\t\tstep: 1\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\tpageName: 'Foo',\n\t\t\t\t\tarticleId: 123,\n\t\t\t\t\tcondition: 'bogus'\n\t\t\t\t} );\n\t\t\t},\n\t\t\tcompareTypeAndMessage( gt.TourDefinitionError, /'bogus' is not a supported condition/ ),\n\t\t\t'gt.TourDefinitionError with correct error message for invalid condition'\n\t\t);\n\n\t\tassert.strictEqual(\n\t\t\tgt.shouldShowTour( {\n\t\t\t\ttourName: 'test',\n\t\t\t\tuserState: {\n\t\t\t\t\tversion: 1,\n\t\t\t\t\ttours: {\n\t\t\t\t\t\ttest: {\n\t\t\t\t\t\t\tfirstArticleId: 123,\n\t\t\t\t\t\t\tstep: 1\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tpageName: 'Foo',\n\t\t\t\tarticleId: 123,\n\t\t\t\tcondition: 'stickToFirstPage'\n\t\t\t} ),\n\t\t\ttrue,\n\t\t\t'Returns true for stickToFirstPage when on the original article'\n\t\t);\n\n\t\tassert.strictEqual(\n\t\t\tgt.shouldShowTour( {\n\t\t\t\ttourName: 'test',\n\t\t\t\tuserState: {\n\t\t\t\t\tversion: 1,\n\t\t\t\t\ttours: {\n\t\t\t\t\t\ttest: {\n\t\t\t\t\t\t\tfirstArticleId: 123,\n\t\t\t\t\t\t\tstep: 1\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tpageName: 'Foo',\n\t\t\t\tarticleId: 987,\n\t\t\t\tcondition: 'stickToFirstPage'\n\t\t\t} ),\n\t\t\tfalse,\n\t\t\t'Returns false for stickToFirstPage when on a different article'\n\t\t);\n\n\t\tassert.strictEqual(\n\t\t\tgt.shouldShowTour( {\n\t\t\t\ttourName: 'test',\n\t\t\t\tuserState: {\n\t\t\t\t\tversion: 1,\n\t\t\t\t\ttours: {\n\t\t\t\t\t\ttest: {\n\t\t\t\t\t\t\tstep: 1\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tpageName: 'Bar',\n\t\t\t\tarticleId: 123\n\t\t\t} ),\n\t\t\ttrue,\n\t\t\t'Returns true when there is no condition'\n\t\t);\n\n\t\tassert.strictEqual(\n\t\t\tgt.shouldShowTour( {\n\t\t\t\ttourName: 'test',\n\t\t\t\tuserState: {\n\t\t\t\t\tversion: 1,\n\t\t\t\t\ttours: {\n\t\t\t\t\t\ttest: {\n\t\t\t\t\t\t\tfirstArticleId: 234,\n\t\t\t\t\t\t\tstep: 1\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tpageName: 'Bar',\n\t\t\t\tarticleId: 123\n\t\t\t} ),\n\t\t\ttrue,\n\t\t\t'Returns true when there is no condition even when there is a non-matching article ID in the cookie'\n\t\t);\n\n\t\tassert.strictEqual(\n\t\t\tgt.shouldShowTour( {\n\t\t\t\ttourName: 'test',\n\t\t\t\tuserState: {\n\t\t\t\t\tversion: 1,\n\t\t\t\t\ttours: {\n\t\t\t\t\t\ttest: {},\n\t\t\t\t\t\tothertour: {\n\t\t\t\t\t\t\tfirstArticleId: 234,\n\t\t\t\t\t\t\tstep: 1\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tpageName: 'Bar',\n\t\t\t\tarticleId: 123\n\t\t\t} ),\n\t\t\ttrue,\n\t\t\t'Returns true when there is no condition even when there is a non-matching article ID in the cookie, for another tour'\n\t\t);\n\n\t\tassert.strictEqual(\n\t\t\tgt.shouldShowTour( {\n\t\t\t\ttourName: 'test',\n\t\t\t\tuserState: {\n\t\t\t\t\tversion: 1,\n\t\t\t\t\ttours: {\n\t\t\t\t\t\ttest: {\n\t\t\t\t\t\t\tfirstSpecialPageName: 'Special:ImportantTask',\n\t\t\t\t\t\t\tstep: 1\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tpageName: 'Special:ImportantTask',\n\t\t\t\tarticleId: 0,\n\t\t\t\tcondition: 'stickToFirstPage'\n\t\t\t} ),\n\t\t\ttrue,\n\t\t\t'Returns true for stickToFirstPage and matching special page'\n\t\t);\n\n\t\tassert.strictEqual(\n\t\t\tgt.shouldShowTour( {\n\t\t\t\ttourName: 'test',\n\t\t\t\tuserState: {\n\t\t\t\t\tversion: 1,\n\t\t\t\t\ttours: {\n\t\t\t\t\t\ttest: {\n\t\t\t\t\t\t\tfirstSpecialPageName: 'Special:ImportantTask',\n\t\t\t\t\t\t\tstep: 1\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tpageName: 'Special:OtherTask',\n\t\t\t\tarticleId: 0,\n\t\t\t\tcondition: 'stickToFirstPage'\n\t\t\t} ),\n\t\t\tfalse,\n\t\t\t'Returns false for stickToFirstPage and different special page'\n\t\t);\n\n\t\tassert.strictEqual(\n\t\t\tgt.shouldShowTour( {\n\t\t\t\ttourName: 'secondtour',\n\t\t\t\tuserState: {\n\t\t\t\t\tversion: 1,\n\t\t\t\t\ttours: {\n\t\t\t\t\t\tfirsttour: {\n\t\t\t\t\t\t\tfirstArticleId: 123,\n\t\t\t\t\t\t\tstep: 1\n\t\t\t\t\t\t},\n\t\t\t\t\t\tsecondtour: {\n\t\t\t\t\t\t\tfirstArticleId: 234,\n\t\t\t\t\t\t\tstep: 2\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tpageName: 'Foo',\n\t\t\t\tarticleId: 123,\n\t\t\t\tcondition: 'stickToFirstPage'\n\t\t\t} ),\n\t\t\tfalse,\n\t\t\t'Returns false for stickToFirstPage for non-matching article ID when another tour\\'s article ID matches'\n\t\t);\n\n\t\t// Mock the ve global, and its array of instances.\n\t\twindow.ve = mockOpenVE;\n\t\tassert.strictEqual(\n\t\t\tgt.shouldShowTour( visualEditorArgs ),\n\t\t\ttrue,\n\t\t\t'Returns true for VisualEditor condition when VisualEditor open'\n\t\t);\n\n\t\t// ve = undefined deliberately applies to all of the below until it is\n\t\t// reset to a mock instance for the expected false text\n\t\twindow.ve = undefined;\n\t\tmw.config.set( 'wgAction', 'view' );\n\t\tassert.strictEqual(\n\t\t\tgt.shouldShowTour( visualEditorArgs ),\n\t\t\ttrue,\n\t\t\t'Returns true for VisualEditor condition when viewing page with VE closed'\n\t\t);\n\n\t\tmw.config.set( 'wgAction', 'edit' );\n\t\tassert.strictEqual(\n\t\t\tgt.shouldShowTour( visualEditorArgs ),\n\t\t\tfalse,\n\t\t\t'Returns false for VisualEditor condition when in wikitext editor'\n\t\t);\n\n\t\tmw.config.set( 'wgAction', 'submit' );\n\t\tassert.strictEqual(\n\t\t\tgt.shouldShowTour( visualEditorArgs ),\n\t\t\tfalse,\n\t\t\t'Returns false for VisualEditor condition when reviewing wikitext changes'\n\t\t);\n\n\t\tmw.config.set( 'wgAction', 'edit' );\n\t\tassert.strictEqual(\n\t\t\tgt.shouldShowTour( wikitextArgs ),\n\t\t\ttrue,\n\t\t\t'Returns true for wikitext condition when editing wikitext'\n\t\t);\n\n\t\tmw.config.set( 'wgAction', 'submit' );\n\t\tassert.strictEqual(\n\t\t\tgt.shouldShowTour( wikitextArgs ),\n\t\t\ttrue,\n\t\t\t'Returns true for wikitext condition when reviewing wikitext'\n\t\t);\n\n\t\tmw.config.set( 'wgAction', 'view' );\n\t\tassert.strictEqual(\n\t\t\tgt.shouldShowTour( wikitextArgs ),\n\t\t\ttrue,\n\t\t\t'Returns true for wikitext condition when viewing page with VE closed'\n\t\t);\n\n\t\twindow.ve = mockOpenVE;\n\t\tmw.config.set( 'wgAction', 'view' );\n\t\tassert.strictEqual(\n\t\t\tgt.shouldShowTour( wikitextArgs ),\n\t\t\tfalse,\n\t\t\t'Returns false for wikitext condition when VisualEditor is open'\n\t\t);\n\n\t\tassert.strictEqual(\n\t\t\tgt.shouldShowTour( {\n\t\t\t\ttourName: 'firsttour',\n\t\t\t\tuserState: {\n\t\t\t\t\tversion: 1,\n\t\t\t\t\ttours: {\n\t\t\t\t\t\tfirsttour: {\n\t\t\t\t\t\t\tfirstSpecialPageName: 'Special:ImportantTask',\n\t\t\t\t\t\t\tstep: 1\n\t\t\t\t\t\t},\n\t\t\t\t\t\tsecondtour: {\n\t\t\t\t\t\t\tfirstSpecialPageName: 'Special:OtherTask',\n\t\t\t\t\t\t\tstep: 2\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tpageName: 'Special:OtherTask',\n\t\t\t\tarticleId: 0,\n\t\t\t\tcondition: 'stickToFirstPage'\n\t\t\t} ),\n\t\t\tfalse,\n\t\t\t'Returns false for non-matching article ID when another tour\\'s special page matches'\n\t\t);\n\t} );\n\n\tQUnit.test( 'defineTour', function ( assert ) {\n\t\tvar SPEC_MUST_BE_OBJECT = /Check your syntax. There must be exactly one argument, 'tourSpec', which must be an object\\./;\n\t\tvar NAME_MUST_BE_STRING = /'tourSpec.name' must be a string, the tour name\\./;\n\t\tvar STEPS_MUST_BE_ARRAY = /'tourSpec.steps' must be an array, a list of one or more steps/;\n\t\tvar VALID_TOUR_SPEC = {\n\t\t\tname: 'valid',\n\n\t\t\tsteps: [ {\n\t\t\t\ttitle: 'First step title',\n\t\t\t\tdescription: 'Second step title',\n\t\t\t\toverlay: true,\n\t\t\t\tbuttons: [ {\n\t\t\t\t\taction: 'next'\n\t\t\t\t} ]\n\t\t\t}, {\n\t\t\t\ttitle: 'Second step title',\n\t\t\t\tdescription: 'Second step description',\n\t\t\t\toverlay: true,\n\t\t\t\tbuttons: [ {\n\t\t\t\t\taction: 'end'\n\t\t\t\t} ]\n\t\t\t} ]\n\t\t};\n\n\t\t// Suppress warnings that defineTour is deprecated\n\t\tthis.suppressWarnings();\n\n\t\tassert.throws(\n\t\t\tfunction () {\n\t\t\t\treturn gt.defineTour();\n\t\t\t},\n\t\t\tcompareTypeAndMessage( gt.TourDefinitionError, SPEC_MUST_BE_OBJECT ),\n\t\t\t'gt.TourDefinitionError with correct error message for empty call'\n\t\t);\n\n\t\tassert.throws(\n\t\t\tfunction () {\n\t\t\t\treturn gt.defineTour( VALID_TOUR_SPEC, VALID_DEFINE_TOUR_STEP_SPEC );\n\t\t\t},\n\t\t\tcompareTypeAndMessage( gt.TourDefinitionError, SPEC_MUST_BE_OBJECT ),\n\t\t\t'gt.TourDefinitionError with correct error message for multiple parameters'\n\t\t);\n\n\t\tassert.throws(\n\t\t\tfunction () {\n\t\t\t\treturn gt.defineTour( null );\n\t\t\t},\n\t\t\tcompareTypeAndMessage( gt.TourDefinitionError, SPEC_MUST_BE_OBJECT ),\n\t\t\t'gt.TourDefinitionError with correct error message for null call'\n\t\t);\n\n\t\tassert.throws(\n\t\t\tfunction () {\n\t\t\t\treturn gt.defineTour( {\n\t\t\t\t\tsteps: [ VALID_DEFINE_TOUR_STEP_SPEC ]\n\t\t\t\t} );\n\t\t\t},\n\t\t\tcompareTypeAndMessage( gt.TourDefinitionError, NAME_MUST_BE_STRING ),\n\t\t\t'gt.TourDefinitionError with correct error message for missing name'\n\t\t);\n\n\t\tassert.throws(\n\t\t\tfunction () {\n\t\t\t\treturn gt.defineTour( {\n\t\t\t\t\tname: 'test',\n\t\t\t\t\tsteps: VALID_DEFINE_TOUR_STEP_SPEC\n\t\t\t\t} );\n\t\t\t},\n\t\t\tcompareTypeAndMessage( gt.TourDefinitionError, STEPS_MUST_BE_ARRAY ),\n\t\t\t'gt.TourDefinitionError with correct error message for object passed for steps'\n\t\t);\n\n\t\tassert.throws(\n\t\t\tfunction () {\n\t\t\t\treturn gt.defineTour( {\n\t\t\t\t\tname: 'test'\n\t\t\t\t} );\n\t\t\t},\n\t\t\tcompareTypeAndMessage( gt.TourDefinitionError, STEPS_MUST_BE_ARRAY ),\n\t\t\t'gt.TourDefinitionError with correct error message for missing steps'\n\t\t);\n\n\t\tassert.strictEqual(\n\t\t\tgt.defineTour( VALID_TOUR_SPEC ),\n\t\t\ttrue,\n\t\t\t'Valid tour is defined successfully'\n\t\t);\n\n\t\tthis.restoreWarnings();\n\t} );\n\n\tQUnit.test( 'StepBuilder.constructor', function ( assert ) {\n\t\tvar STEP_NAME_MUST_BE_STRING = /'stepSpec.name' must be a string, the step name/;\n\n\t\tassert.strictEqual(\n\t\t\tfirstStepBuilder.constructor,\n\t\t\tgt.StepBuilder,\n\t\t\t'Valid StepBuilder constructed in setup is constructed normally'\n\t\t);\n\n\t\tassert.throws(\n\t\t\tfunction () {\n\t\t\t\t// eslint-disable-next-line no-unused-vars\n\t\t\t\tvar missingNameBuilder = new gt.StepBuilder( validTour, {\n\t\t\t\t\tposition: 'bottom',\n\t\t\t\t\tattachTo: '#ca-edit'\n\t\t\t\t} );\n\t\t\t},\n\t\t\tcompareTypeAndMessage( gt.TourDefinitionError, STEP_NAME_MUST_BE_STRING ),\n\t\t\t'gt.TourDefinitionError when name is missing'\n\t\t);\n\n\t\tassert.throws(\n\t\t\tfunction () {\n\t\t\t\t// eslint-disable-next-line no-unused-vars\n\t\t\t\tvar numericNameBuilder = new gt.StepBuilder( validTour, {\n\t\t\t\t\tname: 1,\n\t\t\t\t\tposition: 'bottom',\n\t\t\t\t\tattachTo: '#ca-edit'\n\t\t\t\t} );\n\t\t\t},\n\t\t\tcompareTypeAndMessage( gt.TourDefinitionError, STEP_NAME_MUST_BE_STRING ),\n\t\t\t'gt.TourDefinitionError when name is a Number'\n\t\t);\n\t} );\n\n\tQUnit.test( 'StepBuilder.listenForMwHooks', function ( assert ) {\n\t\tvar listenForMwHookSpy = this.spy( firstStepBuilder.step, 'listenForMwHook' );\n\n\t\tfirstStepBuilder.listenForMwHooks();\n\t\tassert.strictEqual(\n\t\t\tlistenForMwHookSpy.callCount,\n\t\t\t0,\n\t\t\t'If no hook names are passed, step.listenForMwHook should not be called'\n\t\t);\n\n\t\tfirstStepBuilder.listenForMwHooks( 'StepBuilder.listenForMwHooks.happened' );\n\t\tassert.strictEqual(\n\t\t\tlistenForMwHookSpy.callCount,\n\t\t\t1,\n\t\t\t'step.listenMwHook should be called once if a single hook name is passed'\n\t\t);\n\t\tassert.assertTrue(\n\t\t\tlistenForMwHookSpy.calledWithExactly( 'StepBuilder.listenForMwHooks.happened' ),\n\t\t\t'step.listenMwHook should be called once with the correct hook name if a single hook name is passed'\n\t\t);\n\n\t\tlistenForMwHookSpy.reset();\n\t\tfirstStepBuilder.listenForMwHooks( 'StepBuilder.listenForMwHooks.one', 'StepBuilder.listenForMwHooks.another' );\n\t\tassert.strictEqual(\n\t\t\tlistenForMwHookSpy.callCount,\n\t\t\t2,\n\t\t\t'step.listenMwHook should be called twice if two hook names are passed'\n\t\t);\n\t\tassert.assertTrue(\n\t\t\tlistenForMwHookSpy.calledWithExactly( 'StepBuilder.listenForMwHooks.one' ),\n\t\t\t'step.listenMwHook should be called with the first hook name if multiple are passed'\n\t\t);\n\t\tassert.assertTrue(\n\t\t\tlistenForMwHookSpy.calledWithExactly( 'StepBuilder.listenForMwHooks.another' ),\n\t\t\t'step.listenMwHook should be called with the second hook name if multiple are passed'\n\t\t);\n\t} );\n\n\tQUnit.test( 'StepBuilder.next', function ( assert ) {\n\t\tvar VALUE_PASSED_NEXT_NOT_VALID_STEP = /Value passed to \\.next\\(\\) does not refer to a valid step/;\n\t\tvar CALLBACK_PASSED_NEXT_RETURNED_INVALID = /Callback passed to \\.next\\(\\) returned invalid value/;\n\n\t\tvar saveStepBuilder;\n\t\tfunction stepBuilderCallback() {\n\t\t\treturn saveStepBuilder;\n\t\t}\n\n\t\tvar linkStepBuilder = validTourBuilder.step( {\n\t\t\tname: 'link',\n\t\t\tdescription: 'link description'\n\t\t} );\n\n\t\tvar editStepBuilder = validTourBuilder.step( {\n\t\t\tname: 'edit',\n\t\t\tdescription: 'edit description'\n\t\t} );\n\n\t\tvar previewStepBuilder = validTourBuilder.step( {\n\t\t\tname: 'preview',\n\t\t\tdescription: 'preview description'\n\t\t} );\n\n\t\tsaveStepBuilder = validTourBuilder.step( {\n\t\t\tname: 'save',\n\t\t\tdescription: 'save description'\n\t\t} );\n\n\t\tvar pointsInvalidNameStepBuilder = validTourBuilder.step( {\n\t\t\tname: 'returnsToInvalidName',\n\t\t\tdescription: 'returnsToInvalidName description'\n\t\t} );\n\t\tpointsInvalidNameStepBuilder.next( 'bogus' );\n\t\tassert.throws(\n\t\t\tfunction () {\n\t\t\t\tpointsInvalidNameStepBuilder.step.nextCallback();\n\t\t\t},\n\t\t\tcompareTypeAndMessage( gt.TourDefinitionError, VALUE_PASSED_NEXT_NOT_VALID_STEP ),\n\t\t\t'nextCallback throws if an invalid (not present in current tour) step name was passed to next'\n\t\t);\n\n\t\tfirstStepBuilder.next( 'link' );\n\t\tassert.strictEqual(\n\t\t\tfirstStepBuilder.step.nextCallback(),\n\t\t\tlinkStepBuilder.step,\n\t\t\t'Registers a callback that returns the correct Step, given a step name'\n\t\t);\n\n\t\tassert.throws(\n\t\t\tfunction () {\n\t\t\t\tfirstStepBuilder.next( stepBuilderCallback );\n\t\t\t},\n\t\t\tcompareTypeAndMessage( gt.TourDefinitionError, /\\.next\\(\\) can not be called more than once per StepBuilder/ ),\n\t\t\t'Multiple calls should trigger an error'\n\t\t);\n\n\t\tvar pointsOtherTourStepBuilder = validTourBuilder.step( {\n\t\t\tname: 'returnsToOtherTour',\n\t\t\tdescription: 'returnsToOtherTour description'\n\t\t} );\n\t\tpointsOtherTourStepBuilder.next( otherTourStepBuilder );\n\t\tassert.throws(\n\t\t\tfunction () {\n\t\t\t\tpointsOtherTourStepBuilder.step.nextCallback();\n\t\t\t},\n\t\t\tcompareTypeAndMessage( gt.TourDefinitionError, VALUE_PASSED_NEXT_NOT_VALID_STEP ),\n\t\t\t'nextCallback throws if a StepBuilder from a different Tour was passed to next'\n\t\t);\n\n\t\tlinkStepBuilder.next( editStepBuilder );\n\t\tassert.strictEqual(\n\t\t\tlinkStepBuilder.step.nextCallback(),\n\t\t\teditStepBuilder.step,\n\t\t\t'Registers a callback that returns the correct Step, given a StepBuilder'\n\t\t);\n\n\t\tvar returnsInvalidNameCallbackStepBuilder = validTourBuilder.step( {\n\t\t\tname: 'returnsInvalidNameCallback',\n\t\t\tdescription: 'returnsInvalidNameCallback description'\n\t\t} );\n\t\treturnsInvalidNameCallbackStepBuilder.next( function () {\n\t\t\treturn 'bogus';\n\t\t} );\n\t\tassert.throws(\n\t\t\tfunction () {\n\t\t\t\treturnsInvalidNameCallbackStepBuilder.step.nextCallback();\n\t\t\t},\n\t\t\tcompareTypeAndMessage( gt.TourDefinitionError, CALLBACK_PASSED_NEXT_RETURNED_INVALID ),\n\t\t\t'nextCallback throws if a callback that returns an invalid step name was passed to next'\n\t\t);\n\n\t\teditStepBuilder.next( function () {\n\t\t\treturn 'preview';\n\t\t} );\n\t\tassert.strictEqual(\n\t\t\teditStepBuilder.step.nextCallback(),\n\t\t\tpreviewStepBuilder.step,\n\t\t\t'Registers a callback that returns the correct Step, given a callback returning a step name'\n\t\t);\n\n\t\tvar returnsOtherTourCallbackStepBuilder = validTourBuilder.step( {\n\t\t\tname: 'returnsToOtherTourCallback',\n\t\t\tdescription: 'returnsToOtherTourCallback description'\n\t\t} );\n\t\treturnsOtherTourCallbackStepBuilder.next( function () {\n\t\t\treturn otherTourStepBuilder;\n\t\t} );\n\t\tassert.throws(\n\t\t\tfunction () {\n\t\t\t\treturnsOtherTourCallbackStepBuilder.step.nextCallback();\n\t\t\t},\n\t\t\tcompareTypeAndMessage( gt.TourDefinitionError, CALLBACK_PASSED_NEXT_RETURNED_INVALID ),\n\t\t\t'nextCallback throws if a callback that returns a StepBuilder from a different Tour was passed to next'\n\t\t);\n\n\t\tpreviewStepBuilder.next( stepBuilderCallback );\n\t\tassert.strictEqual(\n\t\t\tpreviewStepBuilder.step.nextCallback(),\n\t\t\tsaveStepBuilder.step,\n\t\t\t'Registers a callback that returns the correct Step, given a callback returning a StepBuilder'\n\t\t);\n\t} );\n\n\tQUnit.test( 'StepBuilder.transition', function ( assert ) {\n\t\tvar CALLBACK_PASSED_TRANSITION_RETURNED_INVALID = /Callback passed to \\.transition\\(\\) returned invalid value/;\n\n\t\tvar linkStepBuilder = validTourBuilder.step( {\n\t\t\tname: 'link',\n\t\t\tdescription: 'link description'\n\t\t} );\n\t\tfirstStepBuilder.transition( function () {\n\t\t\treturn linkStepBuilder;\n\t\t} );\n\t\tassert.strictEqual(\n\t\t\tfirstStepBuilder.step.transitionCallback(),\n\t\t\tlinkStepBuilder.step,\n\t\t\t'Registers a callback that returns the correct Step, given a callback returning a StepBuilder'\n\t\t);\n\n\t\tvar editStepBuilder = validTourBuilder.step( {\n\t\t\tname: 'edit',\n\t\t\tdescription: 'edit description'\n\t\t} );\n\t\tlinkStepBuilder.transition( function () {\n\t\t\treturn 'edit';\n\t\t} );\n\t\tassert.strictEqual(\n\t\t\tlinkStepBuilder.step.transitionCallback(),\n\t\t\teditStepBuilder.step,\n\t\t\t'Registers a callback that returns the correct Step, given a callback returning a step name'\n\t\t);\n\n\t\teditStepBuilder.transition( function () {\n\t\t\treturn gt.TransitionAction.HIDE;\n\t\t} );\n\t\tassert.strictEqual(\n\t\t\teditStepBuilder.step.transitionCallback(),\n\t\t\tgt.TransitionAction.HIDE,\n\t\t\t'Valid TransitionAction (HIDE) is preserved'\n\t\t);\n\n\t\tvar previewStepBuilder = validTourBuilder.step( {\n\t\t\tname: 'preview',\n\t\t\tdescription: 'preview description'\n\t\t} );\n\t\tpreviewStepBuilder.transition( function () {} );\n\t\tassert.strictEqual(\n\t\t\tpreviewStepBuilder.step.transitionCallback(),\n\t\t\tpreviewStepBuilder.step,\n\t\t\t'Callback without an explicit return value is treated as returning the current step'\n\t\t);\n\n\t\tvar returnsInvalidNameCallbackStepBuilder = validTourBuilder.step( {\n\t\t\tname: 'returnsInvalidNameCallback',\n\t\t\tdescription: 'returnsInvalidNameCallback description'\n\t\t} );\n\t\treturnsInvalidNameCallbackStepBuilder.transition( function () {\n\t\t\treturn 'bogus';\n\t\t} );\n\t\tassert.throws(\n\t\t\tfunction () {\n\t\t\t\treturnsInvalidNameCallbackStepBuilder.step.transitionCallback();\n\t\t\t},\n\t\t\tcompareTypeAndMessage( gt.TourDefinitionError, CALLBACK_PASSED_TRANSITION_RETURNED_INVALID ),\n\t\t\t'transitionCallback throws if a callback that returns an invalid step name was passed to transition'\n\t\t);\n\n\t\tvar returnsOtherTourCallbackStepBuilder = validTourBuilder.step( {\n\t\t\tname: 'returnsToOtherTourCallback',\n\t\t\tdescription: 'returnsToOtherTourCallback description'\n\t\t} );\n\t\treturnsOtherTourCallbackStepBuilder.transition( function () {\n\t\t\treturn otherTourStepBuilder;\n\t\t} );\n\t\tassert.throws(\n\t\t\tfunction () {\n\t\t\t\treturnsOtherTourCallbackStepBuilder.step.transitionCallback();\n\t\t\t},\n\t\t\tcompareTypeAndMessage( gt.TourDefinitionError, CALLBACK_PASSED_TRANSITION_RETURNED_INVALID ),\n\t\t\t'transitionCallback throws if a callback that returns a StepBuilder from a different Tour was passed to transition'\n\t\t);\n\n\t\tvar returnsInvalidTransitionActionStepBuilder = validTourBuilder.step( {\n\t\t\tname: 'returnsInvalidTransitionAction',\n\t\t\tdescription: 'returnsInvalidTransitionAction description'\n\t\t} );\n\t\treturnsInvalidTransitionActionStepBuilder.transition( function () {\n\t\t\treturn 3;\n\t\t} );\n\t\tassert.throws(\n\t\t\tfunction () {\n\t\t\t\treturnsInvalidTransitionActionStepBuilder.step.transitionCallback();\n\t\t\t},\n\t\t\tcompareTypeAndMessage( gt.TourDefinitionError, /Callback passed to \\.transition\\(\\) returned a number that is not a valid TransitionAction/ ),\n\t\t\t'transitionCallback throws if a callback returns a number that is not a valid TransitionAction'\n\t\t);\n\n\t\tassert.throws(\n\t\t\tfunction () {\n\t\t\t\tfirstStepBuilder.transition( function () {\n\t\t\t\t\treturn editStepBuilder;\n\t\t\t\t} );\n\t\t\t},\n\t\t\tcompareTypeAndMessage( gt.TourDefinitionError, /\\.transition\\(\\) can not be called more than once per StepBuilder/ ),\n\t\t\t'Multiple calls should trigger an error'\n\t\t);\n\n\t\tvar parameterNotFunctionStepBuilder = validTourBuilder.step( {\n\t\t\tname: 'parameterNotFunctionStepBuilder',\n\t\t\tdescription: 'parameterNotFunctionStepBuilder description'\n\t\t} );\n\t\tassert.throws(\n\t\t\tfunction () {\n\t\t\t\tparameterNotFunctionStepBuilder.transition( linkStepBuilder );\n\t\t\t},\n\t\t\tcompareTypeAndMessage( gt.TourDefinitionError, /\\.transition\\(\\) takes one argument, a function/ ),\n\t\t\t'callback is not a function'\n\t\t);\n\t} );\n\n\tQUnit.test( 'Step.constructor', function ( assert ) {\n\t\tvar step = new gt.Step( validTour, {\n\t\t\tname: 'first',\n\t\t\tdescription: 'first description'\n\t\t} );\n\n\t\tassert.strictEqual(\n\t\t\tstep.tour,\n\t\t\tvalidTour,\n\t\t\t'Step is associated with its tour'\n\t\t);\n\n\t\tassert.strictEqual(\n\t\t\tstep.specification.id,\n\t\t\t'gt-placeholder-first',\n\t\t\t'Step ID is correct'\n\t\t);\n\n\t\tassert.throws(\n\t\t\tfunction () {\n\t\t\t\tstep.nextCallback();\n\t\t\t},\n\t\t\tcompareTypeAndMessage( gt.TourDefinitionError, /action: \"next\" used without calling \\.next\\(\\) when building step/ ),\n\t\t\t'Error is flagged if Step is constructed, the nextCallback is used without calling .next() on builder'\n\t\t);\n\n\t\tassert.strictEqual(\n\t\t\tstep.transitionCallback(),\n\t\t\tstep,\n\t\t\t'By default, the transition callback returns the step it was called on'\n\t\t);\n\t} );\n\n\tQUnit.test( 'Step.getButtons', function ( assert ) {\n\t\tvar buttons = [\n\t\t\t{ type: 'destructive' },\n\t\t\t{ type: [ 'progressive', 'quiet' ] },\n\t\t\t{ action: 'wikiLink', type: 'progressive' },\n\t\t\t{ action: 'externalLink' },\n\t\t\t{ action: 'back' },\n\t\t\t{ action: 'okay', onclick: function () {} },\n\t\t\t{ action: 'next' }\n\t\t];\n\t\tvar spy = this.spy( gt.Step.prototype, 'getButtons' );\n\t\tvar tourBuilder = new gt.TourBuilder( { name: 'buttonsTest' } );\n\t\tvar firstStepBuilder = tourBuilder.firstStep( $.extend( true, {}, { buttons: buttons }, VALID_BUILDER_STEP_SPEC ) );\n\t\tvar firstStep = firstStepBuilder.step;\n\n\t\ttourBuilder.tour.showStep( firstStep );\n\t\tvar returnedButtons = spy.lastCall.args[ 0 ].buttons;\n\t\tassert.ok(\n\t\t\treturnedButtons[ 0 ].html.class.indexOf( 'mw-ui-destructive' ) !== -1 &&\n\t\t\treturnedButtons[ 0 ].html.class.indexOf( 'mw-ui-button' ) !== -1,\n\t\t\t'Destructive custom button'\n\t\t);\n\t\tassert.ok(\n\t\t\treturnedButtons[ 1 ].html.class.indexOf( 'mw-ui-button' ) !== -1 &&\n\t\t\treturnedButtons[ 1 ].html.class.indexOf( 'mw-ui-progressive' ) !== -1 &&\n\t\t\treturnedButtons[ 1 ].html.class.indexOf( 'mw-ui-quiet' ) !== -1,\n\t\t\t'A quietly progressive custom button'\n\t\t);\n\t\tassert.notStrictEqual(\n\t\t\treturnedButtons[ 2 ].html.class.indexOf( 'mw-ui-progressive' ),\n\t\t\t-1,\n\t\t\t'Progressive internal link'\n\t\t);\n\t\tassert.strictEqual(\n\t\t\treturnedButtons[ 3 ].html.class.indexOf( 'mw-ui-progressive' ),\n\t\t\t-1,\n\t\t\t'External link button is not progressive by default'\n\t\t);\n\t\tassert.strictEqual(\n\t\t\treturnedButtons[ 4 ].html.class.indexOf( 'mw-ui-progressive' ),\n\t\t\t-1,\n\t\t\t'Back button is not progressive by default'\n\t\t);\n\t\tassert.notStrictEqual(\n\t\t\treturnedButtons[ 5 ].html.class.indexOf( 'mw-ui-progressive' ),\n\t\t\t-1,\n\t\t\t'Okay button is progressive by default'\n\t\t);\n\t\tassert.notStrictEqual(\n\t\t\treturnedButtons[ 6 ].html.class.indexOf( 'mw-ui-progressive' ),\n\t\t\t-1,\n\t\t\t'Next button is progressive by default'\n\t\t);\n\t} );\n\n\tQUnit.test( 'Step.registerMwHookListener', function ( assert ) {\n\t\tvar step = firstStepBuilder.step,\n\t\t\tHOOK_NAME = 'Step.registerMwHookListener.happened',\n\t\t\t// This lets us verify checkTransition is called (and which\n\t\t\t// arguments), but ignore any further behavior (e.g. showing\n\t\t\t// a step and TRANSITION_BEFORE_SHOW)\n\t\t\tcheckTransitionStub, actualTransitionEvent, expectedTransitionEvent;\n\n\t\tcheckTransitionStub = this.stub( step, 'checkTransition' ).returns( null );\n\n\t\tmw.hook( HOOK_NAME ).fire( 'first', 1 );\n\t\tstep.registerMwHookListener( HOOK_NAME );\n\n\t\tassert.strictEqual(\n\t\t\tcheckTransitionStub.callCount,\n\t\t\t0,\n\t\t\t'Memory firing should be ignored'\n\t\t);\n\n\t\tmw.hook( HOOK_NAME ).fire( 'second', 2 );\n\t\tassert.strictEqual(\n\t\t\tcheckTransitionStub.callCount,\n\t\t\t1,\n\t\t\t'checkTransition should be called exactly once when there is a single mw.hook firing'\n\t\t);\n\n\t\tactualTransitionEvent = checkTransitionStub.lastCall.args[ 0 ];\n\t\texpectedTransitionEvent = new gt.TransitionEvent();\n\t\texpectedTransitionEvent.type = gt.TransitionEvent.MW_HOOK;\n\t\texpectedTransitionEvent.hookName = HOOK_NAME;\n\t\texpectedTransitionEvent.hookArguments = [ 'second', 2 ];\n\n\t\tassert.deepEqual(\n\t\t\tactualTransitionEvent,\n\t\t\texpectedTransitionEvent,\n\t\t\t'checkTransition should be called with the right TransitionEvent'\n\t\t);\n\n\t\tcheckTransitionStub.reset();\n\t\tmw.hook( 'Step.registerMwHookListener.otherHook' ).fire( 'third', 3 );\n\t\tassert.strictEqual(\n\t\t\tcheckTransitionStub.callCount,\n\t\t\t0,\n\t\t\t'checkTransition should not be called for hooks that were not registered'\n\t\t);\n\t} );\n\n\tQUnit.test( 'Step.registerMwHooks', function ( assert ) {\n\t\tvar step = firstStepBuilder.step,\n\t\t\tregisterMwHookListenerSpy;\n\t\tregisterMwHookListenerSpy = this.spy( step, 'registerMwHookListener' );\n\n\t\tstep.listenForMwHook( 'Step.registerMwHooks.something' );\n\t\tstep.listenForMwHook( 'Step.registerMwHooks.another' );\n\n\t\tstep.registerMwHooks();\n\t\tassert.strictEqual(\n\t\t\tregisterMwHookListenerSpy.callCount,\n\t\t\t2,\n\t\t\t'registerMwHookListener called once for each hook the step is listening for'\n\t\t);\n\n\t\tassert.assertTrue(\n\t\t\tregisterMwHookListenerSpy.calledWithExactly( 'Step.registerMwHooks.something' ),\n\t\t\t'registerMwHookListener called with the first hook that is being listened for'\n\t\t);\n\n\t\tassert.assertTrue(\n\t\t\tregisterMwHookListenerSpy.calledWithExactly( 'Step.registerMwHooks.another' ),\n\t\t\t'registerMwHookListener called with the second hook that is being listened for'\n\t\t);\n\t} );\n\n\tQUnit.test( 'Step.handleOnShow', function ( assert ) {\n\t\tvar showChangesStepBuilder = validTourBuilder.step( {\n\t\t\tname: 'showChanges',\n\t\t\tdescription: 'showChanges description'\n\t\t} );\n\t\tvar showChangesStep = showChangesStepBuilder.step;\n\t\tvar singlePageTourBuilder = new gt.TourBuilder( {\n\t\t\tname: 'singlePage',\n\t\t\tisSinglePage: true\n\t\t} );\n\t\tvar singlePageStepBuilder = singlePageTourBuilder.step( {\n\t\t\tname: 'beginning',\n\t\t\tdescription: 'beginning description'\n\t\t} );\n\t\tvar singlePageStep = singlePageStepBuilder.step;\n\t\tvar updateUserStateSpy = this.spy( gt, 'updateUserStateForTour' );\n\t\tvar unregisterSpy = this.spy( gt.Step.prototype, 'unregisterMwHooks' );\n\n\t\tfirstStep.handleOnShow( { id: firstStep.specification.id, elem: $() } );\n\t\tassert.strictEqual(\n\t\t\tunregisterSpy.callCount,\n\t\t\t0,\n\t\t\t'unregisterMwHooks is not called when the first step is shown'\n\t\t);\n\t\tassert.deepEqual(\n\t\t\tupdateUserStateSpy.callCount,\n\t\t\t1,\n\t\t\t'For a regular (isSinglePage false) tour, updateUserStateForTour is called'\n\t\t);\n\t\tassert.strictEqual(\n\t\t\tvalidTour.currentStep,\n\t\t\tfirstStep,\n\t\t\t'currentStep is set after handleShow'\n\t\t);\n\n\t\tunregisterSpy.reset();\n\t\tshowChangesStep.handleOnShow( { id: showChangesStep.specification.id, elem: $() } );\n\t\tassert.strictEqual(\n\t\t\tunregisterSpy.thisValues[ 0 ],\n\t\t\tfirstStep,\n\t\t\t'mw.hook listeners for prior current step are unregistered'\n\t\t);\n\n\t\tupdateUserStateSpy.reset();\n\t\tsinglePageStep.handleOnShow( { id: singlePageStep.specification.id, elem: $() } );\n\t\tassert.strictEqual(\n\t\t\tupdateUserStateSpy.callCount,\n\t\t\t0,\n\t\t\t'For an isSinglePage true tour, updateUserStateForTour is never called'\n\t\t);\n\t} );\n\n\tQUnit.test( 'TourBuilder.constructor', function ( assert ) {\n\t\tvar CHECK_YOUR_SYNTAX = /Check your syntax. There must be exactly one argument, 'tourSpec', which must be an object/;\n\n\t\tassert.throws(\n\t\t\tfunction () {\n\t\t\t\t// eslint-disable-next-line no-unused-vars\n\t\t\t\tvar tour = new gt.TourBuilder();\n\t\t\t},\n\t\t\tcompareTypeAndMessage( gt.TourDefinitionError, CHECK_YOUR_SYNTAX ),\n\t\t\t'Throws if no tour specification is passed'\n\t\t);\n\n\t\tassert.throws(\n\t\t\tfunction () {\n\t\t\t\t// eslint-disable-next-line no-unused-vars\n\t\t\t\tvar tour = new gt.TourBuilder( 'test' );\n\t\t\t},\n\t\t\tcompareTypeAndMessage( gt.TourDefinitionError, CHECK_YOUR_SYNTAX ),\n\t\t\t'Throws if the tour specification is not an object'\n\t\t);\n\n\t\tassert.throws(\n\t\t\tfunction () {\n\t\t\t\t// eslint-disable-next-line no-unused-vars\n\t\t\t\tvar tour = new gt.TourBuilder( {\n\t\t\t\t\ttourName: 'test'\n\t\t\t\t} );\n\t\t\t},\n\t\t\tcompareTypeAndMessage( gt.TourDefinitionError, /'tourSpec.name' must be a string, the tour name/ )\n\t\t);\n\n\t\tassert.strictEqual(\n\t\t\tvalidTourBuilder.constructor,\n\t\t\tgt.TourBuilder,\n\t\t\t'Valid TourBuilder constructed in setup is constructed normally'\n\t\t);\n\t} );\n\n\tQUnit.test( 'TourBuilder.step', function ( assert ) {\n\t\tvalidTourBuilder.step( {\n\t\t\tname: 'preview',\n\t\t\tdescription: 'preview description'\n\t\t} );\n\n\t\tvalidTourBuilder.step( {\n\t\t\tname: 'save',\n\t\t\tdescription: 'save description'\n\t\t} );\n\n\t\tassert.strictEqual(\n\t\t\tvalidTour.stepCount,\n\t\t\t3,\n\t\t\t'stepCount is correct after multiple calls'\n\t\t);\n\n\t\tassert.throws(\n\t\t\tfunction () {\n\t\t\t\tvalidTourBuilder.step( {\n\t\t\t\t\tname: 'save',\n\t\t\t\t\tdescription: 'save description'\n\t\t\t\t} );\n\t\t\t},\n\t\t\tcompareTypeAndMessage( gt.TourDefinitionError, /The name \"save\" is already taken\\. {2}Two steps in a tour can not have the same name/ ),\n\t\t\t'Step cname can not repeat'\n\t\t);\n\t} );\n\n\tQUnit.test( 'TourBuilder.firstStep', function ( assert ) {\n\t\tvar previewStepSpec = $.extend( {}, VALID_BUILDER_STEP_SPEC, { name: 'preview' } );\n\n\t\tassert.throws(\n\t\t\tfunction () {\n\t\t\t\tvalidTourBuilder.firstStep( previewStepSpec );\n\t\t\t},\n\t\t\tcompareTypeAndMessage( gt.TourDefinitionError, /You can only specify one first step/ ),\n\t\t\t'Verify that TourBuilder.first can call once per candidate'\n\t\t);\n\t} );\n\n\tQUnit.test( 'Tour.constructor', function ( assert ) {\n\t\tvar tour = new gt.Tour( {\n\t\t\tname: 'addImage'\n\t\t} );\n\n\t\tassert.strictEqual(\n\t\t\tgt.internal.definedTours[ tour.name ],\n\t\t\ttour,\n\t\t\t'Tour is defined in internal list after constructor'\n\t\t);\n\t} );\n\n\tQUnit.test( 'Tour.getShouldFlipHorizontally', function ( assert ) {\n\t\t// Full coverage of all code paths\n\t\tvar EXTENSION_NAME = 'extension';\n\t\tvar ONWIKI_NAME = 'onwiki';\n\n\t\tvar getStateStub = this.stub( mw.loader, 'getState' );\n\t\tgetStateStub.withArgs( gt.internal.getTourModuleName( ONWIKI_NAME ) )\n\t\t\t.returns( null );\n\n\t\tgetStateStub.withArgs( gt.internal.getTourModuleName( EXTENSION_NAME ) )\n\t\t\t.returns( 'loaded' );\n\n\t\tvar extensionTour = new gt.Tour( {\n\t\t\tname: EXTENSION_NAME\n\t\t} );\n\n\t\tvar onwikiTour = new gt.Tour( {\n\t\t\tname: ONWIKI_NAME\n\t\t} );\n\n\t\t// There are two different directionalities\n\t\t// * Site as a whole (sitedir- class)\n\t\t// * User interface (html[dir])\n\t\t//\n\t\t// On wiki tours use the site language as their tour direction.\n\t\t// Extension tours don't care about site language; tour direction is ltr\n\t\t// Should flip if interface direction is different from tour direction\n\n\t\tassert.strictEqual(\n\t\t\textensionTour.getShouldFlipHorizontally( 'ltr', 'ltr' ),\n\t\t\tfalse,\n\t\t\t'No flip for extension tour when interface language and site language are both ltr'\n\t\t);\n\t\tassert.strictEqual(\n\t\t\tonwikiTour.getShouldFlipHorizontally( 'ltr', 'ltr' ),\n\t\t\tfalse,\n\t\t\t'No flip for onwiki tour when interface language and site language are both ltr'\n\t\t);\n\n\t\tassert.strictEqual(\n\t\t\textensionTour.getShouldFlipHorizontally( 'rtl', 'ltr' ),\n\t\t\ttrue,\n\t\t\t'Flip for extension tour when interface language is rtl and site language is ltr'\n\t\t);\n\t\tassert.strictEqual(\n\t\t\tonwikiTour.getShouldFlipHorizontally( 'rtl', 'ltr' ),\n\t\t\ttrue,\n\t\t\t'Flip for onwiki tour when interface language is rtl and site language is ltr'\n\t\t);\n\n\t\tassert.strictEqual(\n\t\t\textensionTour.getShouldFlipHorizontally( 'ltr', 'rtl' ),\n\t\t\tfalse,\n\t\t\t'No flip for extension tour when interface language is ltr and site language is rtl'\n\t\t);\n\t\tassert.strictEqual(\n\t\t\tonwikiTour.getShouldFlipHorizontally( 'ltr', 'rtl' ),\n\t\t\ttrue,\n\t\t\t'Flip for onwiki tour when interface language is ltr and site language is rtl'\n\t\t);\n\n\t\tassert.strictEqual(\n\t\t\textensionTour.getShouldFlipHorizontally( 'rtl', 'rtl' ),\n\t\t\ttrue,\n\t\t\t'Flip for extension tour when interface language is rtl and site language is rtl'\n\t\t);\n\t\tassert.strictEqual(\n\t\t\tonwikiTour.getShouldFlipHorizontally( 'rtl', 'rtl' ),\n\t\t\tfalse,\n\t\t\t'No flip for onwiki tour when interface language and site language are both rtl'\n\t\t);\n\t} );\n\n\tQUnit.test( 'Tour.initialize', function ( assert ) {\n\t\tvar stepInitializeSpy = this.spy( gt.Step.prototype, 'initialize' );\n\n\t\tvar previewStepBuilder = validTourBuilder.step( {\n\t\t\tname: 'preview',\n\t\t\tdescription: 'preview description'\n\t\t} );\n\n\t\tvar done = assert.async();\n\n\t\tvalidTour.initialize().done( function () {\n\t\t\tassert.assertTrue(\n\t\t\t\tstepInitializeSpy.calledOn( firstStep ),\n\t\t\t\t'Initializing tour first time initializes first step'\n\t\t\t);\n\n\t\t\tassert.assertTrue(\n\t\t\t\tstepInitializeSpy.calledOn( previewStepBuilder.step ),\n\t\t\t\t'Initializing tour first time initializes other steps'\n\t\t\t);\n\n\t\t\tstepInitializeSpy.reset();\n\n\t\t\tvalidTour.initialize().done( function () {\n\t\t\t\tassert.strictEqual(\n\t\t\t\t\tstepInitializeSpy.callCount,\n\t\t\t\t\t0,\n\t\t\t\t\t'Steps are not reinitialized if Tour.initialize is called again'\n\t\t\t\t);\n\n\t\t\t\tdone();\n\t\t\t} );\n\t\t} );\n\t} );\n\n\tQUnit.test( 'Tour.getStep', function ( assert ) {\n\t\tassert.strictEqual(\n\t\t\tvalidTour.getStep( 'intro' ),\n\t\t\tfirstStep,\n\t\t\t'getStep can find step by name'\n\t\t);\n\n\t\tassert.strictEqual(\n\t\t\tvalidTour.getStep( firstStep ),\n\t\t\tfirstStep,\n\t\t\t'getStep can validate that a Step belongs to the tour and return it'\n\t\t);\n\n\t\tassert.throws(\n\t\t\tfunction () {\n\t\t\t\tvalidTour.getStep( 'bogus' );\n\t\t\t},\n\t\t\tcompareTypeAndMessage( gt.IllegalArgumentError, /Step \"bogus\" not found in the \"placeholder\" tour/ ),\n\t\t\t'Throws if a step name is not found in the tour'\n\t\t);\n\n\t\tassert.throws(\n\t\t\tfunction () {\n\t\t\t\tvalidTour.getStep( otherTourStepBuilder.step );\n\t\t\t},\n\t\t\tcompareTypeAndMessage( gt.IllegalArgumentError, /Step object must belong to this tour \\(\"placeholder\"\\)/ ),\n\t\t\t'Throws if a step object does not belong to the tour'\n\t\t);\n\t} );\n\n\tQUnit.test( 'Tour.showStep', function ( assert ) {\n\t\tvar checkTransitionSpy = this.spy( firstStep, 'checkTransition' );\n\n\t\tvalidTour.showStep( firstStep );\n\n\t\tvar expectedTransitionEvent = new gt.TransitionEvent();\n\t\texpectedTransitionEvent.type = gt.TransitionEvent.BUILTIN;\n\t\texpectedTransitionEvent.subtype = gt.TransitionEvent.TRANSITION_BEFORE_SHOW;\n\n\t\treturn validTour.initialize().then( function () {\n\t\t\tvar actualTransitionEvent = checkTransitionSpy.lastCall.args[ 0 ];\n\n\t\t\tassert.deepEqual(\n\t\t\t\tactualTransitionEvent,\n\t\t\t\texpectedTransitionEvent,\n\t\t\t\t'Calls checkTransition with expected event'\n\t\t\t);\n\t\t} );\n\t} );\n\n\tQUnit.test( 'Tour.start', function ( assert ) {\n\t\tvar tourBuilder = new gt.TourBuilder( {\n\t\t\tname: 'reference'\n\t\t} );\n\t\tassert.throws(\n\t\t\tfunction () {\n\t\t\t\ttourBuilder.tour.start();\n\t\t\t},\n\t\t\tcompareTypeAndMessage( gt.TourDefinitionError, /The \\.firstStep\\(\\) method must be called for all tours/ ),\n\t\t\t'Throws if firstStep was not called'\n\t\t);\n\t} );\n}() );\n","usedDeprecatedRules":[]}]

--- end ---
$ ./node_modules/.bin/grunt stylelint
--- stdout ---
Running "stylelint:all" (stylelint) task
>> Linted 4 files without errors

Done.

--- end ---
$ /usr/bin/npm ci --legacy-peer-deps
--- stdout ---

added 409 packages, and audited 410 packages in 4s

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

found 0 vulnerabilities

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

> test
> grunt test

Running "eslint:all" (eslint) task

/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/extension.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/ast.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

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

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

/src/repo/i18n/be.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/bn.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

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

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

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

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

/src/repo/i18n/ckb.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/da.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/diq.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/i18n/el.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/eo.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/eu.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/fo.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/fy.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

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

/src/repo/i18n/gl.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/hsb.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/i18n/hu.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/hyw.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/ilo.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

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

/src/repo/i18n/is.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/ka.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

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

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

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

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

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

/src/repo/i18n/ku-latn.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/lki.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

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

/src/repo/i18n/lv.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/ml.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/my.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/ne.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

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

/src/repo/i18n/oc.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/pms.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/i18n/ps.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/rmc.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/i18n/roa-tara.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/sa.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

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

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

/src/repo/i18n/sd.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/sje.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/i18n/sk.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/sq.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/su.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/szy.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/i18n/te.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/tt-cyrl.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/wuu.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/i18n/yi.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/i18n/zh-hant.json
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

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

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

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

/src/repo/modules/ext.guidedTour.lib.internal.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/modules/ext.guidedTour.lib/ext.guidedTour.lib.Step.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/modules/ext.guidedTour.lib/ext.guidedTour.lib.StepBuilder.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/modules/ext.guidedTour.lib/ext.guidedTour.lib.Tour.js
    1:1   error    Definition for rule 'qunit/no-loose-assertions' was not found                 qunit/no-loose-assertions
  188:20  warning  Where possible, maintain application state in JS to avoid slower DOM queries  no-jquery/no-class-state

/src/repo/modules/ext.guidedTour.lib/ext.guidedTour.lib.TourBuilder.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/modules/ext.guidedTour.lib/ext.guidedTour.lib.TransitionAction.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/modules/ext.guidedTour.lib/ext.guidedTour.lib.TransitionEvent.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/modules/ext.guidedTour.lib/ext.guidedTour.lib.WikitextDescription.js
  1:1  error  Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions

/src/repo/modules/ext.guidedTour.lib/ext.guidedTour.lib.main.js
    1:1  error    Definition for rule 'qunit/no-loose-assertions' was not found           qunit/no-loose-assertions
  121:9  warning  'tourName' is already declared in the upper scope on line 105 column 7  no-shadow

/src/repo/modules/mediawiki.libs.guiders/mediawiki.libs.guiders.js
    1:1   error    Definition for rule 'qunit/no-loose-assertions' was not found  qunit/no-loose-assertions
  388:15  warning  Positional selector extensions are not allowed                 no-jquery/no-sizzle
  448:15  warning  Positional selector extensions are not allowed                 no-jquery/no-sizzle
  732:3   warning  Selector extensions are not allowed                            no-jquery/no-sizzle

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

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

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

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

/src/repo/modules/tours/uprightdownleft.js
  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/tests/qunit/ext.guidedTour.lib.tests.js
  986:7  warning  'firstStepBuilder' is already declared in the upper scope on line 23 column 35                              no-shadow
  987:7  warning  'firstStep' is already declared in the upper scope on line 23 column 53                                     no-shadow
  991:3  error    Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual  qunit/no-loose-assertions
  996:3  error    Unexpected assert.ok. Use assert.strictEqual, assert.notStrictEqual, assert.deepEqual, or assert.propEqual  qunit/no-loose-assertions

✖ 126 problems (119 errors, 7 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.