Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 25 additions & 9 deletions schemas/theme/setting.json
Original file line number Diff line number Diff line change
Expand Up @@ -345,21 +345,26 @@
},

"color_palette": {
"allOf": [
{ "$ref": "#/definitions/inputSettingsStandardAttributes" },
{ "$ref": "#/definitions/conditionalSetting" }
],
"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", "additionalProperties": { "type": "string" } },
"label": true,
"info": true,
"id": true,
"visible_if": true
"default": {
"type": "object",
"minProperties": 1,
"maxProperties": 20,
"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)"
}
},
"additionalProperties": false
},
Expand Down Expand Up @@ -1111,6 +1116,17 @@
}
},

"colorPaletteStandardAttributes": {
"required": ["type", "id", "default"],
"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)"
}
Comment on lines +1122 to +1126
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed this is repeated a lot in the schema; not a problem in this PR since it looks like we did it before as well, but should clean it up in a follow-up PR.

}
},

"colorSchemeGroupStandardAttributes": {
"required": ["type", "id"],
"properties": {
Expand Down
3 changes: 1 addition & 2 deletions tests/fixtures/theme-settings-all-settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,7 @@
"default": {
"background": "#ffffff",
"foreground": "#000000"
},
"label": "Color Palette"
}
},
{
"type": "color_scheme",
Expand Down
8 changes: 6 additions & 2 deletions tests/section.spec.ts
Original file line number Diff line number Diff line change
@@ -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');
Expand Down Expand Up @@ -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)(
Expand Down
8 changes: 6 additions & 2 deletions tests/test-constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down Expand Up @@ -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'];
184 changes: 184 additions & 0 deletions tests/theme-settings/color_palette.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
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 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',
settings({ default: { 'primary-1': '#fff' } }),
);
expect(diagnostics).toStrictEqual([
expect.objectContaining({
message:
'Color palette names must start with a letter and contain only letters, digits, and underscores.',
}),
]);
});

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:
'Color palette names must start with a letter and contain only letters, digits, and underscores.',
}),
]);
});
});
});
Loading