diff --git a/modules/ext.RevisionSlider.DiffPage.js b/modules/ext.RevisionSlider.DiffPage.js index 67c0a57b..8478765d 100644 --- a/modules/ext.RevisionSlider.DiffPage.js +++ b/modules/ext.RevisionSlider.DiffPage.js @@ -2,10 +2,13 @@ * Module handling diff page reloading and the RevisionSlider browser history * * @class Diffpage - * @constructor + * @param {History} [historyObj] defaults to the global History object + * ] * @constructor */ -function DiffPage() { +function DiffPage( historyObj ) { this.lastRequest = null; + + this.history = historyObj || history; } Object.assign( DiffPage.prototype, { @@ -106,7 +109,7 @@ Object.assign( DiffPage.prototype, { * @param {SliderView} sliderView */ replaceState: function ( diff, oldid, sliderView ) { - history.replaceState( + this.history.replaceState( this.getStateObject( diff, oldid, sliderView ), $( document ).find( 'title' ).text(), this.getStateUrl( diff, oldid ) @@ -121,7 +124,7 @@ Object.assign( DiffPage.prototype, { * @param {SliderView} sliderView */ pushState: function ( diff, oldid, sliderView ) { - history.pushState( + this.history.pushState( this.getStateObject( diff, oldid, sliderView ), $( document ).find( 'title' ).text(), this.getStateUrl( diff, oldid ) diff --git a/modules/ext.RevisionSlider.SliderView.js b/modules/ext.RevisionSlider.SliderView.js index 435075e9..6e54dd9d 100644 --- a/modules/ext.RevisionSlider.SliderView.js +++ b/modules/ext.RevisionSlider.SliderView.js @@ -12,11 +12,12 @@ const DiffPage = require( './ext.RevisionSlider.DiffPage.js' ), * * @class SliderView * @param {Slider} slider + * @param {DiffPage} [diffPage] Defaults to creating a new DiffPage without parameters * @constructor */ -function SliderView( slider ) { +function SliderView( slider, diffPage ) { this.slider = slider; - this.diffPage = new DiffPage( this.slider.getRevisionList() ); + this.diffPage = diffPage || new DiffPage(); this.diffPage.addHandlersToCoreLinks( this ); this.diffPage.initOnPopState( this ); } diff --git a/package-lock.json b/package-lock.json index 79927b11..22e1724e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "grunt-eslint": "24.3.0", "grunt-stylelint": "0.20.1", "mwbot": "1.0.10", + "sinon": "^18.0.0", "stylelint-config-wikimedia": "0.17.2", "svgo": "3.2.0", "wdio-mediawiki": "2.3.0" @@ -538,6 +539,50 @@ "url": "https://github.com/sindresorhus/is?sponsor=1" } }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-11.2.2.tgz", + "integrity": "sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@sinonjs/samsam": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.0.tgz", + "integrity": "sha512-Bp8KUVlLp8ibJZrnvq2foVhP0IVX2CIprMJPK0vqGqgrDa0OHVKeZyBykqskkrdxV6yKBPmGasO8LVjAKR3Gew==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^2.0.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" + } + }, + "node_modules/@sinonjs/samsam/node_modules/@sinonjs/commons": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", + "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/text-encoding": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", + "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", + "dev": true + }, "node_modules/@stylistic/stylelint-config": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@stylistic/stylelint-config/-/stylelint-config-1.0.1.tgz", @@ -5913,6 +5958,12 @@ "node": ">=8" } }, + "node_modules/just-extend": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz", + "integrity": "sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==", + "dev": true + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -6113,6 +6164,12 @@ "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", "dev": true }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "dev": true + }, "node_modules/lodash.isobject": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-3.0.2.tgz", @@ -6706,6 +6763,19 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, + "node_modules/nise": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/nise/-/nise-6.0.0.tgz", + "integrity": "sha512-K8ePqo9BFvN31HXwEtTNGzgrPpmvgciDsFz8aztFjt4LqKO/JeFD8tBOeuDiCMXrIl/m1YvfH8auSpxfaD09wg==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^11.2.2", + "@sinonjs/text-encoding": "^0.7.2", + "just-extend": "^6.2.0", + "path-to-regexp": "^6.2.1" + } + }, "node_modules/node-fetch": { "version": "2.6.7", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", @@ -7103,6 +7173,12 @@ "node": ">=0.10.0" } }, + "node_modules/path-to-regexp": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.2.tgz", + "integrity": "sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw==", + "dev": true + }, "node_modules/path-type": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", @@ -8105,6 +8181,33 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true }, + "node_modules/sinon": { + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-18.0.0.tgz", + "integrity": "sha512-+dXDXzD1sBO6HlmZDd7mXZCR/y5ECiEiGCBSGuFD/kZ0bDTofPYc6JaeGmPSF+1j1MejGUWkORbYOLDyvqCWpA==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.1", + "@sinonjs/fake-timers": "^11.2.2", + "@sinonjs/samsam": "^8.0.0", + "diff": "^5.2.0", + "nise": "^6.0.0", + "supports-color": "^7" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/sinon" + } + }, + "node_modules/sinon/node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -9051,6 +9154,15 @@ "node": ">= 0.8.0" } }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/type-fest": { "version": "0.21.3", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", @@ -10157,6 +10269,52 @@ "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", "dev": true }, + "@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/fake-timers": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-11.2.2.tgz", + "integrity": "sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw==", + "dev": true, + "requires": { + "@sinonjs/commons": "^3.0.0" + } + }, + "@sinonjs/samsam": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.0.tgz", + "integrity": "sha512-Bp8KUVlLp8ibJZrnvq2foVhP0IVX2CIprMJPK0vqGqgrDa0OHVKeZyBykqskkrdxV6yKBPmGasO8LVjAKR3Gew==", + "dev": true, + "requires": { + "@sinonjs/commons": "^2.0.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" + }, + "dependencies": { + "@sinonjs/commons": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", + "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + } + } + }, + "@sinonjs/text-encoding": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", + "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", + "dev": true + }, "@stylistic/stylelint-config": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@stylistic/stylelint-config/-/stylelint-config-1.0.1.tgz", @@ -14227,6 +14385,12 @@ "xmlbuilder": "^15.1.1" } }, + "just-extend": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz", + "integrity": "sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==", + "dev": true + }, "keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -14404,6 +14568,12 @@ "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", "dev": true }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "dev": true + }, "lodash.isobject": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-3.0.2.tgz", @@ -14848,6 +15018,19 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, + "nise": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/nise/-/nise-6.0.0.tgz", + "integrity": "sha512-K8ePqo9BFvN31HXwEtTNGzgrPpmvgciDsFz8aztFjt4LqKO/JeFD8tBOeuDiCMXrIl/m1YvfH8auSpxfaD09wg==", + "dev": true, + "requires": { + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^11.2.2", + "@sinonjs/text-encoding": "^0.7.2", + "just-extend": "^6.2.0", + "path-to-regexp": "^6.2.1" + } + }, "node-fetch": { "version": "2.6.7", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", @@ -15135,6 +15318,12 @@ "integrity": "sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0=", "dev": true }, + "path-to-regexp": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.2.tgz", + "integrity": "sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw==", + "dev": true + }, "path-type": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", @@ -15871,6 +16060,28 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true }, + "sinon": { + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-18.0.0.tgz", + "integrity": "sha512-+dXDXzD1sBO6HlmZDd7mXZCR/y5ECiEiGCBSGuFD/kZ0bDTofPYc6JaeGmPSF+1j1MejGUWkORbYOLDyvqCWpA==", + "dev": true, + "requires": { + "@sinonjs/commons": "^3.0.1", + "@sinonjs/fake-timers": "^11.2.2", + "@sinonjs/samsam": "^8.0.0", + "diff": "^5.2.0", + "nise": "^6.0.0", + "supports-color": "^7" + }, + "dependencies": { + "diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "dev": true + } + } + }, "slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -16603,6 +16814,12 @@ "prelude-ls": "^1.2.1" } }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, "type-fest": { "version": "0.21.3", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", diff --git a/package.json b/package.json index e7d6a579..4f565c1a 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "grunt-eslint": "24.3.0", "grunt-stylelint": "0.20.1", "mwbot": "1.0.10", + "sinon": "^18.0.0", "stylelint-config-wikimedia": "0.17.2", "svgo": "3.2.0", "wdio-mediawiki": "2.3.0" diff --git a/tests/qunit/.eslintrc.json b/tests/qunit/.eslintrc.json index 6db2b43f..84668308 100644 --- a/tests/qunit/.eslintrc.json +++ b/tests/qunit/.eslintrc.json @@ -4,6 +4,9 @@ "../../modules/.eslintrc.json", "wikimedia/qunit" ], + "globals": { + "sinon": "readonly" + }, "rules": { "no-jquery/no-extend": "warn" } diff --git a/tests/qunit/RevisionSlider.DiffPage.test.js b/tests/qunit/RevisionSlider.DiffPage.test.js index f9027d1d..9d9bf330 100644 --- a/tests/qunit/RevisionSlider.DiffPage.test.js +++ b/tests/qunit/RevisionSlider.DiffPage.test.js @@ -1,40 +1,33 @@ QUnit.module( 'ext.RevisionSlider.DiffPage' ); -QUnit.test( 'Push state', ( assert ) => { +QUnit.test( 'Push state', () => { const SliderModule = require( 'ext.RevisionSlider.Slider' ), - DiffPage = SliderModule.DiffPage, - SliderView = SliderModule.SliderView, - Slider = SliderModule.Slider, - RevisionList = SliderModule.RevisionList, - Revision = SliderModule.Revision; + DiffPage = SliderModule.DiffPage; - const diffPage = new DiffPage(), - sliderView = new SliderView( new Slider( new RevisionList( [ - new Revision( { revid: 1, comment: '' } ), - new Revision( { revid: 3, comment: '' } ), - new Revision( { revid: 37, comment: '' } ) - ] ) ) - ); + const historyStub = { pushState: sinon.spy() }; + const sliderStub = { getOldestVisibleRevisionIndex: sinon.stub().returns( 42 ) }; + const pointerStub = { getPosition: sinon.stub().returns( 5 ) }; + const sliderViewStub = { + pointerOlder: pointerStub, + pointerNewer: pointerStub, + slider: sliderStub + }; + const diffPage = new DiffPage( historyStub ); mw.config.set( { wgDiffOldId: 1, wgDiffNewId: 37 } ); - sliderView.render( $( '
' ) ); - const histLength = history.length; + diffPage.pushState( 3, 37, sliderViewStub ); - diffPage.pushState( 3, 37, sliderView ); - - assert.strictEqual( history.length, histLength + 1 ); - assert.propEqual( - history.state, - { - diff: 3, - oldid: 37, - pointerOlderPos: 1, - pointerNewerPos: 3, - sliderPos: NaN - } + sinon.assert.calledWith( + historyStub.pushState, + { diff: 3, oldid: 37, pointerNewerPos: 5, pointerOlderPos: 5, sliderPos: 42 }, + sinon.match.any, + sinon.match.any ); + + sinon.assert.calledOnce( sliderStub.getOldestVisibleRevisionIndex ); + sinon.assert.calledTwice( pointerStub.getPosition ); } ); diff --git a/tests/qunit/RevisionSlider.SliderView.test.js b/tests/qunit/RevisionSlider.SliderView.test.js index 1fc8366b..05bbe430 100644 --- a/tests/qunit/RevisionSlider.SliderView.test.js +++ b/tests/qunit/RevisionSlider.SliderView.test.js @@ -1,29 +1,22 @@ ( function () { const SliderModule = require( 'ext.RevisionSlider.Slider' ), + DiffPage = SliderModule.DiffPage, Slider = SliderModule.Slider, SliderView = SliderModule.SliderView, Revision = SliderModule.Revision, - RevisionList = SliderModule.RevisionList; - let startHistoryState, startHref; + RevisionList = SliderModule.RevisionList, + historyStub = { replaceState: sinon.spy() }, + diffPage = new DiffPage( historyStub ); QUnit.module( 'ext.RevisionSlider.SliderView' ); - QUnit.testStart( () => { - startHistoryState = history.state; - startHref = window.location.href; - } ); - - QUnit.testDone( () => { - history.replaceState( startHistoryState, 'QUnit', startHref ); - } ); - QUnit.test( 'render adds the slider view with defined revisions selected', ( assert ) => { const $container = $( '
' ), view = new SliderView( new Slider( new RevisionList( [ new Revision( { revid: 1, size: 5, comment: 'Comment1', user: 'User1' } ), new Revision( { revid: 3, size: 21, comment: 'Comment2', user: 'User2' } ), new Revision( { revid: 37, size: 13, comment: 'Comment3', user: 'User3' } ) - ] ) ) ); + ] ) ), diffPage ); mw.config.set( { wgDiffOldId: 1, @@ -39,6 +32,8 @@ assert.strictEqual( $revisionOld.attr( 'data-revid' ), '1' ); assert.strictEqual( $revisionNew.length, 1 ); assert.strictEqual( $revisionNew.attr( 'data-revid' ), '37' ); + + sinon.assert.calledOnce( historyStub.replaceState ); } ); QUnit.test( 'render throws an exception when no selected revisions provided', ( assert ) => { @@ -47,7 +42,7 @@ new Revision( { revid: 1, size: 5, comment: 'Comment1', user: 'User1' } ), new Revision( { revid: 3, size: 21, comment: 'Comment2', user: 'User2' } ), new Revision( { revid: 37, size: 13, comment: 'Comment3', user: 'User3' } ) - ] ) ) ); + ] ) ), diffPage ); mw.config.set( 'wgDiffOldId', null ); mw.config.set( 'wgDiffNewId', null ); @@ -57,5 +52,7 @@ view.render( $container ); } ); + + sinon.assert.calledOnce( historyStub.replaceState ); } ); }() );