Add exceptions in mw.Title where mb_strtoupper doesn't match String.toUpperCase
authorEd Sanders <esanders@wikimedia.org>
Fri, 7 Oct 2016 17:14:03 +0000 (13:14 -0400)
committerBartosz Dziewoński <matma.rex@gmail.com>
Wed, 23 Nov 2016 17:45:44 +0000 (18:45 +0100)
Bug: T147646
Bug: T141723
Change-Id: Ic7a3d0cebbf4aec507db195ba8f587cecc1992aa

jsduck.json
resources/Resources.php
resources/src/mediawiki/mediawiki.Title.js
resources/src/mediawiki/mediawiki.Title.phpCharToUpper.js [new file with mode: 0644]
tests/qunit/suites/resources/mediawiki/mediawiki.Title.test.js

index 5b18365..228c5c4 100644 (file)
@@ -9,6 +9,7 @@
        "--warnings-exit-nonzero": true,
        "--external": "Blob,File,HTMLDocument,HTMLElement,HTMLIframeElement,HTMLInputElement,KeyboardEvent,MouseEvent,Node,Window,XMLDocument",
        "--output": "docs/js",
+       "--exclude": "resources/src/mediawiki/mediawiki.Title.phpCharToUpper.js",
        "--": [
                "maintenance/jsduck/external.js",
                "resources/src/mediawiki",
index 52635f5..7a16e60 100644 (file)
@@ -1194,7 +1194,10 @@ return [
                'targets' => [ 'desktop', 'mobile' ],
        ],
        'mediawiki.Title' => [
-               'scripts' => 'resources/src/mediawiki/mediawiki.Title.js',
+               'scripts' => [
+                       'resources/src/mediawiki/mediawiki.Title.js',
+                       'resources/src/mediawiki/mediawiki.Title.phpCharToUpper.js',
+               ],
                'dependencies' => [
                        'jquery.byteLength',
                        'mediawiki.util',
index 9203e5e..0e2af50 100644 (file)
                        ) {
                                return this.title;
                        }
-                       return this.title[ 0 ].toUpperCase() + this.title.slice( 1 );
+                       // PHP's strtoupper differs from String.toUpperCase in a number of cases
+                       // Bug: T147646
+                       return mw.Title.phpCharToUpper( this.title[ 0 ] ) + this.title.slice( 1 );
                },
 
                /**
diff --git a/resources/src/mediawiki/mediawiki.Title.phpCharToUpper.js b/resources/src/mediawiki/mediawiki.Title.phpCharToUpper.js
new file mode 100644 (file)
index 0000000..2b39c9a
--- /dev/null
@@ -0,0 +1,255 @@
+// This file can't be parsed by JSDuck due to <https://github.com/tenderlove/rkelly/issues/35>.
+// (It is excluded in jsduck.json.)
+// ESLint suggests unquoting some object keys, which would render the file unparseable by Opera 12.
+/* eslint-disable quote-props */
+( function ( mw ) {
+       var toUpperMapping = {
+               'ß': 'ß',
+               'ʼn': 'ʼn',
+               'Dž': 'Dž',
+               'dž': 'Dž',
+               'Lj': 'Lj',
+               'lj': 'Lj',
+               'Nj': 'Nj',
+               'nj': 'Nj',
+               'ǰ': 'ǰ',
+               'Dz': 'Dz',
+               'dz': 'Dz',
+               'ʝ': 'Ʝ',
+               'ͅ': 'ͅ',
+               'ΐ': 'ΐ',
+               'ΰ': 'ΰ',
+               'և': 'և',
+               'ᏸ': 'Ᏸ',
+               'ᏹ': 'Ᏹ',
+               'ᏺ': 'Ᏺ',
+               'ᏻ': 'Ᏻ',
+               'ᏼ': 'Ᏼ',
+               'ᏽ': 'Ᏽ',
+               'ẖ': 'ẖ',
+               'ẗ': 'ẗ',
+               'ẘ': 'ẘ',
+               'ẙ': 'ẙ',
+               'ẚ': 'ẚ',
+               'ὐ': 'ὐ',
+               'ὒ': 'ὒ',
+               'ὔ': 'ὔ',
+               'ὖ': 'ὖ',
+               'ᾀ': 'ᾈ',
+               'ᾁ': 'ᾉ',
+               'ᾂ': 'ᾊ',
+               'ᾃ': 'ᾋ',
+               'ᾄ': 'ᾌ',
+               'ᾅ': 'ᾍ',
+               'ᾆ': 'ᾎ',
+               'ᾇ': 'ᾏ',
+               'ᾈ': 'ᾈ',
+               'ᾉ': 'ᾉ',
+               'ᾊ': 'ᾊ',
+               'ᾋ': 'ᾋ',
+               'ᾌ': 'ᾌ',
+               'ᾍ': 'ᾍ',
+               'ᾎ': 'ᾎ',
+               'ᾏ': 'ᾏ',
+               'ᾐ': 'ᾘ',
+               'ᾑ': 'ᾙ',
+               'ᾒ': 'ᾚ',
+               'ᾓ': 'ᾛ',
+               'ᾔ': 'ᾜ',
+               'ᾕ': 'ᾝ',
+               'ᾖ': 'ᾞ',
+               'ᾗ': 'ᾟ',
+               'ᾘ': 'ᾘ',
+               'ᾙ': 'ᾙ',
+               'ᾚ': 'ᾚ',
+               'ᾛ': 'ᾛ',
+               'ᾜ': 'ᾜ',
+               'ᾝ': 'ᾝ',
+               'ᾞ': 'ᾞ',
+               'ᾟ': 'ᾟ',
+               'ᾠ': 'ᾨ',
+               'ᾡ': 'ᾩ',
+               'ᾢ': 'ᾪ',
+               'ᾣ': 'ᾫ',
+               'ᾤ': 'ᾬ',
+               'ᾥ': 'ᾭ',
+               'ᾦ': 'ᾮ',
+               'ᾧ': 'ᾯ',
+               'ᾨ': 'ᾨ',
+               'ᾩ': 'ᾩ',
+               'ᾪ': 'ᾪ',
+               'ᾫ': 'ᾫ',
+               'ᾬ': 'ᾬ',
+               'ᾭ': 'ᾭ',
+               'ᾮ': 'ᾮ',
+               'ᾯ': 'ᾯ',
+               'ᾲ': 'ᾲ',
+               'ᾳ': 'ᾼ',
+               'ᾴ': 'ᾴ',
+               'ᾶ': 'ᾶ',
+               'ᾷ': 'ᾷ',
+               'ᾼ': 'ᾼ',
+               'ῂ': 'ῂ',
+               'ῃ': 'ῌ',
+               'ῄ': 'ῄ',
+               'ῆ': 'ῆ',
+               'ῇ': 'ῇ',
+               'ῌ': 'ῌ',
+               'ῒ': 'ῒ',
+               'ΐ': 'ΐ',
+               'ῖ': 'ῖ',
+               'ῗ': 'ῗ',
+               'ῢ': 'ῢ',
+               'ΰ': 'ΰ',
+               'ῤ': 'ῤ',
+               'ῦ': 'ῦ',
+               'ῧ': 'ῧ',
+               'ῲ': 'ῲ',
+               'ῳ': 'ῼ',
+               'ῴ': 'ῴ',
+               'ῶ': 'ῶ',
+               'ῷ': 'ῷ',
+               'ῼ': 'ῼ',
+               'ⅰ': 'ⅰ',
+               'ⅱ': 'ⅱ',
+               'ⅲ': 'ⅲ',
+               'ⅳ': 'ⅳ',
+               'ⅴ': 'ⅴ',
+               'ⅵ': 'ⅵ',
+               'ⅶ': 'ⅶ',
+               'ⅷ': 'ⅷ',
+               'ⅸ': 'ⅸ',
+               'ⅹ': 'ⅹ',
+               'ⅺ': 'ⅺ',
+               'ⅻ': 'ⅻ',
+               'ⅼ': 'ⅼ',
+               'ⅽ': 'ⅽ',
+               'ⅾ': 'ⅾ',
+               'ⅿ': 'ⅿ',
+               'ⓐ': 'ⓐ',
+               'ⓑ': 'ⓑ',
+               'ⓒ': 'ⓒ',
+               'ⓓ': 'ⓓ',
+               'ⓔ': 'ⓔ',
+               'ⓕ': 'ⓕ',
+               'ⓖ': 'ⓖ',
+               'ⓗ': 'ⓗ',
+               'ⓘ': 'ⓘ',
+               'ⓙ': 'ⓙ',
+               'ⓚ': 'ⓚ',
+               'ⓛ': 'ⓛ',
+               'ⓜ': 'ⓜ',
+               'ⓝ': 'ⓝ',
+               'ⓞ': 'ⓞ',
+               'ⓟ': 'ⓟ',
+               'ⓠ': 'ⓠ',
+               'ⓡ': 'ⓡ',
+               'ⓢ': 'ⓢ',
+               'ⓣ': 'ⓣ',
+               'ⓤ': 'ⓤ',
+               'ⓥ': 'ⓥ',
+               'ⓦ': 'ⓦ',
+               'ⓧ': 'ⓧ',
+               'ⓨ': 'ⓨ',
+               'ⓩ': 'ⓩ',
+               'ꞵ': 'Ꞵ',
+               'ꞷ': 'Ꞷ',
+               'ꭓ': 'Ꭓ',
+               'ꭰ': 'Ꭰ',
+               'ꭱ': 'Ꭱ',
+               'ꭲ': 'Ꭲ',
+               'ꭳ': 'Ꭳ',
+               'ꭴ': 'Ꭴ',
+               'ꭵ': 'Ꭵ',
+               'ꭶ': 'Ꭶ',
+               'ꭷ': 'Ꭷ',
+               'ꭸ': 'Ꭸ',
+               'ꭹ': 'Ꭹ',
+               'ꭺ': 'Ꭺ',
+               'ꭻ': 'Ꭻ',
+               'ꭼ': 'Ꭼ',
+               'ꭽ': 'Ꭽ',
+               'ꭾ': 'Ꭾ',
+               'ꭿ': 'Ꭿ',
+               'ꮀ': 'Ꮀ',
+               'ꮁ': 'Ꮁ',
+               'ꮂ': 'Ꮂ',
+               'ꮃ': 'Ꮃ',
+               'ꮄ': 'Ꮄ',
+               'ꮅ': 'Ꮅ',
+               'ꮆ': 'Ꮆ',
+               'ꮇ': 'Ꮇ',
+               'ꮈ': 'Ꮈ',
+               'ꮉ': 'Ꮉ',
+               'ꮊ': 'Ꮊ',
+               'ꮋ': 'Ꮋ',
+               'ꮌ': 'Ꮌ',
+               'ꮍ': 'Ꮍ',
+               'ꮎ': 'Ꮎ',
+               'ꮏ': 'Ꮏ',
+               'ꮐ': 'Ꮐ',
+               'ꮑ': 'Ꮑ',
+               'ꮒ': 'Ꮒ',
+               'ꮓ': 'Ꮓ',
+               'ꮔ': 'Ꮔ',
+               'ꮕ': 'Ꮕ',
+               'ꮖ': 'Ꮖ',
+               'ꮗ': 'Ꮗ',
+               'ꮘ': 'Ꮘ',
+               'ꮙ': 'Ꮙ',
+               'ꮚ': 'Ꮚ',
+               'ꮛ': 'Ꮛ',
+               'ꮜ': 'Ꮜ',
+               'ꮝ': 'Ꮝ',
+               'ꮞ': 'Ꮞ',
+               'ꮟ': 'Ꮟ',
+               'ꮠ': 'Ꮠ',
+               'ꮡ': 'Ꮡ',
+               'ꮢ': 'Ꮢ',
+               'ꮣ': 'Ꮣ',
+               'ꮤ': 'Ꮤ',
+               'ꮥ': 'Ꮥ',
+               'ꮦ': 'Ꮦ',
+               'ꮧ': 'Ꮧ',
+               'ꮨ': 'Ꮨ',
+               'ꮩ': 'Ꮩ',
+               'ꮪ': 'Ꮪ',
+               'ꮫ': 'Ꮫ',
+               'ꮬ': 'Ꮬ',
+               'ꮭ': 'Ꮭ',
+               'ꮮ': 'Ꮮ',
+               'ꮯ': 'Ꮯ',
+               'ꮰ': 'Ꮰ',
+               'ꮱ': 'Ꮱ',
+               'ꮲ': 'Ꮲ',
+               'ꮳ': 'Ꮳ',
+               'ꮴ': 'Ꮴ',
+               'ꮵ': 'Ꮵ',
+               'ꮶ': 'Ꮶ',
+               'ꮷ': 'Ꮷ',
+               'ꮸ': 'Ꮸ',
+               'ꮹ': 'Ꮹ',
+               'ꮺ': 'Ꮺ',
+               'ꮻ': 'Ꮻ',
+               'ꮼ': 'Ꮼ',
+               'ꮽ': 'Ꮽ',
+               'ꮾ': 'Ꮾ',
+               'ꮿ': 'Ꮿ',
+               'ff': 'ff',
+               'fi': 'fi',
+               'fl': 'fl',
+               'ffi': 'ffi',
+               'ffl': 'ffl',
+               'ſt': 'ſt',
+               'st': 'st',
+               'ﬓ': 'ﬓ',
+               'ﬔ': 'ﬔ',
+               'ﬕ': 'ﬕ',
+               'ﬖ': 'ﬖ',
+               'ﬗ': 'ﬗ'
+       };
+       mw.Title.phpCharToUpper = function ( chr ) {
+               var mapped = toUpperMapping[ chr ];
+               return mapped || chr.toUpperCase();
+       };
+}( mediaWiki ) );
index 910bcc1..124c49f 100644 (file)
                }, 'Throw error on empty string' );
        } );
 
-       QUnit.test( 'Case-sensivity', 3, function ( assert ) {
+       QUnit.test( 'Case-sensivity', 5, function ( assert ) {
                var title;
 
                // Default config
                title = new mw.Title( 'article' );
                assert.equal( title.toString(), 'Article', 'Default config: No sensitive namespaces by default. First-letter becomes uppercase' );
 
+               title = new mw.Title( 'ß' );
+               assert.equal( title.toString(), 'ß', 'Uppercasing matches PHP behaviour (ß -> ß, not SS)' );
+
+               title = new mw.Title( 'dž (digraph)' );
+               assert.equal( title.toString(), 'Dž_(digraph)', 'Uppercasing matches PHP behaviour (dž -> Dž, not DŽ)' );
+
                // $wgCapitalLinks = false;
                mw.config.set( 'wgCaseSensitiveNamespaces', [ 0, -2, 1, 4, 5, 6, 7, 10, 11, 12, 13, 14, 15 ] );