Repository: DevExpress/devextreme-react
Pull request #564 (2021-05-27 08:03:06)

fix Rollback complex options by fields (T1001452)
 - fix Rollback complex options by fields (T988609) (#537)
 - eslint fix
 - add tests


Patch:
diff --git a/packages/devextreme-react-generator/src/generator.ts b/packages/devextreme-react-generator/src/generator.ts
index 25dc390..cfb0e39 100644
--- a/packages/devextreme-react-generator/src/generator.ts
+++ b/packages/devextreme-react-generator/src/generator.ts
@@ -44,7 +44,7 @@ enum BaseTypes {
   String = 'string',
   Number = 'number',
   Boolean = 'boolean',
-  Object = 'object'
+  Object = 'object',
 }
 
 export function mapSubscribableOption(prop: IProp): ISubscribableOption {
diff --git a/packages/devextreme-react/src/core/configuration/tree.ts b/packages/devextreme-react/src/core/configuration/tree.ts
index 16129a5..a5b61b9 100644
--- a/packages/devextreme-react/src/core/configuration/tree.ts
+++ b/packages/devextreme-react/src/core/configuration/tree.ts
@@ -84,9 +84,17 @@ enum ValueType {
 function findValueInObject(obj: unknown, path: string[]): undefined | IValueDescriptor {
   const key = path.shift();
   if (!key) {
+    let type: ValueType;
+    if (obj instanceof Array) {
+      type = ValueType.Array;
+    } else if (obj instanceof Object) {
+      type = ValueType.Complex;
+    } else {
+      type = ValueType.Simple;
+    }
     return {
       value: obj,
-      type: obj instanceof Object ? ValueType.Complex : ValueType.Simple,
+      type,
     };
   }
 
diff --git a/packages/devextreme-react/src/core/options-manager.ts b/packages/devextreme-react/src/core/options-manager.ts
index d2df91b..cfe2d34 100644
--- a/packages/devextreme-react/src/core/options-manager.ts
+++ b/packages/devextreme-react/src/core/options-manager.ts
@@ -113,9 +113,16 @@ class OptionsManager {
     }
 
     const { value, type } = valueDescriptor;
-    if (type === ValueType.Complex && value instanceof Object) {
+    if (value instanceof Array && type === ValueType.Array) {
+      for (let i = 0; i < value.length; i += 1) {
+        if (value[i] !== (e.value as Array<unknown>)?.[i]) {
+          this.setGuard(e.fullName, value);
+          return;
+        }
+      }
+    } else if (type === ValueType.Complex && value instanceof Object) {
       Object.keys(value).forEach((key) => {
-        if (value[key] === (e.value as Record<string, unknown>)[key]) {
+        if (value[key] === (e.value as Record<string, unknown>)?.[key]) {
           return;
         }
         this.setGuard(mergeNameParts(e.fullName, key), value[key]);
diff --git a/packages/devextreme-react/src/core/template-wrapper.ts b/packages/devextreme-react/src/core/template-wrapper.ts
index f8b06b0..3ff1e86 100644
--- a/packages/devextreme-react/src/core/template-wrapper.ts
+++ b/packages/devextreme-react/src/core/template-wrapper.ts
@@ -24,6 +24,7 @@ class TemplateWrapper extends React.PureComponent<ITemplateWrapperProps, ITempla
   private readonly _removalListenerRef = React.createRef<HTMLElement>();
 
   private element: HTMLElement | undefined | null;
+
   private hiddenElement: HTMLElement | undefined | null;
 
   constructor(props: ITemplateWrapperProps) {
@@ -53,7 +54,7 @@ class TemplateWrapper extends React.PureComponent<ITemplateWrapperProps, ITempla
     if (node) {
       container.appendChild(node);
     }
-    if(hiddenNode) {
+    if (hiddenNode) {
       container.appendChild(hiddenNode);
     }
     if (this._listenerElement) {
diff --git a/packages/devextreme-react/tsconfig.json b/packages/devextreme-react/tsconfig.json
index d7bff72..c9650e3 100644
--- a/packages/devextreme-react/tsconfig.json
+++ b/packages/devextreme-react/tsconfig.json
@@ -9,4 +9,4 @@
     "src/**/*.ts",
     "src/**/*.tsx"
   ]
-}
\ No newline at end of file
+}


Test patch:
diff --git a/packages/devextreme-react/src/core/__tests__/props-updating.test.tsx b/packages/devextreme-react/src/core/__tests__/props-updating.test.tsx
index cc7e056..468ece1 100644
--- a/packages/devextreme-react/src/core/__tests__/props-updating.test.tsx
+++ b/packages/devextreme-react/src/core/__tests__/props-updating.test.tsx
@@ -33,6 +33,7 @@ class NestedComponent extends ConfigurationComponent<{
   a?: number;
   b?: string;
   c?: string;
+  arrayValue?: Array<unknown> | null;
   defaultC?: string;
   complexValue?: Record<string, unknown>;
   value?: number;
@@ -904,6 +905,34 @@ describe('onXXXChange', () => {
       expect(onSubNestedPropChange).toBeCalledWith('4');
     });
 
+    it('is called on nested array option changed', () => {
+      render(
+        <TestComponent>
+          <NestedComponent
+            arrayValue={[1, 2]}
+          />
+        </TestComponent>,
+      );
+      fireOptionChange('nestedOption.arrayValue', [3, 4]);
+      jest.runAllTimers();
+      expect(Widget.option.mock.calls.length).toEqual(1);
+      expect(Widget.option.mock.calls[0]).toEqual(['nestedOption.arrayValue', [1, 2]]);
+    });
+
+    it('is called on nested null array option changed', () => {
+      render(
+        <TestComponent>
+          <NestedComponent
+            arrayValue={null}
+          />
+        </TestComponent>,
+      );
+      fireOptionChange('nestedOption.arrayValue', [1, 2]);
+      jest.runAllTimers();
+      expect(Widget.option.mock.calls.length).toEqual(1);
+      expect(Widget.option.mock.calls[0]).toEqual(['nestedOption.arrayValue', null]);
+    });
+
     it('throws an error if handler is not a function', () => {
       render(
         <TestComponent
diff --git a/packages/devextreme-react/src/core/__tests__/tagbox.test.tsx b/packages/devextreme-react/src/core/__tests__/tagbox.test.tsx
index a488bff..4d1ffe9 100644
--- a/packages/devextreme-react/src/core/__tests__/tagbox.test.tsx
+++ b/packages/devextreme-react/src/core/__tests__/tagbox.test.tsx
@@ -1,4 +1,3 @@
-
 import { render, cleanup } from '@testing-library/react';
 import * as React from 'react';
 import { TagBox } from '../../tag-box';
@@ -16,12 +15,13 @@ describe('templates', () => {
     const { container } = render(<TagBox
       ref={ref}
       dataSource={['1', '2', '3']}
-      showClearButton value={['1']}
-      tagRender={() => <div>test</div>} />);
+      showClearButton
+      value={['1']}
+      tagRender={() => <div>test</div>}
+    />);
     const instance = ref?.current?.instance;
-    instance?.option('value', ['1','2']);
-
+    instance?.option('value', ['1', '2', '3']);
     jest.runAllTimers();
-    expect(container.children[0].getElementsByClassName("dx-tag").length).toBe(2);
+    expect(container.children[0].getElementsByClassName('dx-tag').length).toBe(3);
   });
 });
diff --git a/packages/devextreme-react/src/core/configuration/__tests__/tree.test.ts b/packages/devextreme-react/src/core/configuration/__tests__/tree.test.ts
index cc30573..fb59d80 100644
--- a/packages/devextreme-react/src/core/configuration/__tests__/tree.test.ts
+++ b/packages/devextreme-react/src/core/configuration/__tests__/tree.test.ts
@@ -1,5 +1,5 @@
 /* eslint-disable max-classes-per-file */
-import { findValueInObject } from '../tree';
+import { findValueInObject, ValueType } from '../tree';
 
 describe('check findValueInObject', () => {
   it('get underfined by string', () => {
@@ -10,6 +10,15 @@ describe('check findValueInObject', () => {
     expect(valueInObject).toEqual(undefined);
   });
 
+  it('should return different ValueType', () => {
+    const valueInArray = findValueInObject(['ArrayValue'], []);
+    expect(valueInArray?.type).toEqual(ValueType.Array);
+    const valueInObject = findValueInObject({ value: 'ObjectValue' }, []);
+    expect(valueInObject?.type).toEqual(ValueType.Complex);
+    const valueInSimple = findValueInObject('SimpleValue', []);
+    expect(valueInSimple?.type).toEqual(ValueType.Simple);
+  });
+
   it('get value by object', () => {
     const obj = {
       sortOrder: 'desc',