UNPKG

decap-cms-core

Version:

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

724 lines (636 loc) 22.2 kB
import { List, Map, fromJS } from 'immutable'; import { commitMessageFormatter, prepareSlug, slugFormatter, previewUrlFormatter, summaryFormatter, folderFormatter, } from '../formatters'; jest.spyOn(console, 'warn').mockImplementation(() => {}); jest.mock('../../reducers/collections'); describe('formatters', () => { describe('commitMessageFormatter', () => { const config = { backend: { name: 'git-gateway', }, }; beforeEach(() => { jest.clearAllMocks(); }); it('should return default commit message on create, label_singular', () => { const collection = Map({ label_singular: 'Collection' }); expect( commitMessageFormatter('create', config, { slug: 'doc-slug', path: 'file-path', collection, }), ).toEqual('Create Collection “doc-slug”'); }); it('should return default commit message on create, label', () => { const collection = Map({ label: 'Collections' }); expect( commitMessageFormatter('update', config, { slug: 'doc-slug', path: 'file-path', collection, }), ).toEqual('Update Collections “doc-slug”'); }); it('should return default commit message on delete', () => { const collection = Map({ label_singular: 'Collection' }); expect( commitMessageFormatter('delete', config, { slug: 'doc-slug', path: 'file-path', collection, }), ).toEqual('Delete Collection “doc-slug”'); }); it('should return default commit message on uploadMedia', () => { const collection = Map({}); expect( commitMessageFormatter('uploadMedia', config, { slug: 'doc-slug', path: 'file-path', collection, }), ).toEqual('Upload “file-path”'); }); it('should return default commit message on deleteMedia', () => { const collection = Map({}); expect( commitMessageFormatter('deleteMedia', config, { slug: 'doc-slug', path: 'file-path', collection, }), ).toEqual('Delete “file-path”'); }); it('should log warning on unknown variable', () => { const config = { backend: { commit_messages: { create: 'Create {{collection}} “{{slug}}” with "{{unknown variable}}"', }, }, }; const collection = Map({ label_singular: 'Collection' }); expect( commitMessageFormatter('create', config, { slug: 'doc-slug', path: 'file-path', collection, }), ).toEqual('Create Collection “doc-slug” with ""'); expect(console.warn).toHaveBeenCalledTimes(1); expect(console.warn).toHaveBeenCalledWith( 'Ignoring unknown variable “unknown variable” in commit message template.', ); }); it('should return custom commit message on update', () => { const config = { backend: { commit_messages: { update: 'Custom commit message', }, }, }; const collection = Map({}); expect( commitMessageFormatter('update', config, { slug: 'doc-slug', path: 'file-path', collection, }), ).toEqual('Custom commit message'); }); it('should use empty values if "authorLogin" and "authorName" are missing in commit message', () => { const config = { backend: { commit_messages: { update: '{{author-login}} - {{author-name}}: Create {{collection}} “{{slug}}”', }, }, }; const collection = Map({ label_singular: 'Collection' }); expect( commitMessageFormatter( 'update', config, { slug: 'doc-slug', path: 'file-path', collection, }, true, ), ).toEqual(' - : Create Collection “doc-slug”'); }); it('should return custom create message with author information', () => { const config = { backend: { commit_messages: { create: '{{author-login}} - {{author-name}}: Create {{collection}} “{{slug}}”', }, }, }; const collection = Map({ label_singular: 'Collection' }); expect( commitMessageFormatter( 'create', config, { slug: 'doc-slug', path: 'file-path', collection, authorLogin: 'user-login', authorName: 'Test User', }, true, ), ).toEqual('user-login - Test User: Create Collection “doc-slug”'); }); it('should return custom open authoring message', () => { const config = { backend: { commit_messages: { openAuthoring: '{{author-login}} - {{author-name}}: {{message}}', }, }, }; const collection = Map({ label_singular: 'Collection' }); expect( commitMessageFormatter( 'create', config, { slug: 'doc-slug', path: 'file-path', collection, authorLogin: 'user-login', authorName: 'Test User', }, true, ), ).toEqual('user-login - Test User: Create Collection “doc-slug”'); }); it('should use empty values if "authorLogin" and "authorName" are missing in open authoring message', () => { const config = { backend: { commit_messages: { openAuthoring: '{{author-login}} - {{author-name}}: {{message}}', }, }, }; const collection = Map({ label_singular: 'Collection' }); expect( commitMessageFormatter( 'create', config, { slug: 'doc-slug', path: 'file-path', collection, }, true, ), ).toEqual(' - : Create Collection “doc-slug”'); }); it('should log warning on unknown variable in open authoring template', () => { const config = { backend: { commit_messages: { openAuthoring: '{{author-email}}: {{message}}', }, }, }; const collection = Map({ label_singular: 'Collection' }); commitMessageFormatter( 'create', config, { slug: 'doc-slug', path: 'file-path', collection, authorLogin: 'user-login', authorName: 'Test User', }, true, ); expect(console.warn).toHaveBeenCalledTimes(1); expect(console.warn).toHaveBeenCalledWith( 'Ignoring unknown variable “author-email” in open authoring message template.', ); }); }); describe('prepareSlug', () => { it('should trim slug', () => { expect(prepareSlug(' slug ')).toBe('slug'); }); it('should lowercase slug', () => { expect(prepareSlug('Slug')).toBe('slug'); }); it('should remove single quotes', () => { expect(prepareSlug(`sl'ug`)).toBe('slug'); }); it('should replace periods with slashes', () => { expect(prepareSlug(`sl.ug`)).toBe('sl-ug'); }); }); const slugConfig = { encoding: 'unicode', clean_accents: false, sanitize_replacement: '-', }; describe('slugFormatter', () => { const date = new Date('2020-01-01'); jest.spyOn(global, 'Date').mockImplementation(() => date); const { selectIdentifier } = require('../../reducers/collections'); beforeEach(() => { jest.clearAllMocks(); }); it('should format with default pattern', () => { selectIdentifier.mockReturnValueOnce('title'); expect(slugFormatter(Map(), Map({ title: 'Post Title' }), slugConfig)).toBe('post-title'); }); it('should format with date', () => { selectIdentifier.mockReturnValueOnce('title'); expect( slugFormatter( Map({ slug: '{{year}}-{{month}}-{{day}}_{{slug}}' }), Map({ title: 'Post Title' }), slugConfig, ), ).toBe('2020-01-01_post-title'); }); it('should format with entry field', () => { selectIdentifier.mockReturnValueOnce('slug'); expect( slugFormatter( Map({ slug: '{{fields.slug}}' }), Map({ title: 'Post Title', slug: 'entry-slug' }), slugConfig, ), ).toBe('entry-slug'); }); it('should return slug', () => { selectIdentifier.mockReturnValueOnce('title'); expect( slugFormatter(Map({ slug: '{{slug}}' }), Map({ title: 'Post Title' }), slugConfig), ).toBe('post-title'); }); it('should return slug with path', () => { selectIdentifier.mockReturnValueOnce('title'); expect( slugFormatter( Map({ slug: '{{year}}-{{month}}-{{day}}-{{slug}}', path: 'sub_dir/{{year}}/{{slug}}' }), Map({ title: 'Post Title' }), slugConfig, ), ).toBe('sub_dir/2020/2020-01-01-post-title'); }); it('should only sanitize template variables', () => { selectIdentifier.mockReturnValueOnce('title'); expect( slugFormatter( Map({ slug: '{{year}}-{{month}}-{{day}}-{{slug}}.en', path: 'sub_dir/{{year}}/{{slug}}', }), Map({ title: 'Post Title' }), slugConfig, ), ).toBe('sub_dir/2020/2020-01-01-post-title.en'); }); it(`should replace '.' in path with -`, () => { selectIdentifier.mockReturnValueOnce('title'); expect( slugFormatter( Map({ slug: '{{slug}}.en', path: '../dir/{{slug}}', }), Map({ title: 'Post Title' }), slugConfig, ), ).toBe('--/dir/post-title.en'); }); }); describe('previewUrlFormatter', () => { it('should return undefined when missing baseUrl', () => { expect(previewUrlFormatter('')).toBeUndefined(); }); it('should return baseUrl for collection with no preview_path', () => { expect(previewUrlFormatter('https://www.example.com', Map({}))).toBe( 'https://www.example.com', ); }); it('should return preview url based on preview_path and preview_path_date_field', () => { const date = new Date('2020-01-02T13:28:27.679Z'); expect( previewUrlFormatter( 'https://www.example.com', Map({ preview_path: '{{year}}/{{slug}}/{{title}}/{{fields.slug}}', preview_path_date_field: 'customDateField', }), 'backendSlug', Map({ data: Map({ customDateField: date, slug: 'entrySlug', title: 'title' }) }), slugConfig, ), ).toBe('https://www.example.com/2020/backendslug/title/entryslug'); }); it('should return preview url for files in file collection', () => { const file = Map({ name: 'about-file', preview_path: '{{slug}}/{{fields.slug}}/{{title}}' }); const { getFileFromSlug } = require('../../reducers/collections'); getFileFromSlug.mockReturnValue(file); expect( previewUrlFormatter( 'https://www.example.com', Map({ preview_path: '{{slug}}/{{title}}/{{fields.slug}}', type: 'file_based_collection', files: List([file]), }), 'backendSlug', Map({ data: Map({ slug: 'about-the-project', title: 'title' }), slug: 'about-file' }), slugConfig, ), ).toBe('https://www.example.com/backendslug/about-the-project/title'); }); it('should return preview url for files in file collection when defined on file-level only', () => { const file = Map({ name: 'about-file', preview_path: '{{slug}}/{{fields.slug}}/{{title}}' }); const { getFileFromSlug } = require('../../reducers/collections'); getFileFromSlug.mockReturnValue(file); expect( previewUrlFormatter( 'https://www.example.com', Map({ type: 'file_based_collection', files: List([file]), }), 'backendSlug', Map({ data: Map({ slug: 'about-the-project', title: 'title' }), slug: 'about-file' }), slugConfig, ), ).toBe('https://www.example.com/backendslug/about-the-project/title'); }); it('should fall back to collection preview url for files in file collection', () => { const file = Map({ name: 'about-file' }); const { getFileFromSlug } = require('../../reducers/collections'); getFileFromSlug.mockReturnValue(file); expect( previewUrlFormatter( 'https://www.example.com', Map({ preview_path: '{{slug}}/{{title}}/{{fields.slug}}', type: 'file_based_collection', files: List([file]), }), 'backendSlug', Map({ data: Map({ slug: 'about-the-project', title: 'title' }), slug: 'about-file' }), slugConfig, ), ).toBe('https://www.example.com/backendslug/title/about-the-project'); }); it('should infer date field when preview_path_date_field is not configured', () => { const { selectInferredField } = require('../../reducers/collections'); selectInferredField.mockReturnValue('date'); const date = new Date('2020-01-02T13:28:27.679Z'); expect( previewUrlFormatter( 'https://www.example.com', fromJS({ name: 'posts', preview_path: '{{year}}/{{month}}/{{slug}}/{{title}}/{{fields.slug}}', }), 'backendSlug', Map({ data: Map({ date, slug: 'entrySlug', title: 'title' }) }), slugConfig, ), ).toBe('https://www.example.com/2020/01/backendslug/title/entryslug'); }); it('should compile filename and extension template values', () => { expect( previewUrlFormatter( 'https://www.example.com', Map({ preview_path: 'posts/{{filename}}.{{extension}}', }), 'backendSlug', Map({ data: Map({}), path: 'src/content/posts/title.md' }), slugConfig, ), ).toBe('https://www.example.com/posts/title.md'); }); it('should compile the dirname template value to empty in a regular collection', () => { expect( previewUrlFormatter( 'https://www.example.com', Map({ folder: '_portfolio', preview_path: 'portfolio/{{dirname}}', }), 'backendSlug', Map({ data: Map({}), path: '_portfolio/i-am-the-slug.md' }), slugConfig, ), ).toBe('https://www.example.com/portfolio/'); }); it('should compile dirname template value when in a nested collection', () => { expect( previewUrlFormatter( 'https://www.example.com', Map({ folder: '_portfolio', preview_path: 'portfolio/{{dirname}}', nested: { depth: 100 }, meta: { path: { widget: 'string', label: 'Path', index_file: 'index' } }, }), 'backendSlug', Map({ data: Map({}), path: '_portfolio/drawing/i-am-the-slug/index.md' }), slugConfig, ), ).toBe('https://www.example.com/portfolio/drawing/i-am-the-slug'); }); it('should log error and ignore preview_path when date is missing', () => { jest.spyOn(console, 'error').mockImplementation(() => {}); expect( previewUrlFormatter( 'https://www.example.com', Map({ name: 'posts', preview_path: '{{year}}', preview_path_date_field: 'date', }), 'backendSlug', Map({ data: Map({}) }), slugConfig, ), ).toBe('https://www.example.com'); expect(console.error).toHaveBeenCalledTimes(1); expect(console.error).toHaveBeenCalledWith( 'Collection "posts" configuration error:\n `preview_path_date_field` must be a field with a valid date. Ignoring `preview_path`.', ); }); }); describe('summaryFormatter', () => { it('should return summary from template', () => { const { selectInferredField } = require('../../reducers/collections'); selectInferredField.mockReturnValue('date'); const date = new Date('2020-01-02T13:28:27.679Z'); const entry = fromJS({ data: { date, title: 'title' } }); const collection = fromJS({ fields: [{ name: 'date', widget: 'date' }] }); expect(summaryFormatter('{{title}}-{{year}}', entry, collection)).toBe('title-2020'); }); it('should handle filename and extension variables', () => { const { selectInferredField } = require('../../reducers/collections'); selectInferredField.mockReturnValue('date'); const date = new Date('2020-01-02T13:28:27.679Z'); const entry = fromJS({ path: 'post.md', data: { date, title: 'title' } }); const collection = fromJS({ fields: [{ name: 'date', widget: 'date' }] }); expect( summaryFormatter('{{title}}-{{year}}-{{filename}}.{{extension}}', entry, collection), ).toBe('title-2020-post.md'); }); it('should handle the dirname variable in a regular collection', () => { const { selectInferredField } = require('../../reducers/collections'); selectInferredField.mockReturnValue('date'); const date = new Date('2020-01-02T13:28:27.679Z'); const entry = fromJS({ path: '_portfolio/drawing.md', data: { date, title: 'title' }, }); const collection = fromJS({ folder: '_portfolio', fields: [{ name: 'date', widget: 'date' }], }); expect(summaryFormatter('{{dirname}}/{{title}}-{{year}}', entry, collection)).toBe( '/title-2020', ); }); it('should handle the dirname variable in a nested collection', () => { const { selectInferredField } = require('../../reducers/collections'); selectInferredField.mockReturnValue('date'); const date = new Date('2020-01-02T13:28:27.679Z'); const entry = fromJS({ path: '_portfolio/drawing/index.md', data: { date, title: 'title' }, }); const collection = fromJS({ folder: '_portfolio', nested: { depth: 100 }, meta: { path: { widget: 'string', label: 'Path', index_file: 'index' } }, fields: [{ name: 'date', widget: 'date' }], }); expect(summaryFormatter('{{dirname}}/{{title}}-{{year}}', entry, collection)).toBe( 'drawing/title-2020', ); }); }); describe('folderFormatter', () => { it('should return folder is entry is undefined', () => { expect(folderFormatter('static/images', undefined)).toBe('static/images'); }); it('should return folder is entry data is undefined', () => { expect(folderFormatter('static/images', Map({}))).toBe('static/images'); }); it('should return formatted folder', () => { const { selectIdentifier } = require('../../reducers/collections'); selectIdentifier.mockReturnValue('title'); const entry = fromJS({ path: 'content/en/hosting-and-deployment/deployment-with-nanobox.md', data: { title: 'Deployment With NanoBox', category: 'Hosting And Deployment' }, }); const collection = fromJS({}); expect( folderFormatter( '../../../{{media_folder}}/{{category}}/{{slug}}', entry, collection, 'static/images', 'media_folder', slugConfig, ), ).toBe('../../../static/images/hosting-and-deployment/deployment-with-nanobox'); }); it('should compile filename template value', () => { const entry = fromJS({ path: 'content/en/hosting-and-deployment/deployment-with-nanobox.md', data: { category: 'Hosting And Deployment' }, }); const collection = fromJS({}); expect( folderFormatter( '../../../{{media_folder}}/{{category}}/{{filename}}', entry, collection, 'static/images', 'media_folder', slugConfig, ), ).toBe('../../../static/images/hosting-and-deployment/deployment-with-nanobox'); }); it('should compile extension template value', () => { const entry = fromJS({ path: 'content/en/hosting-and-deployment/deployment-with-nanobox.md', data: { category: 'Hosting And Deployment' }, }); const collection = fromJS({}); expect( folderFormatter( '{{extension}}', entry, collection, 'static/images', 'media_folder', slugConfig, ), ).toBe('md'); }); it('should compile dirname template value in a regular collection', () => { const entry = fromJS({ path: 'content/en/hosting-and-deployment/deployment-with-nanobox.md', data: { category: 'Hosting And Deployment' }, }); const collection = fromJS({ folder: 'content/en/', }); expect( folderFormatter( '{{dirname}}', entry, collection, 'static/images', 'media_folder', slugConfig, ), ).toBe('hosting-and-deployment'); }); it('should compile dirname template value in a nested collection', () => { const entry = fromJS({ path: '_portfolio/drawing/i-am-the-slug/index.md', data: { category: 'Hosting And Deployment' }, }); const collection = fromJS({ folder: '_portfolio', nested: { depth: 100 }, meta: { path: { widget: 'string', label: 'Path', index_file: 'index' } }, fields: [{ name: 'date', widget: 'date' }], }); expect( folderFormatter( '{{dirname}}', entry, collection, 'static/images', 'media_folder', slugConfig, ), ).toBe('drawing/i-am-the-slug'); }); }); });