diff --git a/schemas/manifest_theme.json b/schemas/manifest_theme.json index f825c4a..fd6b02a 100644 --- a/schemas/manifest_theme.json +++ b/schemas/manifest_theme.json @@ -7,6 +7,7 @@ { "fileMatch": ["config/settings_schema.json"], "uri": "theme/theme_settings.json" }, { "fileMatch": ["sections/*.liquid"], "uri": "theme/section.json" }, { "uri": "theme/settings.json" }, + { "uri": "theme/strict_settings.json" }, { "uri": "theme/setting.json" }, { "uri": "theme/default_setting_values.json" }, { "uri": "theme/app_block_entry.json" }, diff --git a/schemas/theme/local_block_entry.json b/schemas/theme/local_block_entry.json index 486d64b..0289ca4 100644 --- a/schemas/theme/local_block_entry.json +++ b/schemas/theme/local_block_entry.json @@ -19,7 +19,7 @@ "description": "The number of blocks of this type that can be used." }, "settings": { - "$ref": "./settings.json" + "$ref": "./strict_settings.json" } } } diff --git a/schemas/theme/section.json b/schemas/theme/section.json index 4036c80..89cad69 100644 --- a/schemas/theme/section.json +++ b/schemas/theme/section.json @@ -32,7 +32,7 @@ }, "settings": { - "$ref": "./settings.json" + "$ref": "./strict_settings.json" }, "max_blocks": { diff --git a/schemas/theme/strict_settings.json b/schemas/theme/strict_settings.json new file mode 100644 index 0000000..b6ba47a --- /dev/null +++ b/schemas/theme/strict_settings.json @@ -0,0 +1,24 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$comment": "Settings for sections and blocks, which have stricter limits than config/settings_schema.json.", + "allOf": [ + { "$ref": "./settings.json" }, + { + "items": { + "properties": { + "options": { + "items": { + "properties": { + "value": { + "$comment": "We skip t: values here because translation references don't have a length limit.", + "if": { "pattern": "^t:" }, + "else": { "maxLength": 50 } + } + } + } + } + } + } + } + ] +} diff --git a/schemas/theme/theme_block.json b/schemas/theme/theme_block.json index 0cb2fa5..e6f19a3 100644 --- a/schemas/theme/theme_block.json +++ b/schemas/theme/theme_block.json @@ -11,7 +11,7 @@ }, "settings": { - "$ref": "./settings.json" + "$ref": "./strict_settings.json" }, "blocks": { diff --git a/tests/setting.spec.ts b/tests/setting.spec.ts index c16e460..c920280 100644 --- a/tests/setting.spec.ts +++ b/tests/setting.spec.ts @@ -8,10 +8,11 @@ const UNSUPPORTED_ICON_MESSAGE = const RADIO_ICON_MESSAGE = 'The property icon is not allowed on radio options. Use a select setting for options with icons.'; const NOT_DIVISIBLE_MESSAGE = 'Value is not divisible by 0.1.'; +const TOO_LONG_MESSAGE = 'String is longer than the maximum length of 50.'; const sectionSchema = (setting: any) => ({ name: 'Test', settings: [setting] }); -describe('JSON Schema validation of setting option icons and number bounds', () => { +describe('JSON Schema validation of setting options and number bounds', () => { describe('Unit: select options', () => { it('accepts a select option with a supported icon', async () => { const diagnostics = await validate( @@ -76,6 +77,133 @@ describe('JSON Schema validation of setting option icons and number bounds', () }); }); + describe('Unit: option value length', () => { + it('accepts a select option value of 50 characters', async () => { + const diagnostics = await validate( + 'sections/section.liquid', + sectionSchema({ + type: 'select', + id: 'variant', + label: 'Variant', + options: [{ value: 'a'.repeat(50), label: 'Long' }], + }), + ); + + expect(diagnostics).toStrictEqual([]); + }); + + it('rejects a select option value of 51 characters', async () => { + const diagnostics = await validate( + 'sections/section.liquid', + sectionSchema({ + type: 'select', + id: 'variant', + label: 'Variant', + options: [{ value: 'a'.repeat(51), label: 'Long' }], + }), + ); + + expect(diagnostics).toStrictEqual([expect.objectContaining({ message: TOO_LONG_MESSAGE })]); + }); + + it('accepts an empty select option value', async () => { + const diagnostics = await validate( + 'sections/section.liquid', + sectionSchema({ + type: 'select', + id: 'variant', + label: 'Variant', + options: [{ value: '', label: 'None' }], + }), + ); + + expect(diagnostics).toStrictEqual([]); + }); + + it('accepts a select option value over 50 characters that starts with t:', async () => { + const diagnostics = await validate( + 'sections/section.liquid', + sectionSchema({ + type: 'select', + id: 'variant', + label: 'Variant', + options: [{ value: `t:${'a'.repeat(60)}`, label: 'Long' }], + }), + ); + + expect(diagnostics).toStrictEqual([]); + }); + + it('rejects a radio option value of 51 characters', async () => { + const diagnostics = await validate( + 'sections/section.liquid', + sectionSchema({ + type: 'radio', + id: 'variant', + label: 'Variant', + options: [{ value: 'a'.repeat(51), label: 'Long' }], + }), + ); + + expect(diagnostics).toStrictEqual([expect.objectContaining({ message: TOO_LONG_MESSAGE })]); + }); + + it('rejects a select option value of 51 characters in a section-local block', async () => { + const diagnostics = await validate('sections/section.liquid', { + name: 'Test', + blocks: [ + { + type: 'item', + name: 'Item', + settings: [ + { + type: 'select', + id: 'variant', + label: 'Variant', + options: [{ value: 'a'.repeat(51), label: 'Long' }], + }, + ], + }, + ], + }); + + expect(diagnostics).toStrictEqual([expect.objectContaining({ message: TOO_LONG_MESSAGE })]); + }); + + it('rejects a select option value of 51 characters in a theme block', async () => { + const diagnostics = await validate( + 'blocks/block.liquid', + sectionSchema({ + type: 'select', + id: 'variant', + label: 'Variant', + options: [{ value: 'a'.repeat(51), label: 'Long' }], + }), + ); + + expect(diagnostics).toStrictEqual([expect.objectContaining({ message: TOO_LONG_MESSAGE })]); + }); + + it('accepts a number option value written with more than 50 characters', async () => { + const diagnostics = await validate( + 'sections/section.liquid', + `{ + "name": "Test", + "settings": [ + { + "type": "number", + "id": "width", + "label": "Width", + "options": [{ "value": ${'1'.repeat(51)}, "label": "Huge" }] + } + ] + }`, + ); + + expect(diagnostics).toStrictEqual([]); + }); + }); + describe('Unit: number settings', () => { it('accepts a number setting with min, max, icon, and options', async () => { const diagnostics = await validate( diff --git a/tests/theme-settings/setting_options.spec.ts b/tests/theme-settings/setting_options.spec.ts index eae258a..4b2edb2 100644 --- a/tests/theme-settings/setting_options.spec.ts +++ b/tests/theme-settings/setting_options.spec.ts @@ -122,4 +122,26 @@ describe('Module: theme settings validation (config/settings_schema.json)', () = ]); }); }); + + describe('Unit: option value length', () => { + it('select setting allows an option value over 50 characters', async () => { + const settings = `[ + { + "name": "some category", + "settings": [ + { + "type": "select", + "id": "variant", + "label": "Variant", + "options": [{ "value": "${'a'.repeat(51)}", "label": "Long" }] + } + ] + } + ]`; + + const diagnostics = await validate('config/settings_schema.json', settings); + + expect(diagnostics).toHaveLength(0); + }); + }); });