Forráskód Böngészése

fix: refact ArrayField, add e2e test

pointhalo 2 éve
szülő
commit
76bb74b100

+ 432 - 215
cypress/integration/form.spec.js

@@ -15,232 +15,449 @@ const C2D = { name: 'Semi C2D', role: 'Designer' };
 const DSM = { name: 'Semi DSM', role: 'Designer' };
 
 describe('Form', () => {
-    // it('formApi-setValue with array field path, 3 -> 2, remove middle line field', () => {
-    //     cy.visit('http://127.0.0.1:6006/iframe.html?path=/story/form--use-form-api-set-value-update-array');
-    //     cy.get(':nth-child(3) > .semi-button').click();
-    //     // line 1
-    //     cy.get('[x-field-id="effects[0].name"] > .semi-form-field-main > .semi-input-wrapper > input').should('have.value', '1-name');
-    //     cy.get('[x-field-id="effects[0].type"] > .semi-form-field-main > .semi-input-wrapper > input').should('have.value', '1-type');
-    //     // line 2
-    //     cy.get('[x-field-id="effects[1].name"] > .semi-form-field-main > .semi-input-wrapper > input').should('have.value', '3-name');
-    //     cy.get('[x-field-id="effects[1].type"] > .semi-form-field-main > .semi-input-wrapper > input').should('have.value', '3-type');
-    //     // cy.get('body').find('.semi-popover .semi-datepicker').should('have.length', 0);
-    // });
+    it('formApi-setValue with array field path, 3 -> 2, remove middle line field', () => {
+        cy.visit('http://127.0.0.1:6006/iframe.html?path=/story/form--use-form-api-set-value-update-array');
+        cy.get(':nth-child(3) > .semi-button').click();
+        // line 1
+        cy.get('[x-field-id="effects[0].name"] > .semi-form-field-main > .semi-input-wrapper > input').should('have.value', '1-name');
+        cy.get('[x-field-id="effects[0].type"] > .semi-form-field-main > .semi-input-wrapper > input').should('have.value', '1-type');
+        // line 2
+        cy.get('[x-field-id="effects[1].name"] > .semi-form-field-main > .semi-input-wrapper > input').should('have.value', '3-name');
+        cy.get('[x-field-id="effects[1].type"] > .semi-form-field-main > .semi-input-wrapper > input').should('have.value', '3-type');
+        // cy.get('body').find('.semi-popover .semi-datepicker').should('have.length', 0);
+    });
 
     // ❌ 发现了bug
     // modify
-    // it('Basic usage - modify、add blank row、add withInitValue row', () => {
-    //     cy.visit('http://127.0.0.1:6006/iframe.html?path=/story/form--basic-array-field-demo');
-    //     cy.get('#data-0-name').type('-new');
-    //     cy.get('#data-0-role').type('-new');
-    //     cy.get('#data-0-name').should('have.value', 'Semi D2C-new');
-    //     cy.get('#data-0-role').should('have.value', 'Engineer-new');
-
-    //     // add blank row
-    //     cy.get('#add').click();
-    //     cy.get('#data-2-name').should('have.value', '');
-    //     cy.get('#data-2-role').should('have.value', '');
-
-    //     // add withInitValue row
-    //     cy.get('#addWithInit').click();
-    //     cy.get('#data-3-name').should('have.value', 'Semi New-3');
-    //     cy.get('#data-3-role').should('have.value', 'Designer');
-    // });
-
-    // it('Basic usage - add withInitValue row、add blank row', () => {
-    //     cy.visit('http://127.0.0.1:6006/iframe.html?path=/story/form--basic-array-field-demo');
-
-    //     // add withInitValue row
-    //     cy.get('#addWithInit').click();
-    //     cy.get('#data-2-name').should('have.value', 'Semi New-3');
-    //     cy.get('#data-2-role').should('have.value', 'Designer');
-
-    //     // add blank row
-    //     cy.get('#add').click();
-    //     cy.get('#data-3-name').should('have.value', '');
-    //     cy.get('#data-3-role').should('have.value', '');
-    // });
-
-    // // // remove row
-    // it('Remove row - 3 -> 2, remove middle', () => {
-    //     cy.visit('http://127.0.0.1:6006/iframe.html?path=/story/form--remove-demo');
-    //     cy.get('#data-1 button').click();
-
-    //     cy.get('#data-0-name').should('have.value', D2C.name);
-    //     cy.get('#data-0-role').should('have.value', D2C.role);
-    //     cy.get('#data-1-name').should('have.value', DSM.name);
-    //     cy.get('#data-1-role').should('have.value', DSM.role);
-    // });
-
-    // it('Remove row - 3 -> 2, remove middle, 2 -> 1, remove head', () => {
-    //     cy.visit('http://127.0.0.1:6006/iframe.html?path=/story/form--remove-demo');
-    //     cy.get('#data-1 button').click();
-    //     cy.get('#data-0-name').should('have.value', D2C.name);
-    //     cy.get('#data-0-role').should('have.value', D2C.role);
-    //     cy.get('#data-1-name').should('have.value', DSM.name);
-    //     cy.get('#data-1-role').should('have.value', DSM.role);
-
-    //     cy.get('#data-0 button').click();
-    //     cy.get('#data-0-name').should('have.value', DSM.name);
-    //     cy.get('#data-0-role').should('have.value', DSM.role);
-    //     cy.get('#data-1').should('not.exist');
-    // });
-
-    // it('Remove row - 3 -> 2, remove head', () => {
-    //     cy.visit('http://127.0.0.1:6006/iframe.html?path=/story/form--remove-demo');
-    //     cy.get('#data-2').should('exist');
-    //     cy.get('#data-0 button').click();
-    //     cy.get('#data-2').should('not.exist');
-
-    //     cy.get('#data-0-name').should('have.value', C2D.name);
-    //     cy.get('#data-0-role').should('have.value', C2D.role);
-    //     cy.get('#data-1-name').should('have.value', DSM.name);
-    //     cy.get('#data-1-role').should('have.value', DSM.role);
-
-    //     cy.get('#data-0 button').click();
-    //     cy.get('#data-0-name').should('have.value', DSM.name);
-    //     cy.get('#data-0-role').should('have.value', DSM.role);
-    // });
-
-    // it('Remove row - 3 -> 2, remove last', () => {
-    //     cy.visit('http://127.0.0.1:6006/iframe.html?path=/story/form--remove-demo');
-    //     cy.get('#data-2').should('exist');
-    //     cy.get('#data-2 button').click();
-    //     cy.get('#data-2').should('not.exist');
-
-    //     cy.get('#data-0-name').should('have.value', D2C.name);
-    //     cy.get('#data-0-role').should('have.value', D2C.role);
-    //     cy.get('#data-1-name').should('have.value', C2D.name);
-    //     cy.get('#data-1-role').should('have.value', C2D.role);
-    // });
-
-    // // it('Basic usage - add、remove、reset', () => { });
-
-    // // reset
-    // it('Reset Usage: modify  => reset ', () => {
-    //     cy.visit('http://127.0.0.1:6006/iframe.html?path=/story/form--reset-demo');
-    //     cy.get('#data-0-name').type('-new');
-    //     cy.get('#data-0-role').type('-new');
-
-    //     cy.get('#data-0-name').should('have.value', `${D2C.name}-new`);
-    //     cy.get('#data-0-role').should('have.value', `${D2C.role}-new`);
-
-    //     cy.get('button[type=reset]').click();
-    //     cy.get('#data-0-name').should('have.value', `${D2C.name}`);
-    //     cy.get('#data-0-name').should('have.value', `${D2C.name}`);
-    // });
-
-    // it('Reset Usage: length 2 -> 1  => reset ', () => {
-    //     cy.visit('http://127.0.0.1:6006/iframe.html?path=/story/form--reset-demo');
-    //     cy.get('#data-0 button').click();
-    //     cy.get('#data-1').should('not.exist');
-    //     cy.get('button[type=reset]').click();
-
-    //     // cy.get('#data-1').should('exist');
-    //     cy.get('#data-0-name').should('have.value', `${D2C.name}`);
-    //     cy.get('#data-0-role').should('have.value', `${D2C.role}`);
-    //     cy.get('#data-1-name').should('have.value', `${C2D.name}`);
-    //     cy.get('#data-1-role').should('have.value', `${C2D.role}`);
-    // });
-
-
-    // it('Reset Usage: length 2 -> 0 -> 3  => reset ', () => { 
-    //     cy.visit('http://127.0.0.1:6006/iframe.html?path=/story/form--reset-demo');
-    //     cy.get('#data-1 button').click();
-    //     cy.get('#data-0 button').click();
-    //     cy.get('#data-0').should('not.exist');
-
-    //     cy.get('#addWithInit').click();
-    //     cy.get('#addWithInit').click();
-    //     cy.get('#addWithInit').click();
-    //     cy.get('#data-2').should('exist');
-
-    //     cy.get('button[type=reset]').click();
-    //     cy.get('#data-0-name').should('have.value', `${D2C.name}`);
-    //     cy.get('#data-0-role').should('have.value', `${D2C.role}`);
-    //     cy.get('#data-1-name').should('have.value', `${C2D.name}`);
-    //     cy.get('#data-1-role').should('have.value', `${C2D.role}`);
-    //     cy.get('#data-2').should('not.exist');
-    // });
-
-    // it('Reset Usage: length 2 -> 0 -> 2  => reset ', () => {
-    //     cy.visit('http://127.0.0.1:6006/iframe.html?path=/story/form--reset-demo');
-    //     cy.get('#data-1 button').click();
-    //     cy.get('#data-0 button').click();
-    //     cy.get('#data-0').should('not.exist');
-
-    //     cy.get('#addWithInit').click();
-    //     cy.get('#addWithInit').click();
-    //     cy.get('.line').should('have.length', 2);
-
-    //     cy.get('button[type=reset]').click();
-    //     cy.get('#data-2').should('not.exist');
-    //     cy.get('#data-0-name').should('have.value', `${D2C.name}`);
-    //     cy.get('#data-0-role').should('have.value', `${D2C.role}`);
-    //     cy.get('#data-1-name').should('have.value', `${C2D.name}`);
-    //     cy.get('#data-1-role').should('have.value', `${C2D.role}`);
-    // });
-
-    // it('Combine Usage', () => {
-    //     // add -> remove -> modify
-    //     cy.visit('http://127.0.0.1:6006/iframe.html?path=/story/form--basic-array-field-demo', {
-    //         onBeforeLoad(win) {
-    //             cy.stub(win.console, 'log').as('consoleLog');
-    //         },
-    //     });
-    //     cy.get('#addWithInit').click();
-    //     cy.get('#data-1 button').click();
-    //     cy.get('#data-0-role').type('-0');
-    //     cy.get('#data-0-name').type('-0');
-
-    //     cy.get('#data-0-name').should('have.value', `${D2C.name}-0`);
-    //     cy.get('#data-0-role').should('have.value', `${D2C.role}-0`);
-    //     cy.get('#data-1-name').should('have.value', `Semi New-3`);
-    //     cy.get('#data-1-role').should('have.value', `Designer`);
-    // });
-
-    // it('combine usage-2', () => {
-    //     // add -> remove -> modify -> reset
-    //     cy.visit('http://127.0.0.1:6006/iframe.html?path=/story/form--basic-array-field-demo');
-    //     cy.get('#data-1 button').click();
-    //     cy.get('#data-0-role').type('-0');
-    //     cy.get('#data-0-name').type('-0');
-    //     cy.get('button[type=reset]').click();
-    //     cy.get('#data-0-name').should('have.value', `${D2C.name}`);
-    //     cy.get('#data-0-role').should('have.value', `${D2C.role}`);
-    //     cy.get('#data-1-name').should('have.value', `${C2D.name}`);
-    //     cy.get('#data-1-role').should('have.value', `${C2D.role}`);
-    // });
+    it('Basic usage - modify、add blank row、add withInitValue row', () => {
+        cy.visit('http://127.0.0.1:6006/iframe.html?path=/story/form--basic-array-field-demo');
+        cy.get('#data-0-name').type('-new');
+        cy.get('#data-0-role').type('-new');
+        cy.get('#data-0-name').should('have.value', 'Semi D2C-new');
+        cy.get('#data-0-role').should('have.value', 'Engineer-new');
+
+        // add blank row
+        cy.get('#add').click();
+        cy.get('#data-2-name').should('have.value', '');
+        cy.get('#data-2-role').should('have.value', '');
+
+        // add withInitValue row
+        cy.get('#addWithInit').click();
+        cy.get('#data-3-name').should('have.value', 'Semi New-3');
+        cy.get('#data-3-role').should('have.value', 'Designer');
+    });
+
+    it('Basic usage - add withInitValue row、add blank row', () => {
+        cy.visit('http://127.0.0.1:6006/iframe.html?path=/story/form--basic-array-field-demo');
+
+        // add withInitValue row
+        cy.get('#addWithInit').click();
+        cy.get('#data-2-name').should('have.value', 'Semi New-3');
+        cy.get('#data-2-role').should('have.value', 'Designer');
+
+        // add blank row
+        cy.get('#add').click();
+        cy.get('#data-3-name').should('have.value', '');
+        cy.get('#data-3-role').should('have.value', '');
+    });
+
+    // // remove row
+    it('Remove row - 3 -> 2, remove middle', () => {
+        cy.visit('http://127.0.0.1:6006/iframe.html?path=/story/form--remove-demo');
+        cy.get('#data-1 button').click();
+
+        cy.get('#data-0-name').should('have.value', D2C.name);
+        cy.get('#data-0-role').should('have.value', D2C.role);
+        cy.get('#data-1-name').should('have.value', DSM.name);
+        cy.get('#data-1-role').should('have.value', DSM.role);
+    });
+
+    it('Remove row - 3 -> 2, remove middle, 2 -> 1, remove head', () => {
+        cy.visit('http://127.0.0.1:6006/iframe.html?path=/story/form--remove-demo');
+        cy.get('#data-1 button').click();
+        cy.get('#data-0-name').should('have.value', D2C.name);
+        cy.get('#data-0-role').should('have.value', D2C.role);
+        cy.get('#data-1-name').should('have.value', DSM.name);
+        cy.get('#data-1-role').should('have.value', DSM.role);
+
+        cy.get('#data-0 button').click();
+        cy.get('#data-0-name').should('have.value', DSM.name);
+        cy.get('#data-0-role').should('have.value', DSM.role);
+        cy.get('#data-1').should('not.exist');
+    });
+
+    it('Remove row - 3 -> 2, remove head', () => {
+        cy.visit('http://127.0.0.1:6006/iframe.html?path=/story/form--remove-demo');
+        cy.get('#data-2').should('exist');
+        cy.get('#data-0 button').click();
+        cy.get('#data-2').should('not.exist');
+
+        cy.get('#data-0-name').should('have.value', C2D.name);
+        cy.get('#data-0-role').should('have.value', C2D.role);
+        cy.get('#data-1-name').should('have.value', DSM.name);
+        cy.get('#data-1-role').should('have.value', DSM.role);
+
+        cy.get('#data-0 button').click();
+        cy.get('#data-0-name').should('have.value', DSM.name);
+        cy.get('#data-0-role').should('have.value', DSM.role);
+    });
+
+    it('Remove row - 3 -> 2, remove last', () => {
+        cy.visit('http://127.0.0.1:6006/iframe.html?path=/story/form--remove-demo');
+        cy.get('#data-2').should('exist');
+        cy.get('#data-2 button').click();
+        cy.get('#data-2').should('not.exist');
+
+        cy.get('#data-0-name').should('have.value', D2C.name);
+        cy.get('#data-0-role').should('have.value', D2C.role);
+        cy.get('#data-1-name').should('have.value', C2D.name);
+        cy.get('#data-1-role').should('have.value', C2D.role);
+    });
+
+    // it('Basic usage - add、remove、reset', () => { });
+
+    // reset
+    it('Reset Usage: modify  => reset ', () => {
+        cy.visit('http://127.0.0.1:6006/iframe.html?path=/story/form--reset-demo');
+        cy.get('#data-0-name').type('-new');
+        cy.get('#data-0-role').type('-new');
+
+        cy.get('#data-0-name').should('have.value', `${D2C.name}-new`);
+        cy.get('#data-0-role').should('have.value', `${D2C.role}-new`);
+
+        cy.get('button[type=reset]').click();
+        cy.get('#data-0-name').should('have.value', `${D2C.name}`);
+        cy.get('#data-0-name').should('have.value', `${D2C.name}`);
+    });
+
+    it('Reset Usage: length 2 -> 1  => reset ', () => {
+        cy.visit('http://127.0.0.1:6006/iframe.html?path=/story/form--reset-demo');
+        cy.get('#data-0 button').click();
+        cy.get('#data-1').should('not.exist');
+        cy.get('button[type=reset]').click();
+
+        // cy.get('#data-1').should('exist');
+        cy.get('#data-0-name').should('have.value', `${D2C.name}`);
+        cy.get('#data-0-role').should('have.value', `${D2C.role}`);
+        cy.get('#data-1-name').should('have.value', `${C2D.name}`);
+        cy.get('#data-1-role').should('have.value', `${C2D.role}`);
+    });
+
+
+    it('Reset Usage: length 2 -> 0 -> 3  => reset ', () => {
+        cy.visit('http://127.0.0.1:6006/iframe.html?path=/story/form--reset-demo');
+        cy.get('#data-1 button').click();
+        cy.get('#data-0 button').click();
+        cy.get('#data-0').should('not.exist');
+
+        cy.get('#addWithInit').click();
+        cy.get('#addWithInit').click();
+        cy.get('#addWithInit').click();
+        cy.get('#data-2').should('exist');
+
+        cy.get('button[type=reset]').click();
+        cy.get('#data-0-name').should('have.value', `${D2C.name}`);
+        cy.get('#data-0-role').should('have.value', `${D2C.role}`);
+        cy.get('#data-1-name').should('have.value', `${C2D.name}`);
+        cy.get('#data-1-role').should('have.value', `${C2D.role}`);
+        cy.get('#data-2').should('not.exist');
+    });
+
+    it('Reset Usage: length 2 -> 0 -> 2  => reset ', () => {
+        cy.visit('http://127.0.0.1:6006/iframe.html?path=/story/form--reset-demo');
+        cy.get('#data-1 button').click();
+        cy.get('#data-0 button').click();
+        cy.get('#data-0').should('not.exist');
+
+        cy.get('#addWithInit').click();
+        cy.get('#addWithInit').click();
+        cy.get('.line').should('have.length', 2);
+
+        cy.get('button[type=reset]').click();
+        cy.get('#data-2').should('not.exist');
+        cy.get('#data-0-name').should('have.value', `${D2C.name}`);
+        cy.get('#data-0-role').should('have.value', `${D2C.role}`);
+        cy.get('#data-1-name').should('have.value', `${C2D.name}`);
+        cy.get('#data-1-role').should('have.value', `${C2D.role}`);
+    });
+
+    it('Combine Usage', () => {
+        // add -> remove -> modify
+        cy.visit('http://127.0.0.1:6006/iframe.html?path=/story/form--basic-array-field-demo', {
+            onBeforeLoad(win) {
+                cy.stub(win.console, 'log').as('consoleLog');
+            },
+        });
+        cy.get('#addWithInit').click();
+        cy.get('#data-1 button').click();
+        cy.get('#data-0-role').type('-0');
+        cy.get('#data-0-name').type('-0');
+
+        cy.get('#data-0-name').should('have.value', `${D2C.name}-0`);
+        cy.get('#data-0-role').should('have.value', `${D2C.role}-0`);
+        cy.get('#data-1-name').should('have.value', `Semi New-3`);
+        cy.get('#data-1-role').should('have.value', `Designer`);
+    });
+
+    it('combine usage-2', () => {
+        // add -> remove -> modify -> reset
+        cy.visit('http://127.0.0.1:6006/iframe.html?path=/story/form--basic-array-field-demo');
+        cy.get('#data-1 button').click();
+        cy.get('#data-0-role').type('-0');
+        cy.get('#data-0-name').type('-0');
+        cy.get('button[type=reset]').click();
+        cy.get('#data-0-name').should('have.value', `${D2C.name}`);
+        cy.get('#data-0-role').should('have.value', `${D2C.role}`);
+        cy.get('#data-1-name').should('have.value', `${C2D.name}`);
+        cy.get('#data-1-role').should('have.value', `${C2D.role}`);
+    });
+
+    it('sync setValue - modify value, 2 -> 2', () => {
+        cy.visit('http://127.0.0.1:6006/iframe.html?path=/story/form--manual-set-demo');
+        cy.get('#updateSync').click();
+        cy.get('#data-0-name').should('have.value', `${D2C.name}-0`);
+        cy.get('#data-0-role').should('have.value', `${D2C.role}-0`);
+        cy.get('#data-1-name').should('have.value', `${C2D.name}-1`);
+        cy.get('#data-1-role').should('have.value', `${C2D.role}`);
+    });
+
+    it('sync setValue - add, 2 -> 3, add tail', () => {
+        cy.visit('http://127.0.0.1:6006/iframe.html?path=/story/form--manual-set-demo');
+        cy.get('#addTailSync').click();
+        cy.get('#data-0-name').should('have.value', `${D2C.name}`);
+        cy.get('#data-0-role').should('have.value', `${D2C.role}`);
+        cy.get('#data-1-name').should('have.value', `${C2D.name}`);
+        cy.get('#data-1-role').should('have.value', `${C2D.role}`);
+        cy.get('#data-2-name').should('have.value', `${DSM.name}`);
+        cy.get('#data-2-role').should('have.value', `${DSM.role}`);
+    });
+
+    it('sync setValue - add, 2 -> 3, add first', () => {
+        cy.visit('http://127.0.0.1:6006/iframe.html?path=/story/form--manual-set-demo');
+        cy.get('#addHeadSync').click();
+        cy.get('#data-0-name').should('have.value', `${DSM.name}`);
+        cy.get('#data-0-role').should('have.value', `${DSM.role}`);
+        cy.get('#data-1-name').should('have.value', `${D2C.name}`);
+        cy.get('#data-1-role').should('have.value', `${D2C.role}`);
+        cy.get('#data-2-name').should('have.value', `${C2D.name}`);
+        cy.get('#data-2-role').should('have.value', `${C2D.role}`);
+    });
+
+    it('sync setValue - add, 2 -> 3, add middle', () => {
+        cy.visit('http://127.0.0.1:6006/iframe.html?path=/story/form--manual-set-demo');
+        cy.get('#addMiddleSync').click();
+        cy.get('#data-0-name').should('have.value', `${D2C.name}`);
+        cy.get('#data-0-role').should('have.value', `${D2C.role}`);
+        cy.get('#data-2-name').should('have.value', `${C2D.name}`);
+        cy.get('#data-2-role').should('have.value', `${C2D.role}`);
+        cy.get('#data-1-name').should('have.value', `${DSM.name}`);
+        cy.get('#data-1-role').should('have.value', `${DSM.role}`);
+    });
+
+    it('sync setValue - remove, 3 -> 2, remove first', () => {
+        cy.visit('http://127.0.0.1:6006/iframe.html?path=/story/form--manual-set-demo');
+        cy.get('#addTailSync').click();
+        cy.get('#removeHeadSync').click();
+        cy.get('#data-0-name').should('have.value', `${C2D.name}`);
+        cy.get('#data-0-role').should('have.value', `${C2D.role}`);
+        cy.get('#data-1-name').should('have.value', `${DSM.name}`);
+        cy.get('#data-1-role').should('have.value', `${DSM.role}`);
+    });
+
+    it('sync setValue - remove, 3 -> 2, remove middle', () => {
+        cy.visit('http://127.0.0.1:6006/iframe.html?path=/story/form--manual-set-demo');
+        cy.get('#addTailSync').click();
+        cy.get('#removeMiddleSync').click();
+        cy.get('#data-0-name').should('have.value', `${D2C.name}`);
+        cy.get('#data-0-role').should('have.value', `${D2C.role}`);
+        cy.get('#data-1-name').should('have.value', `${DSM.name}`);
+        cy.get('#data-1-role').should('have.value', `${DSM.role}`);
+    });
+
+    it('sync setValue - remove, 3 -> 2, remove tail', () => {
+        cy.visit('http://127.0.0.1:6006/iframe.html?path=/story/form--manual-set-demo');
+        cy.get('#addTailSync').click();
+        cy.get('#removeTailSync').click();
+        cy.get('#data-0-name').should('have.value', `${D2C.name}`);
+        cy.get('#data-0-role').should('have.value', `${D2C.role}`);
+        cy.get('#data-1-name').should('have.value', `${C2D.name}`);
+        cy.get('#data-1-role').should('have.value', `${C2D.role}`);
+    });
+
+    it('sync setValue - remove, 3 -> 0, remove all', () => {
+        cy.visit('http://127.0.0.1:6006/iframe.html?path=/story/form--manual-set-demo');
+        cy.get('#addTailSync').click();
+        cy.get('#removeAllSync').click();
+        cy.get('.line').should('have.length', 0);
+    });
+
+    it('Async setValue - modify value, 2 -> 2', () => {
+        cy.visit('http://127.0.0.1:6006/iframe.html?path=/story/form--manual-set-async-demo');
+        cy.get('#updateAsync').click();
+        cy.wait(500);
+        cy.get('#data-0-name').should('have.value', `${D2C.name}-0`);
+        cy.get('#data-0-role').should('have.value', `${D2C.role}-0`);
+        cy.get('#data-1-name').should('have.value', `${C2D.name}-1`);
+        cy.get('#data-1-role').should('have.value', `${C2D.role}`);
+    });
+
+    it('Async setValue - add, 2 -> 3, add tail', () => {
+        cy.visit('http://127.0.0.1:6006/iframe.html?path=/story/form--manual-set-async-demo');
+        cy.get('#addTailAsync').click();
+        cy.wait(500);
+        cy.get('#data-0-name').should('have.value', `${D2C.name}`);
+        cy.get('#data-0-role').should('have.value', `${D2C.role}`);
+        cy.get('#data-1-name').should('have.value', `${C2D.name}`);
+        cy.get('#data-1-role').should('have.value', `${C2D.role}`);
+        cy.get('#data-2-name').should('have.value', `${DSM.name}`);
+        cy.get('#data-2-role').should('have.value', `${DSM.role}`);
+    });
+
+    it('Async setValue - add, 2 -> 3, add first', () => {
+        cy.visit('http://127.0.0.1:6006/iframe.html?path=/story/form--manual-set-async-demo');
+        cy.get('#addHeadAsync').click();
+        cy.wait(500);
+        cy.get('#data-0-name').should('have.value', `${DSM.name}`);
+        cy.get('#data-0-role').should('have.value', `${DSM.role}`);
+        cy.get('#data-1-name').should('have.value', `${D2C.name}`);
+        cy.get('#data-1-role').should('have.value', `${D2C.role}`);
+        cy.get('#data-2-name').should('have.value', `${C2D.name}`);
+        cy.get('#data-2-role').should('have.value', `${C2D.role}`);
+    });
+
+    it('Async setValue - add, 2 -> 3, add middle', () => {
+        cy.visit('http://127.0.0.1:6006/iframe.html?path=/story/form--manual-set-async-demo');
+        cy.get('#addMiddleAsync').click();
+        cy.wait(500);
+        cy.get('#data-0-name').should('have.value', `${D2C.name}`);
+        cy.get('#data-0-role').should('have.value', `${D2C.role}`);
+        cy.get('#data-2-name').should('have.value', `${C2D.name}`);
+        cy.get('#data-2-role').should('have.value', `${C2D.role}`);
+        cy.get('#data-1-name').should('have.value', `${DSM.name}`);
+        cy.get('#data-1-role').should('have.value', `${DSM.role}`);
+    });
+
+    it('Async setValue - remove, 3 -> 2, remove first', () => {
+        cy.visit('http://127.0.0.1:6006/iframe.html?path=/story/form--manual-set-async-demo');
+        cy.get('#addTailAsync').click();
+        cy.wait(500);
+        cy.get('#removeHeadAsync').click();
+        cy.wait(500);
+        cy.get('#data-0-name').should('have.value', `${C2D.name}`);
+        cy.get('#data-0-role').should('have.value', `${C2D.role}`);
+        cy.get('#data-1-name').should('have.value', `${DSM.name}`);
+        cy.get('#data-1-role').should('have.value', `${DSM.role}`);
+    });
+
+    it('Async setValue - remove, 3 -> 2, remove middle', () => {
+        cy.visit('http://127.0.0.1:6006/iframe.html?path=/story/form--manual-set-async-demo');
+        cy.get('#addTailAsync').click();
+        cy.wait(500);
+        cy.get('#removeMiddleAsync').click();
+        cy.wait(500);
+        cy.get('#data-0-name').should('have.value', `${D2C.name}`);
+        cy.get('#data-0-role').should('have.value', `${D2C.role}`);
+        cy.get('#data-1-name').should('have.value', `${DSM.name}`);
+        cy.get('#data-1-role').should('have.value', `${DSM.role}`);
+    });
+
+    it('Async setValue - remove, 3 -> 2, remove tail', () => {
+        cy.visit('http://127.0.0.1:6006/iframe.html?path=/story/form--manual-set-async-demo');
+        cy.get('#addTailAsync').click();
+        cy.wait(500);
+        cy.get('#removeTailAsync').click();
+        cy.wait(500);
+        cy.get('#data-0-name').should('have.value', `${D2C.name}`);
+        cy.get('#data-0-role').should('have.value', `${D2C.role}`);
+        cy.get('#data-1-name').should('have.value', `${C2D.name}`);
+        cy.get('#data-1-role').should('have.value', `${C2D.role}`);
+    });
+
+    it('Async setValue - remove, 2 -> 0, remove all', () => {
+        cy.visit('http://127.0.0.1:6006/iframe.html?path=/story/form--manual-set-async-demo');
+        cy.get('#removeAllAsync').click();
+        cy.wait(500);
+        cy.get('.line').should('have.length', 0);
+    });
+
+    // TODO 
+    it('2 Nested ArrayField - basic', () => {
+        cy.visit('http://127.0.0.1:6006/iframe.html?path=/story/form--basic-nested-demo');
+        // check initValue
+        cy.get('#data-0-name').should('have.value', `0`);
+        cy.get('#data-0-rule-0-type').should('have.value', `0-0-type`);
+        cy.get('#data-0-rule-0-desc').should('have.value', `0-0-desc`);
+        cy.get('#data-0-rule-1-type').should('have.value', `0-1-type`);
+        cy.get('#data-0-rule-1-desc').should('have.value', `0-1-desc`);
+        cy.get('#data-1-name').should('have.value', `1`);
+        cy.get('#data-1-rule-0-type').should('have.value', `1-0-type`);
+        cy.get('#data-1-rule-0-desc').should('have.value', `1-0-desc`);
+    });
+
+
+    it('2 Nested ArrayField - add / remove / reset', () => {
+        // cy.visit('http://127.0.0.1:6006/iframe.html?path=/story/form--basic-nested-demo');
+        // // add level 0 
+        cy.get('#data-1-add').click();
+        cy.get('#data-2-name').should('have.value', `2`);
+        cy.get('#data-2-rule-0-type').should('have.value', `2-0-type`);
+        cy.get('#data-2-rule-0-desc').should('have.value', `2-0-desc`);
+
+        // // add level 1
+        cy.get('#data-0-rule-1-add').click();
+        cy.get('#data-0-rule-2-type').should('have.value', `0-2-type`);
+        cy.get('#data-0-rule-2-desc').should('have.value', `0-2-desc`);
+
+        cy.get('#data-2-rule-0-add').click();
+        cy.get('#data-2-rule-1-type').should('have.value', `2-1-type`);
+        cy.get('#data-2-rule-1-desc').should('have.value', `2-1-desc`);
+
+        // remove
+        /**
+         *  0            =>        0
+         *   0-0                     0-0
+         *   0-1                     0-2
+         *   0-2
+         *  1                      1
+         *   1-0                   
+         *  2                      2 
+         *   2-0                     2-0
+         *   2-1
+         */
+        cy.get('#data-0-rule-1-remove').click();
+        cy.get('#data-1-rule-0-remove').click();
+        cy.get('#data-2-rule-1-remove').click();
+
+        cy.get('#data-0-name').should('have.value', `0`);
+        cy.get('#data-0-rule-0-type').should('have.value', `0-0-type`);
+        cy.get('#data-0-rule-0-desc').should('have.value', `0-0-desc`);
+        cy.get('#data-0-rule-1-type').should('have.value', `0-2-type`); // cause remove middle
+        cy.get('#data-0-rule-1-desc').should('have.value', `0-2-desc`); // cause remove middle
+
+        cy.get('#data-1-name').should('have.value', `1`);
+        cy.get('#data-1-rule-0-type').should('not.exist');
+        cy.get('#data-1-rule-0-desc').should('not.exist');
+
+        cy.get('#data-2-name').should('have.value', `2`);
+        cy.get('#data-2-rule-0-type').should('have.value', `2-0-type`); // cause remove tail
+        cy.get('#data-2-rule-0-desc').should('have.value', `2-0-desc`); // cause remove tail
+
+        // reset
+        //❌ TODO still exist bug here
+    });
 
+    // it('2 Nested ArrayField - formApi.setValues', () => { });
+    // it('2 Nested ArrayField - formApi.setValue level-1', () => { });
+    // it('2 Nested ArrayField - formApi.setValue level-2', () => { });
 
     // it('Init - Form Props initValues、ArrayField initValue、Field initValue', () => { 
     // // 一个 Form 三个 ArrayField
     // });
-    // it('Init - combine', () => {});
 
-    // it('sync setValue - modify value, 2 -> 2', () => { });
-    // it('sync setValue - add, 2 -> 3, add first', () => { });
-    // it('sync setValue - add, 2 -> 3, add middle', () => { });
-    // it('sync setValue - add, 2 -> 3, add last', () => { });
-    // it('sync setValue - remove, 3 -> 2, remove first', () => { });
-    // it('sync setValue - remove, 3 -> 2, remove middle', () => { });
-    // it('sync setValue - remove, 3 -> 2, remove last', () => { });
-    // it('sync setValue - remove, 3 -> 0, remove all', () => { });
+    // it('Init - combine', () => {});
 
     // it('sync setValues - modify', () => { });
     // it('sync setValues - add', () => { });
     // it('sync setValues - remove', () => { });
-
-    // it('ASync setValues', () => { });
-
-    // it('ASync setValue', () => { });
-    // it('ASync setValue: ', () => { });
-
-
-    // it('2 Nested ArrayField - add/remove', () => { });
-    // it('2 Nested ArrayField - reset', () => { });
-    // it('2 Nested ArrayField - formApi.setValues', () => { });
-    // it('2 Nested ArrayField - formApi.setValue level-1', () => { });
-    // it('2 Nested ArrayField - formApi.setValue level-2', () => { });
-    // it('3 Nested ArrayField - add/remove', () => {});
+    // it('Async setValues', () => { });
 });

+ 2 - 2
packages/semi-foundation/form/foundation.ts

@@ -132,13 +132,13 @@ export default class FormFoundation extends BaseFoundation<BaseFormAdapter> {
     }
 
     // in order to slove byted-issue-289
-    registerArrayField(arrayFieldPath: string, initValue: any): void {
+    registerArrayField(arrayFieldPath: string, { initValue, forceUpdate }: Pick<ArrayFieldStaff, 'initValue' | 'forceUpdate'> ): void {
         //  save initValue of arrayField, will be use when calling rest
         // this.updateArrayField(arrayFieldPath, {
         //     updateKey: new Date().valueOf(),
         //     initValue: initValue,
         // });
-        this.registeredArrayField.set(arrayFieldPath, { field: arrayFieldPath, initValue: initValue });
+        this.registeredArrayField.set(arrayFieldPath, { field: arrayFieldPath, initValue: initValue, forceUpdate });
     }
 
     unRegisterArrayField(arrayField: string): void {

+ 0 - 65
packages/semi-ui/form/_story/ArrayField/4-manualSetUsage.jsx

@@ -1,65 +0,0 @@
-import React, { useState, useRef } from 'react';
-import { Form, Col, Row, Button, ArrayField, Space } from '@douyinfe/semi-ui';
-import { IconMinusCircle, IconPlusCircle } from '@douyinfe/semi-icons';
-
-function ManualSetDemo() {
-    const formRef = useRef();
-    const formInitValues = {
-        data: [
-            { name: 'Semi D2C', role: 'Engineer' },
-            { name: 'Semi C2D', role: 'Designer' },
-            // { name: 'Semi DSM', role: 'Designer' },
-        ]
-    };
-
-    const setValue = () => {
-        const formApi = formRef.current.formApi;
-    };
-    
-    return (
-        <Form ref={formRef} initValues={formInitValues}>
-            <ArrayField field="data">
-                {({ add, addWithInitValue, arrayFields }) => (
-                    <React.Fragment>
-                        <Space>
-                            <Button id='add' onClick={add} icon={<IconPlusCircle />} type="primary">Add</Button>
-                            <Button
-                                id='addWithInit'
-                                onClick={() => addWithInitValue({ name: `Semi New-${arrayFields.length + 1}`, role: 'Designer' })}
-                                icon={<IconPlusCircle />}
-                                type="primary">
-                                Add with row initValue
-                            </Button>
-                        </Space>
-                        {
-                            arrayFields.map(({ field, key, remove }, i) => (
-                                <div key={key} style={{ display: 'flex', width: 600 }} id={`data-${i}`} className='line'>
-                                    <Space>
-                                        <Form.Input
-                                            id={`data-${i}-name`}
-                                            field={`${field}[name]`}
-                                            label={`${field}.name`}
-                                            style={{ width: 200 }}
-                                        />
-                                        <Form.Input
-                                            id={`data-${i}-role`}
-                                            field={`${field}[role]`}
-                                            label={`${field}.role`}
-                                            style={{ width: 200 }}
-                                        />
-                                    </Space>
-                                    <Button style={{ margin: "36px 0 0 12px" }} type="danger" icon={<IconMinusCircle/>} onClick={remove}>remove this line</Button>
-                                </div>
-                            ))
-                        }
-                        <Button htmlType='reset'>Reset</Button>
-                    </React.Fragment>
-                )}
-            </ArrayField>
-        </Form>
-    );
-}
-
-ManualSetDemo.storyName = 'ArrayField-ManualSet';
-
-export default ManualSetDemo;

+ 147 - 0
packages/semi-ui/form/_story/ArrayField/4-manualSyncSetUsage.jsx

@@ -0,0 +1,147 @@
+import React, { useState, useRef } from 'react';
+import { Form, Col, Row, Button, ArrayField, Space } from '@douyinfe/semi-ui';
+import { IconMinusCircle, IconPlusCircle } from '@douyinfe/semi-icons';
+
+function ManualSetDemo() {
+    const formRef = useRef();
+    const formInitValues = {
+        data: [
+            { name: 'Semi D2C', role: 'Engineer' },
+            { name: 'Semi C2D', role: 'Designer' },
+            // { name: 'Semi DSM', role: 'Designer' },
+        ]
+    };
+
+    const getFormApi = () => {
+        const formApi = formRef.current.formApi;  
+        return formApi;
+    };
+
+    const setValueSync = () => {
+        const formApi = getFormApi();
+        formApi.setValue('data', [
+            { name: 'Semi D2C-0', role: 'Engineer-0' },
+            { name: 'Semi C2D-1', role: 'Designer' },
+        ]);
+    };
+
+    const removeHeadSync = () => {
+        const formApi = getFormApi();
+        formApi.setValue('data', [
+            { name: 'Semi C2D', role: 'Designer' },
+            { name: 'Semi DSM', role: 'Designer' },
+        ]);
+    };
+
+    const removeTailSync = () => {
+        const formApi = getFormApi();
+        formApi.setValue('data', [
+            { name: 'Semi D2C', role: 'Engineer' },
+            { name: 'Semi C2D', role: 'Designer' },
+        ]);
+    };
+
+    const removeMiddleSync = () => {
+        const formApi = getFormApi();
+        formApi.setValue('data', [
+            { name: 'Semi D2C', role: 'Engineer' },
+            { name: 'Semi DSM', role: 'Designer' },
+        ]);
+    };
+
+    const removeAllSync = () => {
+        const formApi = getFormApi();
+        formApi.setValue('data', []);
+    };
+
+    const addHeadSync = () => {
+        const formApi = getFormApi();
+        formApi.setValue('data', [
+            { name: 'Semi DSM', role: 'Designer' },
+            { name: 'Semi D2C', role: 'Engineer' },
+            { name: 'Semi C2D', role: 'Designer' },
+        ]);
+    };
+
+    const addMiddleSync = () => {
+        const formApi = getFormApi();
+        formApi.setValue('data', [
+            { name: 'Semi D2C', role: 'Engineer' },
+            { name: 'Semi DSM', role: 'Designer' },
+            { name: 'Semi C2D', role: 'Designer' },
+        ]);
+    };
+
+    const addTailSync = () => {
+        const formApi = getFormApi();
+        formApi.setValue('data', [
+            { name: 'Semi D2C', role: 'Engineer' },
+            { name: 'Semi C2D', role: 'Designer' },
+            { name: 'Semi DSM', role: 'Designer' },
+        ]);
+    };
+
+    
+    return (
+        <Form ref={formRef} initValues={formInitValues}>
+            <div>
+                <Space>
+                    <Button id='updateSync' onClick={setValueSync}>setValueSync</Button>
+                    <Button id='addHeadSync' onClick={addHeadSync}>addHeadSync</Button>
+                    <Button id='addMiddleSync' onClick={addMiddleSync}>addMiddleSync</Button>
+                    <Button id='addTailSync' onClick={addTailSync}>addTailSync</Button>
+                </Space>
+            </div>
+            <div style={{ marginTop: 12, marginBottom: 12 }}>
+                <Space>
+                    <Button id='removeHeadSync' onClick={removeHeadSync} icon={<IconMinusCircle />} type='danger'>removeHeadSync</Button>
+                    <Button id='removeTailSync' onClick={removeTailSync} icon={<IconMinusCircle />} type='danger'>removeTailSync</Button>
+                    <Button id='removeMiddleSync' onClick={removeMiddleSync} icon={<IconMinusCircle />} type='danger'>removeMiddleSync</Button>
+                    <Button id='removeAllSync' onClick={removeAllSync} icon={<IconMinusCircle />} type='danger'>removeAllSync</Button>
+                </Space>
+            </div>
+            <ArrayField field="data">
+                {({ add, addWithInitValue, arrayFields }) => (
+                    <React.Fragment>
+                        <Space>
+                            <Button id='add' onClick={add} icon={<IconPlusCircle />} type="primary">Add</Button>
+                            <Button
+                                id='addWithInit'
+                                onClick={() => addWithInitValue({ name: `Semi New-${arrayFields.length + 1}`, role: 'Designer' })}
+                                icon={<IconPlusCircle />}
+                                type="primary">
+                                Add with row initValue
+                            </Button>
+                        </Space>
+                        {
+                            arrayFields.map(({ field, key, remove }, i) => (
+                                <div key={key} style={{ display: 'flex', width: 600 }} id={`data-${i}`} className='line'>
+                                    <Space>
+                                        <Form.Input
+                                            id={`data-${i}-name`}
+                                            field={`${field}[name]`}
+                                            label={`${field}.name`}
+                                            style={{ width: 200 }}
+                                        />
+                                        <Form.Input
+                                            id={`data-${i}-role`}
+                                            field={`${field}[role]`}
+                                            label={`${field}.role`}
+                                            style={{ width: 200 }}
+                                        />
+                                    </Space>
+                                    <Button style={{ margin: "36px 0 0 12px" }} type="danger" icon={<IconMinusCircle/>} onClick={remove}>remove this line</Button>
+                                </div>
+                            ))
+                        }
+                        <Button htmlType='reset'>Reset</Button>
+                    </React.Fragment>
+                )}
+            </ArrayField>
+        </Form>
+    );
+}
+
+ManualSetDemo.storyName = 'ArrayField-ManualSet Sync';
+
+export default ManualSetDemo;

+ 170 - 0
packages/semi-ui/form/_story/ArrayField/5-manualAsyncSetUsage.jsx

@@ -0,0 +1,170 @@
+import React, { useState, useRef } from 'react';
+import { Form, Col, Row, Button, ArrayField, Space } from '@douyinfe/semi-ui';
+import { IconMinusCircle, IconPlusCircle } from '@douyinfe/semi-icons';
+
+function ManualSetDemo() {
+    const formRef = useRef();
+    const formInitValues = {
+        data: [
+            { name: 'Semi D2C', role: 'Engineer' },
+            { name: 'Semi C2D', role: 'Designer' },
+            // { name: 'Semi DSM', role: 'Designer' },
+        ]
+    };
+
+    const getFormApi = () => {
+        const formApi = formRef.current.formApi;  
+        return formApi;
+    };
+
+    const setValueAsync = () => {
+        const formApi = getFormApi();
+        setTimeout(() => {
+            formApi.setValue('data', [
+                { name: 'Semi D2C-0', role: 'Engineer-0' },
+                { name: 'Semi C2D-1', role: 'Designer' },
+            ]);
+        }, 200);
+    };
+
+    const removeHeadAsync = () => {
+        const formApi = getFormApi();
+        setTimeout(() => {
+            formApi.setValue('data', [
+                { name: 'Semi C2D', role: 'Designer' },
+                { name: 'Semi DSM', role: 'Designer' },
+            ]);
+        }, 200);
+
+    };
+
+    const removeTailAsync = () => {
+        const formApi = getFormApi();
+        setTimeout(() => {
+            formApi.setValue('data', [
+                { name: 'Semi D2C', role: 'Engineer' },
+                { name: 'Semi C2D', role: 'Designer' },
+            ]);
+        }, 200);
+
+    };
+
+    const removeMiddleAsync = () => {
+        const formApi = getFormApi();
+        setTimeout(() => {
+            formApi.setValue('data', [
+                { name: 'Semi D2C', role: 'Engineer' },
+                { name: 'Semi DSM', role: 'Designer' },
+            ]);
+        }, 200);
+
+    };
+
+    const removeAllAsync = () => {
+        const formApi = getFormApi();
+        setTimeout(() => {
+            formApi.setValue('data', []);
+        }, 200);
+
+    };
+
+    const addHeadAsync = () => {
+        const formApi = getFormApi();
+        setTimeout(() => {
+            formApi.setValue('data', [
+                { name: 'Semi DSM', role: 'Designer' },
+                { name: 'Semi D2C', role: 'Engineer' },
+                { name: 'Semi C2D', role: 'Designer' },
+            ]);
+        }, 200);
+
+    };
+
+    const addMiddleAsync = () => {
+        const formApi = getFormApi();
+        setTimeout(() => {
+            formApi.setValue('data', [
+                { name: 'Semi D2C', role: 'Engineer' },
+                { name: 'Semi DSM', role: 'Designer' },
+                { name: 'Semi C2D', role: 'Designer' },
+            ]);
+        }, 200);
+
+    };
+
+    const addTailAsync = () => {
+        const formApi = getFormApi();
+        setTimeout(() => {
+            formApi.setValue('data', [
+                { name: 'Semi D2C', role: 'Engineer' },
+                { name: 'Semi C2D', role: 'Designer' },
+                { name: 'Semi DSM', role: 'Designer' },
+            ]);
+        }, 200);
+
+    };
+
+    
+    return (
+        <Form ref={formRef} initValues={formInitValues}>
+            <div>
+                <Space>
+                    <Button id='updateAsync' onClick={setValueAsync}>setValueAsync</Button>
+                    <Button id='addHeadAsync' onClick={addHeadAsync}>addHeadAsync</Button>
+                    <Button id='addMiddleAsync' onClick={addMiddleAsync}>addMiddleAsync</Button>
+                    <Button id='addTailAsync' onClick={addTailAsync}>addTailAsync</Button>
+                </Space>
+            </div>
+            <div style={{ marginTop: 12, marginBottom: 12 }}>
+                <Space>
+                    <Button id='removeHeadAsync' onClick={removeHeadAsync} icon={<IconMinusCircle />} type='danger'>removeHeadAsync</Button>
+                    <Button id='removeTailAsync' onClick={removeTailAsync} icon={<IconMinusCircle />} type='danger'>removeTailAsync</Button>
+                    <Button id='removeMiddleAsync' onClick={removeMiddleAsync} icon={<IconMinusCircle />} type='danger'>removeMiddleAsync</Button>
+                    <Button id='removeAllAsync' onClick={removeAllAsync} icon={<IconMinusCircle />} type='danger'>removeAllAsync</Button>
+                </Space>
+            </div>
+            <ArrayField field="data">
+                {({ add, addWithInitValue, arrayFields }) => (
+                    <React.Fragment>
+                        <Space>
+                            <Button id='add' onClick={add} icon={<IconPlusCircle />} type="primary">Add</Button>
+                            <Button
+                                id='addWithInit'
+                                onClick={() => addWithInitValue({ name: `Semi New-${arrayFields.length + 1}`, role: 'Designer' })}
+                                icon={<IconPlusCircle />}
+                                type="primary">
+                                Add with row initValue
+                            </Button>
+                        </Space>
+                        {
+                            arrayFields.map(({ field, key, remove }, i) => (
+                                <div key={key} style={{ display: 'flex', width: 600 }} id={`data-${i}`} className='line'>
+                                    <Space>
+                                        <Form.Input
+                                            id={`data-${i}-name`}
+                                            field={`${field}[name]`}
+                                            label={`${field}.name`}
+                                            style={{ width: 200 }}
+                                        />
+                                        <Form.Input
+                                            id={`data-${i}-role`}
+                                            field={`${field}[role]`}
+                                            label={`${field}.role`}
+                                            style={{ width: 200 }}
+                                        />
+                                    </Space>
+                                    <Button style={{ margin: "36px 0 0 12px" }} type="danger" icon={<IconMinusCircle/>} onClick={remove}>remove this line</Button>
+                                </div>
+                            ))
+                        }
+                        <Button htmlType='reset'>Reset</Button>
+                    </React.Fragment>
+                )}
+            </ArrayField>
+        </Form>
+    );
+}
+
+ManualSetDemo.storyName = 'ArrayField-ManualSet Async';
+
+export default ManualSetDemo;

+ 144 - 0
packages/semi-ui/form/_story/ArrayField/6-nestedBasicUsage.jsx

@@ -0,0 +1,144 @@
+import React, { useState, useRef } from 'react';
+import { Form, Col, Row, Button, ArrayField, Space } from '@douyinfe/semi-ui';
+import { IconMinusCircle, IconPlusCircle, IconRefresh } from '@douyinfe/semi-icons';
+
+const NestDemo = () => {
+    const formRef = useRef();
+
+    const getFormApi = () => {
+        const formApi = formRef.current.formApi;  
+        return formApi;
+    };
+
+    const formInitValues = {
+        data: [
+            {
+                name: '0',
+                rules: [
+                    { desc: '0-0-desc', type: '0-0-type' },
+                    { desc: '0-1-desc', type: '0-1-type' },
+                ],
+            },
+            {
+                name: '1',
+                rules: [
+                    { desc: '1-0-desc', type: '1-0-type' }
+                ],
+            }
+        ]
+    };
+
+    // const change = () => {
+        
+    // };
+
+    return (
+        <Form
+            ref={formRef}
+            initValues={formInitValues}
+            labelPosition='inset'
+        >
+            <ArrayField field='data'>
+                {({ add, arrayFields, addWithInitValue }) => (
+                    <React.Fragment>
+                        <Space>
+                            <Button htmlType='reset' theme='solid' type='secondary' icon={<IconRefresh />}>Reset</Button>
+                        </Space>
+                        {
+                            arrayFields.map(({ field, key, remove }, i) => (
+                                <div key={key} style={{ width: 800, display: 'flex', flexWrap: 'wrap' }} id={`data-${i}`} className='line'>
+                                    {/* {key} */}
+                                    <Space>
+                                        <Form.Input
+                                            field={`${field}.name`}
+                                            label={`${field}.name`}
+                                            style={{ width: 700 }}
+                                            id={`data-${i}-name`}
+                                        >
+                                        </Form.Input>
+                                        <Button
+                                            onClick={() => addWithInitValue({
+                                                name: arrayFields.length,
+                                                rules: [
+                                                    { desc: `${arrayFields.length}-0-desc`, type: `${arrayFields.length}-0-type` }
+                                                ]
+                                            })}
+                                            icon={<IconPlusCircle/>}
+                                            id={`data-${i}-add`} 
+                                            disabled={i !== arrayFields.length - 1}
+                                        />
+                                        <Button
+                                            type='danger'
+                                            onClick={remove}
+                                            id={`data-${i}-remove`} 
+                                            icon={<IconMinusCircle />}
+                                        />
+                                    </Space>
+                           
+                                    <ArrayField field={`${field}.rules`}>
+                                        {({ add: addNested, arrayFields: nestedArrayFields, addWithInitValue: addNestedWithInitValue }, ) => (
+                                            <React.Fragment>
+                                                {
+                                                    nestedArrayFields.map(({ field: f, key: k, remove: r }, n) => (
+                                                        <section className='rules' key={k} style={{ display: 'flex', flexWrap: 'wrap', marginLeft: 36 }}>
+                                                            <Space>
+                                                                {/* {k} */}
+                                                                <Form.Input
+                                                                    style={{ width: 280, marginRight: 12 }}
+                                                                    field={`${f}.type`}
+                                                                    label={`${f}.type`}
+                                                                    id={`data-${i}-rule-${n}-type`} 
+                                                                />
+                                                                <Form.Input
+                                                                    style={{ width: 280 }}
+                                                                    field={`${f}.desc`}
+                                                                    label={`${f}.desc`}
+                                                                    id={`data-${i}-rule-${n}-desc`} 
+                                                                />
+                                                                <Button
+                                                                    onClick={() => addNestedWithInitValue({ type: `${i}-${n+1}-type`, desc: `${i}-${n+1}-desc` })}
+                                                                    icon={<IconPlusCircle/>}
+                                                                    id={`data-${i}-rule-${n}-add`}
+                                                                    disabled={n !== nestedArrayFields.length - 1}
+                                                                >
+                                                                    add new line
+                                                                </Button>
+                                                                <Button
+                                                                    type='danger'
+                                                                    onClick={r}
+                                                                    id={`data-${i}-rule-${n}-remove`} 
+                                                                    icon={<IconMinusCircle />}
+                                                                />
+                                                            </Space>
+                                                        </section>
+                                                    ))
+                                                }
+                                            </React.Fragment>
+                                        )}
+                                    </ArrayField>
+                                </div>
+                            ))
+                        }
+                    </React.Fragment>
+                )}
+            </ArrayField>
+        </Form>
+    );
+};
+
+NestDemo.storyName = 'ArrayField-Nested Level 2';
+
+export default NestDemo;
+
+// class NestArrayField extends React.Component {
+//     change = () => {
+//         let number = this.formApi.getValue('number');
+//         let newData = {
+//             group: [
+//                 { name: Math.random().toString().slice(0, 3), items: [ { itemName: Math.random(), type: '0-1' } ] },
+//                 // { name: Math.random(), items: [ { itemName: Math.random(), type: '0-1' } ] },
+//             ]
+//         };
+//         this.formApi.setValues(newData, { isOverride: true });
+//     }
+// }

+ 3 - 1
packages/semi-ui/form/_story/ArrayField/index.js

@@ -1,4 +1,6 @@
 export { default as BasicArrayFieldDemo } from './1-basicUsage.jsx';
 export { default as RemoveDemo } from './2-removeUsage.jsx';
 export { default as ResetDemo } from './3-resetUsage.jsx';
-export { default as ManualSetDemo } from './4-manualSetUsage.jsx';
+export { default as ManualSetDemo } from './4-manualSyncSetUsage.jsx';
+export { default as ManualSetAsyncDemo } from './5-manualAsyncSetUsage.jsx';
+export { default as BasicNestedDemo } from './6-nestedBasicUsage.jsx';

+ 1 - 0
packages/semi-ui/form/_story/DynamicField/arrayFieldDemo.jsx

@@ -400,6 +400,7 @@ class ArrayFieldSetValues extends React.Component {
 
 
 
+// TODO 有相似作用的 e2e demo test了,这个可以删掉
 class AsyncSetArrayField extends React.Component {
     constructor() {
         super();

+ 24 - 36
packages/semi-ui/form/arrayField.tsx

@@ -58,14 +58,18 @@ const initValueAdapter = (initValue: any) => {
  *
  * @param {any[]} value
  * @param {string[]} oldKeys
+ * @param {any[]} cacheValue
  * @returns string[]
  */
-const generateKeys = (value : any[] = [], oldKeys?: string[], cacheValue: any[] = []) => {
+const generateKeys = (value: any[] = [], oldKeys?: string[], cacheValues: any[] = []) => {
     const val = initValueAdapter(value);
     const newKeys = getUuidByArray(val);
     const keys = [];
+
+    //  todo cacheValue 与 oldKeys 是对不上的
+
     value.forEach((newRow, i) => {
-        const cacheRow = get(cacheValue, i);
+        const cacheRow = get(cacheValues, i);
         if (!isEqual(newRow, cacheRow)) {
             keys[i] = newKeys[i];
         } else {
@@ -96,27 +100,26 @@ class ArrayFieldComponent extends Component<ArrayFieldProps, ArrayFieldState> {
         this.add = this.add.bind(this);
         this.addWithInitValue = this.addWithInitValue.bind(this);
         this.remove = this.remove.bind(this);
-        this.cacheFieldValues = null;
-        // this.cacheUpdateKey = null;
+        this.cacheFieldValues = initValue;
 
-        /*
-            If updateKey exists, it means that the arrayField (usually a nested ArrayField not at the first level) is only re-mounted due to setValues,
-            and the fields it contains do not need to consume initValue
-        */
-        // whether the fields inside arrayField should use props.initValue in current render process
-        this.shouldUseInitValue = !context.getArrayField(field);
+        // /*
+        //     If updateKey exists, it means that the arrayField (usually a nested ArrayField not at the first level) is only re-mounted due to setValues,
+        //     and the fields it contains do not need to consume initValue
+        // */
+        // // whether the fields inside arrayField should use props.initValue in current render process
+        // this.shouldUseInitValue = !context.getArrayField(field);
 
         // Separate the arrays that reset and the usual add and remove modify, otherwise they will affect each other
         const initValueCopyForFormState = cloneDeep(initValue);
         const initValueCopyForReset = cloneDeep(initValue);
-        context.registerArrayField(field, initValueCopyForReset);
+        context.registerArrayField(field, { initValue: initValueCopyForReset, forceUpdate: this.forceUpdate });
         context.updateStateValue(field, initValueCopyForFormState, { notNotify: true, notUpdate: true });
     }
 
-    componentDidMount(): void {
-        const { field } = this.props;
-        const updater = this.context;
-        updater.updateArrayField(field, { forceUpdate: this.forceUpdate });
+    componentDidMount() {
+        // const { field } = this.props;
+        // const updater = this.context;
+    //     updater.updateArrayField(field, { forceUpdate: this.forceUpdate });
     }
 
     componentWillUnmount() {
@@ -147,38 +150,22 @@ class ArrayFieldComponent extends Component<ArrayFieldProps, ArrayFieldState> {
     // }
 
     forceUpdate = (value?: any): void => {
-        // const updater = this.context;
-        // const { field } = this.props;
-        // const { keys } = this.state;
-        // const fieldValues = value ? value : updater.getValue(field);
-        // const updateKey = getUpdateKey(updater.getArrayField(field));
-        // if (updateKey !== this.cacheUpdateKey) {
-        //     const newKeys = generateKeys(fieldValues, keys);
-        //     // eslint-disable-next-line
-        //     this.setState({ keys: newKeys });
-        //     this.cacheUpdateKey = updateKey;
-        //     if (this.cacheUpdateKey !== null) {
-        //         this.shouldUseInitValue = false;
-        //     }
-        // } else {
-        //     // console.log('not update');
-        // }
         const updater = this.context;
         const { field } = this.props;
         const { keys } = this.state;
+        console.log(this.cacheFieldValues);
         const fieldValues = value ? value : updater.getValue(field);
         // TODO fieldValues 如果长度相同,keys目前仍会相同,需要为新的
         const newKeys = generateKeys(fieldValues, keys, this.cacheFieldValues);
         // eslint-disable-next-line
         this.setState({ keys: newKeys });
-        this.cacheFieldValues = value;
-        this.shouldUseInitValue = false;
+        this.cacheFieldValues = [...value];
     }
 
     add() {
         const { keys } = this.state;
         keys.push(getUuidv4());
-        this.shouldUseInitValue = true;
+        // this.shouldUseInitValue = true;
         // TODO allowEmpty 为 false 的情况下
         this.setState({ keys });
     }
@@ -189,7 +176,7 @@ class ArrayFieldComponent extends Component<ArrayFieldProps, ArrayFieldState> {
         const newArrayFieldVal = updater.getValue(field) ? updater.getValue(field).slice() : [];
         newArrayFieldVal.push(lineObject);
         updater.updateStateValue(field, newArrayFieldVal, {});
-        updater.updateArrayField(field, { updateKey: new Date().valueOf() });
+        updater.updateArrayField(field, { updateValue: newArrayFieldVal });
     }
 
     remove(i: number) {
@@ -217,6 +204,7 @@ class ArrayFieldComponent extends Component<ArrayFieldProps, ArrayFieldState> {
             updater.updateStateValue(field, newArrayFieldValue);
         }
         this.setState({ keys: newKeys });
+        this.cacheFieldValues = [...newArrayFieldValue];
     }
 
     render() {
@@ -230,7 +218,7 @@ class ArrayFieldComponent extends Component<ArrayFieldProps, ArrayFieldState> {
         const { add } = this;
         const { addWithInitValue } = this;
         const contextVal = {
-            shouldUseInitValue: this.shouldUseInitValue,
+            // shouldUseInitValue: this.shouldUseInitValue,
         };
         return (
             <ArrayFieldContext.Provider value={contextVal}>

+ 1 - 1
packages/semi-ui/form/context.tsx

@@ -12,7 +12,7 @@ const FormUpdaterContext = React.createContext<FormUpdaterContextType>({} as For
 FormUpdaterContext.displayName = 'FormUpdater';
 
 const ArrayFieldContext = React.createContext({
-    shouldUseInitValue: true,
+    // shouldUseInitValue: true,
 });
 
 export { FormStateContext, FormApiContext, FormUpdaterContext, ArrayFieldContext };