"resources/src/mediawiki.special",
"resources/src/mediawiki.toolbar",
"resources/src/mediawiki.widgets",
- "resources/src/mediawiki.widgets.visibleByteLimit",
+ "resources/src/mediawiki.widgets.visibleLengthLimit",
"resources/src/jquery/jquery.accessKeyLabel.js",
"resources/src/jquery/jquery.byteLength.js",
- "resources/src/jquery/jquery.byteLimit.js",
"resources/src/jquery/jquery.checkboxShiftClick.js",
"resources/src/jquery/jquery.colorUtil.js",
"resources/src/jquery/jquery.confirmable.js",
"resources/src/jquery/jquery.footHovzer.js",
"resources/src/jquery/jquery.getAttrs.js",
"resources/src/jquery/jquery.hidpi.js",
+ "resources/src/jquery/jquery.lengthLimit.js",
"resources/src/jquery/jquery.localize.js",
"resources/src/jquery/jquery.makeCollapsible.js",
"resources/src/jquery/jquery.spinner.js",
'targets' => [ 'desktop', 'mobile' ],
],
'jquery.byteLimit' => [
- 'scripts' => 'resources/src/jquery/jquery.byteLimit.js',
- 'dependencies' => 'mediawiki.String',
+ 'dependencies' => 'jquery.lengthLimit',
+ 'deprecated' => 'Use "jquery.lengthLimit" instead.',
'targets' => [ 'desktop', 'mobile' ],
],
'jquery.checkboxShiftClick' => [
],
'targets' => [ 'desktop', 'mobile' ],
],
+ 'jquery.lengthLimit' => [
+ 'scripts' => 'resources/src/jquery/jquery.lengthLimit.js',
+ 'dependencies' => 'mediawiki.String',
+ 'targets' => [ 'desktop', 'mobile' ],
+ ],
'jquery.localize' => [
'scripts' => 'resources/src/jquery/jquery.localize.js',
],
],
'dependencies' => [
'mediawiki.RegExp',
- 'jquery.byteLimit',
+ 'jquery.lengthLimit',
],
'messages' => [
'htmlform-chosen-placeholder',
'mediawiki.editfont.styles',
'jquery.textSelection',
'oojs-ui-core',
- 'mediawiki.widgets.visibleByteLimit',
+ 'mediawiki.widgets.visibleLengthLimit',
'mediawiki.api',
],
],
'mediawiki.special.movePage' => [
'scripts' => 'resources/src/mediawiki.special/mediawiki.special.movePage.js',
'dependencies' => [
- 'mediawiki.widgets.visibleByteLimit',
+ 'mediawiki.widgets.visibleLengthLimit',
'mediawiki.widgets',
],
],
],
'mediawiki.legacy.protect' => [
'scripts' => 'resources/src/mediawiki.legacy/protect.js',
- 'dependencies' => 'jquery.byteLimit',
+ 'dependencies' => 'jquery.lengthLimit',
'messages' => [ 'protect-unchain-permissions' ]
],
// Used in the web installer. Test it after modifying this definition!
'targets' => [ 'desktop', 'mobile' ],
],
'mediawiki.widgets.visibleByteLimit' => [
+ 'dependencies' => 'mediawiki.widgets.visibleLengthLimit',
+ 'deprecated' => 'Use "mediawiki.widgets.visibleLengthLimit" instead.',
+ 'targets' => [ 'desktop', 'mobile' ]
+ ],
+ 'mediawiki.widgets.visibleLengthLimit' => [
'scripts' => [
- 'resources/src/mediawiki.widgets.visibleByteLimit/mediawiki.widgets.visibleByteLimit.js'
+ 'resources/src/mediawiki.widgets.visibleLengthLimit/mediawiki.widgets.visibleLengthLimit.js'
],
'dependencies' => [
'oojs-ui-core',
- 'jquery.byteLimit',
+ 'jquery.lengthLimit',
'mediawiki.String',
],
'targets' => [ 'desktop', 'mobile' ]
+++ /dev/null
-/**
- * @class jQuery.plugin.byteLimit
- */
-( function ( $, mw ) {
-
- var
- eventKeys = [
- 'keyup.byteLimit',
- 'keydown.byteLimit',
- 'change.byteLimit',
- 'mouseup.byteLimit',
- 'cut.byteLimit',
- 'paste.byteLimit',
- 'focus.byteLimit',
- 'blur.byteLimit'
- ].join( ' ' ),
- trimByteLength = require( 'mediawiki.String' ).trimByteLength;
-
- /**
- * Utility function to trim down a string, based on byteLimit
- * and given a safe start position. It supports insertion anywhere
- * in the string, so "foo" to "fobaro" if limit is 4 will result in
- * "fobo", not "foba". Basically emulating the native maxlength by
- * reconstructing where the insertion occurred.
- *
- * @method trimByteLength
- * @deprecated Use `require( 'mediawiki.String' ).trimByteLength` instead.
- * @static
- * @param {string} safeVal Known value that was previously returned by this
- * function, if none, pass empty string.
- * @param {string} newVal New value that may have to be trimmed down.
- * @param {number} byteLimit Number of bytes the value may be in size.
- * @param {Function} [fn] See jQuery#byteLimit.
- * @return {Object}
- * @return {string} return.newVal
- * @return {boolean} return.trimmed
- */
- mw.log.deprecate( $, 'trimByteLength', trimByteLength,
- 'Use require( \'mediawiki.String\' ).trimByteLength instead.', '$.trimByteLength' );
-
- /**
- * Enforces a byte limit on an input field, so that UTF-8 entries are counted as well,
- * when, for example, a database field has a byte limit rather than a character limit.
- * Plugin rationale: Browser has native maxlength for number of characters, this plugin
- * exists to limit number of bytes instead.
- *
- * Can be called with a custom limit (to use that limit instead of the maxlength attribute
- * value), a filter function (in case the limit should apply to something other than the
- * exact input value), or both. Order of parameters is important!
- *
- * @param {number} [limit] Limit to enforce, fallsback to maxLength-attribute,
- * called with fetched value as argument.
- * @param {Function} [fn] Function to call on the string before assessing the length.
- * @return {jQuery}
- * @chainable
- */
- $.fn.byteLimit = function ( limit, fn ) {
- // If the first argument is the function,
- // set fn to the first argument's value and ignore the second argument.
- if ( $.isFunction( limit ) ) {
- fn = limit;
- limit = undefined;
- // Either way, verify it is a function so we don't have to call
- // isFunction again after this.
- } else if ( !fn || !$.isFunction( fn ) ) {
- fn = undefined;
- }
-
- // The following is specific to each element in the collection.
- return this.each( function ( i, el ) {
- var $el, elLimit, prevSafeVal;
-
- $el = $( el );
-
- // If no limit was passed to byteLimit(), use the maxlength value.
- // Can't re-use 'limit' variable because it's in the higher scope
- // that would affect the next each() iteration as well.
- // Note that we use attribute to read the value instead of property,
- // because in Chrome the maxLength property by default returns the
- // highest supported value (no indication that it is being enforced
- // by choice). We don't want to bind all of this for some ridiculously
- // high default number, unless it was explicitly set in the HTML.
- // Also cast to a (primitive) number (most commonly because the maxlength
- // attribute contains a string, but theoretically the limit parameter
- // could be something else as well).
- elLimit = Number( limit === undefined ? $el.attr( 'maxlength' ) : limit );
-
- // If there is no (valid) limit passed or found in the property,
- // skip this. The < 0 check is required for Firefox, which returns
- // -1 (instead of undefined) for maxLength if it is not set.
- if ( !elLimit || elLimit < 0 ) {
- return;
- }
-
- if ( fn ) {
- // Save function for reference
- $el.data( 'byteLimit.callback', fn );
- }
-
- // Remove old event handlers (if there are any)
- $el.off( '.byteLimit' );
-
- if ( fn ) {
- // Disable the native maxLength (if there is any), because it interferes
- // with the (differently calculated) byte limit.
- // Aside from being differently calculated (average chars with byteLimit
- // is lower), we also support a callback which can make it to allow longer
- // values (e.g. count "Foo" from "User:Foo").
- // maxLength is a strange property. Removing or setting the property to
- // undefined directly doesn't work. Instead, it can only be unset internally
- // by the browser when removing the associated attribute (Firefox/Chrome).
- // https://bugs.chromium.org/p/chromium/issues/detail?id=136004
- $el.removeAttr( 'maxlength' );
-
- } else {
- // If we don't have a callback the bytelimit can only be lower than the charlimit
- // (that is, there are no characters less than 1 byte in size). So lets (re-)enforce
- // the native limit for efficiency when possible (it will make the while-loop below
- // faster by there being less left to interate over).
- $el.attr( 'maxlength', elLimit );
- }
-
- // Safe base value, used to determine the path between the previous state
- // and the state that triggered the event handler below - and enforce the
- // limit approppiately (e.g. don't chop from the end if text was inserted
- // at the beginning of the string).
- prevSafeVal = '';
-
- // We need to listen to after the change has already happened because we've
- // learned that trying to guess the new value and canceling the event
- // accordingly doesn't work because the new value is not always as simple as:
- // oldValue + String.fromCharCode( e.which ); because of cut, paste, select-drag
- // replacements, and custom input methods and what not.
- // Even though we only trim input after it was changed (never prevent it), we do
- // listen on events that input text, because there are cases where the text has
- // changed while text is being entered and keyup/change will not be fired yet
- // (such as holding down a single key, fires keydown, and after each keydown,
- // we can trim the previous one).
- // See https://www.w3.org/TR/DOM-Level-3-Events/#events-keyboard-event-order for
- // the order and characteristics of the key events.
- $el.on( eventKeys, function () {
- var res = trimByteLength(
- prevSafeVal,
- this.value,
- elLimit,
- fn
- );
-
- // Only set value property if it was trimmed, because whenever the
- // value property is set, the browser needs to re-initiate the text context,
- // which moves the cursor at the end the input, moving it away from wherever it was.
- // This is a side-effect of limiting after the fact.
- if ( res.trimmed === true ) {
- this.value = res.newVal;
- // Trigger a 'change' event to let other scripts attached to this node know that the value
- // was changed. This will also call ourselves again, but that's okay, it'll be a no-op.
- $el.trigger( 'change' );
- }
- // Always adjust prevSafeVal to reflect the input value. Not doing this could cause
- // trimByteLength to compare the new value to an empty string instead of the
- // old value, resulting in trimming always from the end (T42850).
- prevSafeVal = res.newVal;
- } );
- } );
- };
-
- /**
- * @class jQuery
- * @mixins jQuery.plugin.byteLimit
- */
-}( jQuery, mediaWiki ) );
--- /dev/null
+/**
+ * @class jQuery.plugin.lengthLimit
+ */
+( function ( $, mw ) {
+
+ var
+ eventKeys = [
+ 'keyup.lengthLimit',
+ 'keydown.lengthLimit',
+ 'change.lengthLimit',
+ 'mouseup.lengthLimit',
+ 'cut.lengthLimit',
+ 'paste.lengthLimit',
+ 'focus.lengthLimit',
+ 'blur.lengthLimit'
+ ].join( ' ' ),
+ trimByteLength = require( 'mediawiki.String' ).trimByteLength;
+
+ /**
+ * Utility function to trim down a string, based on byteLimit
+ * and given a safe start position. It supports insertion anywhere
+ * in the string, so "foo" to "fobaro" if limit is 4 will result in
+ * "fobo", not "foba". Basically emulating the native maxlength by
+ * reconstructing where the insertion occurred.
+ *
+ * @method trimByteLength
+ * @deprecated Use `require( 'mediawiki.String' ).trimByteLength` instead.
+ * @static
+ * @param {string} safeVal Known value that was previously returned by this
+ * function, if none, pass empty string.
+ * @param {string} newVal New value that may have to be trimmed down.
+ * @param {number} byteLimit Number of bytes the value may be in size.
+ * @param {Function} [fn] See jQuery#byteLimit.
+ * @return {Object}
+ * @return {string} return.newVal
+ * @return {boolean} return.trimmed
+ */
+ mw.log.deprecate( $, 'trimByteLength', trimByteLength,
+ 'Use require( \'mediawiki.String\' ).trimByteLength instead.', '$.trimByteLength' );
+
+ /**
+ * Enforces a byte limit on an input field, so that UTF-8 entries are counted as well,
+ * when, for example, a database field has a byte limit rather than a character limit.
+ * Plugin rationale: Browser has native maxlength for number of characters, this plugin
+ * exists to limit number of bytes instead.
+ *
+ * Can be called with a custom limit (to use that limit instead of the maxlength attribute
+ * value), a filter function (in case the limit should apply to something other than the
+ * exact input value), or both. Order of parameters is important!
+ *
+ * @param {number} [limit] Limit to enforce, fallsback to maxLength-attribute,
+ * called with fetched value as argument.
+ * @param {Function} [fn] Function to call on the string before assessing the length.
+ * @return {jQuery}
+ * @chainable
+ */
+ $.fn.byteLimit = function ( limit, fn ) {
+ // If the first argument is the function,
+ // set fn to the first argument's value and ignore the second argument.
+ if ( $.isFunction( limit ) ) {
+ fn = limit;
+ limit = undefined;
+ // Either way, verify it is a function so we don't have to call
+ // isFunction again after this.
+ } else if ( !fn || !$.isFunction( fn ) ) {
+ fn = undefined;
+ }
+
+ // The following is specific to each element in the collection.
+ return this.each( function ( i, el ) {
+ var $el, elLimit, prevSafeVal;
+
+ $el = $( el );
+
+ // If no limit was passed to byteLimit(), use the maxlength value.
+ // Can't re-use 'limit' variable because it's in the higher scope
+ // that would affect the next each() iteration as well.
+ // Note that we use attribute to read the value instead of property,
+ // because in Chrome the maxLength property by default returns the
+ // highest supported value (no indication that it is being enforced
+ // by choice). We don't want to bind all of this for some ridiculously
+ // high default number, unless it was explicitly set in the HTML.
+ // Also cast to a (primitive) number (most commonly because the maxlength
+ // attribute contains a string, but theoretically the limit parameter
+ // could be something else as well).
+ elLimit = Number( limit === undefined ? $el.attr( 'maxlength' ) : limit );
+
+ // If there is no (valid) limit passed or found in the property,
+ // skip this. The < 0 check is required for Firefox, which returns
+ // -1 (instead of undefined) for maxLength if it is not set.
+ if ( !elLimit || elLimit < 0 ) {
+ return;
+ }
+
+ if ( fn ) {
+ // Save function for reference
+ $el.data( 'byteLimit.callback', fn );
+ }
+
+ // Remove old event handlers (if there are any)
+ $el.off( '.byteLimit' );
+
+ if ( fn ) {
+ // Disable the native maxLength (if there is any), because it interferes
+ // with the (differently calculated) byte limit.
+ // Aside from being differently calculated (average chars with byteLimit
+ // is lower), we also support a callback which can make it to allow longer
+ // values (e.g. count "Foo" from "User:Foo").
+ // maxLength is a strange property. Removing or setting the property to
+ // undefined directly doesn't work. Instead, it can only be unset internally
+ // by the browser when removing the associated attribute (Firefox/Chrome).
+ // https://bugs.chromium.org/p/chromium/issues/detail?id=136004
+ $el.removeAttr( 'maxlength' );
+
+ } else {
+ // If we don't have a callback the bytelimit can only be lower than the charlimit
+ // (that is, there are no characters less than 1 byte in size). So lets (re-)enforce
+ // the native limit for efficiency when possible (it will make the while-loop below
+ // faster by there being less left to interate over).
+ $el.attr( 'maxlength', elLimit );
+ }
+
+ // Safe base value, used to determine the path between the previous state
+ // and the state that triggered the event handler below - and enforce the
+ // limit approppiately (e.g. don't chop from the end if text was inserted
+ // at the beginning of the string).
+ prevSafeVal = '';
+
+ // We need to listen to after the change has already happened because we've
+ // learned that trying to guess the new value and canceling the event
+ // accordingly doesn't work because the new value is not always as simple as:
+ // oldValue + String.fromCharCode( e.which ); because of cut, paste, select-drag
+ // replacements, and custom input methods and what not.
+ // Even though we only trim input after it was changed (never prevent it), we do
+ // listen on events that input text, because there are cases where the text has
+ // changed while text is being entered and keyup/change will not be fired yet
+ // (such as holding down a single key, fires keydown, and after each keydown,
+ // we can trim the previous one).
+ // See https://www.w3.org/TR/DOM-Level-3-Events/#events-keyboard-event-order for
+ // the order and characteristics of the key events.
+ $el.on( eventKeys, function () {
+ var res = trimByteLength(
+ prevSafeVal,
+ this.value,
+ elLimit,
+ fn
+ );
+
+ // Only set value property if it was trimmed, because whenever the
+ // value property is set, the browser needs to re-initiate the text context,
+ // which moves the cursor at the end the input, moving it away from wherever it was.
+ // This is a side-effect of limiting after the fact.
+ if ( res.trimmed === true ) {
+ this.value = res.newVal;
+ // Trigger a 'change' event to let other scripts attached to this node know that the value
+ // was changed. This will also call ourselves again, but that's okay, it'll be a no-op.
+ $el.trigger( 'change' );
+ }
+ // Always adjust prevSafeVal to reflect the input value. Not doing this could cause
+ // trimByteLength to compare the new value to an empty string instead of the
+ // old value, resulting in trimming always from the end (T42850).
+ prevSafeVal = res.newVal;
+ } );
+ } );
+ };
+
+ /**
+ * @class jQuery
+ * @mixins jQuery.plugin.lengthLimit
+ */
+}( jQuery, mediaWiki ) );
+++ /dev/null
-( function ( mw ) {
-
- var byteLength = require( 'mediawiki.String' ).byteLength;
-
- /**
- * @class mw.widgets
- */
-
- /**
- * Add a visible byte limit label to a TextInputWidget.
- *
- * Uses jQuery#byteLimit to enforce the limit.
- *
- * @param {OO.ui.TextInputWidget} textInputWidget Text input widget
- * @param {number} [limit] Byte limit, defaults to $input's maxlength
- */
- mw.widgets.visibleByteLimit = function ( textInputWidget, limit ) {
- limit = limit || +textInputWidget.$input.attr( 'maxlength' );
-
- function updateCount() {
- textInputWidget.setLabel( ( limit - byteLength( textInputWidget.getValue() ) ).toString() );
- }
- textInputWidget.on( 'change', updateCount );
- // Initialise value
- updateCount();
-
- // Actually enforce limit
- textInputWidget.$input.byteLimit( limit );
- };
-
-}( mediaWiki ) );
--- /dev/null
+( function ( mw ) {
+
+ var byteLength = require( 'mediawiki.String' ).byteLength;
+
+ /**
+ * @class mw.widgets
+ */
+
+ /**
+ * Add a visible byte limit label to a TextInputWidget.
+ *
+ * Uses jQuery#byteLimit to enforce the limit.
+ *
+ * @param {OO.ui.TextInputWidget} textInputWidget Text input widget
+ * @param {number} [limit] Byte limit, defaults to $input's maxlength
+ */
+ mw.widgets.visibleByteLimit = function ( textInputWidget, limit ) {
+ limit = limit || +textInputWidget.$input.attr( 'maxlength' );
+
+ function updateCount() {
+ textInputWidget.setLabel( ( limit - byteLength( textInputWidget.getValue() ) ).toString() );
+ }
+ textInputWidget.on( 'change', updateCount );
+ // Initialise value
+ updateCount();
+
+ // Actually enforce limit
+ textInputWidget.$input.byteLimit( limit );
+ };
+
+}( mediaWiki ) );
'scripts' => [
'tests/qunit/suites/resources/startup.test.js',
'tests/qunit/suites/resources/jquery/jquery.accessKeyLabel.test.js',
- 'tests/qunit/suites/resources/jquery/jquery.byteLimit.test.js',
'tests/qunit/suites/resources/jquery/jquery.color.test.js',
'tests/qunit/suites/resources/jquery/jquery.colorUtil.test.js',
'tests/qunit/suites/resources/jquery/jquery.getAttrs.test.js',
'tests/qunit/suites/resources/jquery/jquery.hidpi.test.js',
'tests/qunit/suites/resources/jquery/jquery.highlightText.test.js',
+ 'tests/qunit/suites/resources/jquery/jquery.lengthLimit.test.js',
'tests/qunit/suites/resources/jquery/jquery.localize.test.js',
'tests/qunit/suites/resources/jquery/jquery.makeCollapsible.test.js',
'tests/qunit/suites/resources/jquery/jquery.tabIndex.test.js',
],
'dependencies' => [
'jquery.accessKeyLabel',
- 'jquery.byteLimit',
'jquery.color',
'jquery.colorUtil',
'jquery.getAttrs',
'jquery.hidpi',
'jquery.highlightText',
+ 'jquery.lengthLimit',
'jquery.localize',
'jquery.makeCollapsible',
'jquery.tabIndex',
+++ /dev/null
-( function ( $, mw ) {
- var simpleSample, U_20AC, poop, mbSample;
-
- QUnit.module( 'jquery.byteLimit', QUnit.newMwEnvironment() );
-
- // Simple sample (20 chars, 20 bytes)
- simpleSample = '12345678901234567890';
-
- // 3 bytes (euro-symbol)
- U_20AC = '\u20AC';
-
- // Outside of the BMP (pile of poo emoji)
- poop = '\uD83D\uDCA9'; // "💩"
-
- // Multi-byte sample (22 chars, 26 bytes)
- mbSample = '1234567890' + U_20AC + '1234567890' + U_20AC;
-
- // Basic sendkey-implementation
- function addChars( $input, charstr ) {
- var c, len;
-
- function x( $input, i ) {
- // Add character to the value
- return $input.val() + charstr.charAt( i );
- }
-
- for ( c = 0, len = charstr.length; c < len; c += 1 ) {
- $input
- .val( x( $input, c ) )
- .trigger( 'change' );
- }
- }
-
- /**
- * Test factory for $.fn.byteLimit
- *
- * @param {Object} options
- * @param {string} options.description Test name
- * @param {jQuery} options.$input jQuery object in an input element
- * @param {string} options.sample Sequence of characters to simulate being
- * added one by one
- * @param {string} options.expected Expected final value of `$input`
- */
- function byteLimitTest( options ) {
- var opt = $.extend( {
- description: '',
- $input: null,
- sample: '',
- expected: ''
- }, options );
-
- QUnit.test( opt.description, function ( assert ) {
- opt.$input.appendTo( '#qunit-fixture' );
-
- // Simulate pressing keys for each of the sample characters
- addChars( opt.$input, opt.sample );
-
- assert.equal(
- opt.$input.val(),
- opt.expected,
- 'New value matches the expected string'
- );
- } );
- }
-
- byteLimitTest( {
- description: 'Plain text input',
- $input: $( '<input>' ).attr( 'type', 'text' ),
- sample: simpleSample,
- expected: simpleSample
- } );
-
- byteLimitTest( {
- description: 'Plain text input. Calling byteLimit with no parameters and no maxlength attribute (T38310)',
- $input: $( '<input>' ).attr( 'type', 'text' )
- .byteLimit(),
- sample: simpleSample,
- expected: simpleSample
- } );
-
- byteLimitTest( {
- description: 'Limit using the maxlength attribute',
- $input: $( '<input>' ).attr( 'type', 'text' )
- .attr( 'maxlength', '10' )
- .byteLimit(),
- sample: simpleSample,
- expected: '1234567890'
- } );
-
- byteLimitTest( {
- description: 'Limit using a custom value',
- $input: $( '<input>' ).attr( 'type', 'text' )
- .byteLimit( 10 ),
- sample: simpleSample,
- expected: '1234567890'
- } );
-
- byteLimitTest( {
- description: 'Limit using a custom value, overriding maxlength attribute',
- $input: $( '<input>' ).attr( 'type', 'text' )
- .attr( 'maxlength', '10' )
- .byteLimit( 15 ),
- sample: simpleSample,
- expected: '123456789012345'
- } );
-
- byteLimitTest( {
- description: 'Limit using a custom value (multibyte)',
- $input: $( '<input>' ).attr( 'type', 'text' )
- .byteLimit( 14 ),
- sample: mbSample,
- expected: '1234567890' + U_20AC + '1'
- } );
-
- byteLimitTest( {
- description: 'Limit using a custom value (multibyte, outside BMP)',
- $input: $( '<input>' ).attr( 'type', 'text' )
- .byteLimit( 3 ),
- sample: poop,
- expected: ''
- } );
-
- byteLimitTest( {
- description: 'Limit using a custom value (multibyte) overlapping a byte',
- $input: $( '<input>' ).attr( 'type', 'text' )
- .byteLimit( 12 ),
- sample: mbSample,
- expected: '123456789012'
- } );
-
- byteLimitTest( {
- description: 'Pass the limit and a callback as input filter',
- $input: $( '<input>' ).attr( 'type', 'text' )
- .byteLimit( 6, function ( val ) {
- var title = mw.Title.newFromText( String( val ) );
- // Return without namespace prefix
- return title ? title.getMain() : '';
- } ),
- sample: 'User:Sample',
- expected: 'User:Sample'
- } );
-
- byteLimitTest( {
- description: 'Limit using the maxlength attribute and pass a callback as input filter',
- $input: $( '<input>' ).attr( 'type', 'text' )
- .attr( 'maxlength', '6' )
- .byteLimit( function ( val ) {
- var title = mw.Title.newFromText( String( val ) );
- // Return without namespace prefix
- return title ? title.getMain() : '';
- } ),
- sample: 'User:Sample',
- expected: 'User:Sample'
- } );
-
- byteLimitTest( {
- description: 'Pass the limit and a callback as input filter',
- $input: $( '<input>' ).attr( 'type', 'text' )
- .byteLimit( 6, function ( val ) {
- var title = mw.Title.newFromText( String( val ) );
- // Return without namespace prefix
- return title ? title.getMain() : '';
- } ),
- sample: 'User:Example',
- // The callback alters the value to be used to calculeate
- // the length. The altered value is "Exampl" which has
- // a length of 6, the "e" would exceed the limit.
- expected: 'User:Exampl'
- } );
-
- byteLimitTest( {
- description: 'Input filter that increases the length',
- $input: $( '<input>' ).attr( 'type', 'text' )
- .byteLimit( 10, function ( text ) {
- return 'prefix' + text;
- } ),
- sample: simpleSample,
- // Prefix adds 6 characters, limit is reached after 4
- expected: '1234'
- } );
-
- // Regression tests for T43450
- byteLimitTest( {
- description: 'Input filter of which the base exceeds the limit',
- $input: $( '<input>' ).attr( 'type', 'text' )
- .byteLimit( 3, function ( text ) {
- return 'prefix' + text;
- } ),
- sample: simpleSample,
- expected: ''
- } );
-
- QUnit.test( 'Confirm properties and attributes set', function ( assert ) {
- var $el;
-
- $el = $( '<input>' ).attr( 'type', 'text' )
- .attr( 'maxlength', '7' )
- .appendTo( '#qunit-fixture' )
- .byteLimit();
-
- assert.strictEqual( $el.attr( 'maxlength' ), '7', 'maxlength attribute unchanged for simple limit' );
-
- $el = $( '<input>' ).attr( 'type', 'text' )
- .attr( 'maxlength', '7' )
- .appendTo( '#qunit-fixture' )
- .byteLimit( 12 );
-
- assert.strictEqual( $el.attr( 'maxlength' ), '12', 'maxlength attribute updated for custom limit' );
-
- $el = $( '<input>' ).attr( 'type', 'text' )
- .attr( 'maxlength', '7' )
- .appendTo( '#qunit-fixture' )
- .byteLimit( 12, function ( val ) {
- return val;
- } );
-
- assert.strictEqual( $el.attr( 'maxlength' ), undefined, 'maxlength attribute removed for limit with callback' );
-
- $( '<input>' ).attr( 'type', 'text' )
- .addClass( 'mw-test-byteLimit-foo' )
- .attr( 'maxlength', '7' )
- .appendTo( '#qunit-fixture' );
-
- $( '<input>' ).attr( 'type', 'text' )
- .addClass( 'mw-test-byteLimit-foo' )
- .attr( 'maxlength', '12' )
- .appendTo( '#qunit-fixture' );
-
- $el = $( '.mw-test-byteLimit-foo' );
-
- assert.strictEqual( $el.length, 2, 'Verify that there are no other elements clashing with this test suite' );
-
- $el.byteLimit();
- } );
-
- QUnit.test( 'Trim from insertion when limit exceeded', function ( assert ) {
- var $el;
-
- // Use a new <input> because the bug only occurs on the first time
- // the limit it reached (T42850)
- $el = $( '<input>' ).attr( 'type', 'text' )
- .appendTo( '#qunit-fixture' )
- .byteLimit( 3 )
- .val( 'abc' ).trigger( 'change' )
- .val( 'zabc' ).trigger( 'change' );
-
- assert.strictEqual( $el.val(), 'abc', 'Trim from the insertion point (at 0), not the end' );
-
- $el = $( '<input>' ).attr( 'type', 'text' )
- .appendTo( '#qunit-fixture' )
- .byteLimit( 3 )
- .val( 'abc' ).trigger( 'change' )
- .val( 'azbc' ).trigger( 'change' );
-
- assert.strictEqual( $el.val(), 'abc', 'Trim from the insertion point (at 1), not the end' );
- } );
-
- QUnit.test( 'Do not cut up false matching substrings in emoji insertions', function ( assert ) {
- var $el,
- oldVal = '\uD83D\uDCA9\uD83D\uDCA9', // "💩💩"
- newVal = '\uD83D\uDCA9\uD83D\uDCB9\uD83E\uDCA9\uD83D\uDCA9', // "💩💹🢩💩"
- expected = '\uD83D\uDCA9\uD83D\uDCB9\uD83D\uDCA9'; // "💩💹💩"
-
- // Possible bad results:
- // * With no surrogate support:
- // '\uD83D\uDCA9\uD83D\uDCB9\uD83E\uDCA9' "💩💹🢩"
- // * With correct trimming but bad detection of inserted text:
- // '\uD83D\uDCA9\uD83D\uDCB9\uDCA9' "💩💹�"
-
- $el = $( '<input>' ).attr( 'type', 'text' )
- .appendTo( '#qunit-fixture' )
- .byteLimit( 12 )
- .val( oldVal ).trigger( 'change' )
- .val( newVal ).trigger( 'change' );
-
- assert.strictEqual( $el.val(), expected, 'Pasted emoji correctly trimmed at the end' );
- } );
-
- byteLimitTest( {
- description: 'Unpaired surrogates do not crash',
- $input: $( '<input>' ).attr( 'type', 'text' ).byteLimit( 4 ),
- sample: '\uD800\uD800\uDFFF',
- expected: '\uD800'
- } );
-
-}( jQuery, mediaWiki ) );
--- /dev/null
+( function ( $, mw ) {
+ var simpleSample, U_20AC, poop, mbSample;
+
+ QUnit.module( 'jquery.lengthLimit', QUnit.newMwEnvironment() );
+
+ // Simple sample (20 chars, 20 bytes)
+ simpleSample = '12345678901234567890';
+
+ // 3 bytes (euro-symbol)
+ U_20AC = '\u20AC';
+
+ // Outside of the BMP (pile of poo emoji)
+ poop = '\uD83D\uDCA9'; // "💩"
+
+ // Multi-byte sample (22 chars, 26 bytes)
+ mbSample = '1234567890' + U_20AC + '1234567890' + U_20AC;
+
+ // Basic sendkey-implementation
+ function addChars( $input, charstr ) {
+ var c, len;
+
+ function x( $input, i ) {
+ // Add character to the value
+ return $input.val() + charstr.charAt( i );
+ }
+
+ for ( c = 0, len = charstr.length; c < len; c += 1 ) {
+ $input
+ .val( x( $input, c ) )
+ .trigger( 'change' );
+ }
+ }
+
+ /**
+ * Test factory for $.fn.byteLimit
+ *
+ * @param {Object} options
+ * @param {string} options.description Test name
+ * @param {jQuery} options.$input jQuery object in an input element
+ * @param {string} options.sample Sequence of characters to simulate being
+ * added one by one
+ * @param {string} options.expected Expected final value of `$input`
+ */
+ function byteLimitTest( options ) {
+ var opt = $.extend( {
+ description: '',
+ $input: null,
+ sample: '',
+ expected: ''
+ }, options );
+
+ QUnit.test( opt.description, function ( assert ) {
+ opt.$input.appendTo( '#qunit-fixture' );
+
+ // Simulate pressing keys for each of the sample characters
+ addChars( opt.$input, opt.sample );
+
+ assert.equal(
+ opt.$input.val(),
+ opt.expected,
+ 'New value matches the expected string'
+ );
+ } );
+ }
+
+ byteLimitTest( {
+ description: 'Plain text input',
+ $input: $( '<input>' ).attr( 'type', 'text' ),
+ sample: simpleSample,
+ expected: simpleSample
+ } );
+
+ byteLimitTest( {
+ description: 'Plain text input. Calling byteLimit with no parameters and no maxlength attribute (T38310)',
+ $input: $( '<input>' ).attr( 'type', 'text' )
+ .byteLimit(),
+ sample: simpleSample,
+ expected: simpleSample
+ } );
+
+ byteLimitTest( {
+ description: 'Limit using the maxlength attribute',
+ $input: $( '<input>' ).attr( 'type', 'text' )
+ .attr( 'maxlength', '10' )
+ .byteLimit(),
+ sample: simpleSample,
+ expected: '1234567890'
+ } );
+
+ byteLimitTest( {
+ description: 'Limit using a custom value',
+ $input: $( '<input>' ).attr( 'type', 'text' )
+ .byteLimit( 10 ),
+ sample: simpleSample,
+ expected: '1234567890'
+ } );
+
+ byteLimitTest( {
+ description: 'Limit using a custom value, overriding maxlength attribute',
+ $input: $( '<input>' ).attr( 'type', 'text' )
+ .attr( 'maxlength', '10' )
+ .byteLimit( 15 ),
+ sample: simpleSample,
+ expected: '123456789012345'
+ } );
+
+ byteLimitTest( {
+ description: 'Limit using a custom value (multibyte)',
+ $input: $( '<input>' ).attr( 'type', 'text' )
+ .byteLimit( 14 ),
+ sample: mbSample,
+ expected: '1234567890' + U_20AC + '1'
+ } );
+
+ byteLimitTest( {
+ description: 'Limit using a custom value (multibyte, outside BMP)',
+ $input: $( '<input>' ).attr( 'type', 'text' )
+ .byteLimit( 3 ),
+ sample: poop,
+ expected: ''
+ } );
+
+ byteLimitTest( {
+ description: 'Limit using a custom value (multibyte) overlapping a byte',
+ $input: $( '<input>' ).attr( 'type', 'text' )
+ .byteLimit( 12 ),
+ sample: mbSample,
+ expected: '123456789012'
+ } );
+
+ byteLimitTest( {
+ description: 'Pass the limit and a callback as input filter',
+ $input: $( '<input>' ).attr( 'type', 'text' )
+ .byteLimit( 6, function ( val ) {
+ var title = mw.Title.newFromText( String( val ) );
+ // Return without namespace prefix
+ return title ? title.getMain() : '';
+ } ),
+ sample: 'User:Sample',
+ expected: 'User:Sample'
+ } );
+
+ byteLimitTest( {
+ description: 'Limit using the maxlength attribute and pass a callback as input filter',
+ $input: $( '<input>' ).attr( 'type', 'text' )
+ .attr( 'maxlength', '6' )
+ .byteLimit( function ( val ) {
+ var title = mw.Title.newFromText( String( val ) );
+ // Return without namespace prefix
+ return title ? title.getMain() : '';
+ } ),
+ sample: 'User:Sample',
+ expected: 'User:Sample'
+ } );
+
+ byteLimitTest( {
+ description: 'Pass the limit and a callback as input filter',
+ $input: $( '<input>' ).attr( 'type', 'text' )
+ .byteLimit( 6, function ( val ) {
+ var title = mw.Title.newFromText( String( val ) );
+ // Return without namespace prefix
+ return title ? title.getMain() : '';
+ } ),
+ sample: 'User:Example',
+ // The callback alters the value to be used to calculeate
+ // the length. The altered value is "Exampl" which has
+ // a length of 6, the "e" would exceed the limit.
+ expected: 'User:Exampl'
+ } );
+
+ byteLimitTest( {
+ description: 'Input filter that increases the length',
+ $input: $( '<input>' ).attr( 'type', 'text' )
+ .byteLimit( 10, function ( text ) {
+ return 'prefix' + text;
+ } ),
+ sample: simpleSample,
+ // Prefix adds 6 characters, limit is reached after 4
+ expected: '1234'
+ } );
+
+ // Regression tests for T43450
+ byteLimitTest( {
+ description: 'Input filter of which the base exceeds the limit',
+ $input: $( '<input>' ).attr( 'type', 'text' )
+ .byteLimit( 3, function ( text ) {
+ return 'prefix' + text;
+ } ),
+ sample: simpleSample,
+ expected: ''
+ } );
+
+ QUnit.test( 'Confirm properties and attributes set', function ( assert ) {
+ var $el;
+
+ $el = $( '<input>' ).attr( 'type', 'text' )
+ .attr( 'maxlength', '7' )
+ .appendTo( '#qunit-fixture' )
+ .byteLimit();
+
+ assert.strictEqual( $el.attr( 'maxlength' ), '7', 'maxlength attribute unchanged for simple limit' );
+
+ $el = $( '<input>' ).attr( 'type', 'text' )
+ .attr( 'maxlength', '7' )
+ .appendTo( '#qunit-fixture' )
+ .byteLimit( 12 );
+
+ assert.strictEqual( $el.attr( 'maxlength' ), '12', 'maxlength attribute updated for custom limit' );
+
+ $el = $( '<input>' ).attr( 'type', 'text' )
+ .attr( 'maxlength', '7' )
+ .appendTo( '#qunit-fixture' )
+ .byteLimit( 12, function ( val ) {
+ return val;
+ } );
+
+ assert.strictEqual( $el.attr( 'maxlength' ), undefined, 'maxlength attribute removed for limit with callback' );
+
+ $( '<input>' ).attr( 'type', 'text' )
+ .addClass( 'mw-test-byteLimit-foo' )
+ .attr( 'maxlength', '7' )
+ .appendTo( '#qunit-fixture' );
+
+ $( '<input>' ).attr( 'type', 'text' )
+ .addClass( 'mw-test-byteLimit-foo' )
+ .attr( 'maxlength', '12' )
+ .appendTo( '#qunit-fixture' );
+
+ $el = $( '.mw-test-byteLimit-foo' );
+
+ assert.strictEqual( $el.length, 2, 'Verify that there are no other elements clashing with this test suite' );
+
+ $el.byteLimit();
+ } );
+
+ QUnit.test( 'Trim from insertion when limit exceeded', function ( assert ) {
+ var $el;
+
+ // Use a new <input> because the bug only occurs on the first time
+ // the limit it reached (T42850)
+ $el = $( '<input>' ).attr( 'type', 'text' )
+ .appendTo( '#qunit-fixture' )
+ .byteLimit( 3 )
+ .val( 'abc' ).trigger( 'change' )
+ .val( 'zabc' ).trigger( 'change' );
+
+ assert.strictEqual( $el.val(), 'abc', 'Trim from the insertion point (at 0), not the end' );
+
+ $el = $( '<input>' ).attr( 'type', 'text' )
+ .appendTo( '#qunit-fixture' )
+ .byteLimit( 3 )
+ .val( 'abc' ).trigger( 'change' )
+ .val( 'azbc' ).trigger( 'change' );
+
+ assert.strictEqual( $el.val(), 'abc', 'Trim from the insertion point (at 1), not the end' );
+ } );
+
+ QUnit.test( 'Do not cut up false matching substrings in emoji insertions', function ( assert ) {
+ var $el,
+ oldVal = '\uD83D\uDCA9\uD83D\uDCA9', // "💩💩"
+ newVal = '\uD83D\uDCA9\uD83D\uDCB9\uD83E\uDCA9\uD83D\uDCA9', // "💩💹🢩💩"
+ expected = '\uD83D\uDCA9\uD83D\uDCB9\uD83D\uDCA9'; // "💩💹💩"
+
+ // Possible bad results:
+ // * With no surrogate support:
+ // '\uD83D\uDCA9\uD83D\uDCB9\uD83E\uDCA9' "💩💹🢩"
+ // * With correct trimming but bad detection of inserted text:
+ // '\uD83D\uDCA9\uD83D\uDCB9\uDCA9' "💩💹�"
+
+ $el = $( '<input>' ).attr( 'type', 'text' )
+ .appendTo( '#qunit-fixture' )
+ .byteLimit( 12 )
+ .val( oldVal ).trigger( 'change' )
+ .val( newVal ).trigger( 'change' );
+
+ assert.strictEqual( $el.val(), expected, 'Pasted emoji correctly trimmed at the end' );
+ } );
+
+ byteLimitTest( {
+ description: 'Unpaired surrogates do not crash',
+ $input: $( '<input>' ).attr( 'type', 'text' ).byteLimit( 4 ),
+ sample: '\uD800\uD800\uDFFF',
+ expected: '\uD800'
+ } );
+
+}( jQuery, mediaWiki ) );