From d1d3a058f1f40a28d4566f04edba0dc041748131 Mon Sep 17 00:00:00 2001 From: "K. Adam White" Date: Wed, 20 Jan 2016 18:12:31 -0500 Subject: [PATCH 1/5] Loosen line length requirements in test files Mocha specs and Chai assertions benefit from a longer permitted line length, so that long "it" parameters and long chai assertion chains don't need to be artificially forced to wrap. --- .jscsrc | 2 +- Gruntfile.js | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/.jscsrc b/.jscsrc index c7b647d3..4c17aaf9 100644 --- a/.jscsrc +++ b/.jscsrc @@ -15,7 +15,7 @@ "requireBlocksOnNewline": 1, "maximumLineLength": { "value": 100, - "tabSize": 4, + "tabSize": 2, "allowUrlComments": true, "allowRegex": true }, diff --git a/Gruntfile.js b/Gruntfile.js index 57512bde..63933aa1 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -31,6 +31,15 @@ module.exports = function( grunt ) { src: files.lib }, tests: { + options: { + maximumLineLength: { + // Longer max line length in test files + value: 150, + tabSize: 2, + allowUrlComments: true, + allowRegex: true + } + }, src: files.tests } }, From 90fc8387b1a4835ec717262361ec9cb4ee439cdf Mon Sep 17 00:00:00 2001 From: "K. Adam White" Date: Wed, 20 Jan 2016 18:14:03 -0500 Subject: [PATCH 2/5] Update taxonomy collection handling to use plugin v2 API paths Many of these changes are adapted from work by @jasonphillips in his fork of the project -- full props to him for pioneering how to adapt the existing infrastructure to the new endpoints. --- lib/taxonomies.js | 64 ++++-------- tests/integration/categories.js | 175 +++++++++++++++++++++++++++++++ tests/integration/tags.js | 180 ++++++++++++++++++++++++++++++++ tests/integration/taxonomies.js | 92 ++++++++++++++++ tests/unit/lib/taxonomies.js | 38 +------ tests/unit/wp.js | 8 +- wp.js | 34 +++--- 7 files changed, 497 insertions(+), 94 deletions(-) create mode 100644 tests/integration/categories.js create mode 100644 tests/integration/tags.js create mode 100644 tests/integration/taxonomies.js diff --git a/lib/taxonomies.js b/lib/taxonomies.js index 56133e95..278a3935 100644 --- a/lib/taxonomies.js +++ b/lib/taxonomies.js @@ -50,22 +50,29 @@ function TaxonomiesRequest( options ) { /** * A hash of values to assemble into the API request path * + * Default to requesting the taxonomies "collection" (dictionary of publicly- + * registered taxonomies) if no other collection is specified + * * @property _path * @type Object * @private * @default {} */ - this._path = {}; + this._path = { collection: 'taxonomies' }; /** * The URL template that will be used to assemble endpoint paths * + * There is no path validation for taxonomies requests: terms can be numeric + * (categories) or strings (tags), and the list of registered collections is + * not fixed (it can be augmented or modified through plugin and theme behavior). + * * @property _template * @type String * @private - * @default 'taxonomies(/:taxonomy)(/:action)(/:term)' + * @default '(:collection)(/:term)' */ - this._template = 'taxonomies(/:taxonomy)(/:action)(/:term)'; + this._template = '(:collection)(/:term)'; /** * @property _supportedMethods @@ -83,35 +90,24 @@ function TaxonomiesRequest( options ) { inherit( TaxonomiesRequest, CollectionRequest ); /** - * A hash of path keys to regex validators for those path elements + * Specify the name of the taxonomy collection to query * - * @property _pathValidators - * @type Object - * @private - */ -TaxonomiesRequest.prototype._pathValidators = { - - /** - * The only "action" permitted on a taxonomy is to get a list of terms - * - * @property _pathValidators.action - * @type {RegExp} - */ - action: /terms/ - - // No validation on :taxonomy or :term: they can be numeric or a string -}; - -/** - * Specify the name of the taxonomy to query + * The collections will not be a strict match to defined taxonomies: *e.g.*, to + * get the list of terms for the taxonomy "category," you must specify the + * collection name "categories" (similarly, specify "tags" to get a list of terms + * for the "post_tag" taxonomy). * - * @method taxonomy + * To get the dictionary of all available taxonomies, specify the collection + * "taxonomy" (slight misnomer: this case will return an object, not the array + * that would usually be expected with a "collection" request). + * + * @method collection * @chainable - * @param {String} taxonomyName The name of the taxonomy to query + * @param {String} taxonomyCollection The name of the taxonomy collection to query * @return {TaxonomiesRequest} The TaxonomiesRequest instance (for chaining) */ -TaxonomiesRequest.prototype.taxonomy = function( taxonomyName ) { - this._path.taxonomy = taxonomyName; +TaxonomiesRequest.prototype.collection = function( taxonomyCollection ) { + this._path.collection = taxonomyCollection; return this; }; @@ -125,23 +121,9 @@ TaxonomiesRequest.prototype.taxonomy = function( taxonomyName ) { * @return {TaxonomiesRequest} The TaxonomiesRequest instance (for chaining) */ TaxonomiesRequest.prototype.term = function( term ) { - this._path.action = 'terms'; this._path.term = term; return this; }; -/** - * Specify that we are requesting a collection of terms for a taxonomy - * - * @method terms - * @chainable - * @return {TaxonomiesRequest} The TaxonomiesRequest instance (for chaining) - */ -TaxonomiesRequest.prototype.terms = function() { - this._path.action = 'terms'; - - return this; -}; - module.exports = TaxonomiesRequest; diff --git a/tests/integration/categories.js b/tests/integration/categories.js new file mode 100644 index 00000000..28f22008 --- /dev/null +++ b/tests/integration/categories.js @@ -0,0 +1,175 @@ +'use strict'; +var chai = require( 'chai' ); +// Variable to use as our "success token" in promise assertions +var SUCCESS = 'success'; +// Chai-as-promised and the `expect( prom ).to.eventually.equal( SUCCESS ) is +// used to ensure that the assertions running within the promise chains are +// actually run. +chai.use( require( 'chai-as-promised' ) ); +var expect = chai.expect; + +var WP = require( '../../' ); +var WPRequest = require( '../../lib/shared/wp-request.js' ); + +// Define some arrays to use ensuring the returned data is what we expect +// it to be (e.g. an array of the names from categories on the first page) +var expectedResults = { + names: { + page1: [ + 'aciform', + 'antiquarianism', + 'arrangement', + 'asmodeus', + 'Blogroll', + 'broder', + 'buying', + 'Cat A', + 'Cat B', + 'Cat C' + ], + page2: [ + 'championship', + 'chastening', + 'Child 1', + 'Child 2', + 'Child Category 01', + 'Child Category 02', + 'Child Category 03', + 'Child Category 04', + 'Child Category 05', + 'clerkship' + ], + pageLast: [ + 'ween', + 'wellhead', + 'wellintentioned', + 'whetstone', + 'years' + ] + } +}; + +// Inspecting the titles of the returned categories arrays is an easy way to +// validate that the right page of results was returned +function getNames( categories ) { + return categories.map(function( category ) { + return category.name; + }); +} + +describe( 'integration: categories()', function() { + var wp; + + beforeEach(function() { + wp = new WP({ + endpoint: 'http://wpapi.loc/wp-json' + }); + }); + + it( 'can be used to retrieve a collection of category terms', function() { + var prom = wp.categories().get().then(function( categories ) { + expect( categories ).to.be.an( 'array' ); + expect( categories.length ).to.equal( 10 ); + return SUCCESS; + }); + return expect( prom ).to.eventually.equal( SUCCESS ); + }); + + it( 'retrieves the first 10 categories by default', function() { + var prom = wp.categories().get().then(function( categories ) { + expect( categories ).to.be.an( 'array' ); + expect( categories.length ).to.equal( 10 ); + return SUCCESS; + }); + return expect( prom ).to.eventually.equal( SUCCESS ); + }); + + describe( 'paging properties', function() { + + it( 'are exposed as _paging on the response array', function() { + var prom = wp.categories().get().then(function( categories ) { + expect( categories ).to.have.property( '_paging' ); + expect( categories._paging ).to.be.an( 'object' ); + return SUCCESS; + }); + return expect( prom ).to.eventually.equal( SUCCESS ); + }); + + it( 'include the total number of categories', function() { + var prom = wp.categories().get().then(function( categories ) { + expect( categories._paging ).to.have.property( 'total' ); + expect( categories._paging.total ).to.equal( '65' ); + return SUCCESS; + }); + return expect( prom ).to.eventually.equal( SUCCESS ); + }); + + it( 'include the total number of pages available', function() { + var prom = wp.categories().get().then(function( categories ) { + expect( categories._paging ).to.have.property( 'totalPages' ); + expect( categories._paging.totalPages ).to.equal( '7' ); + return SUCCESS; + }); + return expect( prom ).to.eventually.equal( SUCCESS ); + }); + + it( 'provides a bound WPRequest for the next page as .next', function() { + var prom = wp.categories().get().then(function( categories ) { + expect( categories._paging ).to.have.property( 'next' ); + expect( categories._paging.next ).to.be.an( 'object' ); + expect( categories._paging.next ).to.be.an.instanceOf( WPRequest ); + expect( categories._paging.next._options.endpoint ).to + .equal( 'http://wpapi.loc/wp-json/wp/v2/categories?page=2' ); + // Get last page & ensure "next" no longer appears + return wp.categories().page( categories._paging.totalPages ).get().then(function( categories ) { + expect( categories._paging ).not.to.have.property( 'next' ); + expect( getNames( categories ) ).to.deep.equal( expectedResults.names.pageLast ); + return SUCCESS; + }); + }); + return expect( prom ).to.eventually.equal( SUCCESS ); + }); + + it( 'allows access to the next page of results via .next', function() { + var prom = wp.categories().get().then(function( categories ) { + return categories._paging.next.get().then(function( categories ) { + expect( categories ).to.be.an( 'array' ); + expect( categories.length ).to.equal( 10 ); + expect( getNames( categories ) ).to.deep.equal( expectedResults.names.page2 ); + return SUCCESS; + }); + }); + return expect( prom ).to.eventually.equal( SUCCESS ); + }); + + it( 'provides a bound WPRequest for the previous page as .prev', function() { + var prom = wp.categories().get().then(function( categories ) { + expect( categories._paging ).not.to.have.property( 'prev' ); + return categories._paging.next.get().then(function( categories ) { + expect( categories._paging ).to.have.property( 'prev' ); + expect( categories._paging.prev ).to.be.an( 'object' ); + expect( categories._paging.prev ).to.be.an.instanceOf( WPRequest ); + expect( categories._paging.prev._options.endpoint ).to + .equal( 'http://wpapi.loc/wp-json/wp/v2/categories?page=1' ); + return SUCCESS; + }); + }); + return expect( prom ).to.eventually.equal( SUCCESS ); + }); + + it( 'allows access to the previous page of results via .prev', function() { + var prom = wp.categories().page( 2 ).get().then(function( categories ) { + expect( getNames( categories ) ).to.deep.equal( expectedResults.names.page2 ); + return categories._paging.prev.get().then(function( categories ) { + expect( categories ).to.be.an( 'array' ); + expect( categories.length ).to.equal( 10 ); + expect( getNames( categories ) ).to.deep.equal( expectedResults.names.page1 ); + return SUCCESS; + }); + }); + return expect( prom ).to.eventually.equal( SUCCESS ); + }); + + }); + +}); diff --git a/tests/integration/tags.js b/tests/integration/tags.js new file mode 100644 index 00000000..6ddc1dd1 --- /dev/null +++ b/tests/integration/tags.js @@ -0,0 +1,180 @@ +'use strict'; +var chai = require( 'chai' ); +// Variable to use as our "success token" in promise assertions +var SUCCESS = 'success'; +// Chai-as-promised and the `expect( prom ).to.eventually.equal( SUCCESS ) is +// used to ensure that the assertions running within the promise chains are +// actually run. +chai.use( require( 'chai-as-promised' ) ); +var expect = chai.expect; + +var WP = require( '../../' ); +var WPRequest = require( '../../lib/shared/wp-request.js' ); + +// Define some arrays to use ensuring the returned data is what we expect +// it to be (e.g. an array of the names from tags on the first page) +var expectedResults = { + names: { + page1: [ + '8BIT', + 'alignment', + 'Articles', + 'aside', + 'audio', + 'captions', + 'categories', + 'chat', + 'chattels', + 'cienaga' + ], + page2: [ + 'claycold', + 'Codex', + 'comments', + 'content', + 'crushing', + 'css', + 'depo', + 'dinarchy', + 'doolie', + 'dowork' + ], + pageLast: [ + 'trackbacks', + 'twitter', + 'unculpable', + 'Unseen', + 'video', + 'videopress', + 'withered brandnew', + 'WordPress', + 'wordpress.tv', + 'xanthopsia' + ] + } +}; + +// Inspecting the titles of the returned tags arrays is an easy way to +// validate that the right page of results was returned +function getNames( tags ) { + return tags.map(function( category ) { + return category.name; + }); +} + +describe( 'integration: tags()', function() { + var wp; + + beforeEach(function() { + wp = new WP({ + endpoint: 'http://wpapi.loc/wp-json' + }); + }); + + it( 'can be used to retrieve a collection of category terms', function() { + var prom = wp.tags().get().then(function( tags ) { + expect( tags ).to.be.an( 'array' ); + expect( tags.length ).to.equal( 10 ); + return SUCCESS; + }); + return expect( prom ).to.eventually.equal( SUCCESS ); + }); + + it( 'retrieves the first 10 tags by default', function() { + var prom = wp.tags().get().then(function( tags ) { + expect( tags ).to.be.an( 'array' ); + expect( tags.length ).to.equal( 10 ); + return SUCCESS; + }); + return expect( prom ).to.eventually.equal( SUCCESS ); + }); + + describe( 'paging properties', function() { + + it( 'are exposed as _paging on the response array', function() { + var prom = wp.tags().get().then(function( tags ) { + expect( tags ).to.have.property( '_paging' ); + expect( tags._paging ).to.be.an( 'object' ); + return SUCCESS; + }); + return expect( prom ).to.eventually.equal( SUCCESS ); + }); + + it( 'include the total number of tags', function() { + var prom = wp.tags().get().then(function( tags ) { + expect( tags._paging ).to.have.property( 'total' ); + expect( tags._paging.total ).to.equal( '110' ); + return SUCCESS; + }); + return expect( prom ).to.eventually.equal( SUCCESS ); + }); + + it( 'include the total number of pages available', function() { + var prom = wp.tags().get().then(function( tags ) { + expect( tags._paging ).to.have.property( 'totalPages' ); + expect( tags._paging.totalPages ).to.equal( '11' ); + return SUCCESS; + }); + return expect( prom ).to.eventually.equal( SUCCESS ); + }); + + it( 'provides a bound WPRequest for the next page as .next', function() { + var prom = wp.tags().get().then(function( tags ) { + expect( tags._paging ).to.have.property( 'next' ); + expect( tags._paging.next ).to.be.an( 'object' ); + expect( tags._paging.next ).to.be.an.instanceOf( WPRequest ); + expect( tags._paging.next._options.endpoint ).to + .equal( 'http://wpapi.loc/wp-json/wp/v2/tags?page=2' ); + // Get last page & ensure "next" no longer appears + return wp.tags().page( tags._paging.totalPages ).get().then(function( tags ) { + expect( tags._paging ).not.to.have.property( 'next' ); + expect( getNames( tags ) ).to.deep.equal( expectedResults.names.pageLast ); + return SUCCESS; + }); + }); + return expect( prom ).to.eventually.equal( SUCCESS ); + }); + + it( 'allows access to the next page of results via .next', function() { + var prom = wp.tags().get().then(function( tags ) { + return tags._paging.next.get().then(function( tags ) { + expect( tags ).to.be.an( 'array' ); + expect( tags.length ).to.equal( 10 ); + expect( getNames( tags ) ).to.deep.equal( expectedResults.names.page2 ); + return SUCCESS; + }); + }); + return expect( prom ).to.eventually.equal( SUCCESS ); + }); + + it( 'provides a bound WPRequest for the previous page as .prev', function() { + var prom = wp.tags().get().then(function( tags ) { + expect( tags._paging ).not.to.have.property( 'prev' ); + return tags._paging.next.get().then(function( tags ) { + expect( tags._paging ).to.have.property( 'prev' ); + expect( tags._paging.prev ).to.be.an( 'object' ); + expect( tags._paging.prev ).to.be.an.instanceOf( WPRequest ); + expect( tags._paging.prev._options.endpoint ).to + .equal( 'http://wpapi.loc/wp-json/wp/v2/tags?page=1' ); + return SUCCESS; + }); + }); + return expect( prom ).to.eventually.equal( SUCCESS ); + }); + + it( 'allows access to the previous page of results via .prev', function() { + var prom = wp.tags().page( 2 ).get().then(function( tags ) { + expect( getNames( tags ) ).to.deep.equal( expectedResults.names.page2 ); + return tags._paging.prev.get().then(function( tags ) { + expect( tags ).to.be.an( 'array' ); + expect( tags.length ).to.equal( 10 ); + expect( getNames( tags ) ).to.deep.equal( expectedResults.names.page1 ); + return SUCCESS; + }); + }); + return expect( prom ).to.eventually.equal( SUCCESS ); + }); + + }); + +}); diff --git a/tests/integration/taxonomies.js b/tests/integration/taxonomies.js new file mode 100644 index 00000000..67247405 --- /dev/null +++ b/tests/integration/taxonomies.js @@ -0,0 +1,92 @@ +'use strict'; +var chai = require( 'chai' ); +// Variable to use as our "success token" in promise assertions +var SUCCESS = 'success'; +// Chai-as-promised and the `expect( prom ).to.eventually.equal( SUCCESS ) is +// used to ensure that the assertions running within the promise chains are +// actually run. +chai.use( require( 'chai-as-promised' ) ); +var expect = chai.expect; + +var WP = require( '../../' ); + +describe( 'integration: taxonomies()', function() { + var wp; + + beforeEach(function() { + wp = new WP({ + endpoint: 'http://wpapi.loc/wp-json' + }); + }); + + it( 'can be used to retrieve a dictionary of registered taxonomies', function() { + var prom = wp.taxonomies().get().then(function( taxonomies ) { + expect( taxonomies ).to.be.an( 'object' ); + expect( Object.keys( taxonomies ).length ).to.equal( 2 ); + expect( taxonomies ).to.have.property( 'category' ); + expect( taxonomies ).to.have.property( 'post_tag' ); + return SUCCESS; + }); + return expect( prom ).to.eventually.equal( SUCCESS ); + }); + + it( 'can be chained with a term() call to fetch the category taxonomy', function() { + var prom = wp.taxonomies().term( 'category' ).get().then(function( category ) { + expect( category ).to.be.an( 'object' ); + expect( category ).to.have.property( 'slug' ); + expect( category.slug ).to.equal( 'category' ); + expect( category ).to.have.property( 'hierarchical' ); + expect( category.hierarchical ).to.equal( true ); + return SUCCESS; + }); + return expect( prom ).to.eventually.equal( SUCCESS ); + }); + + it( 'can be chained with a term() call to fetch the post_tag taxonomy', function() { + var prom = wp.taxonomies().term( 'post_tag' ).get().then(function( tag ) { + expect( tag ).to.be.an( 'object' ); + expect( tag ).to.have.property( 'slug' ); + expect( tag.slug ).to.equal( 'post_tag' ); + expect( tag ).to.have.property( 'hierarchical' ); + expect( tag.hierarchical ).to.equal( false ); + return SUCCESS; + }); + return expect( prom ).to.eventually.equal( SUCCESS ); + }); + +}); + +describe( 'integration: taxonomy()', function() { + var wp; + + beforeEach(function() { + wp = new WP({ + endpoint: 'http://wpapi.loc/wp-json' + }); + }); + + it( 'can be used to directly retrieve the category taxonomy object', function() { + var prom = wp.taxonomy( 'category' ).get().then(function( category ) { + expect( category ).to.be.an( 'object' ); + expect( category ).to.have.property( 'slug' ); + expect( category.slug ).to.equal( 'category' ); + expect( category ).to.have.property( 'hierarchical' ); + expect( category.hierarchical ).to.equal( true ); + return SUCCESS; + }); + return expect( prom ).to.eventually.equal( SUCCESS ); + }); + + it( 'can be used to directly retrieve the post_tag taxonomy object', function() { + var prom = wp.taxonomy( 'post_tag' ).get().then(function( tag ) { + expect( tag ).to.be.an( 'object' ); + expect( tag ).to.have.property( 'slug' ); + expect( tag.slug ).to.equal( 'post_tag' ); + expect( tag ).to.have.property( 'hierarchical' ); + expect( tag.hierarchical ).to.equal( false ); + return SUCCESS; + }); + return expect( prom ).to.eventually.equal( SUCCESS ); + }); + +}); diff --git a/tests/unit/lib/taxonomies.js b/tests/unit/lib/taxonomies.js index 657c9283..f247b1a2 100644 --- a/tests/unit/lib/taxonomies.js +++ b/tests/unit/lib/taxonomies.js @@ -35,9 +35,9 @@ describe( 'wp.taxonomies', function() { it( 'should intitialize instance properties', function() { var _supportedMethods = taxonomies._supportedMethods.sort().join( '|' ); expect( taxonomies._filters ).to.deep.equal( {} ); - expect( taxonomies._path ).to.deep.equal( {} ); + expect( taxonomies._path ).to.deep.equal({ collection: 'taxonomies' }); expect( taxonomies._params ).to.deep.equal( {} ); - expect( taxonomies._template ).to.equal( 'taxonomies(/:taxonomy)(/:action)(/:term)' ); + expect( taxonomies._template ).to.equal( '(:collection)(/:term)' ); expect( _supportedMethods ).to.equal( 'get|head' ); }); @@ -59,17 +59,6 @@ describe( 'wp.taxonomies', function() { }); - describe( '_pathValidators', function() { - - it( 'has a validator for the "action" property', function() { - var taxonomies = new TaxonomiesRequest(); - expect( taxonomies._pathValidators ).to.deep.equal({ - action: /terms/ - }); - }); - - }); - describe( 'URL Generation', function() { var taxonomies; @@ -81,33 +70,16 @@ describe( 'wp.taxonomies', function() { }; }); - it( 'should create the URL for retrieving all taxonomies', function() { - var url = taxonomies._renderURI(); + it( 'should create the URL for retrieving a specific collection', function() { + var url = taxonomies.collection( 'taxonomies' )._renderURI(); expect( url ).to.equal( '/wp-json/wp/v2/taxonomies' ); }); it( 'should create the URL for retrieving a specific taxonomy', function() { - var url = taxonomies.taxonomy( 'my-tax' )._renderURI(); + var url = taxonomies.collection( 'taxonomies' ).term( 'my-tax' )._renderURI(); expect( url ).to.equal( '/wp-json/wp/v2/taxonomies/my-tax' ); }); - it( 'should create the URL for retrieving all terms for a specific taxonomy', function() { - var url = taxonomies.taxonomy( 'my-tax' ).terms()._renderURI(); - expect( url ).to.equal( '/wp-json/wp/v2/taxonomies/my-tax/terms' ); - }); - - it( 'should error if any _path.action other than "terms" is set', function() { - taxonomies._path.action = 'something', - expect(function actionMustBeTerms() { - taxonomies._renderURI(); - }).to.throw(); - }); - - it( 'should create the URL for retrieving a specific taxonomy term', function() { - var url = taxonomies.taxonomy( 'my-tax' ).terms().term( 1337 )._renderURI(); - expect( url ).to.equal( '/wp-json/wp/v2/taxonomies/my-tax/terms/1337' ); - }); - }); }); diff --git a/tests/unit/wp.js b/tests/unit/wp.js index 700510dc..465c420d 100644 --- a/tests/unit/wp.js +++ b/tests/unit/wp.js @@ -220,17 +220,17 @@ describe( 'wp', function() { describe( 'taxonomy shortcut handlers', function() { - it( 'defines a .categories() shortcut for the category taxonomy terms', function() { + it( 'defines a .categories() shortcut for the category terms collection', function() { var categories = site.categories(); expect( categories instanceof TaxonomiesRequest ).to.be.true; expect( categories._renderURI() ).to - .equal( 'endpoint/url/wp/v2/taxonomies/category/terms' ); + .equal( 'endpoint/url/wp/v2/categories' ); }); - it( 'defines a .tags() shortcut for the tag taxonomy terms', function() { + it( 'defines a .tags() shortcut for the tag terms collection', function() { var tags = site.tags(); expect( tags instanceof TaxonomiesRequest ).to.be.true; - expect( tags._renderURI() ).to.equal( 'endpoint/url/wp/v2/taxonomies/post_tag/terms' ); + expect( tags._renderURI() ).to.equal( 'endpoint/url/wp/v2/tags' ); }); it( 'defines a generic .taxonomy() handler for arbitrary taxonomy objects', function() { diff --git a/wp.js b/wp.js index 5a47cfcc..9f07b8f2 100644 --- a/wp.js +++ b/wp.js @@ -122,7 +122,7 @@ WP.prototype.posts = function( options ) { }; /** - * Start a request against the `taxonomies` endpoint + * Start a request for a taxonomy or taxonomy term collection * * @method taxonomies * @param {Object} [options] An options hash for a new TaxonomiesRequest @@ -137,21 +137,22 @@ WP.prototype.taxonomies = function( options ) { /** * Start a request for a specific taxonomy object * - * It is repetitive to have to type `.taxonomies().taxonomy()` whenever you want to request - * a taxonomy object or list of terms for a taxonomy. This convenience method lets you - * create a `TaxonomiesRequest` object that is bound to the provided taxonomy name. + * It is slightly unintuitive to consider the name of a taxonomy a "term," as is + * needed in order to retrieve the taxonomy object from the .taxonomies() method. + * This convenience method lets you create a `TaxonomiesRequest` object that is + * bound to the provided taxonomy name, without having to utilize the "term" method. * * @example * If your site uses two custom taxonomies, book_genre and book_publisher, before you would * have had to request these terms using the verbose form: * - * wp.taxonomies().taxonomy( 'book_genre' ).terms()... - * wp.taxonomies().taxonomy( 'book_publisher' )... + * wp.taxonomies().term( 'book_genre' ) + * wp.taxonomies().term( 'book_publisher' ) * * Using `.taxonomy()`, the same query can be achieved much more succinctly: * - * wp.taxonomy( 'book_genre' ).terms()... - * wp.taxonomy( 'book_publisher' )... + * wp.taxonomy( 'book_genre' ) + * wp.taxonomy( 'book_publisher' ) * * @method taxonomy * @param {String} taxonomyName The name of the taxonomy to request @@ -159,7 +160,7 @@ WP.prototype.taxonomies = function( options ) { */ WP.prototype.taxonomy = function( taxonomyName ) { var options = extend( {}, this._options ); - return new TaxonomiesRequest( options ).taxonomy( taxonomyName ); + return new TaxonomiesRequest( options ).term( taxonomyName ); }; /** @@ -170,34 +171,35 @@ WP.prototype.taxonomy = function( taxonomyName ) { * @example * These are equivalent: * - * wp.taxonomies().taxonomy( 'category' ).terms() + * wp.taxonomies().collection( 'categories' ) * wp.categories() * * @method categories - * @return {TaxonomiesRequest} A TaxonomiesRequest object bound to the terms for "category" + * @return {TaxonomiesRequest} A TaxonomiesRequest object bound to the categories collection */ WP.prototype.categories = function() { var options = extend( {}, this._options ); - return new TaxonomiesRequest( options ).taxonomy( 'category' ).terms(); + return new TaxonomiesRequest( options ).collection( 'categories' ); }; /** * Request a list of post_tag terms * - * This is a shortcut method to retrieve the terms for the "post_tag" taxonomy + * This is a shortcut method to interact with the collection of terms for the + * "post_tag" taxonomy. * * @example * These are equivalent: * - * wp.taxonomies().taxonomy( 'post_tag' ).terms() + * wp.taxonomies().collection( 'tags' ) * wp.tags() * * @method tags - * @return {TaxonomiesRequest} A TaxonomiesRequest object bound to the terms for "post_tag" + * @return {TaxonomiesRequest} A TaxonomiesRequest object bound to the tags collection */ WP.prototype.tags = function() { var options = extend( {}, this._options ); - return new TaxonomiesRequest( options ).taxonomy( 'post_tag' ).terms(); + return new TaxonomiesRequest( options ).collection( 'tags' ); }; /** From 23ae892819a1a794fcd1d994ec8ba238b80fc814 Mon Sep 17 00:00:00 2001 From: "K. Adam White" Date: Thu, 21 Jan 2016 00:21:52 -0500 Subject: [PATCH 3/5] Add integration tests for retrieving taxonomy terms by id & slug Slug searches are cumbersome; filtering was originally added in WP-API/WP-API#924 but has been removed again in the v2 betas (see WP-API/WP-API#924 for latest status) Integration tests demonstrate the best option we currently have, which is to search for the term then iterate through the responses from that search until you find a match. --- tests/integration/categories.js | 90 +++++++++++++++++++++++++++++++++ tests/integration/tags.js | 87 +++++++++++++++++++++++++++++++ 2 files changed, 177 insertions(+) diff --git a/tests/integration/categories.js b/tests/integration/categories.js index 28f22008..38ea9a59 100644 --- a/tests/integration/categories.js +++ b/tests/integration/categories.js @@ -172,4 +172,94 @@ describe( 'integration: categories()', function() { }); + describe( 'term()', function() { + + it( 'can be used to access an individual category term', function() { + var selectedCategory; + var prom = wp.categories().get().then(function( categories ) { + // Pick one of the categories + selectedCategory = categories[ 3 ]; + // Query for that category directly + return wp.categories().term( selectedCategory.id ); + }).then(function( category ) { + expect( category ).to.be.an( 'object' ); + expect( category ).to.have.property( 'id' ); + expect( category.id ).to.equal( selectedCategory.id ); + expect( category ).to.have.property( 'slug' ); + expect( category.slug ).to.equal( selectedCategory.slug ); + expect( category ).to.have.property( 'taxonomy' ); + expect( category.taxonomy ).to.equal( 'category' ); + expect( category ).to.have.property( 'parent' ); + expect( category.parent ).to.equal( 0 ); + return SUCCESS; + }); + return expect( prom ).to.eventually.equal( SUCCESS ); + }); + + }); + + describe( 'search()', function() { + + it( 'can be used to retrieve a category by slug', function() { + var selectedCategory; + var prom = wp.categories().get().then(function( categories ) { + // Pick one of the categories + selectedCategory = categories[ 3 ]; + // Search for that category by slug + return wp.categories().search( selectedCategory.slug ); + }).then(function( categories ) { + expect( categories ).to.be.an( 'array' ); + expect( categories.length ).to.equal( 1 ); + return categories[ 0 ]; + }).then(function( category ) { + expect( category ).to.be.an( 'object' ); + expect( category ).to.have.property( 'id' ); + expect( category.id ).to.equal( selectedCategory.id ); + expect( category ).to.have.property( 'slug' ); + expect( category.slug ).to.equal( selectedCategory.slug ); + expect( category ).to.have.property( 'taxonomy' ); + expect( category.taxonomy ).to.equal( 'category' ); + expect( category ).to.have.property( 'parent' ); + expect( category.parent ).to.equal( 0 ); + return SUCCESS; + }); + return expect( prom ).to.eventually.equal( SUCCESS ); + }); + + it( 'returns all categories matching the provided search string', function() { + var prom = wp.categories().search( 'parent' ).get().then(function( categories ) { + expect( categories ).to.be.an( 'array' ); + expect( categories.length ).to.equal( 4 ); + var slugs = categories.map(function( cat ) { + return cat.slug; + }).sort().join( ' '); + expect( slugs ).to.equal( 'foo-a-foo-parent foo-parent parent parent-category' ); + return SUCCESS; + }); + return expect( prom ).to.eventually.equal( SUCCESS ); + }); + + it( 'can be used to retrieve a category by slug from a set of search results', function() { + var prom = wp.categories().search( 'parent' ).get().then(function( categories ) { + // Iterating over response of search is the best we can do until + // filtering for taxonomy term collections is reinstated + for ( var i = 0; i < 4; i++ ) { + if ( categories[ i ].slug === 'parent' ) { + return categories[ i ]; + } + } + }).then(function( category ) { + expect( category ).to.have.property( 'slug' ); + expect( category.slug ).to.equal( 'parent' ); + expect( category ).to.have.property( 'name' ); + expect( category.name ).to.equal( 'Parent' ); + expect( category ).to.have.property( 'parent' ); + expect( category.parent ).to.equal( 0 ); + return SUCCESS; + }); + return expect( prom ).to.eventually.equal( SUCCESS ); + }); + + }); + }); diff --git a/tests/integration/tags.js b/tests/integration/tags.js index 6ddc1dd1..e57ab3a8 100644 --- a/tests/integration/tags.js +++ b/tests/integration/tags.js @@ -177,4 +177,91 @@ describe( 'integration: tags()', function() { }); + describe( 'term()', function() { + + it( 'can be used to access an individual tag term', function() { + var selectedTag; + var prom = wp.tags().get().then(function( tags ) { + // Pick one of the tags + selectedTag = tags[ 3 ]; + // Query for that tag directly + return wp.tags().term( selectedTag.id ); + }).then(function( tag ) { + expect( tag ).to.be.an( 'object' ); + expect( tag ).to.have.property( 'id' ); + expect( tag.id ).to.equal( selectedTag.id ); + expect( tag ).to.have.property( 'slug' ); + expect( tag.slug ).to.equal( selectedTag.slug ); + expect( tag ).to.have.property( 'taxonomy' ); + expect( tag.taxonomy ).to.equal( 'post_tag' ); + expect( tag ).not.to.have.property( 'parent' ); + return SUCCESS; + }); + return expect( prom ).to.eventually.equal( SUCCESS ); + }); + + }); + + describe( 'search()', function() { + + it( 'can be used to retrieve a tag by slug', function() { + var selectedTag; + var prom = wp.tags().get().then(function( tags ) { + // Pick one of the tags + selectedTag = tags[ 3 ]; + // Search for that tag by slug + return wp.tags().search( selectedTag.slug ); + }).then(function( tags ) { + expect( tags ).to.be.an( 'array' ); + expect( tags.length ).to.equal( 1 ); + return tags[ 0 ]; + }).then(function( tag ) { + expect( tag ).to.be.an( 'object' ); + expect( tag ).to.have.property( 'id' ); + expect( tag.id ).to.equal( selectedTag.id ); + expect( tag ).to.have.property( 'slug' ); + expect( tag.slug ).to.equal( selectedTag.slug ); + expect( tag ).to.have.property( 'taxonomy' ); + expect( tag.taxonomy ).to.equal( 'post_tag' ); + expect( tag ).not.to.have.property( 'parent' ); + return SUCCESS; + }); + return expect( prom ).to.eventually.equal( SUCCESS ); + }); + + it( 'returns all tags matching the provided search string', function() { + var prom = wp.tags().search( 'post' ).get().then(function( tags ) { + expect( tags ).to.be.an( 'array' ); + expect( tags.length ).to.equal( 2 ); + var slugs = tags.map(function( tag ) { + return tag.slug; + }).sort().join( ' '); + expect( slugs ).to.equal( 'post post-formats' ); + return SUCCESS; + }); + return expect( prom ).to.eventually.equal( SUCCESS ); + }); + + it( 'can be used to retrieve a tag by slug from a set of search results', function() { + var prom = wp.tags().search( 'post' ).get().then(function( tags ) { + // Iterating over response of search is the best we can do until + // filtering for taxonomy term collections is reinstated + for ( var i = 0; i < tags.length; i++ ) { + if ( tags[ i ].slug === 'post' ) { + return tags[ i ]; + } + } + }).then(function( tag ) { + expect( tag ).to.have.property( 'slug' ); + expect( tag.slug ).to.equal( 'post' ); + expect( tag ).to.have.property( 'name' ); + expect( tag.name ).to.equal( 'post' ); + expect( tag ).not.to.have.property( 'parent' ); + return SUCCESS; + }); + return expect( prom ).to.eventually.equal( SUCCESS ); + }); + + }); + }); From 14fccb5d8ede9b449e71e396e062bcb1ff1d995f Mon Sep 17 00:00:00 2001 From: "K. Adam White" Date: Thu, 21 Jan 2016 00:26:07 -0500 Subject: [PATCH 4/5] Resolve lint errors --- tests/integration/categories.js | 2 +- tests/integration/tags.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration/categories.js b/tests/integration/categories.js index 38ea9a59..3b8a2277 100644 --- a/tests/integration/categories.js +++ b/tests/integration/categories.js @@ -232,7 +232,7 @@ describe( 'integration: categories()', function() { expect( categories.length ).to.equal( 4 ); var slugs = categories.map(function( cat ) { return cat.slug; - }).sort().join( ' '); + }).sort().join( ' ' ); expect( slugs ).to.equal( 'foo-a-foo-parent foo-parent parent parent-category' ); return SUCCESS; }); diff --git a/tests/integration/tags.js b/tests/integration/tags.js index e57ab3a8..b3c89415 100644 --- a/tests/integration/tags.js +++ b/tests/integration/tags.js @@ -235,7 +235,7 @@ describe( 'integration: tags()', function() { expect( tags.length ).to.equal( 2 ); var slugs = tags.map(function( tag ) { return tag.slug; - }).sort().join( ' '); + }).sort().join( ' ' ); expect( slugs ).to.equal( 'post post-formats' ); return SUCCESS; }); From d83b3af8c3c83a44b22fc0d3e51bfbbcbe2eff42 Mon Sep 17 00:00:00 2001 From: "K. Adam White" Date: Thu, 21 Jan 2016 00:42:26 -0500 Subject: [PATCH 5/5] Add parent() query method to taxonomies query chains Again props to @jasonphillips for the initial implementation which I am adapting here (see below for commit link) https://github.com/jasonphillips/wordpress-rest-api/commit/64ae6c40437a77a7ec96a97f003ae058cd95ed89 --- lib/taxonomies.js | 22 +++++++++++++++ tests/integration/categories.js | 47 +++++++++++++++++++++++++++++++++ tests/unit/lib/taxonomies.js | 5 ++++ 3 files changed, 74 insertions(+) diff --git a/lib/taxonomies.js b/lib/taxonomies.js index 278a3935..ff86c62c 100644 --- a/lib/taxonomies.js +++ b/lib/taxonomies.js @@ -126,4 +126,26 @@ TaxonomiesRequest.prototype.term = function( term ) { return this; }; +/** + * Search for hierarchical taxonomy terms that are children of the parent term + * indicated by the provided term ID + * + * @example + * + * wp.categories().parent( 42 ).then(function( categories ) { + * console.log( 'all of these categories are sub-items of cat ID#42:' ); + * console.log( categories ); + * }); + * + * @method parent + * @chainable + * @param {Number} parentId The ID of a (hierarchical) taxonomy term + * @return {TaxonomiesRequest} The TaxonomiesRequest instance (for chaining) + */ +TaxonomiesRequest.prototype.parent = function( parentId ) { + this.param( 'parent', parentId, true ); + + return this; +}; + module.exports = TaxonomiesRequest; diff --git a/tests/integration/categories.js b/tests/integration/categories.js index 3b8a2277..485d5684 100644 --- a/tests/integration/categories.js +++ b/tests/integration/categories.js @@ -262,4 +262,51 @@ describe( 'integration: categories()', function() { }); + describe( 'parent()', function() { + + it( 'can be used to retrieve direct children of a specific category', function() { + var parentCat; + var childCat1; + var childCat2; + // First, find the "parent" category + var prom = wp.categories().search( 'parent' ).get().then(function( categories ) { + for ( var i = 0; i < 4; i++ ) { + if ( categories[ i ].slug === 'parent' ) { + // Return a query for the matching category's child + parentCat = categories[ i ]; + return wp.categories().parent( parentCat.id ); + } + } + }).then(function( categories ) { + expect( categories ).to.be.an( 'array' ); + expect( categories.length ).to.equal( 1 ); + var category = categories[ 0 ]; + expect( category ).to.have.property( 'name' ); + expect( category.name ).to.equal( 'Child 1' ); + expect( category ).to.have.property( 'parent' ); + expect( category.parent ).to.equal( parentCat.id ); + childCat1 = category; + // Go one level deeper + return wp.categories().parent( childCat1.id ); + }).then(function( categories ) { + expect( categories ).to.be.an( 'array' ); + expect( categories.length ).to.equal( 1 ); + var category = categories[ 0 ]; + expect( category ).to.have.property( 'name' ); + expect( category.name ).to.equal( 'Child 2' ); + expect( category ).to.have.property( 'parent' ); + expect( category.parent ).to.equal( childCat1.id ); + childCat2 = category; + // Go one level deeper + return wp.categories().parent( childCat2.id ); + }).then(function( categories ) { + expect( categories ).to.be.an( 'array' ); + expect( categories.length ).to.equal( 0 ); + return SUCCESS; + }); + return expect( prom ).to.eventually.equal( SUCCESS ); + }); + + }); + }); diff --git a/tests/unit/lib/taxonomies.js b/tests/unit/lib/taxonomies.js index f247b1a2..72e1e7d1 100644 --- a/tests/unit/lib/taxonomies.js +++ b/tests/unit/lib/taxonomies.js @@ -80,6 +80,11 @@ describe( 'wp.taxonomies', function() { expect( url ).to.equal( '/wp-json/wp/v2/taxonomies/my-tax' ); }); + it( 'should create the URL for retrieving taxonomies with a shared parent', function() { + var url = taxonomies.collection( 'categories' ).parent( 42 )._renderURI(); + expect( url ).to.equal( '/wp-json/wp/v2/categories?parent=42' ); + }); + }); });