Skip to content

Commit c8fd555

Browse files
fix: handle undefined values in migration generation [ZEND-6547] (#3033)
1 parent c54bac0 commit c8fd555

File tree

2 files changed

+158
-17
lines changed

2 files changed

+158
-17
lines changed

lib/cmds/space_cmds/generate_cmds/migration.js

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,12 @@ const ctVariableEscape = function (ctId) {
151151

152152
module.exports.ctVariableEscape = ctVariableEscape
153153

154+
const filterNullAndUndefined = function (entries) {
155+
return entries.filter(([, value]) => value !== null && value !== undefined)
156+
}
157+
158+
module.exports.filterNullAndUndefined = filterNullAndUndefined
159+
154160
const wrapMigrationWithBase = function (blockStatement) {
155161
const migration = b.program([
156162
b.expressionStatement(
@@ -185,11 +191,11 @@ const createCallChain = function (base, chain) {
185191
const createContentType = function (ct) {
186192
const id = ct.sys.id
187193
const { name, description, displayField } = ct
188-
const chain = [
194+
const chain = filterNullAndUndefined([
189195
['name', name],
190196
['description', description],
191197
['displayField', displayField]
192-
].filter(([, value]) => value !== null)
198+
])
193199

194200
const variableName = ctVariableEscape(id)
195201

@@ -216,9 +222,11 @@ const createField = function (ctId, field) {
216222
const fieldId = field.id
217223
const ctVariable = ctVariableEscape(ctId)
218224

219-
const chain = Object.keys(_.omit(field, 'id')).map(key => {
220-
return [key, field[key]]
221-
})
225+
const chain = filterNullAndUndefined(
226+
Object.keys(_.omit(field, 'id')).map(key => {
227+
return [key, field[key]]
228+
})
229+
)
222230

223231
return createCallChain(
224232
b.callExpression(
@@ -238,13 +246,18 @@ const changeFieldControl = function (
238246
widgetId,
239247
settings
240248
) {
249+
if (!widgetNamespace || !widgetId) {
250+
return null
251+
}
252+
241253
const ctVariable = ctVariableEscape(ctId)
242254
settings = settings || {}
243255

244256
const settingsExpression = b.objectExpression(
245-
_.map(settings, (v, k) => {
246-
return b.property('init', b.identifier(k), b.literal(v))
247-
})
257+
filterNullAndUndefined(Object.entries(settings))
258+
.map(([k, v]) => {
259+
return b.property('init', b.identifier(k), b.literal(v))
260+
})
248261
)
249262

250263
return b.callExpression(
@@ -290,18 +303,22 @@ const generateContentTypeMigration = async function (environment, contentType) {
290303
return control.fieldId === field.id
291304
})
292305

306+
if (!control) {
307+
return null
308+
}
309+
293310
const { widgetId, settings, widgetNamespace = 'builtin' } = control
294311

295-
return b.expressionStatement(
296-
changeFieldControl(
297-
contentType.sys.id,
298-
field.id,
299-
widgetNamespace,
300-
widgetId,
301-
settings
302-
)
312+
const changeFieldControlCall = changeFieldControl(
313+
contentType.sys.id,
314+
field.id,
315+
widgetNamespace,
316+
widgetId,
317+
settings
303318
)
304-
})
319+
320+
return changeFieldControlCall ? b.expressionStatement(changeFieldControlCall) : null
321+
}).filter(creator => creator !== null)
305322
} catch (err) {
306323
if (err.name === 'NotFound') {
307324
log('Skipping editor interfaces. Content type has no fields.')

test/unit/cmds/space_cmds/generate_cmds/migration.test.js

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,25 @@ const simpleContentType = {
4040
]
4141
}
4242

43+
const contentTypeWithUndefined = {
44+
sys: {
45+
id: 'bar'
46+
},
47+
name: 'Bar',
48+
description: undefined,
49+
displayField: undefined,
50+
fields: [
51+
{
52+
id: 'title',
53+
name: 'Title',
54+
type: 'Symbol',
55+
required: undefined,
56+
localized: undefined,
57+
disabled: false
58+
}
59+
]
60+
}
61+
4362
const editorInterface = {
4463
controls: [
4564
{
@@ -53,6 +72,38 @@ const editorInterface = {
5372
]
5473
}
5574

75+
const editorInterfaceWithUndefined = {
76+
controls: [
77+
{
78+
fieldId: 'title',
79+
widgetId: 'singleLine',
80+
widgetNamespace: 'builtin',
81+
settings: {
82+
helpText: undefined,
83+
placeholder: 'Enter title',
84+
showLinkEntityAction: undefined
85+
}
86+
}
87+
]
88+
}
89+
90+
const editorInterfaceWithMissingProperties = {
91+
controls: [
92+
{
93+
fieldId: 'title',
94+
widgetId: 'singleLine',
95+
widgetNamespace: 'builtin',
96+
settings: {
97+
helpText: 'Valid title'
98+
}
99+
},
100+
{
101+
fieldId: 'description'
102+
// Missing widgetId and widgetNamespace
103+
}
104+
]
105+
}
106+
56107
const EditorInterfaceNotFoundErrorMock = function () {
57108
this.name = 'NotFound'
58109
}
@@ -173,6 +224,79 @@ test('it creates the editor interface', async () => {
173224
)
174225
})
175226

227+
test('it handles undefined values in content type', async () => {
228+
const programStub = b.blockStatement([createContentType(contentTypeWithUndefined)])
229+
230+
const expected = `module.exports = function(migration) {
231+
const bar = migration.createContentType("bar").name("Bar");
232+
};`
233+
234+
expect(recast.prettyPrint(wrapMigrationWithBase(programStub)).code).toBe(
235+
expected
236+
)
237+
})
238+
239+
test('it handles undefined values in field properties', async () => {
240+
const programStub = b.blockStatement([
241+
b.expressionStatement(
242+
createField(contentTypeWithUndefined.sys.id, contentTypeWithUndefined.fields[0])
243+
)
244+
])
245+
246+
const expected = `module.exports = function(migration) {
247+
bar.createField("title").name("Title").type("Symbol").disabled(false);
248+
};`
249+
250+
expect(recast.prettyPrint(wrapMigrationWithBase(programStub)).code).toBe(
251+
expected
252+
)
253+
})
254+
255+
test('it handles undefined values in editor interface settings', async () => {
256+
const programStub = b.blockStatement([
257+
b.expressionStatement(
258+
changeFieldControl(
259+
'bar',
260+
editorInterfaceWithUndefined.controls[0].fieldId,
261+
editorInterfaceWithUndefined.controls[0].widgetNamespace,
262+
editorInterfaceWithUndefined.controls[0].widgetId,
263+
editorInterfaceWithUndefined.controls[0].settings
264+
)
265+
)
266+
])
267+
268+
const expected = `module.exports = function(migration) {
269+
bar.changeFieldControl("title", "builtin", "singleLine", {
270+
placeholder: "Enter title"
271+
});
272+
};`
273+
274+
expect(recast.prettyPrint(wrapMigrationWithBase(programStub)).code).toBe(
275+
expected
276+
)
277+
})
278+
279+
test('it skips changeFieldControl when required parameters are undefined', async () => {
280+
const validControl = changeFieldControl(
281+
'bar',
282+
editorInterfaceWithMissingProperties.controls[0].fieldId,
283+
editorInterfaceWithMissingProperties.controls[0].widgetNamespace,
284+
editorInterfaceWithMissingProperties.controls[0].widgetId,
285+
editorInterfaceWithMissingProperties.controls[0].settings
286+
)
287+
288+
const invalidControl = changeFieldControl(
289+
'bar',
290+
editorInterfaceWithMissingProperties.controls[1].fieldId,
291+
editorInterfaceWithMissingProperties.controls[1].widgetNamespace,
292+
editorInterfaceWithMissingProperties.controls[1].widgetId,
293+
editorInterfaceWithMissingProperties.controls[1].settings
294+
)
295+
296+
expect(validControl).not.toBeNull()
297+
expect(invalidControl).toBeNull()
298+
})
299+
176300
test('it creates the full migration script', async () => {
177301
const expected = `module.exports = function (migration) {
178302
const foo = migration

0 commit comments

Comments
 (0)