Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added tests and fixed edge cases for DevTools iterator handling
Browse files Browse the repository at this point in the history
Brian Vaughn committed Sep 22, 2020
1 parent f7052e8 commit f8b4c34
Showing 6 changed files with 125 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -200,6 +200,19 @@ exports[`InspectedElementContext should inspect the currently selected element:
}
`;

exports[`InspectedElementContext should not consume iterables while inspecting: 1: Inspected element 2 1`] = `
{
"id": 2,
"owners": null,
"context": null,
"hooks": null,
"props": {
"prop": {}
},
"state": null
}
`;

exports[`InspectedElementContext should not dehydrate nested values until explicitly requested: 1: Initially inspect element 1`] = `
{
"id": 2,
Original file line number Diff line number Diff line change
@@ -789,6 +789,57 @@ describe('InspectedElementContext', () => {
done();
});

it('should not consume iterables while inspecting', async done => {
const Example = () => null;

function* generator() {
throw Error('Should not be consumed!');
}

const container = document.createElement('div');

const iterable = generator();
await utils.actAsync(() =>
ReactDOM.render(<Example prop={iterable} />, container),
);

const id = ((store.getElementIDAtIndex(0): any): number);

let inspectedElement = null;

function Suspender({target}) {
const {getInspectedElement} = React.useContext(InspectedElementContext);
inspectedElement = getInspectedElement(id);
return null;
}

await utils.actAsync(
() =>
TestRenderer.create(
<Contexts
defaultSelectedElementID={id}
defaultSelectedElementIndex={0}>
<React.Suspense fallback={null}>
<Suspender target={id} />
</React.Suspense>
</Contexts>,
),
false,
);

expect(inspectedElement).not.toBeNull();
expect(inspectedElement).toMatchSnapshot(`1: Inspected element ${id}`);

const {prop} = (inspectedElement: any).props;
expect(prop[meta.inspectable]).toBe(false);
expect(prop[meta.name]).toBe('Generator');
expect(prop[meta.type]).toBe('opaque_iterator');
expect(prop[meta.preview_long]).toBe('Generator');
expect(prop[meta.preview_short]).toBe('Generator');

done();
});

it('should support objects with no prototype', async done => {
const Example = () => null;

Original file line number Diff line number Diff line change
@@ -18,6 +18,23 @@ Object {
}
`;

exports[`InspectedElementContext should not consume iterables while inspecting: 1: Initial inspection 1`] = `
Object {
"id": 2,
"type": "full-data",
"value": {
"id": 2,
"owners": null,
"context": {},
"hooks": null,
"props": {
"iteratable": {}
},
"state": null
},
}
`;

exports[`InspectedElementContext should not dehydrate nested values until explicitly requested: 1: Initially inspect element 1`] = `
Object {
"id": 2,
Original file line number Diff line number Diff line change
@@ -397,6 +397,36 @@ describe('InspectedElementContext', () => {
done();
});

it('should not consume iterables while inspecting', async done => {
const Example = () => null;

function* generator() {
yield 1;
yield 2;
}

const iteratable = generator();

act(() =>
ReactDOM.render(
<Example iteratable={iteratable} />,
document.createElement('div'),
),
);

const id = ((store.getElementIDAtIndex(0): any): number);
const inspectedElement = await read(id);

expect(inspectedElement).toMatchSnapshot('1: Initial inspection');

// Inspecting should not consume the iterable.
expect(iteratable.next().value).toEqual(1);
expect(iteratable.next().value).toEqual(2);
expect(iteratable.next().value).toBeUndefined();

done();
});

it('should support custom objects with enumerable properties and getters', async done => {
class CustomData {
_number = 42;
10 changes: 10 additions & 0 deletions packages/react-devtools-shared/src/hydration.js
Original file line number Diff line number Diff line change
@@ -265,6 +265,16 @@ export function dehydrate(
return unserializableValue;
}

case 'opaque_iterator':
cleaned.push(path);
return {
inspectable: false,
preview_short: formatDataForPreview(data, false),
preview_long: formatDataForPreview(data, true),
name: data[Symbol.toStringTag],
type,
};

case 'date':
cleaned.push(path);
return {
10 changes: 4 additions & 6 deletions packages/react-devtools-shared/src/utils.js
Original file line number Diff line number Diff line change
@@ -199,8 +199,6 @@ export function printOperationsArray(operations: Array<number>) {
throw Error(`Unsupported Bridge operation ${operation}`);
}
}

console.log(logs.join('\n '));
}

export function getDefaultComponentFilters(): Array<ComponentFilter> {
@@ -437,9 +435,9 @@ export function getDataType(data: Object): DataType {
// but this seems kind of awkward and expensive.
return 'array_buffer';
} else if (typeof data[Symbol.iterator] === 'function') {
return 'iterator';
} else if (data[Symbol.iterator] === 'data') {
return 'opaque_iterator';
return data[Symbol.iterator]() === data
? 'opaque_iterator'
: 'iterator';
} else if (data.constructor && data.constructor.name === 'RegExp') {
return 'regexp';
} else {
@@ -658,7 +656,7 @@ export function formatDataForPreview(
return `${name}(${data.size})`;
}
case 'opaque_iterator': {
return `${data.constructor.name}(${data.size})`;
return `${data[Symbol.toStringTag]}`;
}
case 'date':
return data.toString();

0 comments on commit f8b4c34

Please sign in to comment.