import { formatMessage } from '../lib/utils';
import { registerRule, hasRule, getRule, overrideRule, overrideRuleMessage } from '../lib/storage';
import { validate, validateSync, validateRule, validateValue } from '../lib/core';
import ValidationSchema from '../lib/validationSchema';

describe('validation', () => {
  describe('registerRule', () => {
    it('should register rule', () => {
      registerRule('myRule', () => true, 'error');

      expect(hasRule('myRule')).toBe(true);
    });
  });

  describe('overrideRule', () => {
    const { check: originalRule } = getRule('required');

    beforeEach(() => {
      overrideRule('required', (actual, expected) => {
        return actual % expected === 0;
      });
    });

    afterEach(() => {
      overrideRule('required', originalRule);
    });

    it('should fail on overridden required rule', done => {
      validateValue(5, { required: 2 }).then(errors => {
        expect(errors.hasErrors()).toBe(true);

        done();
      });
    });

    it('should pass on overridden required rule', done => {
      validateValue(5, { required: 5 }).then(errors => {
        expect(errors.hasErrors()).toBe(false);

        done();
      });
    });
  });

  describe('overrideRuleMessage', () => {
    const { message: originalMessage } = getRule('required');

    beforeEach(() => {
      overrideRuleMessage('required', 'custom required message');
    });

    afterEach(() => {
      overrideRuleMessage('required', originalMessage);
    });

    it('should use overridden required message', done => {
      validateValue(null, { required: true }).then(errors => {
        expect(errors.required).toBe('custom required message');

        done();
      });
    });
  });

  describe('formatMessage', () => {
    it('should format text', done => {
      formatMessage('%{actual} === %{expected}', 5, 5).then(formattedMessage => {
        expect(formattedMessage).toBe('5 === 5');

        done();
      });
    });

    it('should accept function', done => {
      formatMessage(
        (actual, expected) => {
          return `${actual} === ${expected}`;
        },
        5,
        5,
      ).then(formattedMessage => {
        expect(formattedMessage).toBe('5 === 5');

        done();
      });
    });

    it('should have default message', done => {
      formatMessage().then(formattedMessage => {
        expect(formattedMessage).toBeDefined();

        done();
      });
    });
  });

  describe('validateRule', () => {
    it('should validate separate rule', done => {
      validateRule('required', true, null).then(error => {
        expect(error).toBeDefined();

        done();
      });
    });
  });

  describe('validateValue', () => {
    it('should validate separate value with list of rules', done => {
      validateValue('John', { required: true, minLength: 5 }).then(errors => {
        expect(errors).toBeDefined();

        done();
      });
    });
  });

  describe('validateObject', () => {});

  describe('validateArray', () => {});

  describe('validate', () => {
    it('should validate required rule', done => {
      const obj = {
        FirstName: null,
      };

      const schema = {
        properties: {
          FirstName: {
            rules: {
              required: true,
            },
          },
        },
      };

      validate(schema, obj).then(errors => {
        expect(errors.hasErrors()).toBe(true);
        expect(errors.FirstName.required).toBeDefined();

        done();
      });
    });

    it('should override default global message', done => {
      const obj = {
        FirstName: null,
      };

      const schema = {
        overrides: {
          messages: {
            required: 'Field is required',
          },
        },
        properties: {
          FirstName: {
            rules: {
              required: true,
            },
          },
        },
      };

      validate(schema, obj).then(errors => {
        expect(errors.FirstName.required).toBe('Field is required');

        done();
      });
    });

    it('should override default required rule (allow empty, for example)', done => {
      const obj = {
        FirstName: '',
      };

      const schema = {
        overrides: {
          rules: {
            required: value => {
              return value !== undefined && value !== null;
            },
          },
        },
        properties: {
          FirstName: {
            rules: {
              required: true,
            },
          },
        },
      };

      validate(schema, obj).then(errors => {
        expect(errors.FirstName.hasErrors()).toBe(false);

        done();
      });
    });

    it('should support nested schemas', done => {
      const obj = {
        Person: {
          FirstName: null,
        },
      };

      const schema = {
        properties: {
          Person: {
            rules: {
              required: true,
            },
            properties: {
              FirstName: {
                rules: {
                  required: true,
                  hasPersonObj: (value, exp, obj, property, schema, defaultRule, initial) => !!initial.Person,
                },
              },
            },
          },
        },
      };

      validate(schema, obj).then(errors => {
        expect(errors.hasErrors()).toBe(true);
        expect(errors.Person.hasErrors()).toBe(true);
        expect(errors.Person.FirstName.hasErrors()).toBe(true);
        expect(errors.Person.FirstName.required).toBeDefined();
        expect(errors.Person.FirstName.hasPersonObj).not.toBeDefined();

        done();
      });
    });

    it('should support high level object validation', done => {
      const obj = {
        FirstName: null,
        LastName: 'John',
      };

      const schema = {
        rules: {
          required: (value, a, b, c, d, requiredRule) => {
            return requiredRule(value) && requiredRule(value.FirstName) && requiredRule(value.LastName);
          },
        },
        properties: {
          FirstName: {
            rules: {
              required: true,
            },
          },
          LastName: {
            required: true,
            pattern: [/\d+/, /\w+/],
          },
        },
      };

      validate(schema, obj).then(errors => {
        expect(errors.hasErrors()).toBe(true);
        expect(errors.FirstName.hasErrors()).toBe(true);
        expect(errors.FirstName.required).toBeDefined();
        expect(errors.LastName.hasErrors()).toBe(false);

        done();
      });
    });

    it('should support only rules definitions, if there are no messages or properties', done => {
      const obj = {
        FirstName: null,
      };

      const schema = {
        properties: {
          FirstName: {
            required: true,
          },
        },
      };

      validate(schema, obj).then(errors => {
        expect(errors.hasErrors()).toBe(true);
        expect(errors.FirstName.required).toBeDefined();

        done();
      });
    });

    it('should pass validation for nested schemas', done => {
      const obj = {
        Person: {
          FirstName: 'John',
        },
      };

      const schema = {
        properties: {
          Person: {
            rules: {
              required: true,
            },
            properties: {
              FirstName: {
                rules: {
                  required: true,
                },
              },
            },
          },
        },
      };

      validate(schema, obj).then(errors => {
        expect(errors.hasErrors()).toBe(false);
        expect(errors.Person.hasErrors()).toBe(false);
        expect(errors.Person.FirstName.hasErrors()).toBe(false);
        expect(errors.Person.FirstName.required).not.toBeDefined();

        done();
      });
    });

    it('should support array schemas', done => {
      const obj = {
        Persons: [
          {
            FirstName: 'John',
          },
          {
            FirstName: null,
          },
          {
            FirstName: 'Bob',
          },
        ],
      };

      const schema = {
        properties: {
          Persons: {
            properties: {
              FirstName: {
                rules: {
                  required: true,
                },
              },
            },
          },
        },
      };

      validate(schema, obj).then(errors => {
        expect(errors.Persons[0].FirstName.hasErrors()).toBe(false);
        expect(errors.Persons[1].FirstName.hasErrors()).toBe(true);
        expect(errors.Persons[2].FirstName.hasErrors()).toBe(false);

        done();
      });
    });

    it('should support high level array schemas', done => {
      const obj = [
        {
          FirstName: 'John',
        },
        {
          FirstName: null,
        },
        {
          FirstName: 'Bob',
        },
      ];

      const schema = {
        FirstName: {
          rules: {
            required: true,
          },
        },
      };

      validate(schema, obj).then(errors => {
        expect(errors[0].FirstName.hasErrors()).toBe(false);
        expect(errors[1].FirstName.hasErrors()).toBe(true);
        expect(errors[2].FirstName.hasErrors()).toBe(false);

        done();
      });
    });

    it('should support primitive validation', done => {
      const obj = null;

      const schema = {
        rules: {
          required: true,
        },
      };

      validate(schema, obj).then(errors => {
        expect(errors.hasErrors()).toBe(true);
        expect(errors.required).toBeDefined();

        done();
      });
    });

    it('should support __isArray__ param', done => {
      const obj = null;

      const schema = {
        __isArray__: true,
        rules: {
          required: true,
        },
      };

      validate(schema, obj).then(errors => {
        expect(errors.hasErrors()).toBe(true);
        expect(errors.hasErrorsOfTypes('required')).toBe(true);

        done();
      });
    });

    it('should support array validation and schemas as well', done => {
      const obj = {
        Persons: [
          {
            FirstName: 'John',
          },
          {
            FirstName: null,
          },
          {
            FirstName: 'Bob',
          },
        ],
      };

      const schema = {
        properties: {
          Persons: {
            __isArray__: true,
            rules: {
              minLength: 5,
            },
            properties: {
              FirstName: {
                rules: {
                  required: true,
                },
              },
            },
          },
        },
      };

      validate(schema, obj).then(errors => {
        expect(errors.Persons.hasErrors()).toBe(true);
        expect(errors.Persons.hasErrorsOfTypes('minLength')).toBe(true);
        expect(errors.Persons[0].FirstName.hasErrors()).toBe(false);
        expect(errors.Persons[1].FirstName.hasErrors()).toBe(true);
        expect(errors.Persons[2].FirstName.hasErrors()).toBe(false);

        done();
      });
    });

    it('should support custom rule', done => {
      const obj = {
        FirstName: 2,
      };

      const schema = {
        overrides: {
          rules: {
            myRule: (actual, expected) => {
              return actual === expected * 2;
            },
          },
          messages: {
            myRule: '%{actual} !== %{expected} * 2',
          },
        },
        properties: {
          FirstName: {
            rules: {
              min: 6,
              myRule: 2,
            },
          },
        },
      };

      validate(schema, obj).then(errors => {
        expect(errors.FirstName.min).toBeDefined();
        expect(errors.FirstName.myRule).toBe('2 !== 2 * 2');

        done();
      });
    });

    it('should support error result for custom rule', done => {
      const obj = {
        FirstName: 4,
      };

      const schema = {
        overrides: {
          rules: {
            myRule: (actual, expected) => {
              if (actual === expected * 2) {
                return 'not valid!';
              }

              return true;
            },
          },
        },
        properties: {
          FirstName: {
            rules: {
              min: 6,
              myRule: 2,
            },
          },
        },
      };

      validate(schema, obj).then(errors => {
        expect(errors.FirstName.min).toBeDefined();
        expect(errors.FirstName.myRule).toBe('not valid!');

        done();
      });
    });

    it('should support async rule', done => {
      const obj = {
        FirstName: 2,
      };

      const schema = {
        overrides: {
          rules: {
            myRule: (actual, expected) => {
              return new Promise(resolve => {
                setTimeout(() => {
                  resolve(actual === expected * 2);
                }, 10);
              });
            },
          },
          messages: {
            myRule: '%{actual} !== %{expected} * 2',
          },
        },
        properties: {
          FirstName: {
            rules: {
              min: 6,
              myRule: 2,
            },
          },
        },
      };

      validate(schema, obj).then(errors => {
        expect(errors.FirstName.min).toBeDefined();
        expect(errors.FirstName.myRule).toBe('2 !== 2 * 2');

        done();
      });
    });

    it('should support async message', done => {
      const obj = {
        FirstName: 2,
      };

      const schema = {
        overrides: {
          rules: {
            myRule: (actual, expected) => {
              return actual === expected * 2;
            },
          },
          messages: {
            myRule: (actual, expected) => {
              return new Promise(resolve => {
                setTimeout(() => {
                  resolve(`${actual} !== ${expected} * 2`);
                }, 10);
              });
            },
          },
        },
        properties: {
          FirstName: {
            rules: {
              min: 6,
              myRule: 2,
            },
          },
        },
      };

      validate(schema, obj).then(errors => {
        expect(errors.FirstName.min).toBeDefined();
        expect(errors.FirstName.myRule).toBe('2 !== 2 * 2');

        done();
      });
    });

    it('should not fail on empty schema', done => {
      const obj = {
        FirstName: 2,
      };

      const schema = {};

      validate(schema, obj).then(errors => {
        expect(errors.hasErrors()).toBe(false);

        done();
      });
    });

    it('should not fail on empty obj', done => {
      const obj = {};

      const schema = {
        properties: {
          FirstName: {
            rules: {
              required: true,
            },
          },
        },
      };

      validate(schema, obj).then(errors => {
        expect(errors.FirstName.required).toBeDefined();

        done();
      });
    });

    it('should not fail on null obj', done => {
      const obj = null;

      const schema = {
        __isObject__: true,
        properties: {
          FirstName: {
            rules: {
              required: true,
            },
          },
        },
      };

      validate(schema, obj).then(errors => {
        expect(errors.FirstName.required).toBeDefined();

        done();
      });
    });

    it('should support high level schema', done => {
      const obj = {
        FirstName: null,
      };

      const schema = {
        FirstName: {
          rules: {
            required: true,
          },
        },
      };

      validate(schema, obj).then(errors => {
        expect(errors.FirstName.required).toBeDefined();

        done();
      });
    });

    it('should be fast', done => {
      console.time('should be fast');
      const obj = {
        Id: '9131',
        AccountId: '1',
        UserId: null,
        FirstName: 'Test',
        LastName: 'Test',
        Line1: '15625 Alton Pkwy',
        Line2: 'Suite 200',
        City: 'Irvine',
        State: 'CA',
        Zip: '92620',
        Country: 'US',
        Phone: '+1-2345678901',
        Phone2: '2342342341',
        Company: 'Test',
        Email: 'test@example.com',
        Type: 'primary',
      };

      const schema = {
        messages: {
          required: 'validation.required',
        },
        properties: {
          FirstName: {
            rules: {
              type: 'string',
              required: true,
              maxLength: 45,
              pattern: /^[a-zA-Z0-9]+$/,
            },
            messages: {
              pattern: 'validation.firstName.pattern',
            },
          },
          LastName: {
            rules: {
              type: 'string',
              required: true,
              maxLength: 45,
              pattern: /^[a-zA-Z0-9]+$/,
            },
            messages: {
              pattern: 'validation.firstName.pattern',
            },
          },
          Email: {
            rules: {
              type: 'string',
              required: true,
              maxLength: 50,
              format: 'email',
            },
            messages: {
              format: 'validation.email.format',
            },
          },
          Phone: {
            rules: {
              type: 'string',
              required: true,
            },
          },
          Line1: {
            rules: {
              type: 'string',
              required: true,
              maxLength: 100,
            },
          },
          Line2: {
            rules: {
              type: 'string',
              maxLength: 100,
            },
          },
          Country: {
            rules: {
              type: 'string',
              required: true,
            },
          },
          State: {
            rules: {
              type: 'string',
              required: true,
              maxLength: 50,
            },
          },
          City: {
            rules: {
              type: 'string',
              required: true,
              maxLength: 50,
            },
          },
          Zip: {
            rules: {
              type: 'string',
              required: true,
              maxLength: 15,
            },
          },
        },
      };

      validate(schema, obj).then(() => {
        console.timeEnd('should be fast');

        done();
      });
    });

    it('should be fast with array', done => {
      console.time('should be fast with array');
      const obj = {
        Id: '1',
        Name: 'CalAmp Account',
        ParentAccount: '0',
        SolomonId: 'GAC22222222',
        CreditTerms: 'COD',
        SalesTerritory: 'US West',
        Language: 'english',
        PartnerBranding: 'calamp',
        CreditPurchaseAuthorized: '0',
        ActivationDate: null,
        DeactivationDate: null,
        IsDisabled: 'no',
        IsMarkedArchive: 'no',
        CanViewSubaccounts: '1',
        PartnerLogo: 'calamp_logo_slogan_1_125px.jpg',
        ShowPoweredByLogo: '1',
        AllowNewDeviceUseFromParent: '0',
        CanViewAirtimeStore: '1',
        CanViewHardwareStore: '0',
        LastChangeDate: '2016-08-12 05:37:24',
        AllowCommandAutoRetry: '1',
        SkipInstallProcess: '0',
        AllowAirtimeAutoRenew: '1',
        InvoiceAccount: '0',
        EmailNotificationOnInstall: '1',
        MandatoryInstallOdometer: '0',
        RenewalPlanId: '4',
        RenewalPlanPrice: null,
        EnableLocationValidationReport: '1',
        ParentName: 'N/A',
        MaxScheduleActions: 12,
        is_cac_account: false,
        CustomUserAttributeDefinitions: [],
        CustomVehicleAttributeDefinitions: [],
        AirTimePlan: [
          {
            RenewalPlanSKU: 'rp1111',
            RenewalPlanPrice: '33.33',
          },
          {
            RenewalPlanSKU: 'rp2222',
            RenewalPlanPrice: '444.44',
          },
          {
            RenewalPlanSKU: 'aa',
            RenewalPlanPrice: '',
          },
          {
            RenewalPlanSKU: '',
            RenewalPlanPrice: 'aa',
          },
          {
            RenewalPlanSKU: '',
            RenewalPlanPrice: '',
          },
        ],
      };

      const airTimePlanRowRequired = (
        actual,
        expected,
        property,
        { RenewalPlanSKU, RenewalPlanPrice },
        schema,
        defaultRule,
      ) => {
        let isRequired = {
          allowEmpty: true,
        };

        if (RenewalPlanSKU || RenewalPlanPrice) {
          isRequired = true;
        }

        return defaultRule(actual, isRequired);
      };

      const schema = {
        messages: {
          required: 'validation.required',
        },
        properties: {
          Name: {
            rules: {
              type: 'string',
              required: true,
              maxLength: 64,
            },
          },
          Language: {
            rules: {
              type: 'string',
              required: true,
            },
          },
          AirTimePlan: {
            rules: {
              required: true,
            },
            properties: {
              RenewalPlanSKU: {
                rules: {
                  required: airTimePlanRowRequired,
                  pattern: /^RP|rp[0-9]{4}$/,
                },
                messages: {
                  pattern: 'validation.renewalPlanSKU.pattern',
                },
              },
              RenewalPlanPrice: {
                rules: {
                  required: airTimePlanRowRequired,
                  pattern: /^[0-9]+\.?[0-9]{2}$/,
                },
                messages: {
                  pattern: 'validation.renewalPlanPrice.pattern',
                },
              },
            },
          },
        },
      };

      validate(schema, obj).then(() => {
        console.timeEnd('should be fast with array');

        done();
      });
    });
  });

  describe('validateSync', () => {
    it('should validate required rule', () => {
      const obj = {
        FirstName: null,
      };

      const schema = {
        FirstName: {
          required: true,
        },
      };

      const errors = validateSync(schema, obj);

      expect(errors.hasErrors()).toBe(true);
      expect(errors.FirstName.required).toBeDefined();
    });

    it('should override default global message', () => {
      const obj = {
        FirstName: null,
      };

      const schema = {
        overrides: {
          messages: {
            required: 'Field is required',
          },
        },
        properties: {
          FirstName: {
            rules: {
              required: true,
            },
          },
        },
      };

      const errors = validateSync(schema, obj);

      expect(errors.FirstName.required).toBe('Field is required');
    });

    it('should override default required rule (allow empty, for example)', () => {
      const obj = {
        FirstName: '',
      };

      const schema = {
        overrides: {
          rules: {
            required: value => {
              return value !== undefined && value !== null;
            },
          },
        },
        properties: {
          FirstName: {
            rules: {
              required: true,
            },
          },
        },
      };

      const errors = validateSync(schema, obj);

      expect(errors.FirstName.hasErrors()).toBe(false);
    });

    it('should support nested schemas', () => {
      const obj = {
        Person: {
          FirstName: null,
        },
      };

      const schema = {
        Person: {
          FirstName: {
            required: true,
          },
        },
      };

      const errors = validateSync(schema, obj);

      expect(errors.hasErrors()).toBe(true);
      expect(errors.Person.hasErrors()).toBe(true);
      expect(errors.Person.FirstName.hasErrors()).toBe(true);
      expect(errors.Person.FirstName.required).toBeDefined();
    });

    it('should pass validation for nested schemas', () => {
      const obj = {
        Person: {
          FirstName: 'John',
        },
      };

      const schema = {
        properties: {
          Person: {
            rules: {
              required: true,
            },
            properties: {
              FirstName: {
                rules: {
                  required: true,
                },
              },
            },
          },
        },
      };

      const errors = validateSync(schema, obj);

      expect(errors.hasErrors()).toBe(false);
      expect(errors.Person.hasErrors()).toBe(false);
      expect(errors.Person.FirstName.hasErrors()).toBe(false);
      expect(errors.Person.FirstName.required).not.toBeDefined();
    });

    it('should support array schemas', () => {
      const obj = {
        Persons: [
          {
            FirstName: 'John',
          },
          {
            FirstName: null,
          },
          {
            FirstName: 'Bob',
          },
        ],
      };

      const schema = {
        properties: {
          Persons: {
            properties: {
              FirstName: {
                rules: {
                  required: true,
                },
              },
            },
          },
        },
      };

      const errors = validateSync(schema, obj);

      expect(errors.Persons[0].FirstName.hasErrors()).toBe(false);
      expect(errors.Persons[1].FirstName.hasErrors()).toBe(true);
      expect(errors.Persons[2].FirstName.hasErrors()).toBe(false);
    });

    it('should support array validation and schemas as well', () => {
      const obj = {
        Persons: [
          {
            FirstName: 'John',
          },
          {
            FirstName: null,
          },
          {
            FirstName: 'Bob',
          },
        ],
      };

      const schema = {
        properties: {
          Persons: {
            rules: {
              minLength: 5,
            },
            properties: {
              FirstName: {
                rules: {
                  required: true,
                },
              },
            },
          },
        },
      };

      const errors = validateSync(schema, obj);

      expect(errors.Persons.hasErrors()).toBe(true);
      expect(errors.Persons.hasErrorsOfTypes('minLength')).toBe(true);
      expect(errors.Persons[0].FirstName.hasErrors()).toBe(false);
      expect(errors.Persons[1].FirstName.hasErrors()).toBe(true);
      expect(errors.Persons[2].FirstName.hasErrors()).toBe(false);
    });

    it('should support custom rule', () => {
      const obj = {
        FirstName: 2,
      };

      const schema = {
        overrides: {
          rules: {
            myRule: (actual, expected) => {
              return actual === expected * 2;
            },
          },
          messages: {
            myRule: '%{actual} !== %{expected} * 2',
          },
        },
        properties: {
          FirstName: {
            min: 6,
            myRule: 2,
          },
        },
      };

      const errors = validateSync(schema, obj);

      expect(errors.FirstName.min).toBeDefined();
      expect(errors.FirstName.myRule).toBe('2 !== 2 * 2');
    });

    it('should support error result for custom rule', () => {
      const obj = {
        FirstName: 4,
      };

      const schema = {
        overrides: {
          rules: {
            myRule: (actual, expected) => {
              if (actual === expected * 2) {
                return 'not valid!';
              }

              return true;
            },
          },
        },
        properties: {
          FirstName: {
            rules: {
              min: 6,
              myRule: 2,
            },
          },
        },
      };

      const errors = validateSync(schema, obj);

      expect(errors.FirstName.min).toBeDefined();
      expect(errors.FirstName.myRule).toBe('not valid!');
    });

    it('should not fail on empty schema', () => {
      const obj = {
        FirstName: 2,
      };

      const schema = {};

      const errors = validateSync(schema, obj);

      expect(errors.hasErrors()).toBe(false);
    });

    it('should not fail on empty obj', () => {
      const obj = {};

      const schema = {
        properties: {
          FirstName: {
            rules: {
              required: true,
            },
          },
        },
      };

      const errors = validateSync(schema, obj);

      expect(errors.FirstName.required).toBeDefined();
    });

    it('should support high level schema', () => {
      const obj = {
        FirstName: null,
      };

      const schema = {
        FirstName: {
          rules: {
            required: true,
          },
        },
      };

      const errors = validateSync(schema, obj);

      expect(errors.FirstName.required).toBeDefined();
    });
  });

  describe('ValidationSchema', () => {
    it('should support ValidationSchema for multiple validations', done => {
      const obj = {
        FirstName: 2,
      };

      const schema = new ValidationSchema({
        overrides: {
          rules: {
            myRule: (actual, expected) => {
              return actual === expected * 2;
            },
          },
          messages: {
            myRule: (actual, expected) => {
              return new Promise(resolve => {
                setTimeout(() => {
                  resolve(`${actual} !== ${expected} * 2`);
                }, 10);
              });
            },
          },
        },
        properties: {
          FirstName: {
            rules: {
              min: 6,
              myRule: 2,
            },
          },
        },
      });

      schema.validate(obj).then(errors => {
        expect(errors.FirstName.min).toBeDefined();
        expect(errors.FirstName.myRule).toBe('2 !== 2 * 2');

        done();
      });
    });
  });

  describe('validate simple array', () => {
    it('should pass validation', () => {
      const obj = {
        categories: [1, 2, 3],
      };

      const schema = {
        categories: {
          rules: {
            required: true,
            minItems: 2,
          },
        },
      };

      const errors = validateSync(schema, obj);

      expect(errors.hasErrors()).toBe(false);
    });

    it('should faild validation', () => {
      const obj = {
        categories: [1, 2, 3],
      };

      const schema = {
        categories: {
          rules: {
            required: true,
            maxItems: 2,
          },
        },
      };

      const errors = validateSync(schema, obj);

      expect(errors.getFirstErrors().categories).toBeDefined();
    });
  });

  describe('validate complex array', () => {
    it('should pass validation', () => {
      const obj = {
        categories: [
          {
            id: 1,
          },
          {
            id: 2,
          },
          {
            id: 3,
          },
        ],
      };

      const schema = {
        categories: {
          rules: {
            required: true,
            minItems: 2,
          },
          properties: {
            id: {
              required: true,
            },
          },
        },
      };

      const errors = validateSync(schema, obj);

      expect(errors.hasErrors()).toBe(false);
    });

    it('should faild validation', () => {
      const obj = {
        categories: [
          {
            id: 1,
          },
          {
            id: 2,
          },
          {
            id: 3,
          },
        ],
      };

      const schema = {
        categories: {
          rules: {
            required: true,
            minItems: 2,
          },
          properties: {
            id: {
              required: true,
            },
            name: {
              required: true,
            },
          },
        },
      };

      const errors = validateSync(schema, obj);

      expect(errors.getFirstErrors().categories).toBeDefined();
    });
  });
});
