UNPKG

@stackbit/utils

Version:
1,267 lines (1,205 loc) 43 kB
import { describe, expect, test } from '@jest/globals'; import _ from 'lodash'; import type * as StackbitTypes from '@stackbit/types'; import { updateDocumentWithOperation } from '../src/update-document-operation'; const pageModel: StackbitTypes.Model = { name: 'page_model', type: 'page', fields: [ { type: 'string', name: 'string_field' }, { type: 'image', name: 'image_field' }, { type: 'image', name: 'cloudinary_image_field', source: 'cloudinary' }, { type: 'object', name: 'object_field', fields: [ { type: 'string', name: 'object_string_field' } ] }, { type: 'model', name: 'model_field', models: ['object_model'] }, { type: 'reference', name: 'reference_field', models: ['data_model'] }, { type: 'cross-reference', name: 'cross_reference_field', models: [ { modelName: 'data_model', srcType: 'srcType-1', srcProjectId: 'srcProjectId-1' } ] }, { type: 'list', name: 'string_list_field', items: { type: 'string' } }, { type: 'list', name: 'model_list_field', items: { type: 'model', models: ['object_model'] } } ] }; const dataModel: StackbitTypes.Model = { name: 'data_model', type: 'data', fields: [ { type: 'string', name: 'data_model_string_field' } ] }; const objectModel: StackbitTypes.Model = { name: 'object_model', type: 'object', fields: [ { type: 'string', name: 'object_model_string_field' } ] }; function getModelByName(modelName: string): StackbitTypes.Model | undefined { if (modelName === 'page_model') { return pageModel; } else if (modelName === 'data_model') { return dataModel; } else if (modelName === 'object_model') { return objectModel; } return undefined; } function getFieldByName(model: StackbitTypes.Model, fieldName: string): StackbitTypes.Field | undefined { return model.fields?.find((field) => field.name === fieldName); } const emptyDocument: StackbitTypes.Document = { type: 'document', modelName: 'page_model', id: 'doc-1', status: 'modified', manageUrl: '', createdAt: '2024-01-18T10:00:00.000Z', updatedAt: '2024-01-18T14:00:00.000Z', context: null, fields: {} }; function getDocumentWithFields(fields: StackbitTypes.Document['fields'] = {}): { document: StackbitTypes.Document; documentCopy: StackbitTypes.Document } { const document = { ...emptyDocument, fields }; return { document, documentCopy: _.cloneDeep(document) }; } describe('test updateDocumentWithOperation simple fields', () => { test('should throw if fieldPath is empty', () => { expect(() => { updateDocumentWithOperation({ document: _.cloneDeep(emptyDocument), updateOperation: { opType: 'set', fieldPath: [], modelField: getFieldByName(pageModel, 'string_field')!, field: { type: 'string', value: 'updated value' } }, getModelByName }); }).toThrow("Error updating document 'doc-1' with 'set' operation at field path ''. The operation's fieldPath cannot be empty."); }); test('should throw if first fieldPath item is not a string', () => { expect(() => { updateDocumentWithOperation({ document: _.cloneDeep(emptyDocument), updateOperation: { opType: 'set', fieldPath: [1], modelField: getFieldByName(pageModel, 'string_field')!, field: { type: 'string', value: 'updated value' } }, getModelByName }); }).toThrow("Error updating document 'doc-1' with 'set' operation at field path '1'. The first item in operation's fieldPath must be a string."); }); test('should throw if the first field in the fieldPath points to a not model field', () => { expect(() => { updateDocumentWithOperation({ document: _.cloneDeep(emptyDocument), updateOperation: { opType: 'set', fieldPath: ['non_existing_field'], modelField: getFieldByName(pageModel, 'string_field')!, field: { type: 'string', value: 'updated value' } }, getModelByName }); }).toThrow( "Error updating document 'doc-1' with 'set' operation at field path 'non_existing_field'. The field 'non_existing_field' in operation's fieldPath doesn't match any field of model 'page_model'." ); }); test('should add string field with set operation', () => { const { document, documentCopy } = getDocumentWithFields(); const updatedDocument = updateDocumentWithOperation({ document: document, updateOperation: { opType: 'set', fieldPath: ['string_field'], modelField: getFieldByName(pageModel, 'string_field')!, field: { type: 'string', value: 'updated value' } }, getModelByName }); // test that the passed document was not mutated expect(document).toEqual(documentCopy); expect(updatedDocument).toEqual({ ...document, fields: { string_field: { type: 'string', value: 'updated value' } } }); }); test('should update string field with set operation', () => { const { document, documentCopy } = getDocumentWithFields({ string_field: { type: 'string', value: 'some value' } }); const updatedDocument = updateDocumentWithOperation({ document: document, updateOperation: { opType: 'set', fieldPath: ['string_field'], modelField: getFieldByName(pageModel, 'string_field')!, field: { type: 'string', value: 'updated value' } }, getModelByName }); // test that the passed document was not mutated expect(document).toEqual(documentCopy); expect(updatedDocument).toEqual({ ...document, fields: { string_field: { type: 'string', value: 'updated value' } } }); }); test('should remove string field with unset operation', () => { const { document, documentCopy } = getDocumentWithFields({ string_field: { type: 'string', value: 'some value' } }); const updatedDocument = updateDocumentWithOperation({ document: document, updateOperation: { opType: 'unset', fieldPath: ['string_field'], modelField: getFieldByName(pageModel, 'string_field')! }, getModelByName }); // test that the passed document was not mutated expect(document).toEqual(documentCopy); expect(updatedDocument).toEqual({ ...document, fields: {} }); }); test('should add reference image field with set operation', () => { const { document, documentCopy } = getDocumentWithFields(); const updatedDocument = updateDocumentWithOperation({ document: document, updateOperation: { opType: 'set', fieldPath: ['image_field'], modelField: getFieldByName(pageModel, 'image_field')!, field: { type: 'reference', refType: 'asset', refId: 'image-id' } }, getModelByName }); // test that the passed document was not mutated expect(document).toEqual(documentCopy); expect(updatedDocument).toEqual({ ...document, fields: { image_field: { type: 'reference', refType: 'asset', refId: 'image-id' } } }); }); test('should add asset-source image field with set operation', () => { const { document, documentCopy } = getDocumentWithFields(); const updatedDocument = updateDocumentWithOperation({ document: document, updateOperation: { opType: 'set', fieldPath: ['cloudinary_image_field'], modelField: getFieldByName(pageModel, 'cloudinary_image_field')!, field: { type: 'image', value: 'image-value' } }, getModelByName }); // test that the passed document was not mutated expect(document).toEqual(documentCopy); expect(updatedDocument).toEqual({ ...document, fields: { cloudinary_image_field: { type: 'image', source: 'cloudinary', sourceData: 'image-value' } } }); }); test('should add reference field with set operation', () => { const { document, documentCopy } = getDocumentWithFields(); const updatedDocument = updateDocumentWithOperation({ document: document, updateOperation: { opType: 'set', fieldPath: ['reference_field'], modelField: getFieldByName(pageModel, 'reference_field')!, field: { type: 'reference', refType: 'document', refId: 'doc-id' } }, getModelByName }); // test that the passed document was not mutated expect(document).toEqual(documentCopy); expect(updatedDocument).toEqual({ ...document, fields: { reference_field: { type: 'reference', refType: 'document', refId: 'doc-id' } } }); }); test('should add cross-reference field with set operation', () => { const { document, documentCopy } = getDocumentWithFields(); const updatedDocument = updateDocumentWithOperation({ document: document, updateOperation: { opType: 'set', fieldPath: ['cross_reference_field'], modelField: getFieldByName(pageModel, 'cross_reference_field')!, field: { type: 'cross-reference', value: { refSrcType: 'srcType-1', refProjectId: 'projectId-1', refId: 'doc-1' } } }, getModelByName }); // test that the passed document was not mutated expect(document).toEqual(documentCopy); expect(updatedDocument).toEqual({ ...document, fields: { cross_reference_field: { type: 'cross-reference', refType: 'document', refSrcType: 'srcType-1', refProjectId: 'projectId-1', refId: 'doc-1' } } }); }); }); describe('test updateDocumentWithOperation with object fields', () => { test('should set object with nested field with set operation', () => { const { document, documentCopy } = getDocumentWithFields(); const updatedDocument = updateDocumentWithOperation({ document: document, updateOperation: { opType: 'set', fieldPath: ['object_field'], modelField: getFieldByName(pageModel, 'object_field')!, field: { type: 'object', fields: { object_string_field: { type: 'string', value: 'value' } } } }, getModelByName }); // test that the passed document was not mutated expect(document).toEqual(documentCopy); expect(updatedDocument).toEqual({ ...document, fields: { object_field: { type: 'object', fields: { object_string_field: { type: 'string', value: 'value' } } } } }); }); test('should throw if operation sets an object with non model fields', () => { expect(() => { const { document } = getDocumentWithFields(); updateDocumentWithOperation({ document: document, updateOperation: { opType: 'set', fieldPath: ['object_field'], modelField: getFieldByName(pageModel, 'object_field')!, field: { type: 'object', fields: { non_existing_field: { type: 'string', value: 'value' } } } }, getModelByName }); }).toThrow( "Error updating document 'doc-1' with 'set' operation at field path 'object_field'. Model field with name 'non_existing_field' was not found." ); }); test('should throw if a nested object fieldPath points to a non model field', () => { expect(() => { const { document } = getDocumentWithFields({ object_field: { type: 'object', fields: {} } }); updateDocumentWithOperation({ document: document, updateOperation: { opType: 'set', fieldPath: ['object_field', 'non_existing_field'], modelField: (getFieldByName(pageModel, 'object_field') as StackbitTypes.FieldObject).fields[0]!, field: { type: 'string', value: 'updated value' } }, getModelByName }); }).toThrow( "Error updating document 'doc-1' with 'set' operation at field path 'object_field.non_existing_field'. The 'non_existing_field' in the field path 'object_field.non_existing_field' doesn't match any model field." ); }); test('should throw if a nested object fieldPath points to undefined field', () => { expect(() => { updateDocumentWithOperation({ document: _.cloneDeep(emptyDocument), updateOperation: { opType: 'set', fieldPath: ['object_field', 'object_string_field'], modelField: (getFieldByName(pageModel, 'object_field') as StackbitTypes.FieldObject).fields[0]!, field: { type: 'string', value: 'updated value' } }, getModelByName }); }).toThrow( "Error updating document 'doc-1' with 'set' operation at field path 'object_field.object_string_field'. The field path 'object_field' points to an undefined document field." ); }); test('should set nested object field with set operation', () => { const { document, documentCopy } = getDocumentWithFields({ object_field: { type: 'object', fields: {} } }); const updatedDocument = updateDocumentWithOperation({ document: document, updateOperation: { opType: 'set', fieldPath: ['object_field', 'object_string_field'], modelField: (getFieldByName(pageModel, 'object_field') as StackbitTypes.FieldObject).fields[0]!, field: { type: 'string', value: 'value' } }, getModelByName }); // test that the passed document was not mutated expect(document).toEqual(documentCopy); expect(updatedDocument).toEqual({ ...document, fields: { object_field: { type: 'object', fields: { object_string_field: { type: 'string', value: 'value' } } } } }); }); test('should unset nested object field with unset operation', () => { const { document, documentCopy } = getDocumentWithFields({ object_field: { type: 'object', fields: { object_string_field: { type: 'string', value: 'value' } } } }); const updatedDocument = updateDocumentWithOperation({ document: document, updateOperation: { opType: 'unset', fieldPath: ['object_field', 'object_string_field'], modelField: (getFieldByName(pageModel, 'object_field') as StackbitTypes.FieldObject).fields[0]! }, getModelByName }); // test that the passed document was not mutated expect(document).toEqual(documentCopy); expect(updatedDocument).toEqual({ ...document, fields: { object_field: { type: 'object', fields: {} } } }); }); }); describe('test updateDocumentWithOperation with model fields', () => { test('should set model with nested field with set operation', () => { const { document, documentCopy } = getDocumentWithFields(); const updatedDocument = updateDocumentWithOperation({ document: document, updateOperation: { opType: 'set', fieldPath: ['model_field'], modelField: getFieldByName(pageModel, 'model_field')!, field: { type: 'model', modelName: 'object_model', fields: { object_model_string_field: { type: 'string', value: 'value' } } } }, getModelByName }); // test that the passed document was not mutated expect(document).toEqual(documentCopy); expect(updatedDocument).toEqual({ ...document, fields: { model_field: { type: 'model', modelName: 'object_model', fields: { object_model_string_field: { type: 'string', value: 'value' } } } } }); }); test('should throw if operation sets an model with non model fields', () => { expect(() => { const { document } = getDocumentWithFields(); updateDocumentWithOperation({ document: document, updateOperation: { opType: 'set', fieldPath: ['model_field'], modelField: getFieldByName(pageModel, 'model_field')!, field: { type: 'model', modelName: 'object_model', fields: { non_existing_field: { type: 'string', value: 'value' } } } }, getModelByName }); }).toThrow( "Error updating document 'doc-1' with 'set' operation at field path 'model_field'. Model field with name 'non_existing_field' was not found in model 'object_model'." ); }); test('should throw if a nested model fieldPath points to a non model field', () => { expect(() => { const { document } = getDocumentWithFields({ model_field: { type: 'model', modelName: 'object_model', fields: {} } }); updateDocumentWithOperation({ document: document, updateOperation: { opType: 'set', fieldPath: ['model_field', 'non_existing_field'], modelField: getFieldByName(objectModel, 'object_model_string_field')!, field: { type: 'string', value: 'updated value' } }, getModelByName }); }).toThrow( "Error updating document 'doc-1' with 'set' operation at field path 'model_field.non_existing_field'. The field 'non_existing_field' in field path 'model_field' doesn't match any model field." ); }); test('should throw if a nested model fieldPath points to undefined field', () => { expect(() => { updateDocumentWithOperation({ document: _.cloneDeep(emptyDocument), updateOperation: { opType: 'set', fieldPath: ['model_field', 'object_model_string_field'], modelField: getFieldByName(objectModel, 'object_model_string_field')!, field: { type: 'string', value: 'updated value' } }, getModelByName }); }).toThrow( "Error updating document 'doc-1' with 'set' operation at field path 'model_field.object_model_string_field'. The field path 'model_field' points to an undefined document field." ); }); test('should set nested model field with set operation', () => { const { document, documentCopy } = getDocumentWithFields({ model_field: { type: 'model', modelName: 'object_model', fields: {} } }); const updatedDocument = updateDocumentWithOperation({ document: document, updateOperation: { opType: 'set', fieldPath: ['model_field', 'object_model_string_field'], modelField: getFieldByName(objectModel, 'object_model_string_field')!, field: { type: 'string', value: 'value' } }, getModelByName }); // test that the passed document was not mutated expect(document).toEqual(documentCopy); expect(updatedDocument).toEqual({ ...document, fields: { model_field: { type: 'model', modelName: 'object_model', fields: { object_model_string_field: { type: 'string', value: 'value' } } } } }); }); test('should unset nested model field with unset operation', () => { const { document, documentCopy } = getDocumentWithFields({ model_field: { type: 'model', modelName: 'object_model', fields: { object_model_string_field: { type: 'string', value: 'value' } } } }); const updatedDocument = updateDocumentWithOperation({ document: document, updateOperation: { opType: 'unset', fieldPath: ['model_field', 'object_model_string_field'], modelField: getFieldByName(objectModel, 'object_model_string_field')! }, getModelByName }); // test that the passed document was not mutated expect(document).toEqual(documentCopy); expect(updatedDocument).toEqual({ ...document, fields: { model_field: { type: 'model', modelName: 'object_model', fields: {} } } }); }); }); describe('test updateDocumentWithOperation with list fields', () => { test('should set new list field with set operation', () => { const { document, documentCopy } = getDocumentWithFields(); const updatedDocument = updateDocumentWithOperation({ document: document, updateOperation: { opType: 'set', fieldPath: ['string_list_field'], modelField: getFieldByName(pageModel, 'string_list_field')!, field: { type: 'list', items: [ { type: 'string', value: 'value-1' }, { type: 'string', value: 'value-2' } ] } }, getModelByName }); // test that the passed document was not mutated expect(document).toEqual(documentCopy); expect(updatedDocument).toEqual({ ...document, fields: { string_list_field: { type: 'list', items: [ { type: 'string', value: 'value-1' }, { type: 'string', value: 'value-2' } ] } } }); }); test('should update item in list field with set operation', () => { const { document, documentCopy } = getDocumentWithFields({ string_list_field: { type: 'list', items: [ { type: 'string', value: 'value-1' }, { type: 'string', value: 'value-2' } ] } }); const updatedDocument = updateDocumentWithOperation({ document: document, updateOperation: { opType: 'set', fieldPath: ['string_list_field', 1], modelField: (getFieldByName(pageModel, 'string_list_field') as StackbitTypes.FieldList).items, field: { type: 'string', value: 'value-new' } }, getModelByName }); // test that the passed document was not mutated expect(document).toEqual(documentCopy); expect(updatedDocument).toEqual({ ...document, fields: { string_list_field: { type: 'list', items: [ { type: 'string', value: 'value-1' }, { type: 'string', value: 'value-new' } ] } } }); }); test('should add list and insert first item with insert operation', () => { const { document, documentCopy } = getDocumentWithFields(); const updatedDocument = updateDocumentWithOperation({ document: document, updateOperation: { opType: 'insert', fieldPath: ['string_list_field'], modelField: getFieldByName(pageModel, 'string_list_field') as StackbitTypes.FieldList, item: { type: 'string', value: 'value' } }, getModelByName }); // test that the passed document was not mutated expect(document).toEqual(documentCopy); expect(updatedDocument).toEqual({ ...document, fields: { string_list_field: { type: 'list', items: [ { type: 'string', value: 'value' } ] } } }); }); test('should insert item at index into list field with insert operation', () => { const { document, documentCopy } = getDocumentWithFields({ string_list_field: { type: 'list', items: [ { type: 'string', value: 'value-1' }, { type: 'string', value: 'value-2' } ] } }); const updatedDocument = updateDocumentWithOperation({ document: document, updateOperation: { opType: 'insert', fieldPath: ['string_list_field'], modelField: getFieldByName(pageModel, 'string_list_field') as StackbitTypes.FieldList, index: 1, item: { type: 'string', value: 'value-new' } }, getModelByName }); // test that the passed document was not mutated expect(document).toEqual(documentCopy); expect(updatedDocument).toEqual({ ...document, fields: { string_list_field: { type: 'list', items: [ { type: 'string', value: 'value-1' }, { type: 'string', value: 'value-new' }, { type: 'string', value: 'value-2' } ] } } }); }); test('should remove item from list field with remove operation', () => { const { document, documentCopy } = getDocumentWithFields({ string_list_field: { type: 'list', items: [ { type: 'string', value: 'value-1' }, { type: 'string', value: 'value-2' } ] } }); const updatedDocument = updateDocumentWithOperation({ document: document, updateOperation: { opType: 'remove', fieldPath: ['string_list_field'], modelField: getFieldByName(pageModel, 'string_list_field') as StackbitTypes.FieldList, index: 0 }, getModelByName }); // test that the passed document was not mutated expect(document).toEqual(documentCopy); expect(updatedDocument).toEqual({ ...document, fields: { string_list_field: { type: 'list', items: [ { type: 'string', value: 'value-2' } ] } } }); }); test('should reorder items in list field with reorder operation', () => { const { document, documentCopy } = getDocumentWithFields({ string_list_field: { type: 'list', items: [ { type: 'string', value: 'value-1' }, { type: 'string', value: 'value-2' }, { type: 'string', value: 'value-3' } ] } }); const updatedDocument = updateDocumentWithOperation({ document: document, updateOperation: { opType: 'reorder', fieldPath: ['string_list_field'], modelField: getFieldByName(pageModel, 'string_list_field') as StackbitTypes.FieldList, order: [2, 0, 1] }, getModelByName }); // test that the passed document was not mutated expect(document).toEqual(documentCopy); expect(updatedDocument).toEqual({ ...document, fields: { string_list_field: { type: 'list', items: [ { type: 'string', value: 'value-3' }, { type: 'string', value: 'value-1' }, { type: 'string', value: 'value-2' } ] } } }); }); test('should add object with nested field into list field with insert operation', () => { const { document, documentCopy } = getDocumentWithFields(); const updatedDocument = updateDocumentWithOperation({ document: document, updateOperation: { opType: 'insert', fieldPath: ['model_list_field'], modelField: getFieldByName(pageModel, 'model_list_field') as StackbitTypes.FieldList, item: { type: 'model', modelName: 'object_model', fields: { object_model_string_field: { type: 'string', value: 'value' } } } }, getModelByName }); // test that the passed document was not mutated expect(document).toEqual(documentCopy); expect(updatedDocument).toEqual({ ...document, fields: { model_list_field: { type: 'list', items: [ { type: 'model', modelName: 'object_model', fields: { object_model_string_field: { type: 'string', value: 'value' } } } ] } } }); }); test('should update nested model field in list field with set operation', () => { const { document, documentCopy } = getDocumentWithFields({ model_list_field: { type: 'list', items: [ { type: 'model', modelName: 'object_model', fields: { object_model_string_field: { type: 'string', value: 'value' } } } ] } }); const updatedDocument = updateDocumentWithOperation({ document: document, updateOperation: { opType: 'set', fieldPath: ['model_list_field', 0, 'object_model_string_field'], modelField: getFieldByName(objectModel, 'object_model_string_field')!, field: { type: 'string', value: 'new value' } }, getModelByName }); // test that the passed document was not mutated expect(document).toEqual(documentCopy); expect(updatedDocument).toEqual({ ...document, fields: { model_list_field: { type: 'list', items: [ { type: 'model', modelName: 'object_model', fields: { object_model_string_field: { type: 'string', value: 'new value' } } } ] } } }); }); test('should unset nested model field in list field with unset operation', () => { const { document, documentCopy } = getDocumentWithFields({ model_list_field: { type: 'list', items: [ { type: 'model', modelName: 'object_model', fields: { object_model_string_field: { type: 'string', value: 'value' } } } ] } }); const updatedDocument = updateDocumentWithOperation({ document: document, updateOperation: { opType: 'unset', fieldPath: ['model_list_field', 0, 'object_model_string_field'], modelField: getFieldByName(objectModel, 'object_model_string_field')! }, getModelByName }); // test that the passed document was not mutated expect(document).toEqual(documentCopy); expect(updatedDocument).toEqual({ ...document, fields: { model_list_field: { type: 'list', items: [ { type: 'model', modelName: 'object_model', fields: {} } ] } } }); }); test('should throw when updating nested model field in list field with out of range index', () => { expect(() => { const { document } = getDocumentWithFields({ model_list_field: { type: 'list', items: [ { type: 'model', modelName: 'object_model', fields: { object_model_string_field: { type: 'string', value: 'value' } } } ] } }); updateDocumentWithOperation({ document: document, updateOperation: { opType: 'set', fieldPath: ['model_list_field', 1, 'object_model_string_field'], modelField: getFieldByName(objectModel, 'object_model_string_field')!, field: { type: 'string', value: 'new value' } }, getModelByName }); }).toThrow( "Error updating document 'doc-1' with 'set' operation at field path 'model_list_field.1.object_model_string_field'. The field path 'model_list_field.1' points to an undefined document field." ); }); });