fix(devtools): make sure we update the property explorer on state change (rangle/angular-devtools#821)

Fix rangle/angular-devtools#786

With this PR now we consider not only the property name but also the
value of properties in the differ comparison.

The change also contains e2e and unit tests.
This commit is contained in:
Minko Gechev
2021-05-27 14:40:56 -07:00
committed by GitHub
parent c474ab81f6
commit dfc4437afd
5 changed files with 129 additions and 41 deletions
@@ -0,0 +1,26 @@
describe('change of the state should reflect in property update', () => {
beforeEach(() => {
cy.visit('/');
});
it('should update the property value', () => {
// Complete the todo
cy.enter('#sample-app').then((getBody) => {
getBody().find('input[type="checkbox"].toggle').first().click();
});
// Select the todo item
cy.get('.tree-wrapper').find('.tree-node:contains("app-todo[TooltipDirective]")').first().click({ force: true });
// Expand the todo in the property explorer
cy.get('.explorer-panel:contains("app-todo")').find('ng-property-view mat-tree-node:contains("todo")').click();
// Verify its value is now completed
cy.get('.explorer-panel:contains("app-todo")')
.find('ng-property-view mat-tree-node:contains("completed")')
.find('ng-property-editor .editor')
.should((el) => {
expect(el.text().trim()).equal('true');
});
});
});
@@ -1,28 +1,16 @@
import { Descriptor, PropType, MessageBus, Events, Properties, DirectivePosition, NestedProp } from 'protocol';
import { MatTreeFlattener } from '@angular/material/tree';
import { Descriptor, MessageBus, Events, Properties, DirectivePosition, NestedProp } from 'protocol';
import { PropertyDataSource } from './property-data-source';
import { FlatTreeControl } from '@angular/cdk/tree';
import { getExpandedDirectiveProperties } from './property-expanded-directive-properties';
import { Observable } from 'rxjs';
import { Property, FlatNode } from './element-property-resolver';
import { ViewEncapsulation } from '@angular/core';
import { arrayifyProps } from './arrayify-props';
import { getTreeFlattener } from './flatten';
export interface DirectiveTreeData {
dataSource: PropertyDataSource;
treeControl: FlatTreeControl<FlatNode>;
}
const expandable = (prop: Descriptor) => {
if (!prop) {
return false;
}
if (!prop.expandable) {
return false;
}
return !(prop.type !== PropType.Object && prop.type !== PropType.Array);
};
const getDirectiveControls = (
dataSource: PropertyDataSource
): { dataSource: PropertyDataSource; treeControl: FlatTreeControl<FlatNode> } => {
@@ -43,18 +31,7 @@ export const constructPathOfKeysToPropertyValue = (nodePropToGetKeysFor: Propert
};
export class DirectivePropertyResolver {
private _treeFlattener = new MatTreeFlattener(
(node: Property, level: number): FlatNode => {
return {
expandable: expandable(node.descriptor),
prop: node,
level,
};
},
(node) => node.level,
(node) => node.expandable,
(node) => this._getChildren(node)
);
private _treeFlattener = getTreeFlattener();
private _treeControl = new FlatTreeControl<FlatNode>(
(node) => node.level,
@@ -125,18 +102,6 @@ export class DirectivePropertyResolver {
node.prop.descriptor.value = newValue;
}
private _getChildren(prop: Property): Property[] | undefined {
const descriptor = prop.descriptor;
if (
(descriptor.type === PropType.Object || descriptor.type === PropType.Array) &&
!(descriptor.value instanceof Observable)
) {
return arrayifyProps(descriptor.value || {}, prop);
} else {
console.error('Unexpected data type', descriptor, 'in property', prop);
}
}
private _initDataSources(): void {
const { inputProps, outputProps, stateProps } = this._classifyProperties();
@@ -0,0 +1,41 @@
import { MatTreeFlattener } from '@angular/material/tree';
import { Descriptor, PropType } from 'protocol';
import { Observable } from 'rxjs';
import { arrayifyProps } from './arrayify-props';
import { FlatNode, Property } from './element-property-resolver';
export const getTreeFlattener = () =>
new MatTreeFlattener(
(node: Property, level: number): FlatNode => {
return {
expandable: expandable(node.descriptor),
prop: node,
level,
};
},
(node) => node.level,
(node) => node.expandable,
(node) => getChildren(node)
);
export const expandable = (prop: Descriptor) => {
if (!prop) {
return false;
}
if (!prop.expandable) {
return false;
}
return !(prop.type !== PropType.Object && prop.type !== PropType.Array);
};
const getChildren = (prop: Property): Property[] | undefined => {
const descriptor = prop.descriptor;
if (
(descriptor.type === PropType.Object || descriptor.type === PropType.Array) &&
!(descriptor.value instanceof Observable)
) {
return arrayifyProps(descriptor.value || {}, prop);
} else {
console.error('Unexpected data type', descriptor, 'in property', prop);
}
};
@@ -0,0 +1,58 @@
import { FlatTreeControl } from '@angular/cdk/tree';
import { PropType } from 'protocol';
import { FlatNode } from './element-property-resolver';
import { getTreeFlattener } from './flatten';
import { PropertyDataSource } from './property-data-source';
const flatTreeControl = new FlatTreeControl<FlatNode>(
(node) => node.level,
(node) => node.expandable
);
describe('PropertyDataSource', () => {
it('should detect changes in the collection', () => {
const source = new PropertyDataSource(
{
foo: {
editable: true,
expandable: false,
preview: '42',
type: PropType.Number,
value: 42,
},
},
getTreeFlattener(),
flatTreeControl,
{ element: [1, 2, 3] },
null as any
);
source.update({
foo: {
editable: true,
expandable: false,
preview: '43',
type: PropType.Number,
value: 43,
},
});
expect(source.data).toEqual([
{
expandable: false,
level: 0,
prop: {
descriptor: {
editable: true,
expandable: false,
preview: '43',
type: PropType.Number,
value: 43,
},
name: 'foo',
parent: null,
},
},
]);
});
});
@@ -9,9 +9,7 @@ import { diff } from '../../diffing';
import { FlatNode, Property } from './element-property-resolver';
import { arrayifyProps } from './arrayify-props';
const trackBy = (_: number, item: FlatNode) => {
return `#${item.prop.name}#${item.level}`;
};
const trackBy = (_: number, item: FlatNode) => `#${item.prop.name}#${item.prop.descriptor.preview}#${item.level}`;
export class PropertyDataSource extends DataSource<FlatNode> {
private _data = new BehaviorSubject<FlatNode[]>([]);