From 1806dcd0eb149c8241ed7c28d9507f60bd5f9bd3 Mon Sep 17 00:00:00 2001 From: Timo Tijhof Date: Wed, 23 Nov 2016 09:53:28 -0800 Subject: [PATCH] qunit: Don't assume synchronous Deferred.resolve * mw.loader.test: Most test did this correctly already by returning a Promise to QUnit.test, or by using assert.async(). However two tests did not. * mediawiki.jqueryMsg.test: Previously the async() handles were obtained within the task execution loop. Before jQuery 3.0, simple .then() operations when they are already resolved happen synchronously, so the current handle was resolved and the next handle obtained in the same go and QUnit never saw the state as having no unresolved async handles. With jQuery 3.0 the test fails because QUnit would end the test after the first iteration. Fix by simply obtaining all the async() handles when creating the list of tasks, instead of within the individual task execution. This way they're all reserved and they'll count down as we go. * mediawiki.api.test: Consistently use 'respondImmediately' for the tests that were missing it, and return promise to QUnit.test() Also update hardcoded API urls that encoded space as '+', which may now be encoded as '%20'. Bug: T124742 Change-Id: If7ee1c6025be70fecc0a93d4ac155da4db6571ab --- .../mediawiki.ForeignApi.test.js | 15 +-- .../mediawiki.api.category.test.js | 25 ++--- .../mediawiki.api.messages.test.js | 24 ++--- .../mediawiki.api.options.test.js | 102 +++++++++--------- .../mediawiki.api/mediawiki.api.parse.test.js | 42 ++++---- .../mediawiki.api.upload.test.js | 6 +- .../mediawiki.api/mediawiki.api.watch.test.js | 48 ++++++--- .../mediawiki/mediawiki.jqueryMsg.test.js | 4 +- .../mediawiki/mediawiki.loader.test.js | 10 +- 9 files changed, 139 insertions(+), 137 deletions(-) diff --git a/tests/qunit/suites/resources/mediawiki.api/mediawiki.ForeignApi.test.js b/tests/qunit/suites/resources/mediawiki.api/mediawiki.ForeignApi.test.js index 9d0fdf54b0..1676130e98 100644 --- a/tests/qunit/suites/resources/mediawiki.api/mediawiki.ForeignApi.test.js +++ b/tests/qunit/suites/resources/mediawiki.api/mediawiki.ForeignApi.test.js @@ -3,16 +3,10 @@ setup: function () { this.server = this.sandbox.useFakeServer(); this.server.respondImmediately = true; - this.clock = this.sandbox.useFakeTimers(); - }, - teardown: function () { - // https://github.com/jquery/jquery/issues/2453 - this.clock.tick(); } } ) ); - QUnit.test( 'origin is included in GET requests', function ( assert ) { - QUnit.expect( 1 ); + QUnit.test( 'origin is included in GET requests', 1, function ( assert ) { var api = new mw.ForeignApi( '//localhost:4242/w/api.php' ); this.server.respond( function ( request ) { @@ -20,11 +14,10 @@ request.respond( 200, { 'Content-Type': 'application/json' }, '[]' ); } ); - api.get( {} ); + return api.get( {} ); } ); - QUnit.test( 'origin is included in POST requests', function ( assert ) { - QUnit.expect( 2 ); + QUnit.test( 'origin is included in POST requests', 2, function ( assert ) { var api = new mw.ForeignApi( '//localhost:4242/w/api.php' ); this.server.respond( function ( request ) { @@ -33,7 +26,7 @@ request.respond( 200, { 'Content-Type': 'application/json' }, '[]' ); } ); - api.post( {} ); + return api.post( {} ); } ); }( mediaWiki ) ); diff --git a/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.category.test.js b/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.category.test.js index a0c7daf1a0..a79bff698b 100644 --- a/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.category.test.js +++ b/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.category.test.js @@ -2,29 +2,24 @@ QUnit.module( 'mediawiki.api.category', QUnit.newMwEnvironment( { setup: function () { this.server = this.sandbox.useFakeServer(); + this.server.respondImmediately = true; } } ) ); - QUnit.test( '.getCategoriesByPrefix()', function ( assert ) { - QUnit.expect( 1 ); + QUnit.test( '.getCategoriesByPrefix()', 1, function ( assert ) { + this.server.respondWith( [ 200, { 'Content-Type': 'application/json' }, + '{ "query": { "allpages": [ ' + + '{ "title": "Category:Food" },' + + '{ "title": "Category:Fool Supermarine S.6" },' + + '{ "title": "Category:Fools" }' + + '] } }' + ] ); - var api = new mw.Api(); - - api.getCategoriesByPrefix( 'Foo' ).done( function ( matches ) { + return new mw.Api().getCategoriesByPrefix( 'Foo' ).then( function ( matches ) { assert.deepEqual( matches, [ 'Food', 'Fool Supermarine S.6', 'Fools' ] ); } ); - - this.server.respond( function ( req ) { - req.respond( 200, { 'Content-Type': 'application/json' }, - '{ "query": { "allpages": [ ' + - '{ "title": "Category:Food" },' + - '{ "title": "Category:Fool Supermarine S.6" },' + - '{ "title": "Category:Fools" }' + - '] } }' - ); - } ); } ); }( mediaWiki ) ); diff --git a/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.messages.test.js b/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.messages.test.js index 5880962a74..d8b5db88a3 100644 --- a/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.messages.test.js +++ b/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.messages.test.js @@ -2,14 +2,21 @@ QUnit.module( 'mediawiki.api.messages', QUnit.newMwEnvironment( { setup: function () { this.server = this.sandbox.useFakeServer(); + this.server.respondImmediately = true; } } ) ); - QUnit.test( '.getMessages()', function ( assert ) { - QUnit.expect( 1 ); + QUnit.test( '.getMessages()', 1, function ( assert ) { + this.server.respondWith( /ammessages=foo%7Cbaz/, [ + 200, + { 'Content-Type': 'application/json' }, + '{ "query": { "allmessages": [' + + '{ "name": "foo", "content": "Foo bar" },' + + '{ "name": "baz", "content": "Baz Quux" }' + + '] } }' + ] ); - var api = new mw.Api(); - api.getMessages( [ 'foo', 'baz' ] ).then( function ( messages ) { + return new mw.Api().getMessages( [ 'foo', 'baz' ] ).then( function ( messages ) { assert.deepEqual( messages, { @@ -18,14 +25,5 @@ } ); } ); - - this.server.respond( /ammessages=foo%7Cbaz/, [ - 200, - { 'Content-Type': 'application/json' }, - '{ "query": { "allmessages": [' + - '{ "name": "foo", "content": "Foo bar" },' + - '{ "name": "baz", "content": "Baz Quux" }' + - '] } }' - ] ); } ); }( mediaWiki ) ); diff --git a/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.options.test.js b/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.options.test.js index 0797f32dfb..7ed1875036 100644 --- a/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.options.test.js +++ b/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.options.test.js @@ -2,14 +2,12 @@ QUnit.module( 'mediawiki.api.options', QUnit.newMwEnvironment( { setup: function () { this.server = this.sandbox.useFakeServer(); + this.server.respondImmediately = true; } } ) ); - QUnit.test( 'saveOption', function ( assert ) { - QUnit.expect( 2 ); - - var - api = new mw.Api(), + QUnit.test( 'saveOption', 2, function ( assert ) { + var api = new mw.Api(), stub = this.sandbox.stub( mw.Api.prototype, 'saveOptions' ); api.saveOption( 'foo', 'bar' ); @@ -18,9 +16,7 @@ assert.deepEqual( stub.getCall( 0 ).args, [ { foo: 'bar' } ], '#saveOptions called correctly' ); } ); - QUnit.test( 'saveOptions without Unit Separator', function ( assert ) { - QUnit.expect( 13 ); - + QUnit.test( 'saveOptions without Unit Separator', 13, function ( assert ) { var api = new mw.Api( { useUS: false } ); // We need to respond to the request for token first, otherwise the other requests won't be sent @@ -32,25 +28,6 @@ '{ "query": { "tokens": { "csrftoken": "+\\\\" } } }' ] ); - api.saveOptions( {} ).done( function () { - assert.ok( true, 'Request completed: empty case' ); - } ); - api.saveOptions( { foo: 'bar' } ).done( function () { - assert.ok( true, 'Request completed: simple' ); - } ); - api.saveOptions( { foo: 'bar', baz: 'quux' } ).done( function () { - assert.ok( true, 'Request completed: two options' ); - } ); - api.saveOptions( { foo: 'bar|quux', bar: 'a|b|c', baz: 'quux' } ).done( function () { - assert.ok( true, 'Request completed: not bundleable' ); - } ); - api.saveOptions( { foo: null } ).done( function () { - assert.ok( true, 'Request completed: reset an option' ); - } ); - api.saveOptions( { 'foo|bar=quux': null } ).done( function () { - assert.ok( true, 'Request completed: reset an option, not bundleable' ); - } ); - // Requests are POST, match requestBody instead of url this.server.respond( function ( request ) { switch ( request.requestBody ) { @@ -74,11 +51,30 @@ assert.ok( false, 'Unexpected request: ' + request.requestBody ); } } ); - } ); - QUnit.test( 'saveOptions with Unit Separator', function ( assert ) { - QUnit.expect( 14 ); + return QUnit.whenPromisesComplete( + api.saveOptions( {} ).then( function () { + assert.ok( true, 'Request completed: empty case' ); + } ), + api.saveOptions( { foo: 'bar' } ).then( function () { + assert.ok( true, 'Request completed: simple' ); + } ), + api.saveOptions( { foo: 'bar', baz: 'quux' } ).then( function () { + assert.ok( true, 'Request completed: two options' ); + } ), + api.saveOptions( { foo: 'bar|quux', bar: 'a|b|c', baz: 'quux' } ).then( function () { + assert.ok( true, 'Request completed: not bundleable' ); + } ), + api.saveOptions( { foo: null } ).then( function () { + assert.ok( true, 'Request completed: reset an option' ); + } ), + api.saveOptions( { 'foo|bar=quux': null } ).then( function () { + assert.ok( true, 'Request completed: reset an option, not bundleable' ); + } ) + ); + } ); + QUnit.test( 'saveOptions with Unit Separator', 14, function ( assert ) { var api = new mw.Api( { useUS: true } ); // We need to respond to the request for token first, otherwise the other requests won't be sent @@ -90,28 +86,6 @@ '{ "query": { "tokens": { "csrftoken": "+\\\\" } } }' ] ); - api.saveOptions( {} ).done( function () { - assert.ok( true, 'Request completed: empty case' ); - } ); - api.saveOptions( { foo: 'bar' } ).done( function () { - assert.ok( true, 'Request completed: simple' ); - } ); - api.saveOptions( { foo: 'bar', baz: 'quux' } ).done( function () { - assert.ok( true, 'Request completed: two options' ); - } ); - api.saveOptions( { foo: 'bar|quux', bar: 'a|b|c', baz: 'quux' } ).done( function () { - assert.ok( true, 'Request completed: bundleable with unit separator' ); - } ); - api.saveOptions( { foo: 'bar|quux', bar: 'a|b|c', 'baz=baz': 'quux' } ).done( function () { - assert.ok( true, 'Request completed: not bundleable with unit separator' ); - } ); - api.saveOptions( { foo: null } ).done( function () { - assert.ok( true, 'Request completed: reset an option' ); - } ); - api.saveOptions( { 'foo|bar=quux': null } ).done( function () { - assert.ok( true, 'Request completed: reset an option, not bundleable' ); - } ); - // Requests are POST, match requestBody instead of url this.server.respond( function ( request ) { switch ( request.requestBody ) { @@ -136,5 +110,29 @@ assert.ok( false, 'Unexpected request: ' + request.requestBody ); } } ); + + return QUnit.whenPromisesComplete( + api.saveOptions( {} ).done( function () { + assert.ok( true, 'Request completed: empty case' ); + } ), + api.saveOptions( { foo: 'bar' } ).done( function () { + assert.ok( true, 'Request completed: simple' ); + } ), + api.saveOptions( { foo: 'bar', baz: 'quux' } ).done( function () { + assert.ok( true, 'Request completed: two options' ); + } ), + api.saveOptions( { foo: 'bar|quux', bar: 'a|b|c', baz: 'quux' } ).done( function () { + assert.ok( true, 'Request completed: bundleable with unit separator' ); + } ), + api.saveOptions( { foo: 'bar|quux', bar: 'a|b|c', 'baz=baz': 'quux' } ).done( function () { + assert.ok( true, 'Request completed: not bundleable with unit separator' ); + } ), + api.saveOptions( { foo: null } ).done( function () { + assert.ok( true, 'Request completed: reset an option' ); + } ), + api.saveOptions( { 'foo|bar=quux': null } ).done( function () { + assert.ok( true, 'Request completed: reset an option, not bundleable' ); + } ) + ); } ); }( mediaWiki ) ); diff --git a/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.parse.test.js b/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.parse.test.js index dc0cff40e6..7d27352200 100644 --- a/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.parse.test.js +++ b/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.parse.test.js @@ -2,42 +2,44 @@ QUnit.module( 'mediawiki.api.parse', QUnit.newMwEnvironment( { setup: function () { this.server = this.sandbox.useFakeServer(); + this.server.respondImmediately = true; } } ) ); - QUnit.test( 'Hello world', function ( assert ) { - QUnit.expect( 3 ); + QUnit.test( '.parse( string )', function ( assert ) { + this.server.respondWith( /action=parse.*&text='''Hello(\+|%20)world'''/, [ 200, + { 'Content-Type': 'application/json' }, + '{ "parse": { "text": "

Hello world

" } }' + ] ); - var api = new mw.Api(); - - api.parse( '\'\'\'Hello world\'\'\'' ).done( function ( html ) { + return new mw.Api().parse( '\'\'\'Hello world\'\'\'' ).done( function ( html ) { assert.equal( html, '

Hello world

', 'Parse wikitext by string' ); } ); + } ); - api.parse( { + QUnit.test( '.parse( Object.toString )', function ( assert ) { + this.server.respondWith( /action=parse.*&text='''Hello(\+|%20)world'''/, [ 200, + { 'Content-Type': 'application/json' }, + '{ "parse": { "text": "

Hello world

" } }' + ] ); + + return new mw.Api().parse( { toString: function () { return '\'\'\'Hello world\'\'\''; } } ).done( function ( html ) { assert.equal( html, '

Hello world

', 'Parse wikitext by toString object' ); } ); + } ); - this.server.respondWith( /action=parse.*&text='''Hello\+world'''/, function ( request ) { - request.respond( 200, { 'Content-Type': 'application/json' }, - '{ "parse": { "text": "

Hello world

" } }' - ); - } ); + QUnit.test( '.parse( mw.Title )', function ( assert ) { + this.server.respondWith( /action=parse.*&page=Earth/, [ 200, + { 'Content-Type': 'application/json' }, + '{ "parse": { "text": "

Earth is a planet.

" } }' + ] ); - api.parse( new mw.Title( 'Earth' ) ).done( function ( html ) { + return new mw.Api().parse( new mw.Title( 'Earth' ) ).done( function ( html ) { assert.equal( html, '

Earth is a planet.

', 'Parse page by Title object' ); } ); - - this.server.respondWith( /action=parse.*&page=Earth/, function ( request ) { - request.respond( 200, { 'Content-Type': 'application/json' }, - '{ "parse": { "text": "

Earth is a planet.

" } }' - ); - } ); - - this.server.respond(); } ); }( mediaWiki ) ); diff --git a/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.upload.test.js b/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.upload.test.js index 10fcd5da68..b1bd12ba17 100644 --- a/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.upload.test.js +++ b/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.upload.test.js @@ -1,8 +1,7 @@ ( function ( mw, $ ) { QUnit.module( 'mediawiki.api.upload', QUnit.newMwEnvironment( {} ) ); - QUnit.test( 'Basic functionality', function ( assert ) { - QUnit.expect( 2 ); + QUnit.test( 'Basic functionality', 2, function ( assert ) { var api = new mw.Api(); assert.ok( api.upload ); assert.throws( function () { @@ -10,8 +9,7 @@ } ); } ); - QUnit.test( 'Set up iframe upload', function ( assert ) { - QUnit.expect( 5 ); + QUnit.test( 'Set up iframe upload', 5, function ( assert ) { var $iframe, $form, $input, api = new mw.Api(); diff --git a/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.watch.test.js b/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.watch.test.js index 64a5184711..86414691d8 100644 --- a/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.watch.test.js +++ b/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.watch.test.js @@ -2,37 +2,45 @@ QUnit.module( 'mediawiki.api.watch', QUnit.newMwEnvironment( { setup: function () { this.server = this.sandbox.useFakeServer(); + this.server.respondImmediately = true; } } ) ); - QUnit.test( '.watch()', function ( assert ) { - QUnit.expect( 4 ); - - var api = new mw.Api(); - - // Ensure we don't mistake a single item array for a single item and vice versa. - // The query parameter in request is the same either way (separated by pipe). - api.watch( 'Foo' ).done( function ( item ) { - assert.equal( item.title, 'Foo' ); - } ); - - api.watch( [ 'Foo' ] ).done( function ( items ) { - assert.equal( items[ 0 ].title, 'Foo' ); + QUnit.test( '.watch( string )', function ( assert ) { + this.server.respond( function ( req ) { + // Match POST requestBody + if ( /action=watch.*&titles=Foo(&|$)/.test( req.requestBody ) ) { + req.respond( 200, { 'Content-Type': 'application/json' }, + '{ "watch": [ { "title": "Foo", "watched": true, "message": "Added" } ] }' + ); + } } ); - api.watch( [ 'Foo', 'Bar' ] ).done( function ( items ) { - assert.equal( items[ 0 ].title, 'Foo' ); - assert.equal( items[ 1 ].title, 'Bar' ); + return new mw.Api().watch( 'Foo' ).done( function ( item ) { + assert.equal( item.title, 'Foo' ); } ); + } ); - // Requests are POST, match requestBody instead of url + // Ensure we don't mistake a single item array for a single item and vice versa. + // The query parameter in request is the same either way (separated by pipe). + QUnit.test( '.watch( Array ) - single', function ( assert ) { this.server.respond( function ( req ) { + // Match POST requestBody if ( /action=watch.*&titles=Foo(&|$)/.test( req.requestBody ) ) { req.respond( 200, { 'Content-Type': 'application/json' }, '{ "watch": [ { "title": "Foo", "watched": true, "message": "Added" } ] }' ); } + } ); + + return new mw.Api().watch( [ 'Foo' ] ).done( function ( items ) { + assert.equal( items[ 0 ].title, 'Foo' ); + } ); + } ); + QUnit.test( '.watch( Array ) - multi', function ( assert ) { + this.server.respond( function ( req ) { + // Match POST requestBody if ( /action=watch.*&titles=Foo%7CBar/.test( req.requestBody ) ) { req.respond( 200, { 'Content-Type': 'application/json' }, '{ "watch": [ ' + @@ -42,5 +50,11 @@ ); } } ); + + return new mw.Api().watch( [ 'Foo', 'Bar' ] ).done( function ( items ) { + assert.equal( items[ 0 ].title, 'Foo' ); + assert.equal( items[ 1 ].title, 'Bar' ); + } ); } ); + }( mediaWiki ) ); diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js index 12181379f0..7a00943e7f 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js @@ -367,8 +367,8 @@ QUnit.test( 'Match PHP parser', mw.libs.phpParserData.tests.length, function ( assert ) { mw.messages.set( mw.libs.phpParserData.messages ); var tasks = $.map( mw.libs.phpParserData.tests, function ( test ) { + var done = assert.async(); return function ( next, abort ) { - var done = assert.async(); getMwLanguage( test.lang ) .then( function ( langClass ) { mw.config.set( 'wgUserLanguage', test.lang ); @@ -895,8 +895,8 @@ mw.messages.set( 'formatnum-msg', '{{formatnum:$1}}' ); mw.messages.set( 'formatnum-msg-int', '{{formatnum:$1|R}}' ); var queue = $.map( formatnumTests, function ( test ) { + var done = assert.async(); return function ( next, abort ) { - var done = assert.async(); getMwLanguage( test.lang ) .then( function ( langClass ) { mw.config.set( 'wgUserLanguage', test.lang ); diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.loader.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.loader.test.js index 92d13260e9..92ee7dd360 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.loader.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.loader.test.js @@ -127,7 +127,6 @@ .done( function () { assert.strictEqual( isAwesomeDone, true, 'test.promise module should\'ve caused isAwesomeDone to be true' ); delete mw.loader.testCallback; - } ) .fail( function () { assert.ok( false, 'Error callback fired while loader.using "test.promise" module' ); @@ -135,6 +134,8 @@ } ); QUnit.test( '.using() Error: Circular dependency', function ( assert ) { + var done = assert.async(); + mw.loader.register( [ [ 'test.circle1', '0', [ 'test.circle2' ] ], [ 'test.circle2', '0', [ 'test.circle3' ] ], @@ -147,7 +148,8 @@ function fail( e ) { assert.ok( /Circular/.test( String( e ) ), 'Detect circular dependency' ); } - ); + ) + .always( done ); } ); QUnit.test( '.load() - Error: Circular dependency', function ( assert ) { @@ -162,6 +164,8 @@ } ); QUnit.test( '.using() - Error: Unregistered', function ( assert ) { + var done = assert.async(); + mw.loader.using( 'test.using.unreg' ).then( function done() { assert.ok( false, 'Unexpected resolution, expected error.' ); @@ -169,7 +173,7 @@ function fail( e ) { assert.ok( /Unknown/.test( String( e ) ), 'Detect unknown dependency' ); } - ); + ).always( done ); } ); QUnit.test( '.load() - Error: Unregistered (ignored)', 0, function ( assert ) { -- 2.20.1