From 4b788d19a00dcfb9781e9c69caf05000c36b8ac6 Mon Sep 17 00:00:00 2001 From: Nathan Ferguson Date: Mon, 11 May 2026 20:29:42 -0400 Subject: [PATCH 1/4] update color_palette with unique definition --- schemas/theme/setting.json | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/schemas/theme/setting.json b/schemas/theme/setting.json index 5689ecf..92654ae 100644 --- a/schemas/theme/setting.json +++ b/schemas/theme/setting.json @@ -345,21 +345,13 @@ }, "color_palette": { - "allOf": [ - { "$ref": "#/definitions/inputSettingsStandardAttributes" }, - { "$ref": "#/definitions/conditionalSetting" } - ], + "allOf": [{ "$ref": "#/definitions/colorPaletteStandardAttributes" }], "properties": { "type": { "const": "color_palette", "description": "A setting of type color_palette outputs a picker with all of the available theme palette colors.", "markdownDescription": "A setting of type `color_palette` outputs a picker with all of the available theme palette colors.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#color_palette)" - }, - "default": { "type": "object", "additionalProperties": { "type": "string" } }, - "label": true, - "info": true, - "id": true, - "visible_if": true + } }, "additionalProperties": false }, @@ -1111,6 +1103,22 @@ } }, + "colorPaletteStandardAttributes": { + "required": ["type", "id", "default"], + "properties": { + "id": { + "type": "string", + "description": "The unique identifier for the setting, which is used to access the setting value." + }, + "default": { + "type": "object", + "description": "A map of palette color names to color values.", + "additionalProperties": { "type": "string" } + } + }, + "additionalProperties": false + }, + "colorSchemeGroupStandardAttributes": { "required": ["type", "id"], "properties": { From 3f0032deea850f6ab3fcc186fa977228cfae804d Mon Sep 17 00:00:00 2001 From: Nathan Ferguson Date: Mon, 11 May 2026 20:45:20 -0400 Subject: [PATCH 2/4] update color_palette with unique definition --- schemas/theme/setting.json | 21 ++++++++++++------- .../fixtures/theme-settings-all-settings.json | 3 +-- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/schemas/theme/setting.json b/schemas/theme/setting.json index 92654ae..146f008 100644 --- a/schemas/theme/setting.json +++ b/schemas/theme/setting.json @@ -347,10 +347,20 @@ "color_palette": { "allOf": [{ "$ref": "#/definitions/colorPaletteStandardAttributes" }], "properties": { + "id": true, "type": { "const": "color_palette", "description": "A setting of type color_palette outputs a picker with all of the available theme palette colors.", "markdownDescription": "A setting of type `color_palette` outputs a picker with all of the available theme palette colors.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#color_palette)" + }, + "default": { + "type": "object", + "minProperties": 1, + "maxProperties": 20, + "propertyNames": { "pattern": "^[a-zA-Z][\\w-]*$" }, + "additionalProperties": { "type": "string" }, + "description": "A map of palette color names to color values. Each key is a slot name (you choose, e.g. \"background\", \"foreground\"), and each value is a CSS color (typically a hex code).", + "markdownDescription": "A map of palette color names to color values.\n\nEach **key** is a slot name (you choose them — e.g. `background`, `foreground`, `accent`). Each **value** is a CSS color, typically a hex code.\n\n**Example:**\n```json\n\"default\": {\n \"background\": \"#ffffff\",\n \"foreground\": \"#000000\"\n}\n```\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#color_palette)" } }, "additionalProperties": false @@ -1108,15 +1118,10 @@ "properties": { "id": { "type": "string", - "description": "The unique identifier for the setting, which is used to access the setting value." - }, - "default": { - "type": "object", - "description": "A map of palette color names to color values.", - "additionalProperties": { "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)" } - }, - "additionalProperties": false + } }, "colorSchemeGroupStandardAttributes": { diff --git a/tests/fixtures/theme-settings-all-settings.json b/tests/fixtures/theme-settings-all-settings.json index c32b0e5..edc1013 100644 --- a/tests/fixtures/theme-settings-all-settings.json +++ b/tests/fixtures/theme-settings-all-settings.json @@ -123,8 +123,7 @@ "default": { "background": "#ffffff", "foreground": "#000000" - }, - "label": "Color Palette" + } }, { "type": "color_scheme", From 597edbbaec72901233667438af564d095e4d89f5 Mon Sep 17 00:00:00 2001 From: Nathan Ferguson Date: Mon, 11 May 2026 20:57:25 -0400 Subject: [PATCH 3/4] beef up test case --- schemas/theme/setting.json | 6 +- tests/section.spec.ts | 8 +- tests/test-constants.ts | 8 +- tests/theme-settings/color_palette.spec.ts | 155 +++++++++++++++++++++ 4 files changed, 170 insertions(+), 7 deletions(-) create mode 100644 tests/theme-settings/color_palette.spec.ts diff --git a/schemas/theme/setting.json b/schemas/theme/setting.json index 146f008..3fbb924 100644 --- a/schemas/theme/setting.json +++ b/schemas/theme/setting.json @@ -357,10 +357,10 @@ "type": "object", "minProperties": 1, "maxProperties": 20, - "propertyNames": { "pattern": "^[a-zA-Z][\\w-]*$" }, + "propertyNames": { "pattern": "^[a-zA-Z]\\w*$" }, "additionalProperties": { "type": "string" }, - "description": "A map of palette color names to color values. Each key is a slot name (you choose, e.g. \"background\", \"foreground\"), and each value is a CSS color (typically a hex code).", - "markdownDescription": "A map of palette color names to color values.\n\nEach **key** is a slot name (you choose them — e.g. `background`, `foreground`, `accent`). Each **value** is a CSS color, typically a hex code.\n\n**Example:**\n```json\n\"default\": {\n \"background\": \"#ffffff\",\n \"foreground\": \"#000000\"\n}\n```\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#color_palette)" + "description": "A map of palette color names to color values. Each key is an arbitrary name and each value is a CSS color.", + "markdownDescription": "A map of palette color names to color values.\n\nEach **key** is an arbitrary name. Each **value** is a CSS color, typically a hex code.\n\n**Example:**\n```json\n\"default\": {\n \"background\": \"#ffffff\",\n \"foreground\": \"#000000\"\n}\n```\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#color_palette)" } }, "additionalProperties": false diff --git a/tests/section.spec.ts b/tests/section.spec.ts index b3452ad..fa5b970 100644 --- a/tests/section.spec.ts +++ b/tests/section.spec.ts @@ -1,6 +1,10 @@ import set from 'lodash.set'; import { assert, describe, expect, it } from 'vitest'; -import { INPUT_SETTING_TYPES, SETTINGS_TYPES_NOT_SUPPORTING_VISIBLE_IF } from './test-constants'; +import { + INPUT_SETTING_TYPES, + SETTINGS_TYPES_EXCLUSIVE_TO_SETTINGS_SCHEMA, + SETTINGS_TYPES_NOT_SUPPORTING_VISIBLE_IF, +} from './test-constants'; import { complete, getService, hover, loadFixture, validateSchema } from './test-helpers'; const sectionSchema1 = loadFixture('section-schema-1.json'); @@ -220,7 +224,7 @@ describe('JSON Schema validation of Liquid theme section schema tags', () => { const settingsTypesSupportingVisibleIf = INPUT_SETTING_TYPES.filter( (settingType) => - !SETTINGS_TYPES_NOT_SUPPORTING_VISIBLE_IF.concat('color_scheme_group').includes(settingType), + !SETTINGS_TYPES_NOT_SUPPORTING_VISIBLE_IF.concat(SETTINGS_TYPES_EXCLUSIVE_TO_SETTINGS_SCHEMA).includes(settingType), ); it.each(settingsTypesSupportingVisibleIf)( diff --git a/tests/test-constants.ts b/tests/test-constants.ts index afccfb2..e8b0dda 100644 --- a/tests/test-constants.ts +++ b/tests/test-constants.ts @@ -9,8 +9,8 @@ export const SETTINGS_TYPES_NOT_SUPPORTING_VISIBLE_IF = [ 'page', 'product', 'product_list', - // Not featured here is `color_scheme_group` which is exclusive to settings_schema.json. - // That setting type is tested in `color_scheme_group.spec.ts`. + // Not featured here is `color_scheme_group` and `color_palette` which are exclusive to settings_schema.json. + // Those setting types are tested in `color_scheme_group.spec.ts` and `color_palette.spec.ts`. ]; export const INPUT_SETTING_TYPES = [ @@ -49,6 +49,10 @@ export const INPUT_SETTING_TYPES = [ 'video', ]; +// Setting types that are only valid in config/settings_schema.json, +// not in section or block schemas. Tested in their own *.spec.ts files. +export const SETTINGS_TYPES_EXCLUSIVE_TO_SETTINGS_SCHEMA = ['color_scheme_group', 'color_palette']; + export const SIDEBAR_SETTING_TYPES = ['header', 'paragraph']; export const RESOURCE_LIST_SETTING_TYPES = ['article_list', 'collection_list', 'metaobject_list', 'product_list']; diff --git a/tests/theme-settings/color_palette.spec.ts b/tests/theme-settings/color_palette.spec.ts new file mode 100644 index 0000000..b4660d7 --- /dev/null +++ b/tests/theme-settings/color_palette.spec.ts @@ -0,0 +1,155 @@ +import { describe, expect, it } from 'vitest'; +import { validateSchema } from '../test-helpers'; + +const validate = validateSchema(); + +describe('Module: theme settings validation (config/settings_schema.json)', () => { + describe('Unit: color_palette', () => { + const colorPaletteSetting = { + type: 'color_palette', + id: 'color_palette', + default: { + background: '#ffffff', + foreground: '#000000', + }, + }; + + const settings = (override: any) => `[ + { + "name": "setting category", + "settings": [ + ${JSON.stringify(Object.assign({}, colorPaletteSetting, override), null, 2)} + ] + } + ]`; + + it('accepts a valid color_palette setting', async () => { + const diagnostics = await validate('config/settings_schema.json', settings({})); + expect(diagnostics).toHaveLength(0); + }); + + 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('refuses the info property', async () => { + const diagnostics = await validate( + 'config/settings_schema.json', + settings({ info: 'uh oh' }), + ); + expect(diagnostics).toStrictEqual([ + expect.objectContaining({ + message: 'Property info is not allowed.', + }), + ]); + }); + + it('refuses the visible_if property', async () => { + const diagnostics = await validate( + 'config/settings_schema.json', + settings({ visible_if: '{{ section.settings.show_palette }}' }), + ); + expect(diagnostics).toStrictEqual([ + expect.objectContaining({ + message: 'Property visible_if is not allowed.', + }), + ]); + }); + + it('requires the default property', async () => { + const { default: _, ...settingWithoutDefault } = colorPaletteSetting; + const json = `[ + { + "name": "setting category", + "settings": [ + ${JSON.stringify(settingWithoutDefault, null, 2)} + ] + } + ]`; + + const diagnostics = await validate('config/settings_schema.json', json); + + expect(diagnostics).toStrictEqual([ + expect.objectContaining({ + message: 'Missing property "default".', + }), + ]); + }); + + it('requires the id property', async () => { + const { id: _, ...settingWithoutId } = colorPaletteSetting; + const json = `[ + { + "name": "setting category", + "settings": [ + ${JSON.stringify(settingWithoutId, null, 2)} + ] + } + ]`; + + const diagnostics = await validate('config/settings_schema.json', json); + + expect(diagnostics).toStrictEqual([ + expect.objectContaining({ + message: 'Missing property "id".', + }), + ]); + }); + + it('validates that default must be an object', async () => { + const diagnostics = await validate( + 'config/settings_schema.json', + settings({ default: '#ffffff' }), + ); + expect(diagnostics).toStrictEqual([ + expect.objectContaining({ + message: 'Incorrect type. Expected "object".', + }), + ]); + }); + + it('validates that default values must be strings', async () => { + const diagnostics = await validate( + 'config/settings_schema.json', + settings({ default: { background: 123 } }), + ); + expect(diagnostics).toStrictEqual([ + expect.objectContaining({ + message: 'Incorrect type. Expected "string".', + }), + ]); + }); + + it('refuses default keys with hyphens', async () => { + const diagnostics = await validate( + 'config/settings_schema.json', + settings({ default: { 'primary-1': '#fff' } }), + ); + expect(diagnostics).toStrictEqual([ + expect.objectContaining({ + message: expect.stringMatching(/does not match the pattern of "\^\[a-zA-Z\]\\w\*\$"/), + }), + ]); + }); + + it('refuses default keys starting with a digit', async () => { + const diagnostics = await validate( + 'config/settings_schema.json', + settings({ default: { '1bad': '#fff' } }), + ); + expect(diagnostics).toStrictEqual([ + expect.objectContaining({ + message: expect.stringMatching(/does not match the pattern of "\^\[a-zA-Z\]\\w\*\$"/), + }), + ]); + }); + }); +}); From 3e7feed6eb0c983a595b128bdec22b0019e36d12 Mon Sep 17 00:00:00 2001 From: Nathan Ferguson Date: Tue, 12 May 2026 10:47:41 -0400 Subject: [PATCH 4/4] review feedback --- schemas/theme/setting.json | 5 +++- tests/theme-settings/color_palette.spec.ts | 33 ++++++++++++++++++++-- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/schemas/theme/setting.json b/schemas/theme/setting.json index 3fbb924..a3ea573 100644 --- a/schemas/theme/setting.json +++ b/schemas/theme/setting.json @@ -357,7 +357,10 @@ "type": "object", "minProperties": 1, "maxProperties": 20, - "propertyNames": { "pattern": "^[a-zA-Z]\\w*$" }, + "propertyNames": { + "pattern": "^[a-zA-Z]\\w*$", + "patternErrorMessage": "Color palette names must start with a letter and contain only letters, digits, and underscores." + }, "additionalProperties": { "type": "string" }, "description": "A map of palette color names to color values. Each key is an arbitrary name and each value is a CSS color.", "markdownDescription": "A map of palette color names to color values.\n\nEach **key** is an arbitrary name. Each **value** is a CSS color, typically a hex code.\n\n**Example:**\n```json\n\"default\": {\n \"background\": \"#ffffff\",\n \"foreground\": \"#000000\"\n}\n```\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#color_palette)" diff --git a/tests/theme-settings/color_palette.spec.ts b/tests/theme-settings/color_palette.spec.ts index b4660d7..551736c 100644 --- a/tests/theme-settings/color_palette.spec.ts +++ b/tests/theme-settings/color_palette.spec.ts @@ -128,6 +128,33 @@ describe('Module: theme settings validation (config/settings_schema.json)', () = ]); }); + it('refuses an empty default', async () => { + const diagnostics = await validate( + 'config/settings_schema.json', + settings({ default: {} }), + ); + expect(diagnostics).toStrictEqual([ + expect.objectContaining({ + message: 'Object has fewer properties than the required number of 1', + }), + ]); + }); + + it('refuses a default with more than 20 entries', async () => { + const tooMany = Object.fromEntries( + Array.from({ length: 21 }, (_, i) => [`color${i}`, '#000000']), + ); + const diagnostics = await validate( + 'config/settings_schema.json', + settings({ default: tooMany }), + ); + expect(diagnostics).toStrictEqual([ + expect.objectContaining({ + message: 'Object has more properties than limit of 20.', + }), + ]); + }); + it('refuses default keys with hyphens', async () => { const diagnostics = await validate( 'config/settings_schema.json', @@ -135,7 +162,8 @@ describe('Module: theme settings validation (config/settings_schema.json)', () = ); expect(diagnostics).toStrictEqual([ expect.objectContaining({ - message: expect.stringMatching(/does not match the pattern of "\^\[a-zA-Z\]\\w\*\$"/), + message: + 'Color palette names must start with a letter and contain only letters, digits, and underscores.', }), ]); }); @@ -147,7 +175,8 @@ describe('Module: theme settings validation (config/settings_schema.json)', () = ); expect(diagnostics).toStrictEqual([ expect.objectContaining({ - message: expect.stringMatching(/does not match the pattern of "\^\[a-zA-Z\]\\w\*\$"/), + message: + 'Color palette names must start with a letter and contain only letters, digits, and underscores.', }), ]); });