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."
        );
    });
});
