[QUnit] Avoid manipulating the global history

Also introducing sinon to mock some parts.

Bug: T370573
Change-Id: I5a7573bcac9501a7b37a12b86bb0d4f46055f70e
This commit is contained in:
WMDE-Fisch 2024-07-23 21:48:46 +02:00
parent 5815371cf1
commit 37dc0b5adb
7 changed files with 261 additions and 46 deletions

View file

@ -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 )

View file

@ -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 );
}

217
package-lock.json generated
View file

@ -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",

View file

@ -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"

View file

@ -4,6 +4,9 @@
"../../modules/.eslintrc.json",
"wikimedia/qunit"
],
"globals": {
"sinon": "readonly"
},
"rules": {
"no-jquery/no-extend": "warn"
}

View file

@ -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( $( '<div>' ) );
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 );
} );

View file

@ -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 = $( '<div>' ),
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 );
} );
}() );