Skip to content
Open
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
38 changes: 38 additions & 0 deletions packages/decap-cms-core/src/lib/__tests__/i18n.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,44 @@ describe('i18n', () => {
});

describe('getI18nFiles', () => {
it('should serialize translated preSave entries without nesting entry metadata in content', async () => {
const { registerEventListener, removeEventListener, invokeEvent } = require('../registry');
const entry = fromJS({
collection: 'posts',
slug: 'article',
path: 'content/article.md',
data: { title: 'English' },
i18n: { 'pt-br': { data: { title: 'Old translation' } } },
});
function handler({ entry }) {
return entry.setIn(['i18n', 'pt-br', 'data', 'title'], 'Portuguese');
}
registerEventListener({ name: 'preSave', handler });
try {
const result = await invokeEvent({ name: 'preSave', data: { entry } });
const files = i18n.getI18nFiles(
fromJS({
i18n: {
structure: i18n.I18N_STRUCTURE.MULTIPLE_FOLDERS,
locales: ['en', 'pt-br'],
default_locale: 'en',
},
}),
'md',
result,
draft => JSON.stringify(draft.get('data').toJS()),
'content/article.md',
'article',
);
expect(files).toEqual([
{ path: 'content/en/article.md', slug: 'article', raw: '{"title":"English"}' },
{ path: 'content/pt-br/article.md', slug: 'article', raw: '{"title":"Portuguese"}' },
]);
} finally {
removeEventListener({ name: 'preSave', handler });
}
});

const locales = ['en', 'de', 'fr'];
const default_locale = 'en';
const args = [
Expand Down
69 changes: 69 additions & 0 deletions packages/decap-cms-core/src/lib/__tests__/registry.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,75 @@ describe('registry', () => {
expect(result).toEqual(dataAfterSecondHandlerExecution.entry);
});

it('should preserve a complete entry returned by a preSave handler', async () => {
const { registerEventListener, invokeEvent } = require('../registry');
const entry = fromJS({
collection: 'posts',
slug: 'article',
path: 'content/en/article.md',
meta: { path: 'content/en/article.md' },
data: { title: 'English' },
i18n: { 'pt-br': { data: { title: 'Old translation' } } },
});
const translated = entry.setIn(['i18n', 'pt-br', 'data', 'title'], 'Portuguese');
registerEventListener({ name: 'preSave', handler: async () => translated });

const result = await invokeEvent({ name: 'preSave', data: { entry } });

expect(result).toBe(translated);
expect(result.get('data')).toEqual(fromJS({ title: 'English' }));
expect(result.getIn(['i18n', 'pt-br', 'data', 'title'])).toBe('Portuguese');
});

it('should chain data-only and full-entry handlers without losing metadata or translations', async () => {
const { registerEventListener, invokeEvent } = require('../registry');
const entry = fromJS({
collection: 'posts',
slug: 'article',
path: 'content/en/article.md',
data: { title: 'English' },
i18n: { 'pt-br': { data: { title: 'Old translation' } } },
});
registerEventListener({
name: 'preSave',
handler: ({ entry }) => entry.get('data').set('title', 'Updated English'),
});
registerEventListener({
name: 'preSave',
handler: ({ entry }) => entry.setIn(['i18n', 'pt-br', 'data', 'title'], 'Portuguese'),
});
registerEventListener({ name: 'preSave', handler: () => undefined });
registerEventListener({
name: 'preSave',
handler: ({ entry }) => entry.get('data').set('description', 'Description'),
});

const result = await invokeEvent({ name: 'preSave', data: { entry } });

expect(result).toEqual(
entry
.setIn(['data', 'title'], 'Updated English')
.setIn(['data', 'description'], 'Description')
.setIn(['i18n', 'pt-br', 'data', 'title'], 'Portuguese'),
);
});

it('should treat a content field named data as content rather than a full entry', async () => {
const { registerEventListener, invokeEvent } = require('../registry');
const entry = fromJS({
collection: 'posts',
slug: 'article',
path: 'content/article.md',
data: { data: { title: 'Nested content' } },
});
const content = entry.get('data').setIn(['data', 'title'], 'Updated nested content');
registerEventListener({ name: 'preSave', handler: () => content });

const result = await invokeEvent({ name: 'preSave', data: { entry } });

expect(result).toEqual(entry.set('data', content));
});

it('should allow multiple events to not return a value', async () => {
const { registerEventListener, invokeEvent } = require('../registry');

Expand Down
6 changes: 5 additions & 1 deletion packages/decap-cms-core/src/lib/registry.js
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,11 @@ export async function invokeEvent({ name, data }) {
for (const { handler, options } of handlers) {
const result = await handler(_data, options);
if (result !== undefined) {
const entry = _data.entry.set('data', result);
// Handlers may return either field data or the complete entry (for example,
// to update i18n). Check entry metadata too: content can have a data field.
const isEntry =
Map.isMap(result) && ['data', 'collection', 'slug', 'path'].every(key => result.has(key));
const entry = isEntry ? result : _data.entry.set('data', result);
_data = { ...data, entry };
}
}
Expand Down