Replace all direct localStorage interaction by mw.storage

Meanwhile I also updated a few places where a non-string was
passed to/expected from localStorage - this could’ve led to
inconsistencies because localStorage stringifies everything.

And I also removed CP.canSetMediaViewerEnabledOnClick, which
was no longer used anywhere except for a test case.

Bug: T137777
Change-Id: I21ad4ba15ca751bb03e5e2c523d09a45c0444ccf
This commit is contained in:
Matthias Mullie 2017-06-02 11:29:41 +02:00
parent 8d07d09085
commit 3e5ad4c83a
11 changed files with 145 additions and 139 deletions

View file

@ -106,6 +106,7 @@
"mediawiki.Title",
"mediawiki.Uri",
"mediawiki.jqueryMsg",
"mediawiki.storage",
"oojs",
"jquery.fullscreen",
"jquery.hidpi",
@ -344,6 +345,7 @@
"mediawiki.ui.icon",
"mediawiki.Title",
"mediawiki.user",
"mediawiki.storage",
"mmv.head",
"oojs"
],
@ -369,7 +371,8 @@
"mmv/mmv.head.js"
],
"dependencies": [
"mediawiki.user"
"mediawiki.user",
"mediawiki.storage"
],
"position": "top"
}

View file

@ -26,7 +26,7 @@
* @param {mw.Map} mwConfig
* @param {Object} mwUser
* @param {mw.Api} api
* @param {Object} localStorage
* @param {mw.storage} localStorage
*/
function Config( viewerConfig, mwConfig, mwUser, api, localStorage ) {
/**
@ -55,7 +55,7 @@
/**
* The localStorage object, for dependency injection
* @type {Object}
* @type {mw.storage}
*/
this.localStorage = localStorage;
}
@ -66,21 +66,24 @@
*
* @param {string} key
* @param {*} [fallback] value to return when key is not set or localStorage is not supported
* @return {*} stored value or fallback or null if neither exists
* @return {string|null} stored value or fallback or null if neither exists
*/
CP.getFromLocalStorage = function ( key, fallback ) {
var value = null;
if ( this.localStorage ) {
try {
value = this.localStorage.getItem( key );
} catch ( e ) {
mw.log( 'Failed to fetch item ' + key + ' from localStorage', e );
}
var value = this.localStorage.get( key );
// localStorage will only store strings; if values `null`, `false` or
// `0` are set, they'll come out as `"null"`, `"false"` or `"0"`, so we
// can be certain that an actual null is a failure to locate the item,
// and false is an issue with localStorage itself
if ( value !== null && value !== false ) {
return value;
}
if ( value === null && fallback !== undefined ) {
value = fallback;
if ( value === null ) {
mw.log( 'Failed to fetch item ' + key + ' from localStorage' );
}
return value;
return fallback !== undefined ? fallback : null;
};
/**
@ -91,14 +94,7 @@
* @return {boolean} whether storing the item was successful
*/
CP.setInLocalStorage = function ( key, value ) {
var success = false;
if ( this.localStorage ) {
try {
this.localStorage.setItem( key, value );
success = true;
} catch ( e ) {}
}
return success;
return this.localStorage.set( key, value );
};
/**
@ -108,15 +104,15 @@
* @return {boolean} whether storing the item was successful
*/
CP.removeFromLocalStorage = function ( key ) {
if ( this.localStorage ) {
try {
this.localStorage.removeItem( key );
return true;
} catch ( e ) {
return false;
}
}
return true; // since we never even stored the value, this is considered a success
this.localStorage.remove( key );
// mw.storage.remove catches all exceptions and returns false if any
// occur, so we can't distinguish between actual issues, and
// localStorage not being supported - however, localStorage.removeItem
// is not documented to throw any errors, so nothing to worry about;
// when localStorage is not supported, we'll consider removal successful
// (it can't have been there in the first place)
return true;
};
/**
@ -143,7 +139,7 @@
// IMPORTANT: mmv.head.js uses the same logic but does not use this class to be lightweight. Make sure to keep it in sync.
return this.mwConfig.get( 'wgMediaViewer' ) && // global opt-out switch, can be set in user JS
this.mwConfig.get( 'wgMediaViewerOnClick' ) && // thumbnail opt-out, can be set in preferences
( !this.mwUser.isAnon() || this.getFromLocalStorage( 'wgMediaViewerOnClick', 1 ) === 1 ); // thumbnail opt-out for anons
( !this.mwUser.isAnon() || this.getFromLocalStorage( 'wgMediaViewerOnClick', '1' ) === '1' ); // thumbnail opt-out for anons
};
/**
@ -198,15 +194,6 @@
} );
};
/**
* Returns true if #setMediaViewerEnabledOnClick() is supported.
*
* @return {boolean}
*/
CP.canSetMediaViewerEnabledOnClick = function () {
return !this.mwUser.isAnon() || !!this.localStorage;
};
/**
* True if info about enable/disable status should be displayed (mingle #719).
*

View file

@ -25,12 +25,6 @@
* @class mw.mmv.MultimediaViewerBootstrap
*/
function MultimediaViewerBootstrap() {
var localStorage = false;
try {
localStorage = window.localStorage || false;
} catch ( e ) { }
// Exposed for tests
this.hoverWaitDuration = 200;
@ -42,7 +36,7 @@
mw.config,
mw.user,
new mw.Api(),
localStorage
mw.storage
);
this.validExtensions = this.config.extensions();

View file

@ -25,15 +25,11 @@
// If the user disabled MediaViewer in his preferences, we do not set up click handling.
// This is loaded before user JS so we cannot check wgMediaViewer.
try {
if (
mw.config.get( 'wgMediaViewerOnClick' ) !== true ||
mw.user.isAnon() && window.localStorage && localStorage.getItem( 'wgMediaViewerOnClick' ) === false
) {
return;
}
} catch ( e ) { // localStorage.getItem can throw exceptions
mw.log( 'Could not check value of wgMediaViewerOnClick in localStorage' );
if (
mw.config.get( 'wgMediaViewerOnClick' ) !== true ||
mw.user.isAnon() && mw.storage.get( 'wgMediaViewerOnClick', '1' ) !== '1'
) {
return;
}
$document.on( 'click.mmv-head', 'a.image', function ( e ) {

View file

@ -26,10 +26,7 @@
* @constructor
*/
function LightboxInterface() {
this.localStorage = false;
try {
this.localStorage = window.localStorage || false;
} catch ( e ) {}
this.localStorage = mw.storage;
/** @property {mw.mmv.Config} config - */
this.config = new mw.mmv.Config(

View file

@ -29,7 +29,7 @@
* @param {jQuery} $aboveFold The brighter headline of the metadata panel (.mw-mmv-above-fold).
* Called "aboveFold" for historical reasons, but actually a part of the next sibling of the element
* is also above the fold (bottom of the screen).
* @param {Object} localStorage the localStorage object, for dependency injection
* @param {mw.storage} localStorage the localStorage object, for dependency injection
* @param {mw.mmv.Config} config A configuration object.
*/
function MetadataPanel( $container, $aboveFold, localStorage, config ) {
@ -185,7 +185,7 @@
/**
* Initializes the header, which contains the title, credit, and license elements.
*
* @param {Object} localStorage the localStorage object, for dependency injection
* @param {mw.storage} localStorage the localStorage object, for dependency injection
*/
MPP.initializeHeader = function ( localStorage ) {
this.progressBar = new mw.mmv.ui.ProgressBar( this.$aboveFold );

View file

@ -26,14 +26,14 @@
* @constructor
* @param {jQuery} $container The container for the panel (.mw-mmv-post-image).
* @param {jQuery} $aboveFold The control bar element (.mw-mmv-above-fold).
* @param {Object} localStorage the localStorage object, for dependency injection
* @param {mw.storage} localStorage the localStorage object, for dependency injection
*/
function MetadataPanelScroller( $container, $aboveFold, localStorage ) {
mw.mmv.ui.Element.call( this, $container );
this.$aboveFold = $aboveFold;
/** @property {Object} localStorage the window.localStorage object */
/** @property {mw.storage} localStorage */
this.localStorage = localStorage;
/** @property {boolean} panelWasOpen state flag which will be used to detect open <-> closed transitions */
@ -72,13 +72,9 @@
} ) );
this.$container.on( 'mmv-metadata-open', function () {
if ( !panel.hasOpenedMetadata && panel.localStorage ) {
if ( !panel.hasOpenedMetadata && panel.localStorage.store ) {
panel.hasOpenedMetadata = true;
try {
panel.localStorage.setItem( 'mmv.hasOpenedMetadata', true );
} catch ( e ) {
// localStorage is full or disabled
}
panel.localStorage.set( 'mmv.hasOpenedMetadata', '1' );
}
} );
@ -141,9 +137,16 @@
};
MPSP.initialize = function () {
try {
this.hasOpenedMetadata = !this.localStorage || this.localStorage.getItem( 'mmv.hasOpenedMetadata' );
} catch ( e ) { // localStorage.getItem can throw exceptions
var value = this.localStorage.get( 'mmv.hasOpenedMetadata' );
// localStorage will only store strings; if values `null`, `false` or
// `0` are set, they'll come out as `"null"`, `"false"` or `"0"`, so we
// can be certain that an actual null is a failure to locate the item,
// and false is an issue with localStorage itself
if ( value !== false ) {
this.hasOpenedMetadata = value !== null;
} else {
// if there was an issue with localStorage, treat it as opened
this.hasOpenedMetadata = true;
}
};

View file

@ -26,25 +26,25 @@
QUnit.test( 'Localstorage get', 8, function ( assert ) {
var localStorage, config;
localStorage = undefined; // no browser support
localStorage = mw.mmv.testHelpers.getUnsupportedLocalStorage(); // no browser support
config = new mw.mmv.Config( {}, {}, {}, {}, localStorage );
assert.strictEqual( config.getFromLocalStorage( 'foo' ), null, 'Returns null when not supported' );
assert.strictEqual( config.getFromLocalStorage( 'foo', 'bar' ), 'bar', 'Returns fallback when not supported' );
localStorage = null; // browser supports it but disabled
localStorage = mw.mmv.testHelpers.getDisabledLocalStorage(); // browser supports it but disabled
config = new mw.mmv.Config( {}, {}, {}, {}, localStorage );
assert.strictEqual( config.getFromLocalStorage( 'foo' ), null, 'Returns null when disabled' );
assert.strictEqual( config.getFromLocalStorage( 'foo', 'bar' ), 'bar', 'Returns fallback when disabled' );
localStorage = { getItem: this.sandbox.stub() };
localStorage = mw.mmv.testHelpers.createLocalStorage( { getItem: this.sandbox.stub() } );
config = new mw.mmv.Config( {}, {}, {}, {}, localStorage );
localStorage.getItem.withArgs( 'foo' ).returns( null );
localStorage.store.getItem.withArgs( 'foo' ).returns( null );
assert.strictEqual( config.getFromLocalStorage( 'foo' ), null, 'Returns null when key not set' );
assert.strictEqual( config.getFromLocalStorage( 'foo', 'bar' ), 'bar', 'Returns fallback when key not set' );
localStorage.getItem.reset();
localStorage.getItem.withArgs( 'foo' ).returns( 'boom' );
localStorage.store.getItem.reset();
localStorage.store.getItem.withArgs( 'foo' ).returns( 'boom' );
assert.strictEqual( config.getFromLocalStorage( 'foo' ), 'boom', 'Returns correct value' );
assert.strictEqual( config.getFromLocalStorage( 'foo', 'bar' ), 'boom', 'Returns correct value ignoring fallback' );
} );
@ -52,45 +52,41 @@
QUnit.test( 'Localstorage set', 4, function ( assert ) {
var localStorage, config;
localStorage = undefined; // no browser support
localStorage = mw.mmv.testHelpers.getUnsupportedLocalStorage(); // no browser support
config = new mw.mmv.Config( {}, {}, {}, {}, localStorage );
assert.strictEqual( config.setInLocalStorage( 'foo', 'bar' ), false, 'Returns false when not supported' );
localStorage = null; // browser supports it but disabled
localStorage = mw.mmv.testHelpers.getDisabledLocalStorage(); // browser supports it but disabled
config = new mw.mmv.Config( {}, {}, {}, {}, localStorage );
assert.strictEqual( config.setInLocalStorage( 'foo', 'bar' ), false, 'Returns false when disabled' );
localStorage = { setItem: this.sandbox.stub() };
localStorage = mw.mmv.testHelpers.createLocalStorage( { setItem: this.sandbox.stub() } );
config = new mw.mmv.Config( {}, {}, {}, {}, localStorage );
assert.strictEqual( config.setInLocalStorage( 'foo', 'bar' ), true, 'Returns true when works' );
localStorage.setItem.throwsException( 'localStorage full!' );
localStorage.store.setItem.throwsException( 'localStorage full!' );
assert.strictEqual( config.setInLocalStorage( 'foo', 'bar' ), false, 'Returns false on error' );
} );
QUnit.test( 'Localstorage remove', 4, function ( assert ) {
QUnit.test( 'Localstorage remove', 3, function ( assert ) {
var localStorage, config;
localStorage = undefined; // no browser support
localStorage = mw.mmv.testHelpers.getUnsupportedLocalStorage(); // no browser support
config = new mw.mmv.Config( {}, {}, {}, {}, localStorage );
assert.strictEqual( config.removeFromLocalStorage( 'foo' ), true, 'Returns true when not supported' );
localStorage = null; // browser supports it but disabled
localStorage = mw.mmv.testHelpers.getDisabledLocalStorage(); // browser supports it but disabled
config = new mw.mmv.Config( {}, {}, {}, {}, localStorage );
assert.strictEqual( config.removeFromLocalStorage( 'foo' ), true, 'Returns true when disabled' );
localStorage = { removeItem: this.sandbox.stub() };
localStorage = mw.mmv.testHelpers.createLocalStorage( { removeItem: this.sandbox.stub() } );
config = new mw.mmv.Config( {}, {}, {}, {}, localStorage );
assert.strictEqual( config.removeFromLocalStorage( 'foo' ), true, 'Returns true when works' );
localStorage.removeItem.throwsException( 'cannot write localStorage!' );
assert.strictEqual( config.removeFromLocalStorage( 'foo' ), false, 'Returns false on error' );
} );
QUnit.test( 'isMediaViewerEnabledOnClick', 7, function ( assert ) {
var localStorage = { getItem: this.sandbox.stub() },
var localStorage = mw.mmv.testHelpers.createLocalStorage( { getItem: this.sandbox.stub() } ),
mwConfig = { get: this.sandbox.stub() },
mwUser = { isAnon: this.sandbox.stub() },
config = new mw.mmv.Config( {}, mwConfig, mwUser, {}, localStorage );
@ -123,18 +119,22 @@
mwUser.isAnon.returns( true );
mwConfig.get.withArgs( 'wgMediaViewer' ).returns( true );
mwConfig.get.withArgs( 'wgMediaViewerOnClick' ).returns( true );
localStorage.getItem.withArgs( 'wgMediaViewerOnClick' ).returns( null );
localStorage.store.getItem.withArgs( 'wgMediaViewerOnClick' ).returns( null );
assert.strictEqual( config.isMediaViewerEnabledOnClick(), true, 'Returns true for anon with standard settings' );
mwUser.isAnon.returns( true );
mwConfig.get.withArgs( 'wgMediaViewer' ).returns( true );
mwConfig.get.withArgs( 'wgMediaViewerOnClick' ).returns( true );
localStorage.getItem.withArgs( 'wgMediaViewerOnClick' ).returns( '0' );
localStorage.store.getItem.withArgs( 'wgMediaViewerOnClick' ).returns( '0' );
assert.strictEqual( config.isMediaViewerEnabledOnClick(), false, 'Returns true for anon opted out via localSettings' );
} );
QUnit.test( 'setMediaViewerEnabledOnClick sanity check', 3, function ( assert ) {
var localStorage = { getItem: this.sandbox.stub(), setItem: this.sandbox.stub(), removeItem: this.sandbox.stub() },
var localStorage = mw.mmv.testHelpers.createLocalStorage( {
getItem: this.sandbox.stub(),
setItem: this.sandbox.stub(),
removeItem: this.sandbox.stub()
} ),
mwUser = { isAnon: this.sandbox.stub() },
mwConfig = new mw.Map(),
api = { postWithToken: this.sandbox.stub().returns( $.Deferred().resolve() ) },
@ -148,31 +148,11 @@
mwUser.isAnon.returns( true );
config.setMediaViewerEnabledOnClick( false );
assert.ok( localStorage.setItem.called, 'For anons, opt-out is set in localStorage' );
assert.ok( localStorage.store.setItem.called, 'For anons, opt-out is set in localStorage' );
mwUser.isAnon.returns( true );
config.setMediaViewerEnabledOnClick( true );
assert.ok( localStorage.removeItem.called, 'For anons, opt-in means clearing localStorage' );
} );
QUnit.test( 'canSetMediaViewerEnabledOnClick', 4, function ( assert ) {
var mwUser = { isAnon: this.sandbox.stub() },
config = new mw.mmv.Config( {}, {}, mwUser, {}, {} );
mwUser.isAnon.returns( false );
assert.strictEqual( config.canSetMediaViewerEnabledOnClick(), true, 'Logged-in users can always disable' );
mwUser.isAnon.returns( true );
config = new mw.mmv.Config( {}, {}, mwUser, {}, {} );
assert.strictEqual( config.canSetMediaViewerEnabledOnClick(), true, 'Anons can disable when they have localStorage support' );
mwUser.isAnon.returns( true );
config = new mw.mmv.Config( {}, {}, mwUser, {}, null );
assert.strictEqual( config.canSetMediaViewerEnabledOnClick(), false, 'Anons cannot disable when they have no localStorage support' );
mwUser.isAnon.returns( true );
config = new mw.mmv.Config( {}, {}, mwUser, {}, undefined );
assert.strictEqual( config.canSetMediaViewerEnabledOnClick(), false, 'Anons cannot disable when disabled localStorage' );
assert.ok( localStorage.store.removeItem.called, 'For anons, opt-in means clearing localStorage' );
} );
QUnit.test( 'shouldShowStatusInfo', 12, function ( assert ) {

View file

@ -29,21 +29,57 @@
return ex;
};
/**
* Creates an mw.storage-like object.
*
* @param {Object} storage localStorage stub with getItem, setItem, removeItem methods
* @return {mw.storage} Local storage-like object
*/
MTH.createLocalStorage = function ( storage ) {
return new ( Object.getPrototypeOf( mw.storage ) ).constructor( storage );
};
/**
* Returns an mw.storage that mimicks lack of localStorage support.
*
* @return {mw.storage} Local storage-like object
*/
MTH.getUnsupportedLocalStorage = function () {
return this.createLocalStorage( undefined );
};
/**
* Returns an mw.storage that mimicks localStorage being disabled in browser.
*
* @return {mw.storage} Local storage-like object
*/
MTH.getDisabledLocalStorage = function () {
var e = function () {
throw new Error( 'Error' );
};
return this.createLocalStorage( {
getItem: e,
setItem: e,
removeItem: e
} );
};
/**
* Returns a fake local storage which is not saved between reloads.
*
* @param {Object} [initialData]
* @return {Object} Local storage-like object
* @return {mw.storage} Local storage-like object
*/
MTH.getFakeLocalStorage = function ( initialData ) {
var bag = new mw.Map();
bag.set( initialData );
return {
return this.createLocalStorage( {
getItem: function ( key ) { return bag.get( key ); },
setItem: function ( key, value ) { bag.set( key, value ); },
removeItem: function ( key ) { bag.set( key, null ); }
};
} );
};
/**

View file

@ -18,7 +18,7 @@
QUnit.test( 'The panel is emptied properly when necessary', thingsShouldBeEmptied.length + thingsShouldHaveEmptyClass.length, function ( assert ) {
var i,
$qf = $( '#qunit-fixture' ),
panel = new mw.mmv.ui.MetadataPanel( $qf, $( '<div>' ).appendTo( $qf ), window.localStorage, new mw.mmv.Config( {}, mw.config, mw.user, new mw.Api(), window.localStorage ) );
panel = new mw.mmv.ui.MetadataPanel( $qf, $( '<div>' ).appendTo( $qf ), mw.storage, new mw.mmv.Config( {}, mw.config, mw.user, new mw.Api(), mw.storage ) );
panel.empty();
@ -33,7 +33,7 @@
QUnit.test( 'Setting location information works as expected', 6, function ( assert ) {
var $qf = $( '#qunit-fixture' ),
panel = new mw.mmv.ui.MetadataPanel( $qf, $( '<div>' ).appendTo( $qf ), window.localStorage, new mw.mmv.Config( {}, mw.config, mw.user, new mw.Api(), window.localStorage ) ),
panel = new mw.mmv.ui.MetadataPanel( $qf, $( '<div>' ).appendTo( $qf ), mw.storage, new mw.mmv.Config( {}, mw.config, mw.user, new mw.Api(), mw.storage ) ),
fileName = 'Foobar.jpg',
latitude = 12.3456789,
longitude = 98.7654321,
@ -98,7 +98,7 @@
QUnit.test( 'Setting image information works as expected', 17, function ( assert ) {
var creditPopupText,
$qf = $( '#qunit-fixture' ),
panel = new mw.mmv.ui.MetadataPanel( $qf, $( '<div>' ).appendTo( $qf ), window.localStorage, new mw.mmv.Config( {}, mw.config, mw.user, new mw.Api(), window.localStorage ) ),
panel = new mw.mmv.ui.MetadataPanel( $qf, $( '<div>' ).appendTo( $qf ), mw.storage, new mw.mmv.Config( {}, mw.config, mw.user, new mw.Api(), mw.storage ) ),
title = 'Foo bar',
image = {
filePageTitle: mw.Title.newFromText( 'File:' + title + '.jpg' )
@ -170,7 +170,7 @@
QUnit.test( 'Setting permission information works as expected', 1, function ( assert ) {
var $qf = $( '#qunit-fixture' ),
panel = new mw.mmv.ui.MetadataPanel( $qf, $( '<div>' ).appendTo( $qf ), window.localStorage, new mw.mmv.Config( {}, mw.config, mw.user, new mw.Api(), window.localStorage ) );
panel = new mw.mmv.ui.MetadataPanel( $qf, $( '<div>' ).appendTo( $qf ), mw.storage, new mw.mmv.Config( {}, mw.config, mw.user, new mw.Api(), mw.storage ) );
panel.setLicense( null, 'http://example.com' ); // make sure license is visible as it contains the permission
panel.setPermission( 'Look at me, I am a permission!' );
@ -179,7 +179,7 @@
QUnit.test( 'Date formatting', 1, function ( assert ) {
var $qf = $( '#qunit-fixture' ),
panel = new mw.mmv.ui.MetadataPanel( $qf, $( '<div>' ).appendTo( $qf ), window.localStorage, new mw.mmv.Config( {}, mw.config, mw.user, new mw.Api(), window.localStorage ) ),
panel = new mw.mmv.ui.MetadataPanel( $qf, $( '<div>' ).appendTo( $qf ), mw.storage, new mw.mmv.Config( {}, mw.config, mw.user, new mw.Api(), mw.storage ) ),
date1 = 'Garbage',
promise = panel.formatDate( date1 );
@ -198,7 +198,7 @@
this.sandbox.stub( mw.user, 'isAnon' );
mw.config.set( 'wgMediaViewerIsInBeta', false );
// eslint-disable-next-line no-new
new mw.mmv.ui.MetadataPanel( $qf.empty(), $( '<div>' ).appendTo( $qf ), window.localStorage, new mw.mmv.Config( {}, mw.config, mw.user, new mw.Api(), window.localStorage ) );
new mw.mmv.ui.MetadataPanel( $qf.empty(), $( '<div>' ).appendTo( $qf ), mw.storage, new mw.mmv.Config( {}, mw.config, mw.user, new mw.Api(), mw.storage ) );
assert.strictEqual( $qf.find( '.mw-mmv-about-link' ).length, 1, 'About link is created.' );
assert.strictEqual( $qf.find( '.mw-mmv-discuss-link' ).length, 1, 'Discuss link is created.' );

View file

@ -24,7 +24,8 @@
QUnit.test( 'empty()', 1, function ( assert ) {
var $qf = $( '#qunit-fixture' ),
scroller = new mw.mmv.ui.MetadataPanelScroller( $qf, $( '<div>' ).appendTo( $qf ) );
localStorage = mw.mmv.testHelpers.getFakeLocalStorage(),
scroller = new mw.mmv.ui.MetadataPanelScroller( $qf, $( '<div>' ).appendTo( $qf ), localStorage );
scroller.empty();
assert.ok( !scroller.$container.hasClass( 'invite' ), 'We successfully reset the invite' );
@ -32,12 +33,13 @@
QUnit.test( 'Metadata div is only animated once', 5, function ( assert ) {
var $qf = $( '#qunit-fixture' ),
displayCount,
scroller = new mw.mmv.ui.MetadataPanelScroller( $qf, $( '<div>' ).appendTo( $qf ), {
displayCount = null, // pretend it doesn't exist at first
localStorage = mw.mmv.testHelpers.createLocalStorage( {
// We simulate localStorage to avoid test side-effects
getItem: function () { return displayCount; },
setItem: function ( _, val ) { displayCount = val; }
} );
} ),
scroller = new mw.mmv.ui.MetadataPanelScroller( $qf, $( '<div>' ).appendTo( $qf ), localStorage );
scroller.attach();
@ -69,7 +71,8 @@
QUnit.test( 'No localStorage', 1, function ( assert ) {
var $qf = $( '#qunit-fixture' ),
scroller = new mw.mmv.ui.MetadataPanelScroller( $qf, $( '<div>' ).appendTo( $qf ) );
localStorage = mw.mmv.testHelpers.getUnsupportedLocalStorage(),
scroller = new mw.mmv.ui.MetadataPanelScroller( $qf, $( '<div>' ).appendTo( $qf ), localStorage );
this.sandbox.stub( $, 'scrollTo', function () { return { scrollTop: function () { return 10; } }; } );
@ -80,7 +83,10 @@
QUnit.test( 'localStorage is full', 2, function ( assert ) {
var $qf = $( '#qunit-fixture' ),
localStorage = { getItem: $.noop, setItem: this.sandbox.stub().throwsException( 'I am full' ) },
localStorage = mw.mmv.testHelpers.createLocalStorage( {
getItem: this.sandbox.stub().returns( null ),
setItem: this.sandbox.stub().throwsException( 'I am full' )
} ),
scroller = new mw.mmv.ui.MetadataPanelScroller( $qf, $( '<div>' ).appendTo( $qf ), localStorage );
this.sandbox.stub( $, 'scrollTo', function () {
@ -99,7 +105,7 @@
scroller.scroll();
assert.ok( localStorage.setItem.calledOnce, 'localStorage only written once' );
assert.ok( localStorage.store.setItem.calledOnce, 'localStorage only written once' );
scroller.unattach();
} );
@ -153,13 +159,16 @@
var $qf = $( '#qunit-fixture' ),
$container = $( '<div>' ).css( 'height', 100 ).appendTo( $qf ),
$aboveFold = $( '<div>' ).css( 'height', 50 ).appendTo( $container ),
fakeLocalStorage = { getItem: $.noop, setItem: $.noop },
fakeLocalStorage = mw.mmv.testHelpers.createLocalStorage( {
getItem: this.sandbox.stub().returns( null ),
setItem: $.noop
} ),
scroller = new mw.mmv.ui.MetadataPanelScroller( $container, $aboveFold, fakeLocalStorage ),
keydown = $.Event( 'keydown' );
stubScrollFunctions( this.sandbox, scroller );
this.sandbox.stub( fakeLocalStorage, 'setItem' );
this.sandbox.stub( fakeLocalStorage.store, 'setItem' );
// First phase of the test: up and down arrows
@ -169,13 +178,13 @@
assert.strictEqual( $.scrollTo().scrollTop(), 0, 'scrollTo scrollTop should be set to 0' );
assert.ok( !fakeLocalStorage.setItem.called, 'The metadata hasn\'t been open yet, no entry in localStorage' );
assert.ok( !fakeLocalStorage.store.setItem.called, 'The metadata hasn\'t been open yet, no entry in localStorage' );
keydown.which = 38; // Up arrow
scroller.keydown( keydown );
this.clock.tick( scroller.toggleScrollDuration );
assert.ok( fakeLocalStorage.setItem.calledWithExactly( 'mmv.hasOpenedMetadata', true ), 'localStorage knows that the metadata has been open' );
assert.ok( fakeLocalStorage.store.setItem.calledWithExactly( 'mmv.hasOpenedMetadata', '1' ), 'localStorage knows that the metadata has been open' );
keydown.which = 40; // Down arrow
scroller.keydown( keydown );
@ -211,7 +220,8 @@
var $qf = $( '#qunit-fixture' ),
$container = $( '<div>' ).css( 'height', 100 ).appendTo( $qf ),
$aboveFold = $( '<div>' ).css( 'height', 50 ).appendTo( $container ),
scroller = new mw.mmv.ui.MetadataPanelScroller( $container, $aboveFold ),
localStorage = mw.mmv.testHelpers.getFakeLocalStorage(),
scroller = new mw.mmv.ui.MetadataPanelScroller( $container, $aboveFold, localStorage ),
keydown = $.Event( 'keydown' );
stubScrollFunctions( this.sandbox, scroller );