From de72945fe7e2b1552da7aa8da5e8136de6c74115 Mon Sep 17 00:00:00 2001 From: Bernhard Reiter Date: Wed, 7 Dec 2016 19:20:48 +0100 Subject: [PATCH 01/27] lib/wporg: Add fetchThemeInformation() function --- client/lib/wporg/index.js | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/client/lib/wporg/index.js b/client/lib/wporg/index.js index 7cffaacbbf58e..17c6d3e89fa25 100644 --- a/client/lib/wporg/index.js +++ b/client/lib/wporg/index.js @@ -95,5 +95,39 @@ module.exports = { .end( function( err, data ) { callback( err, data.body ); } ); - } + }, + /** + * Get information about a given theme from the WordPress.org API. + * If provided with a callback, will call that on succes with an object with theme details. + * Otherwise, will return a promise. + * + * @param {string} themeId The theme identifier. + * @param {function} callback Callback that gets executed after the XHR returns the results. + * @returns {?Promise} Promise that is returned if no callback parameter is passed + */ + fetchThemeInformation: function( themeId, callback ) { + const url = 'https://api.wordpress.org/themes/info/1.1/'; + const query = { action: 'theme_information', 'request[slug]': themeId }; + // if callback is provided, behave traditionally + if ( 'function' === typeof callback ) { + return superagent + .get( url ) + .set( 'Accept', 'application/json' ) + .query( query ) + .end( ( err, { body } ) => { + callback( err, body ); + } ); + } + + // otherwise, return a Promise + return new Promise( ( resolve, reject ) => { + return superagent + .get( url ) + .set( 'Accept', 'application/json' ) + .query( query ) + .end( ( err, { body } ) => { + err ? reject( err ) : resolve( body ); + } ); + } ); + }, }; From f15e9b5d5063fd45480b2ed35ff2d6f5f7cde791 Mon Sep 17 00:00:00 2001 From: Bernhard Reiter Date: Wed, 7 Dec 2016 19:21:33 +0100 Subject: [PATCH 02/27] state/themes/actions: Enable fetching of theme information from wporg --- client/state/themes/actions.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/client/state/themes/actions.js b/client/state/themes/actions.js index fe9e85ebe2ee6..652b2af305f19 100644 --- a/client/state/themes/actions.js +++ b/client/state/themes/actions.js @@ -8,6 +8,7 @@ import debugFactory from 'debug'; * Internal dependencies */ import wpcom from 'lib/wp'; +import wporg from 'lib/wporg'; import { // Old action names THEME_BACK_PATH_SET, @@ -257,6 +258,24 @@ export function requestTheme( themeId, siteId ) { themeId } ); + if ( siteId === 'wporg' ) { + return wporg.fetchThemeInformation( themeId ).then( ( theme ) => { + dispatch( receiveTheme( theme, siteId ) ); + dispatch( { + type: THEME_REQUEST_SUCCESS, + siteId, + themeId + } ); + } ).catch( ( error ) => { + dispatch( { + type: THEME_REQUEST_FAILURE, + siteId, + themeId, + error + } ); + } ); + } + if ( siteId === 'wpcom' ) { return wpcom.undocumented().themeDetails( themeId ).then( ( theme ) => { dispatch( receiveTheme( normalizeWpcomTheme( theme ), siteId ) ); From b4b6fcccacf102981a6620e5680d9b1eee4e14ee Mon Sep 17 00:00:00 2001 From: Bernhard Reiter Date: Wed, 7 Dec 2016 19:22:04 +0100 Subject: [PATCH 03/27] Theme Sheet: On JP sites, try querying theme information from wporg --- client/my-sites/theme/main.jsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/my-sites/theme/main.jsx b/client/my-sites/theme/main.jsx index 80ae8fd0efd67..11e8a3ce495cf 100644 --- a/client/my-sites/theme/main.jsx +++ b/client/my-sites/theme/main.jsx @@ -426,7 +426,7 @@ const ThemeSheet = React.createClass( { const analyticsPath = `/theme/:slug${ section ? '/' + section : '' }${ siteID ? '/:site_id' : '' }`; const analyticsPageTitle = `Themes > Details Sheet${ section ? ' > ' + titlecase( section ) : '' }${ siteID ? ' > Site' : '' }`; - const { name: themeName, description, currentUserId, siteIdOrWpcom } = this.props; + const { name: themeName, description, currentUserId, isJetpack, siteIdOrWpcom } = this.props; const title = themeName && i18n.translate( '%(themeName)s Theme', { args: { themeName } } ); @@ -451,6 +451,7 @@ const ThemeSheet = React.createClass( { return (
+ { isJetpack && } { currentUserId && } { siteID && } { siteID && } From 6205d1b2e1deeb142892c51cf5d94c9d8a8cff56 Mon Sep 17 00:00:00 2001 From: Bernhard Reiter Date: Thu, 8 Dec 2016 00:16:38 +0100 Subject: [PATCH 04/27] state/themes/utils: Add normalizeWporgTheme() util --- client/state/themes/test/utils.js | 26 +++++++++++++++++++++++++ client/state/themes/utils.js | 32 ++++++++++++++++++++++++++++++- 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/client/state/themes/test/utils.js b/client/state/themes/test/utils.js index 2f278bee0f6bf..9ab32085a3be0 100644 --- a/client/state/themes/test/utils.js +++ b/client/state/themes/test/utils.js @@ -8,6 +8,7 @@ import { expect } from 'chai'; */ import { normalizeWpcomTheme, + normalizeWporgTheme, getThemeIdFromStylesheet, getNormalizedThemesQuery, getSerializedThemesQuery, @@ -51,6 +52,31 @@ describe( 'utils', () => { } ); } ); + describe( '#normalizeWporgTheme()', () => { + it( 'should return an empty object when given no argument', () => { + const normalizedTheme = normalizeWporgTheme(); + expect( normalizedTheme ).to.deep.equal( {} ); + } ); + it( 'should rename some keys', () => { + const normalizedTheme = normalizeWporgTheme( { + slug: 'twentyfifteen', + name: 'Twenty Fifteen', + author: 'wordpressdotorg', + screenshot_url: '//ts.w.org/wp-content/themes/twentyfifteen/screenshot.png?ver=1.7', + preview_url: 'https://wp-themes.com/twentyfifteen', + download_link: 'http://downloads.wordpress.org/theme/twentyfifteen.1.7.zip' + } ); + expect( normalizedTheme ).to.deep.equal( { + id: 'twentyfifteen', + name: 'Twenty Fifteen', + author: 'wordpressdotorg', + screenshot: '//ts.w.org/wp-content/themes/twentyfifteen/screenshot.png?ver=1.7', + demo_uri: 'https://wp-themes.com/twentyfifteen', + download: 'http://downloads.wordpress.org/theme/twentyfifteen.1.7.zip' + } ); + } ); + } ); + describe( '#getThemeIdFromStylesheet()', () => { it( 'should return undefined when given no argument', () => { const themeId = getThemeIdFromStylesheet(); diff --git a/client/state/themes/utils.js b/client/state/themes/utils.js index 4c68bc89c90f3..c42ce34e4bc14 100644 --- a/client/state/themes/utils.js +++ b/client/state/themes/utils.js @@ -2,7 +2,7 @@ * External dependencies */ import startsWith from 'lodash/startsWith'; -import { filter, get, mapKeys, omit, omitBy, split } from 'lodash'; +import { filter, get, map, mapKeys, omit, omitBy, split } from 'lodash'; /** * Internal dependencies @@ -39,6 +39,36 @@ export function normalizeWpcomTheme( theme ) { ) ); } +/** + * Normalizes a theme obtained from the WordPress.org REST API + * + * @param {Object} theme Themes object + * @return {Object} Normalized theme object + */ +export function normalizeWporgTheme( theme ) { + const attributesMap = { + slug: 'id', + preview_url: 'demo_uri', + screenshot_url: 'screenshot', + download_link: 'download' + }; + + const normalizedTheme = mapKeys( theme, ( value, key ) => ( + get( attributesMap, key, key ) + ) ); + + if ( ! normalizedTheme.tags ) { + return normalizedTheme; + } + + return { + ...omit( normalizedTheme, 'tags' ), + taxonomies: { theme_feature: map( normalizedTheme.tags, + ( name, slug ) => ( { name, slug } ) + ) } + }; +} + /** * Given a theme stylesheet string (like 'pub/twentysixteen'), returns the corresponding theme ID ('twentysixteen'). * From 5d99f41ff0042e4be5d1d979ed6cb319b2156c29 Mon Sep 17 00:00:00 2001 From: Bernhard Reiter Date: Thu, 8 Dec 2016 00:18:00 +0100 Subject: [PATCH 05/27] state/theme/actions: Normalize wporg themes --- client/state/themes/actions.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/client/state/themes/actions.js b/client/state/themes/actions.js index 652b2af305f19..cedcb0c068fee 100644 --- a/client/state/themes/actions.js +++ b/client/state/themes/actions.js @@ -44,7 +44,12 @@ import { import { isJetpackSite } from 'state/sites/selectors'; import { getActiveTheme } from './selectors'; import { getQueryParams } from './themes-list/selectors'; -import { getThemeIdFromStylesheet, filterThemesForJetpack, normalizeWpcomTheme } from './utils'; +import { + getThemeIdFromStylesheet, + filterThemesForJetpack, + normalizeWpcomTheme, + normalizeWporgTheme +} from './utils'; const debug = debugFactory( 'calypso:themes:actions' ); //eslint-disable-line no-unused-vars @@ -260,7 +265,7 @@ export function requestTheme( themeId, siteId ) { if ( siteId === 'wporg' ) { return wporg.fetchThemeInformation( themeId ).then( ( theme ) => { - dispatch( receiveTheme( theme, siteId ) ); + dispatch( receiveTheme( normalizeWporgTheme( theme ), siteId ) ); dispatch( { type: THEME_REQUEST_SUCCESS, siteId, From 8a13f1cadc15ea20499088fc3c4b63aa88c61bb2 Mon Sep 17 00:00:00 2001 From: Bernhard Reiter Date: Thu, 8 Dec 2016 00:19:23 +0100 Subject: [PATCH 06/27] state/themes/selectors#getTheme(): On JP sites, merge WP.org data if available --- client/my-sites/theme/main.jsx | 1 + client/my-sites/themes/helpers.js | 6 +-- client/state/themes/selectors.js | 17 +++++++- client/state/themes/test/selectors.js | 56 +++++++++++++++++++++++++++ 4 files changed, 73 insertions(+), 7 deletions(-) diff --git a/client/my-sites/theme/main.jsx b/client/my-sites/theme/main.jsx index 11e8a3ce495cf..06dd81b39a6d0 100644 --- a/client/my-sites/theme/main.jsx +++ b/client/my-sites/theme/main.jsx @@ -577,6 +577,7 @@ export default connect( const isCurrentUserPaid = isUserPaid( state, currentUserId ); const theme = getTheme( state, siteIdOrWpcom, id ); const error = theme ? false : getThemeRequestErrors( state, id, siteIdOrWpcom ); + return { ...theme, id, diff --git a/client/my-sites/themes/helpers.js b/client/my-sites/themes/helpers.js index f7b4a0d7e71cd..e7042d48d60f6 100644 --- a/client/my-sites/themes/helpers.js +++ b/client/my-sites/themes/helpers.js @@ -16,11 +16,7 @@ import config from 'config'; import { sectionify } from 'lib/route/path'; import { oldShowcaseUrl, isPremiumTheme as isPremium } from 'state/themes/utils'; -export function getPreviewUrl( theme, site ) { - if ( site && site.jetpack ) { - return site.options.admin_url + 'customize.php?theme=' + theme.id + '&return=' + encodeURIComponent( window.location ); - } - +export function getPreviewUrl( theme ) { return `${ theme.demo_uri }?demo=true&iframe=true&theme_preview=true`; } diff --git a/client/state/themes/selectors.js b/client/state/themes/selectors.js index 2487d3d7195ce..fc355d453a559 100644 --- a/client/state/themes/selectors.js +++ b/client/state/themes/selectors.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { includes, isEqual, omit, some, get } from 'lodash'; +import { includes, isEqual, omit, some, get, pick } from 'lodash'; import createSelector from 'lib/create-selector'; /** @@ -60,7 +60,20 @@ export const getTheme = createSelector( return null; } - return manager.getItem( themeId ); + const theme = manager.getItem( themeId ); + if ( siteId === 'wpcom' || siteId === 'wporg' ) { + return theme; + } + // We're dealing with a Jetpack site. If we have theme info obtained from the + // WordPress.org API, merge it. + const wporgTheme = getTheme( state, 'wporg', themeId ); + if ( ! wporgTheme ) { + return theme; + } + return { + ...theme, + ...pick( wporgTheme, [ 'demo_uri' ] ) + }; }, ( state ) => state.themes.queries ); diff --git a/client/state/themes/test/selectors.js b/client/state/themes/test/selectors.js index 94ecea4abea4f..1a293dc50fffa 100644 --- a/client/state/themes/test/selectors.js +++ b/client/state/themes/test/selectors.js @@ -130,6 +130,62 @@ describe( 'themes selectors', () => { expect( theme ).to.equal( twentysixteen ); } ); + + context( 'on a Jetpack site', () => { + it( 'with a theme not found on WP.org, should return the theme object', () => { + const jetpackTheme = { + id: 'twentyseventeen', + name: 'Twenty Seventeen', + author: 'the WordPress team', + }; + + const theme = getTheme( { + themes: { + queries: { + 2916284: new ThemeQueryManager( { + items: { twentyseventeen: jetpackTheme } + } ) + } + } + }, 2916284, 'twentyseventeen' ); + + expect( theme ).to.deep.equal( jetpackTheme ); + } ); + + it( 'with a theme found on WP.org, should return an object with some attrs merged from WP.org', () => { + const jetpackTheme = { + id: 'twentyseventeen', + name: 'Twenty Seventeen', + author: 'the WordPress team', + }; + const wporgTheme = { + demo_uri: 'https://wp-themes.com/twentyseventeen', + download: 'http://downloads.wordpress.org/theme/twentyseventeen.1.1.zip', + taxonomies: { + theme_feature: { + 'custom-header': 'Custom Header' + } + } + }; + const theme = getTheme( { + themes: { + queries: { + 2916284: new ThemeQueryManager( { + items: { twentyseventeen: jetpackTheme } + } ), + wporg: new ThemeQueryManager( { + items: { twentyseventeen: wporgTheme } + } ), + } + } + }, 2916284, 'twentyseventeen' ); + + expect( theme ).to.deep.equal( { + ...jetpackTheme, + ...wporgTheme + } ); + } ); + } ); } ); describe( '#getThemesRequestError()', () => { From 0c70ad7735e67a269b3f4b8bf68e96e2418f2e9a Mon Sep 17 00:00:00 2001 From: Bernhard Reiter Date: Thu, 8 Dec 2016 18:34:09 +0100 Subject: [PATCH 07/27] Theme Sheet: Only show 'Open Live Demo' link when we have a Demo URL --- client/my-sites/theme/main.jsx | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/client/my-sites/theme/main.jsx b/client/my-sites/theme/main.jsx index 06dd81b39a6d0..9198ced545855 100644 --- a/client/my-sites/theme/main.jsx +++ b/client/my-sites/theme/main.jsx @@ -156,6 +156,17 @@ const ThemeSheet = React.createClass( { return null; }, + renderPreviewButton() { + return ( + + + + { i18n.translate( 'Open Live Demo', { context: 'Individual theme live preview button' } ) } + + + ); + }, + renderScreenshot() { let screenshot; if ( this.props.isJetpack ) { @@ -166,12 +177,7 @@ const ThemeSheet = React.createClass( { const img = screenshot && ; return ( ); From 26ec67e785df68828919cfce220d751065eb398e Mon Sep 17 00:00:00 2001 From: Bernhard Reiter Date: Tue, 6 Dec 2016 19:25:25 +0100 Subject: [PATCH 08/27] Themes: Use getThemeForumUrl() selector instead of getForumUrl() helper --- client/my-sites/theme/main.jsx | 6 +++--- client/my-sites/theme/test/main.jsx | 1 - client/my-sites/themes/helpers.js | 6 +----- client/my-sites/themes/thanks-modal.jsx | 6 ++++-- 4 files changed, 8 insertions(+), 11 deletions(-) diff --git a/client/my-sites/theme/main.jsx b/client/my-sites/theme/main.jsx index 9198ced545855..cfe87b7ddd114 100644 --- a/client/my-sites/theme/main.jsx +++ b/client/my-sites/theme/main.jsx @@ -28,7 +28,6 @@ import { getSelectedSite } from 'state/ui/selectors'; import { getSiteSlug, isJetpackSite } from 'state/sites/selectors'; import { getCurrentUserId } from 'state/current-user/selectors'; import { isUserPaid } from 'state/purchases/selectors'; -import { getForumUrl } from 'my-sites/themes/helpers'; import { isPremiumTheme as isPremium } from 'state/themes/utils'; import ThanksModal from 'my-sites/themes/thanks-modal'; import QueryActiveTheme from 'components/data/query-active-theme'; @@ -37,7 +36,7 @@ import QueryUserPurchases from 'components/data/query-user-purchases'; import QuerySitePurchases from 'components/data/query-site-purchases'; import ThemesSiteSelectorModal from 'my-sites/themes/themes-site-selector-modal'; import { connectOptions } from 'my-sites/themes/theme-options'; -import { isThemeActive, isThemePurchased, getThemeRequestErrors } from 'state/themes/selectors'; +import { isThemeActive, isThemePurchased, getThemeRequestErrors, getThemeForumUrl } from 'state/themes/selectors'; import { getBackPath } from 'state/themes/themes-ui/selectors'; import EmptyContentComponent from 'components/empty-content'; import ThemePreview from 'my-sites/themes/theme-preview'; @@ -278,7 +277,7 @@ const ThemeSheet = React.createClass( { { i18n.translate( 'Have a question about this theme?' ) } { description } - + ); }, @@ -601,6 +600,7 @@ export default connect( isThemePurchased( state, id, selectedSite.ID ) || hasFeature( state, selectedSite.ID, FEATURE_UNLIMITED_PREMIUM_THEMES ) ), + forumUrl: getThemeForumUrl( state, id ) }; } )( ThemeSheetWithOptions ); diff --git a/client/my-sites/theme/test/main.jsx b/client/my-sites/theme/test/main.jsx index 1865588c06d0e..0fb373ca90551 100644 --- a/client/my-sites/theme/test/main.jsx +++ b/client/my-sites/theme/test/main.jsx @@ -32,7 +32,6 @@ describe( 'main', function() { mockery.registerMock( 'lib/analytics', {} ); mockery.registerMock( 'my-sites/themes/helpers', { isPremium: noop, - getForumUrl: noop, getDetailsUrl: noop, } ); mockery.registerSubstitute( 'matches-selector', 'component-matches-selector' ); diff --git a/client/my-sites/themes/helpers.js b/client/my-sites/themes/helpers.js index e7042d48d60f6..2b0fcc762cecc 100644 --- a/client/my-sites/themes/helpers.js +++ b/client/my-sites/themes/helpers.js @@ -14,7 +14,7 @@ import mapValues from 'lodash/mapValues'; */ import config from 'config'; import { sectionify } from 'lib/route/path'; -import { oldShowcaseUrl, isPremiumTheme as isPremium } from 'state/themes/utils'; +import { oldShowcaseUrl } from 'state/themes/utils'; export function getPreviewUrl( theme ) { return `${ theme.demo_uri }?demo=true&iframe=true&theme_preview=true`; @@ -64,10 +64,6 @@ export function getSupportUrl( theme, site ) { return `${ oldShowcaseUrl }${ sitePart }${ theme.id }/support`; } -export function getForumUrl( theme ) { - return isPremium( theme ) ? '//premium-themes.forums.wordpress.com/forum/' + theme.id : '//en.forums.wordpress.com/forum/themes'; -} - export function getHelpUrl( theme, site ) { if ( site && site.jetpack ) { return getSupportUrl( theme, site ); diff --git a/client/my-sites/themes/thanks-modal.jsx b/client/my-sites/themes/thanks-modal.jsx index 83dabab354aaf..839a488a65140 100644 --- a/client/my-sites/themes/thanks-modal.jsx +++ b/client/my-sites/themes/thanks-modal.jsx @@ -11,13 +11,14 @@ import { translate } from 'i18n-calypso'; */ import Dialog from 'components/dialog'; import PulsingDot from 'components/pulsing-dot'; -import { getForumUrl, trackClick } from './helpers'; +import { trackClick } from './helpers'; import { isJetpackSite } from 'state/sites/selectors'; import { getActiveTheme, getTheme, getThemeDetailsUrl, getThemeCustomizeUrl, + getThemeForumUrl, isActivatingTheme, hasActivatedTheme } from 'state/themes/selectors'; @@ -82,7 +83,7 @@ const ThanksModal = React.createClass( {
  • { translate( 'Have questions? Stop by our {{a}}support forums.{{/a}}', { components: { - a: } } ) } @@ -209,6 +210,7 @@ export default connect( currentTheme, detailsUrl: site && getThemeDetailsUrl( state, currentTheme, site.ID ), customizeUrl: site && getThemeCustomizeUrl( state, currentTheme, site.ID ), + forumUrl: getThemeForumUrl( state, currentThemeId ), isActivating: !! ( site && isActivatingTheme( state, site.ID ) ), hasActivated: !! ( site && hasActivatedTheme( state, site.ID ) ) }; From ef5a3394c26bf37d31f788584d55ccb4c3bb3a85 Mon Sep 17 00:00:00 2001 From: Bernhard Reiter Date: Thu, 8 Dec 2016 19:45:29 +0100 Subject: [PATCH 09/27] Theme Sheet: Hide Download button for non-wporg themes on HP sites --- client/my-sites/theme/main.jsx | 8 +++++++- client/state/themes/selectors.js | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/client/my-sites/theme/main.jsx b/client/my-sites/theme/main.jsx index cfe87b7ddd114..45e56eb9d88c9 100644 --- a/client/my-sites/theme/main.jsx +++ b/client/my-sites/theme/main.jsx @@ -339,7 +339,13 @@ const ThemeSheet = React.createClass( { }, renderDownload() { - if ( isPremium( this.props ) ) { + // Don't render download button: + // * If it's a premium theme + // * If it's on a Jetpack site, and the theme object doesn't have a 'download' attr + // Note that not having a 'download' attr would be permissible for a theme on WPCOM + // since we don't provide any for some themes found on WordPress.org (notably the 'Twenties'). + // The component can handle that case. + if ( isPremium( this.props ) || ( this.props.isJetpack && ! this.props.download ) ) { return null; } return ; diff --git a/client/state/themes/selectors.js b/client/state/themes/selectors.js index fc355d453a559..7aa9b97cfb3cd 100644 --- a/client/state/themes/selectors.js +++ b/client/state/themes/selectors.js @@ -72,7 +72,7 @@ export const getTheme = createSelector( } return { ...theme, - ...pick( wporgTheme, [ 'demo_uri' ] ) + ...pick( wporgTheme, [ 'demo_uri', 'download' ] ) }; }, ( state ) => state.themes.queries From 52e4b1d52179fc6fac0361be1d181021306b235b Mon Sep 17 00:00:00 2001 From: Bernhard Reiter Date: Thu, 8 Dec 2016 20:03:35 +0100 Subject: [PATCH 10/27] Theme Sheet: Hide related theme suggestions on Jetpack sites --- client/my-sites/theme/main.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/my-sites/theme/main.jsx b/client/my-sites/theme/main.jsx index 45e56eb9d88c9..d0b1bfde7e781 100644 --- a/client/my-sites/theme/main.jsx +++ b/client/my-sites/theme/main.jsx @@ -237,7 +237,7 @@ const ThemeSheet = React.createClass( { { this.renderFeaturesCard() } { this.renderDownload() } - { this.renderRelatedThemes() } + { ! this.props.isJetpack && this.renderRelatedThemes() } ); }, From 4000d43e6c9467a5155b2f2a563d7e5db2a78dc4 Mon Sep 17 00:00:00 2001 From: Bernhard Reiter Date: Thu, 8 Dec 2016 20:42:49 +0100 Subject: [PATCH 11/27] state/themes/selectors: Add isWporgTheme() selector --- client/state/themes/selectors.js | 11 ++++++++ client/state/themes/test/selectors.js | 40 +++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/client/state/themes/selectors.js b/client/state/themes/selectors.js index 7aa9b97cfb3cd..4dfbbfcb43bd9 100644 --- a/client/state/themes/selectors.js +++ b/client/state/themes/selectors.js @@ -291,6 +291,17 @@ export function isRequestingActiveTheme( state, siteId ) { return get( state.themes.activeThemeRequests, siteId, false ); } +/** + * Whether a theme is present in the WordPress.org Theme Directory + * + * @param {Object} state Global state tree + * @param {Number} themeId Theme ID + * @return {Boolean} Whether theme is in WP.org theme directory + */ +export function isWporgTheme( state, themeId ) { + return !! getTheme( state, 'wporg', themeId ); +} + /** * Returns the URL for a given theme's details sheet. * diff --git a/client/state/themes/test/selectors.js b/client/state/themes/test/selectors.js index 1a293dc50fffa..f6dc6bfc2c464 100644 --- a/client/state/themes/test/selectors.js +++ b/client/state/themes/test/selectors.js @@ -29,6 +29,7 @@ import { getThemeForumUrl, getActiveTheme, isRequestingActiveTheme, + isWporgTheme, isThemeActive, isActivatingTheme, hasActivatedTheme, @@ -1375,6 +1376,45 @@ describe( 'themes selectors', () => { } ); } ); + describe( '#isWporgTheme()', () => { + it( 'should return false if theme is not found on WP.org', () => { + const isWporg = isWporgTheme( { + themes: { + queries: { + } + } + }, 'twentyseventeen' ); + + expect( isWporg ).to.be.false; + } ); + + it( 'should return true if theme is found on WP.org', () => { + const wporgTheme = { + id: 'twentyseventeen', + name: 'Twenty Seventeen', + author: 'wordpressdotorg', + demo_uri: 'https://wp-themes.com/twentyseventeen', + download: 'http://downloads.wordpress.org/theme/twentyseventeen.1.1.zip', + taxonomies: { + theme_feature: { + 'custom-header': 'Custom Header' + } + } + }; + const isWporg = isWporgTheme( { + themes: { + queries: { + wporg: new ThemeQueryManager( { + items: { twentyseventeen: wporgTheme } + } ), + } + } + }, 'twentyseventeen' ); + + expect( isWporg ).to.be.true; + } ); + } ); + describe( '#isPremium()', () => { it( 'given no theme object, should return false', () => { const premium = isThemePremium( From 83517d6e80dd23355c82043fbd956707e0021f78 Mon Sep 17 00:00:00 2001 From: Bernhard Reiter Date: Thu, 8 Dec 2016 20:44:03 +0100 Subject: [PATCH 12/27] state/themes/selectors#getThemeForumUrl: Have test cover WP.org themes on JP sites --- client/state/themes/selectors.js | 10 +- client/state/themes/test/selectors.js | 132 +++++++++++++++++++++----- 2 files changed, 116 insertions(+), 26 deletions(-) diff --git a/client/state/themes/selectors.js b/client/state/themes/selectors.js index 4dfbbfcb43bd9..7df015acdb55e 100644 --- a/client/state/themes/selectors.js +++ b/client/state/themes/selectors.js @@ -447,9 +447,17 @@ export function getThemeSignupUrl( state, theme ) { * * @param {Object} state Global state tree * @param {String} themeId Theme ID + * @param {String} siteId Site ID * @return {?String} Theme forum URL */ -export function getThemeForumUrl( state, themeId ) { +export function getThemeForumUrl( state, themeId, siteId ) { + if ( isJetpackSite( state, siteId ) ) { + if ( isWporgTheme( state, themeId ) ) { + return '//wordpress.org/support/theme/' + themeId; + } + return null; + } + if ( isThemePremium( state, themeId ) ) { return '//premium-themes.forums.wordpress.com/forum/' + themeId; } diff --git a/client/state/themes/test/selectors.js b/client/state/themes/test/selectors.js index f6dc6bfc2c464..73fa51558281a 100644 --- a/client/state/themes/test/selectors.js +++ b/client/state/themes/test/selectors.js @@ -1160,38 +1160,120 @@ describe( 'themes selectors', () => { } ); describe( '#getThemeForumUrl', () => { - it( 'given a free theme, should return the general themes forum URL', () => { - const forumUrl = getThemeForumUrl( - { - themes: { - queries: { - wpcom: new ThemeQueryManager( { - items: { twentysixteen } - } ) + context( 'on a WP.com site', () => { + it( 'given a free theme, should return the general themes forum URL', () => { + const forumUrl = getThemeForumUrl( + { + sites: { + items: {} + }, + themes: { + queries: { + wpcom: new ThemeQueryManager( { + items: { twentysixteen } + } ) + } } - } - }, - 'twentysixteen' - ); + }, + 'twentysixteen' + ); - expect( forumUrl ).to.equal( '//en.forums.wordpress.com/forum/themes' ); + expect( forumUrl ).to.equal( '//en.forums.wordpress.com/forum/themes' ); + } ); + + it( 'given a premium theme, should return the specific theme forum URL', () => { + const forumUrl = getThemeForumUrl( + { + sites: { + items: {} + }, + themes: { + queries: { + wpcom: new ThemeQueryManager( { + items: { mood } + } ) + } + } + }, + 'mood' + ); + + expect( forumUrl ).to.equal( '//premium-themes.forums.wordpress.com/forum/mood' ); + } ); } ); - it( 'given a premium theme, should return the specific theme forum URL', () => { - const forumUrl = getThemeForumUrl( - { - themes: { - queries: { - wpcom: new ThemeQueryManager( { - items: { mood } - } ) + context( 'on a Jetpack site', () => { + it( 'given a theme that\'s not found on WP.org, should return null', () => { + const forumUrl = getThemeForumUrl( + { + sites: { + items: { + 77203074: { + ID: 77203074, + URL: 'https://example.net', + jetpack: true + } + } + }, + themes: { + queries: { + wpcom: new ThemeQueryManager( { + items: { twentysixteen } + } ) + } + } + }, + 'twentysixteen', + 77203074 + ); + + expect( forumUrl ).to.be.null; + } ); + + it( 'given a theme that\'s found on WP.org, should return the correspoding WP.org theme forum URL', () => { + const jetpackTheme = { + id: 'twentyseventeen', + name: 'Twenty Seventeen', + author: 'the WordPress team', + }; + const wporgTheme = { + demo_uri: 'https://wp-themes.com/twentyseventeen', + download: 'http://downloads.wordpress.org/theme/twentyseventeen.1.1.zip', + taxonomies: { + theme_feature: { + 'custom-header': 'Custom Header' } } - }, - 'mood' - ); + }; - expect( forumUrl ).to.equal( '//premium-themes.forums.wordpress.com/forum/mood' ); + const forumUrl = getThemeForumUrl( + { + sites: { + items: { + 77203074: { + ID: 77203074, + URL: 'https://example.net', + jetpack: true + } + } + }, + themes: { + queries: { + 77203074: new ThemeQueryManager( { + items: { twentyseventeen: jetpackTheme } + } ), + wporg: new ThemeQueryManager( { + items: { twentyseventeen: wporgTheme } + } ) + } + } + }, + 'twentyseventeen', + 77203074 + ); + + expect( forumUrl ).to.equal( '//wordpress.org/support/theme/twentyseventeen' ); + } ); } ); } ); From 5a08543627447313817e3b556e2ff77decae1bff Mon Sep 17 00:00:00 2001 From: Bernhard Reiter Date: Thu, 8 Dec 2016 21:43:44 +0100 Subject: [PATCH 13/27] Theme Sheet: Hide forum link for non-wporg themes on JP sites --- client/my-sites/theme/main.jsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/client/my-sites/theme/main.jsx b/client/my-sites/theme/main.jsx index d0b1bfde7e781..5726f941c2972 100644 --- a/client/my-sites/theme/main.jsx +++ b/client/my-sites/theme/main.jsx @@ -266,6 +266,10 @@ const ThemeSheet = React.createClass( { }, renderThemeForumCard( isPrimary = false ) { + if ( ! this.props.forumUrl ) { + return null; + } + const description = isPremium( this.props ) ? i18n.translate( 'Get in touch with the theme author' ) : i18n.translate( 'Get help from volunteers and staff' ); @@ -606,7 +610,7 @@ export default connect( isThemePurchased( state, id, selectedSite.ID ) || hasFeature( state, selectedSite.ID, FEATURE_UNLIMITED_PREMIUM_THEMES ) ), - forumUrl: getThemeForumUrl( state, id ) + forumUrl: selectedSite && getThemeForumUrl( state, id, selectedSite.ID ) }; } )( ThemeSheetWithOptions ); From 5a9185be49c49243df92b6ca5ff594d7126594cf Mon Sep 17 00:00:00 2001 From: Bernhard Reiter Date: Thu, 8 Dec 2016 22:35:19 +0100 Subject: [PATCH 14/27] state/themes/selectors: Have getTheme use taxonomies from wporg (for JP sites) --- client/state/themes/selectors.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/state/themes/selectors.js b/client/state/themes/selectors.js index 7df015acdb55e..1b57ea9675a88 100644 --- a/client/state/themes/selectors.js +++ b/client/state/themes/selectors.js @@ -72,7 +72,7 @@ export const getTheme = createSelector( } return { ...theme, - ...pick( wporgTheme, [ 'demo_uri', 'download' ] ) + ...pick( wporgTheme, [ 'demo_uri', 'download', 'taxonomies' ] ) }; }, ( state ) => state.themes.queries From 6c80ebc705f8f8c83192cf06067c9151d7c4bb29 Mon Sep 17 00:00:00 2001 From: Bernhard Reiter Date: Thu, 8 Dec 2016 22:38:44 +0100 Subject: [PATCH 15/27] Theme Sheet: Don't link feature tags on JP sites --- client/my-sites/theme/main.jsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/client/my-sites/theme/main.jsx b/client/my-sites/theme/main.jsx index 5726f941c2972..ac3130fdfb911 100644 --- a/client/my-sites/theme/main.jsx +++ b/client/my-sites/theme/main.jsx @@ -319,13 +319,16 @@ const ThemeSheet = React.createClass( { }, renderFeaturesCard() { - const { siteSlug, taxonomies } = this.props; + const { isJetpack, siteSlug, taxonomies } = this.props; const themeFeatures = taxonomies && taxonomies.theme_feature instanceof Array ? taxonomies.theme_feature.map( function( item ) { const term = isValidTerm( item.slug ) ? item.slug : `feature:${ item.slug }`; return (
  • - { item.name } + { isJetpack + ? { item.name } + : { item.name } + }
  • ); } ) : []; From 23f07df1c73e6b1c8e77495d4adcfe3cf4777574 Mon Sep 17 00:00:00 2001 From: Bernhard Reiter Date: Thu, 8 Dec 2016 23:31:35 +0100 Subject: [PATCH 16/27] state/themes/actions: Fix one requestTheme() test to really mimic the endpoint --- client/state/themes/test/actions.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/state/themes/test/actions.js b/client/state/themes/test/actions.js index 9c21ed505a172..5a59f50e49ce8 100644 --- a/client/state/themes/test/actions.js +++ b/client/state/themes/test/actions.js @@ -233,7 +233,7 @@ describe( 'actions', () => { nock( 'https://public-api.wordpress.com:443' ) .persist() .get( '/rest/v1.2/themes/twentysixteen' ) - .reply( 200, { id: 'twentysixteen', title: 'Twenty Sixteen' } ) + .reply( 200, { id: 'twentysixteen', name: 'Twenty Sixteen' } ) .get( '/rest/v1.2/themes/twentyumpteen' ) .reply( 404, { error: 'unknown_theme', @@ -257,7 +257,7 @@ describe( 'actions', () => { expect( spy ).to.have.been.calledWith( { type: THEMES_RECEIVE, themes: [ - sinon.match( { id: 'twentysixteen', title: 'Twenty Sixteen' } ) + sinon.match( { id: 'twentysixteen', name: 'Twenty Sixteen' } ) ], siteId: 'wpcom' } ); From f80aab5eb6c9fa7d976611fee21e194c61f48f91 Mon Sep 17 00:00:00 2001 From: Bernhard Reiter Date: Thu, 8 Dec 2016 23:37:33 +0100 Subject: [PATCH 17/27] state/themes/actions: Add tests for requestTheme() with a Jetpack site --- client/state/themes/test/actions.js | 82 ++++++++++++++++++++++++----- 1 file changed, 68 insertions(+), 14 deletions(-) diff --git a/client/state/themes/test/actions.js b/client/state/themes/test/actions.js index 5a59f50e49ce8..bf0b4a4e1704d 100644 --- a/client/state/themes/test/actions.js +++ b/client/state/themes/test/actions.js @@ -229,19 +229,19 @@ describe( 'actions', () => { } ); describe( '#requestTheme()', () => { - useNock( ( nock ) => { - nock( 'https://public-api.wordpress.com:443' ) - .persist() - .get( '/rest/v1.2/themes/twentysixteen' ) - .reply( 200, { id: 'twentysixteen', name: 'Twenty Sixteen' } ) - .get( '/rest/v1.2/themes/twentyumpteen' ) - .reply( 404, { - error: 'unknown_theme', - message: 'Unknown theme' - } ); - } ); - context( 'with a wpcom site', () => { + useNock( ( nock ) => { + nock( 'https://public-api.wordpress.com:443' ) + .persist() + .get( '/rest/v1.2/themes/twentysixteen' ) + .reply( 200, { id: 'twentysixteen', name: 'Twenty Sixteen' } ) + .get( '/rest/v1.2/themes/twentyumpteen' ) + .reply( 404, { + error: 'unknown_theme', + message: 'Unknown theme' + } ); + } ); + it( 'should dispatch request action when thunk triggered', () => { requestTheme( 'twentysixteen', 'wpcom' )( spy ); @@ -285,9 +285,63 @@ describe( 'actions', () => { } ); } ); } ); + context( 'with a Jetpack site', () => { - // TODO! - // But do we have a theme details endpoint on Jetpack sites at all? + // see lib/wpcom-undocumented/lib/undocumented#jetpackThemeDetails + useNock( ( nock ) => { + nock( 'https://public-api.wordpress.com:443' ) + .persist() + .post( '/rest/v1.1/sites/77203074/themes', { themes: 'twentyfifteen' } ) + .reply( 200, { themes: [ { id: 'twentyfifteen', name: 'Twenty Fifteen' } ] } ) + .post( '/rest/v1.1/sites/77203074/themes', { themes: 'twentyumpteen' } ) + .reply( 404, { + error: 'unknown_theme', + message: 'Unknown theme' + } ); + } ); + + it( 'should dispatch request action when thunk triggered', () => { + requestTheme( 'twentyfifteen', 77203074 )( spy ); + + expect( spy ).to.have.been.calledWith( { + type: THEME_REQUEST, + siteId: 77203074, + themeId: 'twentyfifteen' + } ); + } ); + + it( 'should dispatch themes receive action when request completes', () => { + return requestTheme( 'twentyfifteen', 77203074 )( spy ).then( () => { + expect( spy ).to.have.been.calledWith( { + type: THEMES_RECEIVE, + themes: [ + sinon.match( { id: 'twentyfifteen', name: 'Twenty Fifteen' } ) + ], + siteId: 77203074 + } ); + } ); + } ); + + it( 'should dispatch themes request success action when request completes', () => { + return requestTheme( 'twentyfifteen', 77203074 )( spy ).then( () => { + expect( spy ).to.have.been.calledWith( { + type: THEME_REQUEST_SUCCESS, + siteId: 77203074, + themeId: 'twentyfifteen' + } ); + } ); + } ); + + it( 'should dispatch fail action when request fails', () => { + return requestTheme( 'twentyumpteen', 77203074 )( spy ).then( () => { + expect( spy ).to.have.been.calledWith( { + type: THEME_REQUEST_FAILURE, + siteId: 77203074, + themeId: 'twentyumpteen', + error: sinon.match( { message: 'Unknown theme' } ) + } ); + } ); + } ); } ); } ); From 9a5491638fa4a595fc509345420a03ec7a5b891e Mon Sep 17 00:00:00 2001 From: Bernhard Reiter Date: Fri, 9 Dec 2016 00:05:36 +0100 Subject: [PATCH 18/27] state/themes/actions: Add tests for requestTheme() with the WP.org API --- client/state/themes/test/actions.js | 57 +++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/client/state/themes/test/actions.js b/client/state/themes/test/actions.js index bf0b4a4e1704d..66699de33e05c 100644 --- a/client/state/themes/test/actions.js +++ b/client/state/themes/test/actions.js @@ -343,6 +343,63 @@ describe( 'actions', () => { } ); } ); } ); + + context( 'with the WP.org API', () => { + useNock( ( nock ) => { + nock( 'https://api.wordpress.org' ) + .persist() + .get( '/themes/info/1.1/?action=theme_information&request%5Bslug%5D=twentyseventeen' ) + .reply( 200, { slug: 'twentyseventeen', name: 'Twenty Seventeen' } ) + .get( '/themes/info/1.1/?action=theme_information&request%5Bslug%5D=twentyumpteen' ) + .reply( 404, { + error: 'not_found', + message: 'Not found' + } ); + } ); + + it( 'should dispatch request action when thunk triggered', () => { + requestTheme( 'twentyseventeen', 'wporg' )( spy ); + + expect( spy ).to.have.been.calledWith( { + type: THEME_REQUEST, + siteId: 'wporg', + themeId: 'twentyseventeen' + } ); + } ); + + it( 'should dispatch themes receive action when request completes', () => { + return requestTheme( 'twentyseventeen', 'wporg' )( spy ).then( () => { + expect( spy ).to.have.been.calledWith( { + type: THEMES_RECEIVE, + themes: [ + sinon.match( { id: 'twentyseventeen', name: 'Twenty Seventeen' } ) + ], + siteId: 'wporg' + } ); + } ); + } ); + + it( 'should dispatch themes request success action when request completes', () => { + return requestTheme( 'twentyseventeen', 'wporg' )( spy ).then( () => { + expect( spy ).to.have.been.calledWith( { + type: THEME_REQUEST_SUCCESS, + siteId: 'wporg', + themeId: 'twentyseventeen' + } ); + } ); + } ); + + it( 'should dispatch fail action when request fails', () => { + return requestTheme( 'twentyumpteen', 'wporg' )( spy ).then( () => { + expect( spy ).to.have.been.calledWith( { + type: THEME_REQUEST_FAILURE, + siteId: 'wporg', + themeId: 'twentyumpteen', + error: sinon.match( { message: 'Not Found' } ) + } ); + } ); + } ); + } ); } ); describe( '#themeActivated()', () => { From 4cd7918ef6cc16efb28cbe6072075acc03b034da Mon Sep 17 00:00:00 2001 From: Bernhard Reiter Date: Fri, 9 Dec 2016 00:14:58 +0100 Subject: [PATCH 19/27] state/test/utils: Add tags/theme_feature check to normalizeWporgTheme test --- client/state/themes/test/utils.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/client/state/themes/test/utils.js b/client/state/themes/test/utils.js index 9ab32085a3be0..71659b0cf442f 100644 --- a/client/state/themes/test/utils.js +++ b/client/state/themes/test/utils.js @@ -64,7 +64,11 @@ describe( 'utils', () => { author: 'wordpressdotorg', screenshot_url: '//ts.w.org/wp-content/themes/twentyfifteen/screenshot.png?ver=1.7', preview_url: 'https://wp-themes.com/twentyfifteen', - download_link: 'http://downloads.wordpress.org/theme/twentyfifteen.1.7.zip' + download_link: 'http://downloads.wordpress.org/theme/twentyfifteen.1.7.zip', + tags: { + 'custom-header': 'Custom Header', + 'two-columns': 'Two Columns' + } } ); expect( normalizedTheme ).to.deep.equal( { id: 'twentyfifteen', @@ -72,7 +76,13 @@ describe( 'utils', () => { author: 'wordpressdotorg', screenshot: '//ts.w.org/wp-content/themes/twentyfifteen/screenshot.png?ver=1.7', demo_uri: 'https://wp-themes.com/twentyfifteen', - download: 'http://downloads.wordpress.org/theme/twentyfifteen.1.7.zip' + download: 'http://downloads.wordpress.org/theme/twentyfifteen.1.7.zip', + taxonomies: { + theme_feature: [ + { slug: 'custom-header', name: 'Custom Header' }, + { slug: 'two-columns', name: 'Two Columns' } + ] + } } ); } ); } ); From 157a342ad61702e6c56411058619b3a770d419be Mon Sep 17 00:00:00 2001 From: Bernhard Reiter Date: Fri, 9 Dec 2016 00:18:28 +0100 Subject: [PATCH 20/27] QueryTheme: Accept 'wporg' as possible siteId propType --- client/components/data/query-theme/index.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/components/data/query-theme/index.jsx b/client/components/data/query-theme/index.jsx index e62fb1d0e32f2..d6cc9a184853e 100644 --- a/client/components/data/query-theme/index.jsx +++ b/client/components/data/query-theme/index.jsx @@ -14,7 +14,7 @@ class QueryTheme extends Component { static propTypes = { siteId: PropTypes.oneOfType( [ PropTypes.number, - PropTypes.oneOf( [ 'wpcom' ] ) + PropTypes.oneOf( [ 'wpcom', 'wporg' ] ) ] ).isRequired, themeId: PropTypes.string.isRequired, // Connected props From 2b0a677158a10bc463c99fce9308412e9bf9ce9a Mon Sep 17 00:00:00 2001 From: Bernhard Reiter Date: Fri, 9 Dec 2016 00:19:02 +0100 Subject: [PATCH 21/27] state/themes/schema: Accept 'wporg' as possible siteId key --- client/state/themes/schema.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/state/themes/schema.js b/client/state/themes/schema.js index b80fc6c775004..492c38829ff81 100644 --- a/client/state/themes/schema.js +++ b/client/state/themes/schema.js @@ -28,7 +28,7 @@ export const queriesSchema = { type: 'object', patternProperties: { // Site ID - '^(wpcom|\\d+)$': { + '^(wpcom|wporg|\\d+)$': { type: 'object', properties: { data: { From 443292edb0f10800356f8e05d275b983da1d73ba Mon Sep 17 00:00:00 2001 From: Bernhard Reiter Date: Fri, 9 Dec 2016 00:19:31 +0100 Subject: [PATCH 22/27] state/themes/schema: Don't accept wpcom as possible activeThemes siteId key There's no such thing as an active theme on all of WP.com :-) --- client/state/themes/schema.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/state/themes/schema.js b/client/state/themes/schema.js index 492c38829ff81..198bff967c041 100644 --- a/client/state/themes/schema.js +++ b/client/state/themes/schema.js @@ -80,7 +80,7 @@ export const queriesSchema = { export const activeThemesSchema = { type: 'object', patternProperties: { - '^(wpcom|\\d+)$': { + '^\\d+$': { description: 'Theme ID', type: 'string' } From 738baabc8198a5d49d2dadd693cc7105f96b3063 Mon Sep 17 00:00:00 2001 From: Bernhard Reiter Date: Fri, 9 Dec 2016 00:21:00 +0100 Subject: [PATCH 23/27] QueryActiveTheme: Don't allow 'wpcom' as a siteId prop --- client/components/data/query-active-theme/index.jsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/client/components/data/query-active-theme/index.jsx b/client/components/data/query-active-theme/index.jsx index 2cad7ffa52c8c..73197c67dbd68 100644 --- a/client/components/data/query-active-theme/index.jsx +++ b/client/components/data/query-active-theme/index.jsx @@ -12,10 +12,7 @@ import { isRequestingActiveTheme } from 'state/themes/selectors'; class QueryActiveTheme extends Component { static propTypes = { - siteId: PropTypes.oneOfType( [ - PropTypes.number, - PropTypes.oneOf( [ 'wpcom' ] ) - ] ).isRequired, + siteId: PropTypes.number.isRequired, // Connected props isRequesting: PropTypes.bool.isRequired, requestActiveTheme: PropTypes.func.isRequired, From 8cbee7ed3ec30d6a5ffe7fc2ed59d7e77ec2b390 Mon Sep 17 00:00:00 2001 From: Bernhard Reiter Date: Fri, 9 Dec 2016 18:39:56 +0100 Subject: [PATCH 24/27] Theme Sheet: Hide Features Card if no features are found --- client/my-sites/theme/main.jsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/client/my-sites/theme/main.jsx b/client/my-sites/theme/main.jsx index ac3130fdfb911..a66257330fb9f 100644 --- a/client/my-sites/theme/main.jsx +++ b/client/my-sites/theme/main.jsx @@ -8,6 +8,7 @@ import React from 'react'; import { connect } from 'react-redux'; import i18n from 'i18n-calypso'; import titlecase from 'to-title-case'; +import { isArray } from 'lodash'; /** * Internal dependencies @@ -320,8 +321,11 @@ const ThemeSheet = React.createClass( { renderFeaturesCard() { const { isJetpack, siteSlug, taxonomies } = this.props; - const themeFeatures = taxonomies && taxonomies.theme_feature instanceof Array - ? taxonomies.theme_feature.map( function( item ) { + if ( ! taxonomies || ! isArray( taxonomies.theme_feature ) ) { + return null; + } + + const themeFeatures = taxonomies.theme_feature.map( function( item ) { const term = isValidTerm( item.slug ) ? item.slug : `feature:${ item.slug }`; return (
  • @@ -331,7 +335,7 @@ const ThemeSheet = React.createClass( { }
  • ); - } ) : []; + } ); return (
    From 62b67889d243015c9fafce429cdeb3f41613e820 Mon Sep 17 00:00:00 2001 From: Bernhard Reiter Date: Fri, 9 Dec 2016 19:19:46 +0100 Subject: [PATCH 25/27] state/themes/schema: stylesheet and demo_uri aren't mandatory for themes on JP sites --- client/state/themes/schema.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/client/state/themes/schema.js b/client/state/themes/schema.js index 198bff967c041..d0eb519568532 100644 --- a/client/state/themes/schema.js +++ b/client/state/themes/schema.js @@ -18,8 +18,6 @@ const themeSchema = { 'name', 'author', 'screenshot', - 'stylesheet', - 'demo_uri', 'author_uri' ] }; From 42429c51d77d958cdcfcfd53d881c0bcd0c9bf79 Mon Sep 17 00:00:00 2001 From: Bernhard Reiter Date: Fri, 9 Dec 2016 19:22:41 +0100 Subject: [PATCH 26/27] state/themes/schema: author_uri isn't mandatory for WP.org themes --- client/state/themes/schema.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/client/state/themes/schema.js b/client/state/themes/schema.js index d0eb519568532..7a7f4a85e1497 100644 --- a/client/state/themes/schema.js +++ b/client/state/themes/schema.js @@ -17,8 +17,7 @@ const themeSchema = { 'id', 'name', 'author', - 'screenshot', - 'author_uri' + 'screenshot' ] }; From 18346073249d1c5dcebe35b4bbdf3c5c75f7ba7c Mon Sep 17 00:00:00 2001 From: Bernhard Reiter Date: Fri, 9 Dec 2016 19:40:17 +0100 Subject: [PATCH 27/27] state/themes/actions: Guard agains false return value from WP.org API --- client/state/themes/actions.js | 5 +++++ client/state/themes/test/actions.js | 10 +++++----- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/client/state/themes/actions.js b/client/state/themes/actions.js index cedcb0c068fee..6a322f3d0ba23 100644 --- a/client/state/themes/actions.js +++ b/client/state/themes/actions.js @@ -265,6 +265,11 @@ export function requestTheme( themeId, siteId ) { if ( siteId === 'wporg' ) { return wporg.fetchThemeInformation( themeId ).then( ( theme ) => { + // Apparently, the WP.org REST API endpoint doesn't 404 but instead returns false + // if a theme can't be found. + if ( ! theme ) { + throw ( 'Theme not found' ); // Will be caught by .catch() below + } dispatch( receiveTheme( normalizeWporgTheme( theme ), siteId ) ); dispatch( { type: THEME_REQUEST_SUCCESS, diff --git a/client/state/themes/test/actions.js b/client/state/themes/test/actions.js index 66699de33e05c..3cd73f31ae580 100644 --- a/client/state/themes/test/actions.js +++ b/client/state/themes/test/actions.js @@ -348,13 +348,13 @@ describe( 'actions', () => { useNock( ( nock ) => { nock( 'https://api.wordpress.org' ) .persist() + .defaultReplyHeaders( { + 'Content-Type': 'application/json' + } ) .get( '/themes/info/1.1/?action=theme_information&request%5Bslug%5D=twentyseventeen' ) .reply( 200, { slug: 'twentyseventeen', name: 'Twenty Seventeen' } ) .get( '/themes/info/1.1/?action=theme_information&request%5Bslug%5D=twentyumpteen' ) - .reply( 404, { - error: 'not_found', - message: 'Not found' - } ); + .reply( 200, false ); } ); it( 'should dispatch request action when thunk triggered', () => { @@ -395,7 +395,7 @@ describe( 'actions', () => { type: THEME_REQUEST_FAILURE, siteId: 'wporg', themeId: 'twentyumpteen', - error: sinon.match( { message: 'Not Found' } ) + error: sinon.match( 'not found' ) } ); } ); } );