src/model/__tests__/Model.spec.js

Summary

Maintainability
B
4 hrs
Test Coverage
import fixtures from '../../__fixtures__/fixtures'
import Model from '../Model'
import ModelDefinition from '../ModelDefinition'

describe('Model', () => {
    let model

    beforeEach(() => {
        model = new Model({
            modelProperties: [],
        })
    })

    it('should not be allowed to be called without new', () => {
        expect(() => Model()).toThrowErrorMatchingSnapshot()
    })

    it('should throw when modelDefinition is not defined', () => {
        function shouldThrow() {
            return new Model()
        }
        expect(shouldThrow).toThrowError('modelDefinition should be provided')
    })

    it('should throw when modelDefinition.modelProperties is not provided', () => {
        function shouldThrow() {
            return new Model({})
        }
        expect(shouldThrow).toThrowError('modelProperties should be provided')
    })

    it('should have a create method on the class', () => {
        expect(typeof Model.create).toBe('function')
    })

    it('should have a save method', () => {
        expect(typeof model.save).toBe('function')
    })

    it('should have a validate method', () => {
        expect(typeof model.validate).toBe('function')
    })

    it('should have a dirty property that is set to false', () => {
        expect(model.dirty).toBe(false)
    })

    it('should not show the dirty property in the enumerable properties', () => {
        const keys = Object.keys(model)

        expect(keys).not.toContain('dirty')
    })

    it('should add properties based on the modelDefinition', () => {
        // TODO: This fixture is outdated and we should update to a fixture with getters and setters.
        const dataElementModel = Model.create(
            fixtures.get('/modelDefinitions/dataElement')
        )

        expect(Object.keys(dataElementModel).length).toBe(34)
    })

    it('should keep a reference to its definition', () => {
        const modelDefinition = { modelProperties: [] }
        const dataElementModel = Model.create(modelDefinition)

        expect(dataElementModel.modelDefinition).toBe(modelDefinition)
    })

    it('should not show the modelDefinition property in the enumerable properties', () => {
        const keys = Object.keys(model)

        expect(keys).not.toContain('modelDefinition')
    })

    describe('properties based off model definition', () => {
        let modelDefinition

        beforeEach(() => {
            modelDefinition = {
                modelProperties: {
                    name: {
                        configurable: false,
                        enumerable: true,
                        get() {
                            return this.dataValues.name
                        },
                        set(value) {
                            this.dataValues.name = value
                        },
                    },
                },
            }
        })

        it('should call the set method for name', () => {
            modelDefinition.modelProperties.name.set = jest.fn()
            const dataElementModel = Model.create(modelDefinition)

            dataElementModel.name = 'ANC'

            expect(
                modelDefinition.modelProperties.name.set
            ).toHaveBeenCalledWith('ANC')
        })

        it('should set the correct value', () => {
            const dataElementModel = Model.create(modelDefinition)

            dataElementModel.name = 'ANC'

            expect(dataElementModel.dataValues.name).toBe('ANC')
        })

        it('should call the get method for name', () => {
            modelDefinition.modelProperties.name.get = jest
                .fn()
                .mockReturnValue('ANC')
            const dataElementModel = Model.create(modelDefinition)
            const { name } = dataElementModel

            expect(modelDefinition.modelProperties.name.get).toHaveBeenCalled()
            expect(name).toBe('ANC')
        })

        it('should return the correct value', () => {
            const dataElementModel = Model.create(modelDefinition)

            dataElementModel.name = 'ANC'

            expect(dataElementModel.name).toBe('ANC')
        })
    })

    describe('getDirtyPropertyNames', () => {
        let dataElementModel

        beforeEach(() => {
            const dataElementSchema = fixtures.get('/api/schemas/dataElement')
            const dataElementModelDefinition =
                ModelDefinition.createFromSchema(dataElementSchema)

            dataElementModel = new Model(dataElementModelDefinition)
        })

        it('should be a method', () => {
            expect(dataElementModel.getDirtyPropertyNames).toBeInstanceOf(
                Function
            )
        })

        it('should return the names of properties that are dirty', () => {
            dataElementModel.name = 'ANC new'

            expect(dataElementModel.getDirtyPropertyNames()).toEqual(['name'])
        })

        it('should return an empty array for a clean model', () => {
            expect(dataElementModel.getDirtyPropertyNames()).toEqual([])
        })
    })

    describe('attributes', () => {
        let dataElementModel

        beforeEach(() => {
            const dataElementModelDefinition = ModelDefinition.createFromSchema(
                fixtures.get('/api/schemas/dataElement'),
                fixtures.get('/dataElementAttributes')
            )

            dataElementModel = Model.create(dataElementModelDefinition)
        })

        it('should not create the property when there are no attributes', () => {
            const dataElementSchema = fixtures.get('/api/schemas/dataElement')
            const dataElementModelDefinition =
                ModelDefinition.createFromSchema(dataElementSchema)

            dataElementModel = Model.create(dataElementModelDefinition)

            expect(dataElementModel.attributes).toBeUndefined()
        })

        it('should create the property when there are attributes available', () => {
            expect(dataElementModel.attributes).toBeDefined()
        })

        it('should have a property for each of the attributes that belong to this model type', () => {
            expect(Object.keys(dataElementModel.attributes)).toEqual([
                'marktribute',
                'marktribute2',
                'name',
            ])
        })

        it('should set the correct value onto the attributeValues properties', () => {
            dataElementModel.attributes.name = 'Mark'

            expect(dataElementModel.attributeValues.length).toBe(1)
            expect(dataElementModel.attributeValues[0].value).toBe('Mark')
            expect(dataElementModel.attributeValues[0].attribute).toEqual({
                id: 'S8a2OBRnqEc',
                name: 'name',
            })
        })

        it('should get the correct value from the attributeValues property', () => {
            dataElementModel.dataValues.attributeValues = [
                {
                    value: 'Mark',
                    attribute: { id: 'FpoWdhxCMwH', name: 'marktribute' },
                },
            ]

            expect(dataElementModel.attributes.marktribute).toBe('Mark')
        })

        it('should not add a value for the same attribute twice', () => {
            dataElementModel.dataValues.attributeValues = [
                {
                    value: 'Mark',
                    attribute: { id: 'FpoWdhxCMwH', name: 'marktribute' },
                },
            ]

            dataElementModel.attributes.marktribute = 'John'

            expect(dataElementModel.attributes.marktribute).toBe('John')
            expect(dataElementModel.attributeValues[0].value).toBe('John')
            expect(dataElementModel.attributeValues.length).toBe(1)
        })

        it('should add a value for the attribute when it does not exist yet', () => {
            dataElementModel.dataValues.attributeValues = [
                {
                    value: 'Mark',
                    attribute: { id: 'FpoWdhxCMwH', name: 'marktribute' },
                },
            ]

            dataElementModel.attributes.name = 'John'

            expect(dataElementModel.attributes.marktribute).toBe('Mark')
            expect(dataElementModel.attributes.name).toBe('John')
            expect(dataElementModel.attributeValues[0].value).toBe('Mark')
            expect(dataElementModel.attributeValues[1].value).toBe('John')
            expect(dataElementModel.attributeValues.length).toBe(2)
        })

        it('should remove the attributeValue from the attributeValue array when the value is cleared out', () => {
            dataElementModel.dataValues.attributeValues = [
                {
                    value: 'Mark',
                    attribute: { id: 'FpoWdhxCMwH', name: 'name' },
                },
            ]

            dataElementModel.attributes.name = ''

            expect(dataElementModel.attributes.name).toBe(undefined)
            expect(dataElementModel.attributeValues.length).toBe(0)
        })

        it('should not remove the attributeValue when the attribute is set to false', () => {
            dataElementModel.dataValues.attributeValues = [
                {
                    value: 'Mark',
                    attribute: { id: 'FpoWdhxCMwH', name: 'name' },
                },
            ]

            dataElementModel.attributes.name = false

            expect(dataElementModel.attributes.name).toBe(false)
            expect(dataElementModel.attributeValues.length).toBe(1)
        })

        it('should not remove the attributeValue when the attribute is set 0', () => {
            dataElementModel.dataValues.attributeValues = [
                {
                    value: 'Mark',
                    attribute: { id: 'FpoWdhxCMwH', name: 'name' },
                },
            ]

            dataElementModel.attributes.name = 0

            expect(dataElementModel.attributes.name).toBe(0)
            expect(dataElementModel.attributeValues.length).toBe(1)
        })

        it('should remove the attributeValue when the attribute is set to undefined', () => {
            dataElementModel.dataValues.attributeValues = [
                {
                    value: 'Mark',
                    attribute: { id: 'FpoWdhxCMwH', name: 'name' },
                },
            ]

            dataElementModel.attributes.name = undefined

            expect(dataElementModel.attributes.name).toBe(undefined)
            expect(dataElementModel.attributeValues.length).toBe(0)
        })

        it('should remove the attributeValue when the attribute is set to null', () => {
            dataElementModel.dataValues.attributeValues = [
                {
                    value: 'Mark',
                    attribute: { id: 'FpoWdhxCMwH', name: 'name' },
                },
            ]

            dataElementModel.attributes.name = null

            expect(dataElementModel.attributes.name).toBe(undefined)
            expect(dataElementModel.attributeValues.length).toBe(0)
        })

        it('should not show up in the list of model keys', () => {
            const modelKeys = Object.keys(dataElementModel)

            expect(modelKeys).not.toContain('attributes')
        })

        it('should not be able to set attributes to something else', () => {
            const changeAttributesProperty = () => {
                dataElementModel.attributes = 'something else'
            }

            expect(changeAttributesProperty).toThrow()
            expect(dataElementModel.attributes).not.toBe('something else')
        })

        it('should set the model to dirty when an attribute was changed', () => {
            dataElementModel.dataValues.attributeValues = [
                {
                    value: 'Mark',
                    attribute: { id: 'FpoWdhxCMwH', name: 'marktribute' },
                },
            ]

            expect(dataElementModel.dirty).toBe(false)

            dataElementModel.attributes.marktribute = 'John'

            expect(dataElementModel.dirty).toBe(true)
        })

        it('should not set the model to be dirty when the attribute value is the same', () => {
            dataElementModel.dataValues.attributeValues = [
                {
                    value: 'Mark',
                    attribute: { id: 'FpoWdhxCMwH', name: 'marktribute' },
                },
            ]

            dataElementModel.attributes.marktribute = 'Mark'

            expect(dataElementModel.dirty).toBe(false)
        })

        it('should not fail if requesting an attribute but the model has no attributeValues', () => {
            dataElementModel.dataValues.attributeValues = undefined

            expect(
                () => dataElementModel.attributes.marktribute
            ).not.toThrowError()
        })

        it('should still correctly set the attributeValue if the model has initially no attributeValues', () => {
            dataElementModel.dataValues.attributeValues = undefined

            dataElementModel.attributes.marktribute = 'John'

            expect(dataElementModel.attributes.marktribute).toBe('John')
            expect(dataElementModel.attributeValues[0].attribute.id).toBe(
                'FpoWdhxCMwH'
            )
        })
    })
})