From 0cd1931b8ef7f7cc924d341cfeb4a967479af0e9 Mon Sep 17 00:00:00 2001 From: Kavit Trivedi Date: Mon, 5 Jan 2026 10:16:05 +0530 Subject: [PATCH 1/6] Media: Normalize order prop in Attachments.initialize() Add order normalization in the Attachments model initialize method to ensure the 'order' property is always 'ASC' or 'DESC' (defaulting to 'DESC' for invalid values). This provides consistent behavior across all attachment collections. Part of fixing Media Library Grid view ordering when order query var is not normalized. See #64467. --- src/js/media/models/attachments.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/js/media/models/attachments.js b/src/js/media/models/attachments.js index 23510bd949f4c..0b764de042ae6 100644 --- a/src/js/media/models/attachments.js +++ b/src/js/media/models/attachments.js @@ -44,7 +44,17 @@ var Attachments = Backbone.Collection.extend(/** @lends wp.media.model.Attachmen this.props.on( 'change:orderby', this._changeOrderby, this ); this.props.on( 'change:query', this._changeQuery, this ); - this.props.set( _.defaults( options.props || {} ) ); + options.props = _.defaults( options.props || {} ); + + // Normalize the order if it exists. + if ( 'string' === typeof options.props.order ) { + options.props.order = options.props.order.toUpperCase(); + if ( 'ASC' !== options.props.order && 'DESC' !== options.props.order ) { + options.props.order = 'DESC'; + } + } + + this.props.set( options.props ); if ( options.observe ) { this.observe( options.observe ); From 78a9ebe0897679087d052c9f8978ba3f22eb085c Mon Sep 17 00:00:00 2001 From: Kavit Trivedi Date: Mon, 5 Jan 2026 10:16:11 +0530 Subject: [PATCH 2/6] Media: Remove duplicate order normalization from Query model Since Query inherits from Attachments, and Attachments now normalizes the order property in its initialize method, remove the duplicate normalization code from Query.get() to avoid redundancy and ensure consistent behavior. Part of fixing Media Library Grid view ordering when order query var is not normalized. See #64467. --- src/js/media/models/query.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/js/media/models/query.js b/src/js/media/models/query.js index b3f62018f5cd4..3c47215c39833 100644 --- a/src/js/media/models/query.js +++ b/src/js/media/models/query.js @@ -251,12 +251,6 @@ Query = Attachments.extend(/** @lends wp.media.model.Query.prototype */{ // Fill default args. _.defaults( props, defaults ); - // Normalize the order. - props.order = props.order.toUpperCase(); - if ( 'DESC' !== props.order && 'ASC' !== props.order ) { - props.order = defaults.order.toUpperCase(); - } - // Ensure we have a valid orderby value. if ( ! _.contains( orderby.allowed, props.orderby ) ) { props.orderby = defaults.orderby; From dc75929461e6f4046c286e66d860d9e482fb08b5 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Mon, 5 Jan 2026 13:03:41 -0800 Subject: [PATCH 3/6] add qunit tests for order normalization --- tests/qunit/index.html | 1 + .../wp-includes/js/media/test-media-models.js | 248 ++++++++++++++++++ 2 files changed, 249 insertions(+) create mode 100644 tests/qunit/wp-includes/js/media/test-media-models.js diff --git a/tests/qunit/index.html b/tests/qunit/index.html index bcd6d8c1c6ddd..ff81b959217c1 100644 --- a/tests/qunit/index.html +++ b/tests/qunit/index.html @@ -152,6 +152,7 @@ + diff --git a/tests/qunit/wp-includes/js/media/test-media-models.js b/tests/qunit/wp-includes/js/media/test-media-models.js new file mode 100644 index 0000000000000..97bf87081564e --- /dev/null +++ b/tests/qunit/wp-includes/js/media/test-media-models.js @@ -0,0 +1,248 @@ +/* globals wp, Backbone */ +/* jshint qunit: true */ +/* eslint-env qunit */ +/* eslint-disable no-magic-numbers */ + +( function() { + 'use strict'; + + QUnit.module( 'Media Models - Order Normalization' ); + + // Test valid uppercase values + QUnit.test( 'Attachments should accept uppercase "ASC" order', function( assert ) { + var collection = new wp.media.model.Attachments( [], { + props: { + order: 'ASC' + } + }); + + assert.strictEqual( collection.props.get('order'), 'ASC', + 'Order should remain ASC when passed as uppercase' ); + }); + + QUnit.test( 'Attachments should accept uppercase "DESC" order', function( assert ) { + var collection = new wp.media.model.Attachments( [], { + props: { + order: 'DESC' + } + }); + + assert.strictEqual( collection.props.get('order'), 'DESC', + 'Order should remain DESC when passed as uppercase' ); + }); + + // Test lowercase normalization + QUnit.test( 'Attachments should normalize lowercase "asc" to uppercase', function( assert ) { + var collection = new wp.media.model.Attachments( [], { + props: { + order: 'asc' + } + }); + + assert.strictEqual( collection.props.get('order'), 'ASC', + 'Order should be converted from lowercase asc to uppercase ASC' ); + }); + + QUnit.test( 'Attachments should normalize lowercase "desc" to uppercase', function( assert ) { + var collection = new wp.media.model.Attachments( [], { + props: { + order: 'desc' + } + }); + + assert.strictEqual( collection.props.get('order'), 'DESC', + 'Order should be converted from lowercase desc to uppercase DESC' ); + }); + + // Test mixed case normalization + QUnit.test( 'Attachments should normalize mixed case "AsC" to uppercase', function( assert ) { + var collection = new wp.media.model.Attachments( [], { + props: { + order: 'AsC' + } + }); + + assert.strictEqual( collection.props.get('order'), 'ASC', + 'Order should be converted from mixed case AsC to uppercase ASC' ); + }); + + QUnit.test( 'Attachments should normalize mixed case "DeSc" to uppercase', function( assert ) { + var collection = new wp.media.model.Attachments( [], { + props: { + order: 'DeSc' + } + }); + + assert.strictEqual( collection.props.get('order'), 'DESC', + 'Order should be converted from mixed case DeSc to uppercase DESC' ); + }); + + // Test invalid string values + QUnit.test( 'Attachments should default invalid string order to "DESC"', function( assert ) { + var collection = new wp.media.model.Attachments( [], { + props: { + order: 'invalid' + } + }); + + assert.strictEqual( collection.props.get('order'), 'DESC', + 'Invalid string order should default to DESC' ); + }); + + QUnit.test( 'Attachments should default empty string order to "DESC"', function( assert ) { + var collection = new wp.media.model.Attachments( [], { + props: { + order: '' + } + }); + + assert.strictEqual( collection.props.get('order'), 'DESC', + 'Empty string order should default to DESC' ); + }); + + // Test non-string type edge cases + QUnit.test( 'Attachments should not process null order value', function( assert ) { + var collection = new wp.media.model.Attachments( [], { + props: { + order: null + } + }); + + assert.strictEqual( collection.props.get('order'), null, + 'Null order should remain null (not processed by normalization)' ); + }); + + QUnit.test( 'Attachments should not process undefined order value', function( assert ) { + var collection = new wp.media.model.Attachments( [], { + props: { + order: undefined + } + }); + + assert.strictEqual( collection.props.get('order'), undefined, + 'Undefined order should remain undefined (not processed by normalization)' ); + }); + + QUnit.test( 'Attachments should not process numeric order value', function( assert ) { + var collection = new wp.media.model.Attachments( [], { + props: { + order: 123 + } + }); + + assert.strictEqual( collection.props.get('order'), 123, + 'Numeric order should remain unchanged (not processed by normalization)' ); + }); + + QUnit.test( 'Attachments should not process boolean true order value', function( assert ) { + var collection = new wp.media.model.Attachments( [], { + props: { + order: true + } + }); + + assert.strictEqual( collection.props.get('order'), true, + 'Boolean true order should remain unchanged (not processed by normalization)' ); + }); + + QUnit.test( 'Attachments should not process boolean false order value', function( assert ) { + var collection = new wp.media.model.Attachments( [], { + props: { + order: false + } + }); + + assert.strictEqual( collection.props.get('order'), false, + 'Boolean false order should remain unchanged (not processed by normalization)' ); + }); + + QUnit.test( 'Attachments should not process object order value', function( assert ) { + var orderObj = { value: 'ASC' }; + var collection = new wp.media.model.Attachments( [], { + props: { + order: orderObj + } + }); + + assert.strictEqual( collection.props.get('order'), orderObj, + 'Object order should remain unchanged (not processed by normalization)' ); + }); + + QUnit.test( 'Attachments should not process array order value', function( assert ) { + var orderArray = ['ASC', 'DESC']; + var collection = new wp.media.model.Attachments( [], { + props: { + order: orderArray + } + }); + + assert.strictEqual( collection.props.get('order'), orderArray, + 'Array order should remain unchanged (not processed by normalization)' ); + }); + + // Test when no order property is provided + QUnit.test( 'Attachments should work when no order property is provided', function( assert ) { + var collection = new wp.media.model.Attachments( [], { + props: { + orderby: 'date' + } + }); + + assert.strictEqual( collection.props.get('order'), undefined, + 'Order should be undefined when not provided' ); + }); + + // Test Query model inheritance + QUnit.test( 'Query should inherit order normalization from Attachments', function( assert ) { + var query = new wp.media.model.Query( [], { + props: { + order: 'asc', + query: true + } + }); + + assert.strictEqual( query.props.get('order'), 'ASC', + 'Query model should normalize order through inheritance from Attachments' ); + assert.ok( query instanceof wp.media.model.Attachments, + 'Query should be instance of Attachments' ); + }); + + QUnit.test( 'Query should default invalid order to "DESC"', function( assert ) { + var query = new wp.media.model.Query( [], { + props: { + order: 'random', + query: true + } + }); + + assert.strictEqual( query.props.get('order'), 'DESC', + 'Query model should default invalid order to DESC' ); + }); + + // Test whitespace handling + QUnit.test( 'Attachments should handle order with whitespace', function( assert ) { + var collection = new wp.media.model.Attachments( [], { + props: { + order: ' asc ' + } + }); + + assert.notStrictEqual( collection.props.get('order'), 'ASC', + 'Order with whitespace should not match ASC exactly' ); + assert.strictEqual( collection.props.get('order'), 'DESC', + 'Order with whitespace should default to DESC as it does not match ASC/DESC after toUpperCase' ); + }); + + // Test unicode characters + QUnit.test( 'Attachments should handle order with unicode characters', function( assert ) { + var collection = new wp.media.model.Attachments( [], { + props: { + order: 'asc\u200B' // Zero-width space + } + }); + + assert.strictEqual( collection.props.get('order'), 'DESC', + 'Order with unicode characters should default to DESC' ); + }); + +})(); From 5429fc34b7dadeddc56ee7fb30468c1aa08f0276 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Tue, 6 Jan 2026 09:46:32 -0800 Subject: [PATCH 4/6] backbone not used in tests --- tests/qunit/wp-includes/js/media/test-media-models.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/qunit/wp-includes/js/media/test-media-models.js b/tests/qunit/wp-includes/js/media/test-media-models.js index 97bf87081564e..99642aaecebdd 100644 --- a/tests/qunit/wp-includes/js/media/test-media-models.js +++ b/tests/qunit/wp-includes/js/media/test-media-models.js @@ -1,4 +1,4 @@ -/* globals wp, Backbone */ +/* globals wp */ /* jshint qunit: true */ /* eslint-env qunit */ /* eslint-disable no-magic-numbers */ From 49934935ac436c94960da77b3b062463076347d4 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Mon, 12 Jan 2026 13:53:17 -0800 Subject: [PATCH 5/6] Update src/js/media/models/attachments.js Co-authored-by: Mukesh Panchal --- src/js/media/models/attachments.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/js/media/models/attachments.js b/src/js/media/models/attachments.js index 0b764de042ae6..4d29d0ecb097c 100644 --- a/src/js/media/models/attachments.js +++ b/src/js/media/models/attachments.js @@ -48,10 +48,10 @@ var Attachments = Backbone.Collection.extend(/** @lends wp.media.model.Attachmen // Normalize the order if it exists. if ( 'string' === typeof options.props.order ) { - options.props.order = options.props.order.toUpperCase(); - if ( 'ASC' !== options.props.order && 'DESC' !== options.props.order ) { - options.props.order = 'DESC'; - } + const normalizedOrder = options.props.order.toUpperCase(); + options.props.order = ( 'ASC' === normalizedOrder || 'DESC' === normalizedOrder ) + ? normalizedOrder + : 'DESC'; } this.props.set( options.props ); From 57b7fe00e0e85bb2cb4595e3fb8d4e3e18063af4 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Mon, 12 Jan 2026 14:39:04 -0800 Subject: [PATCH 6/6] address js linting --- src/js/media/models/attachments.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/js/media/models/attachments.js b/src/js/media/models/attachments.js index 4d29d0ecb097c..85b5f7a30041f 100644 --- a/src/js/media/models/attachments.js +++ b/src/js/media/models/attachments.js @@ -49,9 +49,7 @@ var Attachments = Backbone.Collection.extend(/** @lends wp.media.model.Attachmen // Normalize the order if it exists. if ( 'string' === typeof options.props.order ) { const normalizedOrder = options.props.order.toUpperCase(); - options.props.order = ( 'ASC' === normalizedOrder || 'DESC' === normalizedOrder ) - ? normalizedOrder - : 'DESC'; + options.props.order = ( 'ASC' === normalizedOrder || 'DESC' === normalizedOrder ) ? normalizedOrder : 'DESC'; } this.props.set( options.props );