Skip to content
16 changes: 15 additions & 1 deletion src/js/media/models/attachments.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ var Attachments = Backbone.Collection.extend(/** @lends wp.media.model.Attachmen
* @param {Object} [options={}]
*/
initialize: function( models, options ) {
var normalizedOrder;

options = options || {};

this.props = new Backbone.Model();
Expand All @@ -44,7 +46,19 @@ 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 = options.props || {};

/*
* Normalize the order, if one is set. `Attachments.comparator()` and the
* `order` filter in `wp.media.model.Query` both test for the literal
* strings 'ASC' and 'DESC', so anything else has to fall back to 'DESC'.
*/
if ( ! _.isUndefined( options.props.order ) && ! _.isNull( options.props.order ) ) {
normalizedOrder = String( options.props.order ).toUpperCase();
options.props.order = ( 'ASC' === normalizedOrder || 'DESC' === normalizedOrder ) ? normalizedOrder : 'DESC';
}

this.props.set( options.props );

if ( options.observe ) {
this.observe( options.observe );
Expand Down
6 changes: 0 additions & 6 deletions src/js/media/models/query.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions tests/qunit/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@
<script src="wp-admin/js/customize-header.js"></script>
<script src="wp-admin/js/dashboard.js"></script>
<script src="wp-includes/js/shortcode.js"></script>
<script src="wp-includes/js/media/test-media-models.js"></script>
<script src="wp-includes/js/api-request.js"></script>
<script src="wp-includes/js/jquery.js"></script>
<script src="wp-includes/js/wp-api.js"></script>
Expand Down
254 changes: 254 additions & 0 deletions tests/qunit/wp-includes/js/media/test-media-models.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
/* globals wp */
/* 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' );
});

/*
* An unset order is left alone so the existing 'DESC' fallbacks in
* Attachments.comparator() still apply. Any value that *is* set gets
* normalized, otherwise a truthy non-string would sort ascending.
*/
QUnit.test( 'Attachments should leave a null order value unset', function( assert ) {
var collection = new wp.media.model.Attachments( [], {
props: {
order: null
}
});

assert.strictEqual( collection.props.get('order'), null,
'Null order should remain null' );
});

QUnit.test( 'Attachments should leave an undefined order value unset', function( assert ) {
var collection = new wp.media.model.Attachments( [], {
props: {
order: undefined
}
});

assert.strictEqual( collection.props.get('order'), undefined,
'Undefined order should remain undefined' );
});

QUnit.test( 'Attachments should default a numeric order to "DESC"', function( assert ) {
var collection = new wp.media.model.Attachments( [], {
props: {
order: 123
}
});

assert.strictEqual( collection.props.get('order'), 'DESC',
'Numeric order should default to DESC' );
});

QUnit.test( 'Attachments should default a boolean true order to "DESC"', function( assert ) {
var collection = new wp.media.model.Attachments( [], {
props: {
order: true
}
});

assert.strictEqual( collection.props.get('order'), 'DESC',
'Boolean true order should default to DESC' );
});

QUnit.test( 'Attachments should default a boolean false order to "DESC"', function( assert ) {
var collection = new wp.media.model.Attachments( [], {
props: {
order: false
}
});

assert.strictEqual( collection.props.get('order'), 'DESC',
'Boolean false order should default to DESC' );
});

QUnit.test( 'Attachments should default an object order to "DESC"', function( assert ) {
var collection = new wp.media.model.Attachments( [], {
props: {
order: { value: 'ASC' }
}
});

assert.strictEqual( collection.props.get('order'), 'DESC',
'Object order should default to DESC' );
});

QUnit.test( 'Attachments should default an array order to "DESC"', function( assert ) {
var collection = new wp.media.model.Attachments( [], {
props: {
order: ['ASC', 'DESC']
}
});

assert.strictEqual( collection.props.get('order'), 'DESC',
'Array order should default to DESC' );
});

// 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' );
});

/*
* Query no longer normalizes the order itself, it relies on inheriting the
* normalization above. Note these pass `args` rather than `props.query`:
* setting `query` would kick off a server request via `_requery()`.
*/
QUnit.test( 'Query should inherit order normalization from Attachments', function( assert ) {
var query = new wp.media.model.Query( [], {
props: {
order: 'asc'
},
args: {}
});

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'
},
args: {}
});

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' );
});

})();
Loading