diff --git a/docs/how-to-guides/themes/theme-json.md b/docs/how-to-guides/themes/theme-json.md index 20bbb828e94ba0..e9c6b4f3f1fb1d 100644 --- a/docs/how-to-guides/themes/theme-json.md +++ b/docs/how-to-guides/themes/theme-json.md @@ -6,18 +6,18 @@ This is documentation for the current direction and work in progress about how themes can hook into the various sub-systems that the Block Editor provides. -- Rationale - - Settings can be controlled per block - - Some block styles are managed - - CSS Custom Properties: presets & custom -- Specification - - Settings - - Styles - - Other theme metadata -- FAQ - - The naming schema of CSS Custom Properties - - Why using -- as a separator? - - How settings under "custom" create new CSS Custom Properties +- Rationale + - Settings can be controlled per block + - Some block styles are managed + - CSS Custom Properties: presets & custom +- Specification + - Settings + - Styles + - Other theme metadata +- FAQ + - The naming schema of CSS Custom Properties + - Why using -- as a separator? + - How settings under "custom" create new CSS Custom Properties ## Rationale @@ -29,18 +29,19 @@ This describes the current efforts to consolidate the various APIs related to st Instead of the proliferation of theme support flags or alternative methods, the `experimental-theme.json` files provides a canonical way to define the settings of the block editor. These settings includes things like: - - What customization options should be made available or hidden from the user. - - What are the default colors, font sizes... available to the user. - - Defines the default layout of the editor. (widths and available alignments). +- What customization options should be made available or hidden from the user. +- What are the default colors, font sizes... available to the user. +- Defines the default layout of the editor. (widths and available alignments). + ### Settings can be controlled per block For more granularity, these settings also work at the block level in `experimental-theme.json`. Examples of what can be achieved are: -- Use a particular preset for a block (e.g.: table) but the common one for the rest of blocks. -- Enable font size UI controls for all blocks but the headings block. -- etc. +- Use a particular preset for a block (e.g.: table) but the common one for the rest of blocks. +- Enable font size UI controls for all blocks but the headings block. +- etc. ### Some block styles are managed @@ -48,8 +49,8 @@ By using the `experimental-theme.json` file to set style properties in a structu Some of the advantages are: -- Reduce the amount of CSS enqueued. -- Prevent specificity wars. +- Reduce the amount of CSS enqueued. +- Prevent specificity wars. ### CSS Custom Properties @@ -57,66 +58,74 @@ There are some areas of styling that would benefit from having shared values tha To address this need, we've started to experiment with CSS Custom Properties, aka CSS Variables, in some places: -- **Presets**: [color palettes](https://developer.wordpress.org/block-editor/developers/themes/theme-support/#block-color-palettes), [font sizes](https://developer.wordpress.org/block-editor/developers/themes/theme-support/#block-font-sizes), or [gradients](https://developer.wordpress.org/block-editor/developers/themes/theme-support/#block-gradient-presets) declared by the theme are converted to CSS Custom Properties and enqueued both the front-end and the editors. +- **Presets**: [color palettes](https://developer.wordpress.org/block-editor/developers/themes/theme-support/#block-color-palettes), [font sizes](https://developer.wordpress.org/block-editor/developers/themes/theme-support/#block-font-sizes), or [gradients](https://developer.wordpress.org/block-editor/developers/themes/theme-support/#block-gradient-presets) declared by the theme are converted to CSS Custom Properties and enqueued both the front-end and the editors. {% codetabs %} {% Input %} + ```json { - "settings": { - "defaults": { - "color": { - "palette": [ - { - "name": "Black", - "slug": "black", - "color": "#000000" - }, - { - "name": "White", - "slug": "white", - "color": "#ffffff" - } - ], - }, - }, - }, + "settings": { + "defaults": { + "color": { + "palette": [ + { + "name": "Black", + "slug": "black", + "color": "#000000" + }, + { + "name": "White", + "slug": "white", + "color": "#ffffff" + } + ] + } + } + } } ``` + {% Output %} + ```css :root { - --wp--preset--color--black: #000000; - --wp--preset--color--white: #ffffff; + --wp--preset--color--black: #000000; + --wp--preset--color--white: #ffffff; } ``` + {% end %} -- **Custom properties**: there's also a mechanism to create your own CSS Custom Properties. +- **Custom properties**: there's also a mechanism to create your own CSS Custom Properties. {% codetabs %} {% Input %} + ```json { - "settings": { - "defaults": { - "custom": { - "line-height": { - "body": 1.7, - "heading": 1.3 - }, - }, - }, - }, + "settings": { + "defaults": { + "custom": { + "line-height": { + "body": 1.7, + "heading": 1.3 + } + } + } + } } ``` + {% Output %} + ```css :root { - --wp--custom--line-height--body: 1.7; - --wp--custom--line-height--heading: 1.3; + --wp--custom--line-height--body: 1.7; + --wp--custom--line-height--heading: 1.3; } ``` + {% end %} ## Specification @@ -230,59 +239,72 @@ For example: {% codetabs %} {% Input %} + ```json { - "settings": { - "defaults": { - "color": { - "palette": [ - { - "slug": "strong-magenta", - "color": "#a156b4" - }, - { - "slug": "very-dark-grey", - "color": "rgb(131, 12, 8)" - } - ], - "gradients": [ - { - "slug": "blush-bordeaux", - "gradient": "linear-gradient(135deg,rgb(254,205,165) 0%,rgb(254,45,45) 50%,rgb(107,0,62) 100%)" - }, - { - "slug": "blush-light-purple", - "gradient": "linear-gradient(135deg,rgb(255,206,236) 0%,rgb(152,150,240) 100%)" - }, - ] - }, - "typography": { - "fontSizes": [ - { - "slug": "normal", - "size": 16 - }, - { - "slug": "big", - "size": 32 - } - ] - } - } - } + "settings": { + "defaults": { + "color": { + "palette": [ + { + "slug": "strong-magenta", + "color": "#a156b4" + }, + { + "slug": "very-dark-grey", + "color": "rgb(131, 12, 8)" + } + ], + "gradients": [ + { + "slug": "blush-bordeaux", + "gradient": "linear-gradient(135deg,rgb(254,205,165) 0%,rgb(254,45,45) 50%,rgb(107,0,62) 100%)" + }, + { + "slug": "blush-light-purple", + "gradient": "linear-gradient(135deg,rgb(255,206,236) 0%,rgb(152,150,240) 100%)" + } + ] + }, + "typography": { + "fontSizes": [ + { + "slug": "normal", + "size": 16 + }, + { + "slug": "big", + "size": 32 + } + ] + } + } + } } ``` + {% Output %} + ```css :root { - --wp--preset--color--strong-magenta: #a156b4; - --wp--preset--color--very-dark-gray: #444; - --wp--preset--font-size--big: 32; - --wp--preset--font-size--normal: 16; - --wp--preset--gradient--blush-bordeaux: linear-gradient(135deg,rgb(254,205,165) 0%,rgb(254,45,45) 50%,rgb(107,0,62) 100%); - --wp--preset--gradient--blush-light-purple: linear-gradient(135deg,rgb(255,206,236) 0%,rgb(152,150,240) 100%); + --wp--preset--color--strong-magenta: #a156b4; + --wp--preset--color--very-dark-gray: #444; + --wp--preset--font-size--big: 32; + --wp--preset--font-size--normal: 16; + --wp--preset--gradient--blush-bordeaux: linear-gradient( + 135deg, + rgb( 254, 205, 165 ) 0%, + rgb( 254, 45, 45 ) 50%, + rgb( 107, 0, 62 ) 100% + ); + --wp--preset--gradient--blush-light-purple: linear-gradient( + 135deg, + rgb( 255, 206, 236 ) 0%, + rgb( 152, 150, 240 ) 100% + ); } ``` + {% end %} To maintain backward compatibility, the presets declared via `add_theme_support` will also generate the CSS Custom Properties. If the `experimental-theme.json` contains any presets, these will take precedence over the ones declared via `add_theme_support`. @@ -295,31 +317,35 @@ For example: {% codetabs %} {% Input %} + ```json { - "settings": { - "defaults": { - "custom": { - "base-font": 16, - "line-height": { - "small": 1.2, - "medium": 1.4, - "large": 1.8 - } - } - } - } + "settings": { + "defaults": { + "custom": { + "base-font": 16, + "line-height": { + "small": 1.2, + "medium": 1.4, + "large": 1.8 + } + } + } + } } ``` + {% Output %} + ```css :root { - --wp--custom--base-font: 16; - --wp--custom--line-height--small: 1.2; - --wp--custom--line-height--medium: 1.4; - --wp--custom--line-height--large: 1.8; + --wp--custom--base-font: 16; + --wp--custom--line-height--small: 1.2; + --wp--custom--line-height--medium: 1.4; + --wp--custom--line-height--large: 1.8; } ``` + {% end %} Note that, the name of the variable is created by adding `--` in between each nesting level. @@ -330,36 +356,36 @@ Each block declares which style properties it exposes via the [block supports me ```json { - "styles": { - "some/block/selector": { - "border": { - "radius": "value" - }, - "color": { - "background": "value", - "gradient": "value", - "link": "value", - "text": "value" - }, - "spacing": { - "padding": { - "top": "value", - "right": "value", - "bottom": "value", - "left": "value", - }, - }, - "typography": { - "fontFamily": "value", - "fontSize": "value", - "fontStyle": "value", - "fontWeight": "value", - "lineHeight": "value", - "textDecoration": "value", - "textTransform": "value" - } - } - } + "styles": { + "some/block/selector": { + "border": { + "radius": "value" + }, + "color": { + "background": "value", + "gradient": "value", + "link": "value", + "text": "value" + }, + "spacing": { + "padding": { + "top": "value", + "right": "value", + "bottom": "value", + "left": "value" + } + }, + "typography": { + "fontFamily": "value", + "fontSize": "value", + "fontStyle": "value", + "fontWeight": "value", + "lineHeight": "value", + "textDecoration": "value", + "textTransform": "value" + } + } + } } ``` @@ -367,47 +393,51 @@ For example: {% codetabs %} {% Input %} + ```json { - "styles": { - "root": { - "color": { - "text": "var(--wp--preset--color--primary)" - }, - }, - "core/heading/h1": { - "color": { - "text": "var(--wp--preset--color--primary)" - }, - "typography": { - "fontSize": "calc(1px * var(--wp--preset--font-size--huge))" - } - }, - "core/heading/h4": { - "color": { - "text": "var(--wp--preset--color--secondary)" - }, - "typography": { - "fontSize": "var(--wp--preset--font-size--normal)" - } - } - } + "styles": { + "root": { + "color": { + "text": "var(--wp--preset--color--primary)" + } + }, + "core/heading/h1": { + "color": { + "text": "var(--wp--preset--color--primary)" + }, + "typography": { + "fontSize": "calc(1px * var(--wp--preset--font-size--huge))" + } + }, + "core/heading/h4": { + "color": { + "text": "var(--wp--preset--color--secondary)" + }, + "typography": { + "fontSize": "var(--wp--preset--font-size--normal)" + } + } + } } ``` + {% Output %} + ```css :root { - color: var(--wp--preset--color--primary); + color: var( --wp--preset--color--primary ); } h1 { - color: var(--wp--preset--color--primary); - font-size: calc(1px * var(--wp--preset--font-size--huge)); + color: var( --wp--preset--color--primary ); + font-size: calc( 1px * var( --wp--preset--font-size--huge ) ); } h4 { - color: var(--wp--preset--color--secondary); - font-size: calc(1px * var(--wp--preset--font-size--normal)); + color: var( --wp--preset--color--secondary ); + font-size: calc( 1px * var( --wp--preset--font-size--normal ) ); } ``` + {% end %} The `defaults` block selector can't be part of the `styles` section and will be ignored if it's present. The `root` block selector will generate a style rule with the `:root` CSS selector. @@ -416,16 +446,21 @@ The `defaults` block selector can't be part of the `styles` section and will be There's a growing need to add more theme metadata to the theme.json. This section lists those other fields: -**customTemplates**: within this field themes can list the custom templates present in the `block-templates` folder, the keys should match the custom template name. For example, for a custom template named `my-custom-template.html`, the `theme.json` can declare what post types can use it and what's the title to show the user: +**customTemplates**: within this field themes can list the custom templates present in the `block-templates` folder. For example, for a custom template named `my-custom-template.html`, the `theme.json` can declare what post types can use it and what's the title to show the user: ```json { - "customTemplates": { - "my-custom-template": { - "title": "The template title", /* Mandatory */ - "postTypes": [ "page", "post", "my-cpt" ] /* Optional, will only apply to "page" by default. */ - } - } + "customTemplates": [ + { + "name": "my-custom-template" /* Mandatory */, + "title": "The template title" /* Mandatory, translatable */, + "postTypes": [ + "page", + "post", + "my-cpt" + ] /* Optional, will only apply to "page" by default. */ + } + ] } ``` @@ -437,21 +472,21 @@ One thing you may have noticed is the naming schema used for the CSS Custom Prop **Presets** such as `--wp--preset--color--black` can be divided into the following chunks: -- `--wp`: prefix to namespace the CSS variable. -- `preset `: indicates is a CSS variable that belongs to the presets. -- `color`: indicates which preset category the variable belongs to. It can be `color`, `font-size`, `gradients`. -- `black`: the `slug` of the particular preset value. +- `--wp`: prefix to namespace the CSS variable. +- `preset `: indicates is a CSS variable that belongs to the presets. +- `color`: indicates which preset category the variable belongs to. It can be `color`, `font-size`, `gradients`. +- `black`: the `slug` of the particular preset value. **Custom** properties such as `--wp--custom--line-height--body`, which can be divided into the following chunks: -- `--wp`: prefix to namespace the CSS variable. -- `custom`: indicates is a "free-form" CSS variable created by the theme. -- `line-height--body`: the result of converting the "custom" object keys into a string. +- `--wp`: prefix to namespace the CSS variable. +- `custom`: indicates is a "free-form" CSS variable created by the theme. +- `line-height--body`: the result of converting the "custom" object keys into a string. The `--` as a separator has two functions: -- Readibility, for human understanding. It can be thought as similar to the BEM naming schema, it separates "categories". -- Parseability, for machine understanding. Using a defined structure allows machines to understand the meaning of the property `--wp--preset--color--black`: it's a value bounded to the color preset whose slug is "black", which then gives us room to do more things with them. +- Readibility, for human understanding. It can be thought as similar to the BEM naming schema, it separates "categories". +- Parseability, for machine understanding. Using a defined structure allows machines to understand the meaning of the property `--wp--preset--color--black`: it's a value bounded to the color preset whose slug is "black", which then gives us room to do more things with them. ### Why using `--` as a separator? @@ -471,46 +506,49 @@ For example: {% codetabs %} {% Input %} + ```json { - "settings": { - "defaults": { - "custom": { - "lineHeight": { - "body": 1.7 - }, - "font-primary": "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif" - }, - }, - }, + "settings": { + "defaults": { + "custom": { + "lineHeight": { + "body": 1.7 + }, + "font-primary": "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif" + } + } + } } ``` + {% Output %} + ```css :root { - --wp--custom--line-height--body: 1.7; - --wp--custom--font-primary: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif", + --wp--custom--line-height--body: 1.7; + --wp--custom--font-primary: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif"; } ``` + {% end %} A few notes about this process: -- `camelCased` keys are transformed into its `kebab-case` form, as to follow the CSS property naming schema. Example: `lineHeight` is transformed into `line-height`. -- Keys at different depth levels are separated by `--`. That's why `line-height` and `body` are separated by `--`. -- You shouldn't use `--` in the names of the keys within the `custom` object. Example, **don't do** this: - +- `camelCased` keys are transformed into its `kebab-case` form, as to follow the CSS property naming schema. Example: `lineHeight` is transformed into `line-height`. +- Keys at different depth levels are separated by `--`. That's why `line-height` and `body` are separated by `--`. +- You shouldn't use `--` in the names of the keys within the `custom` object. Example, **don't do** this: ```json { - "settings": { - "defaults": { - "custom": { - "line--height": { - "body": 1.7 - }, - }, - }, - }, + "settings": { + "defaults": { + "custom": { + "line--height": { + "body": 1.7 + } + } + } + } } ``` diff --git a/lib/class-wp-theme-json-resolver.php b/lib/class-wp-theme-json-resolver.php index 7719dbf9ddf15d..f6707b39528458 100644 --- a/lib/class-wp-theme-json-resolver.php +++ b/lib/class-wp-theme-json-resolver.php @@ -140,9 +140,9 @@ private static function extract_paths_to_translate( $i18n_partial, $current_path /** * Returns a data structure used in theme.json translation. * - * @return array An array of theme.json paths that are translatable and the keys that are translatable + * @return array An array of theme.json fields that are translatable and the keys that are translatable */ - public static function get_presets_to_translate() { + public static function get_fields_to_translate() { static $theme_json_i18n = null; if ( null === $theme_json_i18n ) { $file_structure = self::read_json_file( __DIR__ . '/experimental-i18n-theme.json' ); @@ -151,6 +151,33 @@ public static function get_presets_to_translate() { return $theme_json_i18n; } + /** + * Translates a chunk of the loaded theme.json structure. + * + * @param array $array_to_translate The chunk of theme.json to translate. + * @param string $key The key of the field that contains the string to translate. + * @param string $context The context to apply in the translation call. + * @param string $domain Text domain. Unique identifier for retrieving translated strings. + * + * @return array Returns the modified $theme_json chunk. + */ + private static function translate_theme_json_chunk( $array_to_translate, $key, $context, $domain ) { + if ( null === $array_to_translate ) { + return $array_to_translate; + } + + foreach ( $array_to_translate as $item_key => $item_to_translate ) { + if ( empty( $item_to_translate[ $key ] ) ) { + continue; + } + + // phpcs:ignore WordPress.WP.I18n.LowLevelTranslationFunction,WordPress.WP.I18n.NonSingularStringLiteralText,WordPress.WP.I18n.NonSingularStringLiteralContext,WordPress.WP.I18n.NonSingularStringLiteralDomain + $array_to_translate[ $item_key ][ $key ] = translate_with_gettext_context( $array_to_translate[ $item_key ][ $key ], $context, $domain ); + } + + return $array_to_translate; + } + /** * Given a theme.json structure modifies it in place * to update certain values by its translated strings @@ -163,37 +190,25 @@ public static function get_presets_to_translate() { * @return array Returns the modified $theme_json_structure. */ private static function translate( $theme_json, $domain = 'default' ) { - if ( ! isset( $theme_json['settings'] ) ) { - return $theme_json; - } - - $presets = self::get_presets_to_translate(); - foreach ( $theme_json['settings'] as $setting_key => $settings ) { - if ( empty( $settings ) ) { - continue; - } - - foreach ( $presets as $preset ) { - $path = array_slice( $preset['path'], 2 ); - $key = $preset['key']; - $context = $preset['context']; - - $array_to_translate = _wp_array_get( $theme_json['settings'][ $setting_key ], $path, null ); - if ( null === $array_to_translate ) { + $fields = self::get_fields_to_translate(); + foreach ( $fields as $field ) { + $path = $field['path']; + $key = $field['key']; + $context = $field['context']; + if ( 'settings' === $path[0] ) { + if ( empty( $theme_json['settings'] ) ) { continue; } - - foreach ( $array_to_translate as $item_key => $item_to_translate ) { - if ( empty( $item_to_translate[ $key ] ) ) { - continue; - } - - // phpcs:ignore WordPress.WP.I18n.LowLevelTranslationFunction,WordPress.WP.I18n.NonSingularStringLiteralText,WordPress.WP.I18n.NonSingularStringLiteralContext,WordPress.WP.I18n.NonSingularStringLiteralDomain - $array_to_translate[ $item_key ][ $key ] = translate_with_gettext_context( $array_to_translate[ $item_key ][ $key ], $context, $domain ); - // phpcs:enable + $path = array_slice( $path, 2 ); + foreach ( $theme_json['settings'] as $setting_key => $setting ) { + $array_to_translate = _wp_array_get( $setting, $path, null ); + $translated_array = self::translate_theme_json_chunk( $array_to_translate, $key, $context, $domain ); + gutenberg_experimental_set( $theme_json['settings'][ $setting_key ], $path, $translated_array ); } - - gutenberg_experimental_set( $theme_json['settings'][ $setting_key ], $path, $array_to_translate ); + } else { + $array_to_translate = _wp_array_get( $theme_json, $path, null ); + $translated_array = self::translate_theme_json_chunk( $array_to_translate, $key, $context, $domain ); + gutenberg_experimental_set( $theme_json, $path, $translated_array ); } } diff --git a/lib/class-wp-theme-json.php b/lib/class-wp-theme-json.php index 0cde9fc265d2ed..d36da7604d7c94 100644 --- a/lib/class-wp-theme-json.php +++ b/lib/class-wp-theme-json.php @@ -981,11 +981,20 @@ public function get_settings() { * @return array */ public function get_custom_templates() { + $custom_templates = array(); if ( ! isset( $this->theme_json['customTemplates'] ) ) { - return array(); - } else { - return $this->theme_json['customTemplates']; + return $custom_templates; + } + + foreach ( $this->theme_json['customTemplates'] as $item ) { + if ( isset( $item['name'] ) ) { + $custom_templates[ $item['name'] ] = array( + 'title' => isset( $item['title'] ) ? $item['title'] : '', + 'postTypes' => isset( $item['postTypes'] ) ? $item['postTypes'] : array( 'page' ), + ); + } } + return $custom_templates; } /** @@ -994,10 +1003,19 @@ public function get_custom_templates() { * @return array */ public function get_template_parts() { + $template_parts = array(); if ( ! isset( $this->theme_json['templateParts'] ) ) { - return array(); + return $template_parts; + } + + foreach ( $this->theme_json['templateParts'] as $item ) { + if ( isset( $item['name'] ) ) { + $template_parts[ $item['name'] ] = array( + 'area' => isset( $item['area'] ) ? $item['area'] : '', + ); + } } - return $this->theme_json['templateParts']; + return $template_parts; } /** diff --git a/lib/experimental-i18n-theme.json b/lib/experimental-i18n-theme.json index 80c7011208d53a..94889f733a3310 100644 --- a/lib/experimental-i18n-theme.json +++ b/lib/experimental-i18n-theme.json @@ -2,17 +2,54 @@ "settings": { "*": { "typography": { - "fontSizes": [ { "name": "Font size name" } ], - "fontStyles": [ { "name": "Font style name" } ], - "fontWeights": [ { "name": "Font weight name" } ], - "fontFamilies": [ { "name": "Font family name" } ], - "textTransforms": [ { "name": "Text transform name" } ], - "textDecorations": [ { "name": "Text decoration name" } ] + "fontSizes": [ + { + "name": "Font size name" + } + ], + "fontStyles": [ + { + "name": "Font style name" + } + ], + "fontWeights": [ + { + "name": "Font weight name" + } + ], + "fontFamilies": [ + { + "name": "Font family name" + } + ], + "textTransforms": [ + { + "name": "Text transform name" + } + ], + "textDecorations": [ + { + "name": "Text decoration name" + } + ] }, "color": { - "palette": [ { "name": "Color name" } ], - "gradients": [ { "name": "Gradient name" } ] + "palette": [ + { + "name": "Color name" + } + ], + "gradients": [ + { + "name": "Gradient name" + } + ] } } - } + }, + "customTemplates": [ + { + "title": "Custom template name" + } + ] } diff --git a/phpunit/class-wp-theme-json-resolver-test.php b/phpunit/class-wp-theme-json-resolver-test.php index 6c2fa216bc9b0b..279821c0a63bd2 100644 --- a/phpunit/class-wp-theme-json-resolver-test.php +++ b/phpunit/class-wp-theme-json-resolver-test.php @@ -8,8 +8,40 @@ class WP_Theme_JSON_Resolver_Test extends WP_UnitTestCase { - function test_presets_are_extracted() { - $actual = WP_Theme_JSON_Resolver::get_presets_to_translate(); + function setUp() { + parent::setUp(); + $this->theme_root = realpath( __DIR__ . '/data/themedir1' ); + + $this->orig_theme_dir = $GLOBALS['wp_theme_directories']; + + // /themes is necessary as theme.php functions assume /themes is the root if there is only one root. + $GLOBALS['wp_theme_directories'] = array( WP_CONTENT_DIR . '/themes', $this->theme_root ); + + add_filter( 'theme_root', array( $this, 'filter_set_theme_root' ) ); + add_filter( 'stylesheet_root', array( $this, 'filter_set_theme_root' ) ); + add_filter( 'template_root', array( $this, 'filter_set_theme_root' ) ); + // Clear caches. + wp_clean_themes_cache(); + unset( $GLOBALS['wp_themes'] ); + } + + function tearDown() { + $GLOBALS['wp_theme_directories'] = $this->orig_theme_dir; + wp_clean_themes_cache(); + unset( $GLOBALS['wp_themes'] ); + parent::tearDown(); + } + + function filter_set_theme_root() { + return $this->theme_root; + } + + function filter_set_locale_to_polish() { + return 'pl_PL'; + } + + function test_fields_are_extracted() { + $actual = WP_Theme_JSON_Resolver::get_fields_to_translate(); $expected = array( array( @@ -52,8 +84,51 @@ function test_presets_are_extracted() { 'key' => 'name', 'context' => 'Gradient name', ), + array( + 'path' => array( 'customTemplates' ), + 'key' => 'title', + 'context' => 'Custom template name', + ), ); $this->assertEquals( $expected, $actual ); } + + function test_translations_are_applied() { + add_filter( 'locale', array( $this, 'filter_set_locale_to_polish' ) ); + load_textdomain( 'fse', realpath( __DIR__ . '/data/languages/themes/fse-pl_PL.mo' ) ); + + switch_theme( 'fse' ); + + $actual = WP_Theme_JSON_Resolver::get_theme_data(); + + unload_textdomain( 'fse' ); + remove_filter( 'locale', array( $this, 'filter_set_locale_to_polish' ) ); + + $this->assertSame( wp_get_theme()->get( 'TextDomain' ), 'fse' ); + $this->assertSame( + $actual->get_settings()['root']['color']['palette'], + array( + array( + 'slug' => 'light', + 'name' => 'Jasny', + 'color' => '#f5f7f9', + ), + array( + 'slug' => 'dark', + 'name' => 'Ciemny', + 'color' => '#000', + ), + ) + ); + $this->assertSame( + $actual->get_custom_templates(), + array( + 'page-home' => array( + 'title' => 'Szablon strony głównej', + 'postTypes' => array( 'page' ), + ), + ) + ); + } } diff --git a/phpunit/class-wp-theme-json-test.php b/phpunit/class-wp-theme-json-test.php index 504ef7e049f415..baedc89d747632 100644 --- a/phpunit/class-wp-theme-json-test.php +++ b/phpunit/class-wp-theme-json-test.php @@ -778,8 +778,9 @@ function test_get_custom_templates() { $theme_json = new WP_Theme_JSON( array( 'customTemplates' => array( - 'page-home' => array( - 'title' => 'Some title', + array( + 'name' => 'page-home', + 'title' => 'Homepage template', ), ), ) @@ -791,7 +792,8 @@ function test_get_custom_templates() { $page_templates, array( 'page-home' => array( - 'title' => 'Some title', + 'title' => 'Homepage template', + 'postTypes' => array( 'page' ), ), ) ); @@ -801,8 +803,9 @@ function test_get_template_parts() { $theme_json = new WP_Theme_JSON( array( 'templateParts' => array( - 'header' => array( - 'area' => 'Some area', + array( + 'name' => 'small-header', + 'area' => 'header', ), ), ) @@ -813,8 +816,8 @@ function test_get_template_parts() { $this->assertEqualSetsWithIndex( $template_parts, array( - 'header' => array( - 'area' => 'Some area', + 'small-header' => array( + 'area' => 'header', ), ) ); diff --git a/phpunit/data/languages/themes/fse-pl_PL.mo b/phpunit/data/languages/themes/fse-pl_PL.mo new file mode 100644 index 00000000000000..0ff620e64ae4ad Binary files /dev/null and b/phpunit/data/languages/themes/fse-pl_PL.mo differ diff --git a/phpunit/data/languages/themes/fse-pl_PL.po b/phpunit/data/languages/themes/fse-pl_PL.po new file mode 100644 index 00000000000000..d55e02b8b54682 --- /dev/null +++ b/phpunit/data/languages/themes/fse-pl_PL.po @@ -0,0 +1,31 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"POT-Creation-Date: 2015-12-31 16:31+0100\n" +"PO-Revision-Date: 2021-03-15 13:10+0100\n" +"Language: pl_PL\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 2.4.2\n" +"X-Poedit-Basepath: .\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Poedit-KeywordsList: __;_e;_x:1,2c;_ex:1,2c;_n:1,2;_nx:1,2,4c;_n_noop:1,2;" +"_nx_noop:1,2,3c;esc_attr__;esc_html__;esc_attr_e;esc_html_e;esc_attr_x:1,2c;" +"esc_html_x:1,2c\n" +"X-Textdomain-Support: yes\n" +"Last-Translator: \n" +"Language-Team: \n" +"X-Poedit-SearchPath-0: .\n" + +msgctxt "Custom template name" +msgid "Homepage template" +msgstr "Szablon strony głównej" + +msgctxt "Color name" +msgid "Light" +msgstr "Jasny" + +msgctxt "Color name" +msgid "Dark" +msgstr "Ciemny" diff --git a/phpunit/data/themedir1/fse/experimental-theme.json b/phpunit/data/themedir1/fse/experimental-theme.json new file mode 100644 index 00000000000000..3cfe4accc5bebe --- /dev/null +++ b/phpunit/data/themedir1/fse/experimental-theme.json @@ -0,0 +1,33 @@ +{ + "settings": { + "root": { + "color": { + "palette": [ + { + "slug": "light", + "name": "Light", + "color": "#f5f7f9" + }, + { + "slug": "dark", + "name": "Dark", + "color": "#000" + } + ], + "custom": false + } + } + }, + "customTemplates": [ + { + "name": "page-home", + "title": "Homepage template" + } + ], + "templateParts": [ + { + "name": "small-header", + "area": "header" + } + ] +} diff --git a/phpunit/data/themedir1/fse/style.css b/phpunit/data/themedir1/fse/style.css new file mode 100644 index 00000000000000..efc417305327a2 --- /dev/null +++ b/phpunit/data/themedir1/fse/style.css @@ -0,0 +1,7 @@ +/* +Theme Name: FSE Theme +Theme URI: https://wordpress.org/ +Description: For testing purposes only. +Version: 1.0.0 +Text Domain: fse +*/