UNPKG

decap-cms-core

Version:

Decap CMS core application, see decap-cms package for the main distribution.

525 lines (471 loc) 17.9 kB
import merge from 'lodash/merge'; import { validateConfig } from '../configSchema'; jest.mock('../../lib/registry'); describe('config', () => { /** * Suppress error logging to reduce noise during testing. Jest will still * log test failures and associated errors as expected. */ beforeEach(() => { jest.spyOn(console, 'error').mockImplementation(() => {}); }); const { getWidgets } = require('../../lib/registry'); getWidgets.mockImplementation(() => [{}]); describe('validateConfig', () => { const validConfig = { foo: 'bar', backend: { name: 'bar' }, media_folder: 'baz', collections: [ { name: 'posts', label: 'Posts', folder: '_posts', fields: [{ name: 'title', label: 'title', widget: 'string' }], }, ], }; it('should not throw if no errors', () => { expect(() => { validateConfig(validConfig); }).not.toThrowError(); }); it('should throw if backend is not defined in config', () => { expect(() => { validateConfig({ foo: 'bar' }); }).toThrowError("config must have required property 'backend'"); }); it('should throw if backend name is not defined in config', () => { expect(() => { validateConfig({ foo: 'bar', backend: {} }); }).toThrowError("'backend' must have required property 'name'"); }); it('should throw if backend name is not a string in config', () => { expect(() => { validateConfig({ foo: 'bar', backend: { name: {} } }); }).toThrowError("'backend.name' must be string"); }); it('should throw if backend.open_authoring is not a boolean in config', () => { expect(() => { validateConfig(merge(validConfig, { backend: { open_authoring: 'true' } })); }).toThrowError("'backend.open_authoring' must be boolean"); }); it('should not throw if backend.open_authoring is boolean in config', () => { expect(() => { validateConfig(merge(validConfig, { backend: { open_authoring: true } })); }).not.toThrowError(); }); it('should throw if backend.auth_scope is not "repo" or "public_repo" in config', () => { expect(() => { validateConfig(merge(validConfig, { backend: { auth_scope: 'user' } })); }).toThrowError("'backend.auth_scope' must be equal to one of the allowed values"); }); it('should not throw if backend.auth_scope is one of "repo" or "public_repo" in config', () => { expect(() => { validateConfig(merge(validConfig, { backend: { auth_scope: 'repo' } })); }).not.toThrowError(); expect(() => { validateConfig(merge(validConfig, { backend: { auth_scope: 'public_repo' } })); }).not.toThrowError(); }); it('should throw if media_folder is not defined in config', () => { expect(() => { validateConfig({ foo: 'bar', backend: { name: 'bar' } }); }).toThrowError("config must have required property 'media_folder'"); }); it('should throw if media_folder is not a string in config', () => { expect(() => { validateConfig({ foo: 'bar', backend: { name: 'bar' }, media_folder: {} }); }).toThrowError("'media_folder' must be string"); }); it('should throw if collections is not defined in config', () => { expect(() => { validateConfig({ foo: 'bar', backend: { name: 'bar' }, media_folder: 'baz' }); }).toThrowError("config must have required property 'collections'"); }); it('should throw if collections not an array in config', () => { expect(() => { validateConfig({ foo: 'bar', backend: { name: 'bar' }, media_folder: 'baz', collections: {}, }); }).toThrowError("'collections' must be array"); }); it('should throw if collections is an empty array in config', () => { expect(() => { validateConfig({ foo: 'bar', backend: { name: 'bar' }, media_folder: 'baz', collections: [], }); }).toThrowError("'collections' must NOT have fewer than 1 items"); }); it('should throw if collections is an array with a single null element in config', () => { expect(() => { validateConfig({ foo: 'bar', backend: { name: 'bar' }, media_folder: 'baz', collections: [null], }); }).toThrowError("'collections[0]' must be object"); }); it('should throw if local_backend is not a boolean or plain object', () => { expect(() => { validateConfig({ ...validConfig, local_backend: [] }); }).toThrowError("'local_backend' must be boolean"); }); it('should throw if local_backend url is not a string', () => { expect(() => { validateConfig({ ...validConfig, local_backend: { url: [] } }); }).toThrowError("'local_backend.url' must be string"); }); it('should throw if local_backend allowed_hosts is not a string array', () => { expect(() => { validateConfig({ ...validConfig, local_backend: { allowed_hosts: [true] } }); }).toThrowError("'local_backend.allowed_hosts[0]' must be string"); }); it('should not throw if local_backend is a boolean', () => { expect(() => { validateConfig({ ...validConfig, local_backend: true }); }).not.toThrowError(); }); it('should not throw if local_backend is a plain object with url string property', () => { expect(() => { validateConfig({ ...validConfig, local_backend: { url: 'http://localhost:8081/api/v1' } }); }).not.toThrowError(); }); it('should not throw if local_backend is a plain object with allowed_hosts string array property', () => { expect(() => { validateConfig({ ...validConfig, local_backend: { allowed_hosts: ['192.168.0.1'] }, }); }).not.toThrowError(); }); it('should throw if collection publish is not a boolean', () => { expect(() => { validateConfig(merge({}, validConfig, { collections: [{ publish: 'false' }] })); }).toThrowError("'collections[0].publish' must be boolean"); }); it('should not throw if collection publish is a boolean', () => { expect(() => { validateConfig(merge({}, validConfig, { collections: [{ publish: false }] })); }).not.toThrowError(); }); it('should throw if collections sortable_fields is not a boolean or a string array', () => { expect(() => { validateConfig(merge({}, validConfig, { collections: [{ sortable_fields: 'title' }] })); }).toThrowError("'collections[0].sortable_fields' must be array"); }); it('should allow sortable_fields to be a string array', () => { expect(() => { validateConfig(merge({}, validConfig, { collections: [{ sortable_fields: ['title'] }] })); }).not.toThrow(); }); it('should allow sortable_fields to be a an empty array', () => { expect(() => { validateConfig(merge({}, validConfig, { collections: [{ sortable_fields: [] }] })); }).not.toThrow(); }); it('should allow sortableFields instead of sortable_fields', () => { expect(() => { validateConfig(merge({}, validConfig, { collections: [{ sortableFields: [] }] })); }).not.toThrow(); }); it('should throw if both sortable_fields and sortableFields exist', () => { expect(() => { validateConfig( merge({}, validConfig, { collections: [{ sortable_fields: [], sortableFields: [] }] }), ); }).toThrowError("'collections[0]' must NOT be valid"); }); it('should throw if collection names are not unique', () => { expect(() => { validateConfig( merge({}, validConfig, { collections: [validConfig.collections[0], validConfig.collections[0]], }), ); }).toThrowError("'collections' collections names must be unique"); }); it('should throw if collection file names are not unique', () => { expect(() => { validateConfig( merge({}, validConfig, { collections: [ {}, { files: [ { name: 'a', label: 'a', file: 'a.md', fields: [{ name: 'title', label: 'title', widget: 'string' }], }, { name: 'a', label: 'b', file: 'b.md', fields: [{ name: 'title', label: 'title', widget: 'string' }], }, ], }, ], }), ); }).toThrowError("'collections[1].files' files names must be unique"); }); it('should throw if collection fields names are not unique', () => { expect(() => { validateConfig( merge({}, validConfig, { collections: [ { fields: [ { name: 'title', label: 'title', widget: 'string' }, { name: 'title', label: 'other title', widget: 'string' }, ], }, ], }), ); }).toThrowError("'collections[0].fields' fields names must be unique"); }); it('should not throw if collection fields are unique across nesting levels', () => { expect(() => { validateConfig( merge({}, validConfig, { collections: [ { fields: [ { name: 'title', label: 'title', widget: 'string' }, { name: 'object', label: 'Object', widget: 'object', fields: [{ name: 'title', label: 'title', widget: 'string' }], }, ], }, ], }), ); }).not.toThrow(); }); describe('nested validation', () => { const { getWidgets } = require('../../lib/registry'); getWidgets.mockImplementation(() => [ { name: 'relation', schema: { properties: { search_fields: { type: 'array', items: { type: 'string' } }, display_fields: { type: 'array', items: { type: 'string' } }, }, }, }, ]); it('should throw if nested relation display_fields and search_fields are not arrays', () => { expect(() => { validateConfig( merge({}, validConfig, { collections: [ { fields: [ { name: 'title', label: 'title', widget: 'string' }, { name: 'object', label: 'Object', widget: 'object', fields: [ { name: 'title', label: 'title', widget: 'string' }, { name: 'relation', label: 'relation', widget: 'relation', display_fields: 'title', search_fields: 'title', }, ], }, ], }, ], }), ); }).toThrowError( "'collections[0].fields[1].fields[1].search_fields' must be array\n'collections[0].fields[1].fields[1].display_fields' must be array", ); }); it('should not throw if nested relation display_fields and search_fields are arrays', () => { expect(() => { validateConfig( merge({}, validConfig, { collections: [ { fields: [ { name: 'title', label: 'title', widget: 'string' }, { name: 'object', label: 'Object', widget: 'object', fields: [ { name: 'title', label: 'title', widget: 'string' }, { name: 'relation', label: 'relation', widget: 'relation', display_fields: ['title'], search_fields: ['title'], }, ], }, ], }, ], }), ); }).not.toThrow(); }); }); it('should throw if collection meta is not a plain object', () => { expect(() => { validateConfig(merge({}, validConfig, { collections: [{ meta: [] }] })); }).toThrowError("'collections[0].meta' must be object"); }); it('should throw if collection meta is an empty object', () => { expect(() => { validateConfig(merge({}, validConfig, { collections: [{ meta: {} }] })); }).toThrowError("'collections[0].meta' must NOT have fewer than 1 properties"); }); it('should throw if collection meta is an empty object', () => { expect(() => { validateConfig(merge({}, validConfig, { collections: [{ meta: { path: {} } }] })); }).toThrowError("'collections[0].meta.path' must have required property 'label'"); expect(() => { validateConfig( merge({}, validConfig, { collections: [{ meta: { path: { label: 'Label' } } }] }), ); }).toThrowError("'collections[0].meta.path' must have required property 'widget'"); expect(() => { validateConfig( merge({}, validConfig, { collections: [{ meta: { path: { label: 'Label', widget: 'widget' } } }], }), ); }).toThrowError("'collections[0].meta.path' must have required property 'index_file'"); }); it('should allow collection meta to have a path configuration', () => { expect(() => { validateConfig( merge({}, validConfig, { collections: [ { meta: { path: { label: 'Path', widget: 'string', index_file: 'index' } } }, ], }), ); }).not.toThrow(); }); it('should throw if collection field pattern is not an array', () => { expect(() => { validateConfig(merge({}, validConfig, { collections: [{ fields: [{ pattern: '' }] }] })); }).toThrowError("'collections[0].fields[0].pattern' must be array"); }); it('should throw if collection field pattern is not an array of [string|regex, string]', () => { expect(() => { validateConfig( merge({}, validConfig, { collections: [{ fields: [{ pattern: [1, ''] }] }] }), ); }).toThrowError( "'collections[0].fields[0].pattern[0]' must be string\n'collections[0].fields[0].pattern[0]' must be a regular expression", ); expect(() => { validateConfig( merge({}, validConfig, { collections: [{ fields: [{ pattern: ['', 1] }] }] }), ); }).toThrowError("'collections[0].fields[0].pattern[1]' must be string"); }); it('should allow collection field pattern to be an array of [string|regex, string]', () => { expect(() => { validateConfig( merge({}, validConfig, { collections: [{ fields: [{ pattern: ['pattern', 'error'] }] }], }), ); }).not.toThrow(); expect(() => { validateConfig( merge({}, validConfig, { collections: [{ fields: [{ pattern: [/pattern/, 'error'] }] }], }), ); }).not.toThrow(); }); describe('i18n', () => { it('should throw error when locale has invalid characters', () => { expect(() => { validateConfig( merge({}, validConfig, { i18n: { structure: 'multiple_folders', locales: ['en', 'tr.TR'], }, }), ); }).toThrowError(`'i18n.locales[1]' must match pattern "^[a-zA-Z-_]+$"`); }); it('should throw error when locale is less than 2 characters', () => { expect(() => { validateConfig( merge({}, validConfig, { i18n: { structure: 'multiple_folders', locales: ['en', 't'], }, }), ); }).toThrowError(`'i18n.locales[1]' must NOT have fewer than 2 characters`); }); it('should throw error when locale is more than 10 characters', () => { expect(() => { validateConfig( merge({}, validConfig, { i18n: { structure: 'multiple_folders', locales: ['en', 'a_very_long_locale'], }, }), ); }).toThrowError(`'i18n.locales[1]' must NOT have more than 10 characters`); }); it('should throw error when locales is less than 1 items', () => { expect(() => { validateConfig( merge({}, validConfig, { i18n: { structure: 'multiple_folders', locales: [], }, }), ); }).toThrowError(`'i18n.locales' must NOT have fewer than 1 items`); }); it('should allow valid locales strings', () => { expect(() => { validateConfig( merge({}, validConfig, { i18n: { structure: 'multiple_folders', locales: ['en', 'tr-TR', 'zh_CHS'], }, }), ); }).not.toThrow(); }); }); }); });