939 lines
23 KiB
JavaScript
939 lines
23 KiB
JavaScript
/**
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @flow
|
|
*/
|
|
|
|
import type {FrontendBridge} from 'react-devtools-shared/src/bridge';
|
|
import type Store from 'react-devtools-shared/src/devtools/store';
|
|
|
|
describe('InspectedElementContext', () => {
|
|
let React;
|
|
let ReactDOM;
|
|
let bridge: FrontendBridge;
|
|
let store: Store;
|
|
|
|
let backendAPI;
|
|
|
|
const act = (callback: Function) => {
|
|
callback();
|
|
|
|
jest.runAllTimers(); // Flush Bridge operations
|
|
};
|
|
|
|
async function read(
|
|
id: number,
|
|
path?: Array<string | number> = null,
|
|
): Promise<Object> {
|
|
const rendererID = ((store.getRendererIDForElement(id): any): number);
|
|
const promise = backendAPI
|
|
.inspectElement({
|
|
bridge,
|
|
id,
|
|
path,
|
|
rendererID,
|
|
})
|
|
.then(data =>
|
|
backendAPI.convertInspectedElementBackendToFrontend(data.value),
|
|
);
|
|
|
|
jest.runOnlyPendingTimers();
|
|
|
|
return promise;
|
|
}
|
|
|
|
beforeEach(() => {
|
|
bridge = global.bridge;
|
|
store = global.store;
|
|
|
|
backendAPI = require('react-devtools-shared/src/backendAPI');
|
|
|
|
// Redirect all React/ReactDOM requires to the v15 UMD.
|
|
// We use the UMD because Jest doesn't enable us to mock deep imports (e.g. "react/lib/Something").
|
|
jest.mock('react', () => jest.requireActual('react-15/dist/react.js'));
|
|
jest.mock('react-dom', () =>
|
|
jest.requireActual('react-dom-15/dist/react-dom.js'),
|
|
);
|
|
|
|
React = require('react');
|
|
ReactDOM = require('react-dom');
|
|
});
|
|
|
|
// @reactVersion >= 16.0
|
|
it('should inspect the currently selected element', async () => {
|
|
const Example = () => null;
|
|
|
|
act(() =>
|
|
ReactDOM.render(<Example a={1} b="abc" />, document.createElement('div')),
|
|
);
|
|
|
|
const id = ((store.getElementIDAtIndex(0): any): number);
|
|
const inspectedElement = await read(id);
|
|
|
|
expect(inspectedElement).toMatchInlineSnapshot(`
|
|
{
|
|
"context": {},
|
|
"events": undefined,
|
|
"hooks": null,
|
|
"id": 2,
|
|
"owners": null,
|
|
"props": {
|
|
"a": 1,
|
|
"b": "abc",
|
|
},
|
|
"rootType": null,
|
|
"state": null,
|
|
}
|
|
`);
|
|
});
|
|
|
|
// @reactVersion >= 16.0
|
|
it('should support simple data types', async () => {
|
|
const Example = () => null;
|
|
|
|
act(() =>
|
|
ReactDOM.render(
|
|
<Example
|
|
boolean_false={false}
|
|
boolean_true={true}
|
|
infinity={Infinity}
|
|
integer_zero={0}
|
|
integer_one={1}
|
|
float={1.23}
|
|
string="abc"
|
|
string_empty=""
|
|
nan={NaN}
|
|
value_null={null}
|
|
value_undefined={undefined}
|
|
/>,
|
|
document.createElement('div'),
|
|
),
|
|
);
|
|
|
|
const id = ((store.getElementIDAtIndex(0): any): number);
|
|
const inspectedElement = await read(id);
|
|
|
|
expect(inspectedElement).toMatchInlineSnapshot(`
|
|
{
|
|
"context": {},
|
|
"events": undefined,
|
|
"hooks": null,
|
|
"id": 2,
|
|
"owners": null,
|
|
"props": {
|
|
"boolean_false": false,
|
|
"boolean_true": true,
|
|
"float": 1.23,
|
|
"infinity": Infinity,
|
|
"integer_one": 1,
|
|
"integer_zero": 0,
|
|
"nan": NaN,
|
|
"string": "abc",
|
|
"string_empty": "",
|
|
"value_null": null,
|
|
"value_undefined": undefined,
|
|
},
|
|
"rootType": null,
|
|
"state": null,
|
|
}
|
|
`);
|
|
});
|
|
|
|
// @reactVersion >= 16.0
|
|
it('should support complex data types', async () => {
|
|
const Immutable = require('immutable');
|
|
|
|
const Example = () => null;
|
|
|
|
const arrayOfArrays = [[['abc', 123, true], []]];
|
|
const div = document.createElement('div');
|
|
const exampleFunction = () => {};
|
|
const setShallow = new Set(['abc', 123]);
|
|
const mapShallow = new Map([
|
|
['name', 'Brian'],
|
|
['food', 'sushi'],
|
|
]);
|
|
const setOfSets = new Set([new Set(['a', 'b', 'c']), new Set([1, 2, 3])]);
|
|
const mapOfMaps = new Map([
|
|
['first', mapShallow],
|
|
['second', mapShallow],
|
|
]);
|
|
const objectOfObjects = {
|
|
inner: {string: 'abc', number: 123, boolean: true},
|
|
};
|
|
const typedArray = Int8Array.from([100, -100, 0]);
|
|
const arrayBuffer = typedArray.buffer;
|
|
const dataView = new DataView(arrayBuffer);
|
|
const immutableMap = Immutable.fromJS({
|
|
a: [{hello: 'there'}, 'fixed', true],
|
|
b: 123,
|
|
c: {
|
|
'1': 'xyz',
|
|
xyz: 1,
|
|
},
|
|
});
|
|
|
|
class Class {
|
|
anonymousFunction = () => {};
|
|
}
|
|
const instance = new Class();
|
|
|
|
act(() =>
|
|
ReactDOM.render(
|
|
<Example
|
|
anonymous_fn={instance.anonymousFunction}
|
|
array_buffer={arrayBuffer}
|
|
array_of_arrays={arrayOfArrays}
|
|
// eslint-disable-next-line no-undef
|
|
big_int={BigInt(123)}
|
|
bound_fn={exampleFunction.bind(this)}
|
|
data_view={dataView}
|
|
date={new Date(123)}
|
|
fn={exampleFunction}
|
|
html_element={div}
|
|
immutable={immutableMap}
|
|
map={mapShallow}
|
|
map_of_maps={mapOfMaps}
|
|
object_of_objects={objectOfObjects}
|
|
react_element={<span />}
|
|
regexp={/abc/giu}
|
|
set={setShallow}
|
|
set_of_sets={setOfSets}
|
|
symbol={Symbol('symbol')}
|
|
typed_array={typedArray}
|
|
/>,
|
|
document.createElement('div'),
|
|
),
|
|
);
|
|
|
|
const id = ((store.getElementIDAtIndex(0): any): number);
|
|
const inspectedElement = await read(id);
|
|
|
|
expect(inspectedElement.props).toMatchInlineSnapshot(`
|
|
{
|
|
"anonymous_fn": Dehydrated {
|
|
"preview_short": ƒ () {},
|
|
"preview_long": ƒ () {},
|
|
},
|
|
"array_buffer": Dehydrated {
|
|
"preview_short": ArrayBuffer(3),
|
|
"preview_long": ArrayBuffer(3),
|
|
},
|
|
"array_of_arrays": [
|
|
Dehydrated {
|
|
"preview_short": Array(2),
|
|
"preview_long": [Array(3), Array(0)],
|
|
},
|
|
],
|
|
"big_int": Dehydrated {
|
|
"preview_short": 123n,
|
|
"preview_long": 123n,
|
|
},
|
|
"bound_fn": Dehydrated {
|
|
"preview_short": ƒ bound exampleFunction() {},
|
|
"preview_long": ƒ bound exampleFunction() {},
|
|
},
|
|
"data_view": Dehydrated {
|
|
"preview_short": DataView(3),
|
|
"preview_long": DataView(3),
|
|
},
|
|
"date": Dehydrated {
|
|
"preview_short": Thu Jan 01 1970 00:00:00 GMT+0000 (Coordinated Universal Time),
|
|
"preview_long": Thu Jan 01 1970 00:00:00 GMT+0000 (Coordinated Universal Time),
|
|
},
|
|
"fn": Dehydrated {
|
|
"preview_short": ƒ exampleFunction() {},
|
|
"preview_long": ƒ exampleFunction() {},
|
|
},
|
|
"html_element": Dehydrated {
|
|
"preview_short": <div />,
|
|
"preview_long": <div />,
|
|
},
|
|
"immutable": {
|
|
"0": Dehydrated {
|
|
"preview_short": Array(2),
|
|
"preview_long": ["a", List(3)],
|
|
},
|
|
"1": Dehydrated {
|
|
"preview_short": Array(2),
|
|
"preview_long": ["b", 123],
|
|
},
|
|
"2": Dehydrated {
|
|
"preview_short": Array(2),
|
|
"preview_long": ["c", Map(2)],
|
|
},
|
|
},
|
|
"map": {
|
|
"0": Dehydrated {
|
|
"preview_short": Array(2),
|
|
"preview_long": ["name", "Brian"],
|
|
},
|
|
"1": Dehydrated {
|
|
"preview_short": Array(2),
|
|
"preview_long": ["food", "sushi"],
|
|
},
|
|
},
|
|
"map_of_maps": {
|
|
"0": Dehydrated {
|
|
"preview_short": Array(2),
|
|
"preview_long": ["first", Map(2)],
|
|
},
|
|
"1": Dehydrated {
|
|
"preview_short": Array(2),
|
|
"preview_long": ["second", Map(2)],
|
|
},
|
|
},
|
|
"object_of_objects": {
|
|
"inner": Dehydrated {
|
|
"preview_short": {…},
|
|
"preview_long": {boolean: true, number: 123, string: "abc"},
|
|
},
|
|
},
|
|
"react_element": Dehydrated {
|
|
"preview_short": <span />,
|
|
"preview_long": <span />,
|
|
},
|
|
"regexp": Dehydrated {
|
|
"preview_short": /abc/giu,
|
|
"preview_long": /abc/giu,
|
|
},
|
|
"set": {
|
|
"0": "abc",
|
|
"1": 123,
|
|
},
|
|
"set_of_sets": {
|
|
"0": Dehydrated {
|
|
"preview_short": Set(3),
|
|
"preview_long": Set(3) {"a", "b", "c"},
|
|
},
|
|
"1": Dehydrated {
|
|
"preview_short": Set(3),
|
|
"preview_long": Set(3) {1, 2, 3},
|
|
},
|
|
},
|
|
"symbol": Dehydrated {
|
|
"preview_short": Symbol(symbol),
|
|
"preview_long": Symbol(symbol),
|
|
},
|
|
"typed_array": {
|
|
"0": 100,
|
|
"1": -100,
|
|
"2": 0,
|
|
},
|
|
}
|
|
`);
|
|
});
|
|
|
|
// @reactVersion >= 16.0
|
|
it('should support objects with no prototype', async () => {
|
|
const Example = () => null;
|
|
|
|
const object = Object.create(null);
|
|
object.string = 'abc';
|
|
object.number = 123;
|
|
object.boolean = true;
|
|
|
|
act(() =>
|
|
ReactDOM.render(
|
|
<Example object={object} />,
|
|
document.createElement('div'),
|
|
),
|
|
);
|
|
|
|
const id = ((store.getElementIDAtIndex(0): any): number);
|
|
const inspectedElement = await read(id);
|
|
|
|
expect(inspectedElement.props).toMatchInlineSnapshot(`
|
|
{
|
|
"object": {
|
|
"boolean": true,
|
|
"number": 123,
|
|
"string": "abc",
|
|
},
|
|
}
|
|
`);
|
|
});
|
|
|
|
// @reactVersion >= 16.0
|
|
it('should support objects with overridden hasOwnProperty', async () => {
|
|
const Example = () => null;
|
|
|
|
const object = {
|
|
name: 'blah',
|
|
hasOwnProperty: true,
|
|
};
|
|
|
|
act(() =>
|
|
ReactDOM.render(
|
|
<Example object={object} />,
|
|
document.createElement('div'),
|
|
),
|
|
);
|
|
|
|
const id = ((store.getElementIDAtIndex(0): any): number);
|
|
const inspectedElement = await read(id);
|
|
|
|
// TRICKY: Don't use toMatchInlineSnapshot() for this test!
|
|
// Our snapshot serializer relies on hasOwnProperty() for feature detection.
|
|
expect(inspectedElement.props.object.name).toBe('blah');
|
|
expect(inspectedElement.props.object.hasOwnProperty).toBe(true);
|
|
});
|
|
|
|
// @reactVersion >= 16.0
|
|
it('should not consume iterables while inspecting', async () => {
|
|
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).toMatchInlineSnapshot(`
|
|
{
|
|
"context": {},
|
|
"events": undefined,
|
|
"hooks": null,
|
|
"id": 2,
|
|
"owners": null,
|
|
"props": {
|
|
"iteratable": Dehydrated {
|
|
"preview_short": Generator,
|
|
"preview_long": Generator,
|
|
},
|
|
},
|
|
"rootType": null,
|
|
"state": null,
|
|
}
|
|
`);
|
|
|
|
// Inspecting should not consume the iterable.
|
|
expect(iteratable.next().value).toEqual(1);
|
|
expect(iteratable.next().value).toEqual(2);
|
|
expect(iteratable.next().value).toBeUndefined();
|
|
});
|
|
|
|
// @reactVersion >= 16.0
|
|
it('should support custom objects with enumerable properties and getters', async () => {
|
|
class CustomData {
|
|
_number = 42;
|
|
get number() {
|
|
return this._number;
|
|
}
|
|
set number(value) {
|
|
this._number = value;
|
|
}
|
|
}
|
|
|
|
const descriptor = ((Object.getOwnPropertyDescriptor(
|
|
CustomData.prototype,
|
|
'number',
|
|
): any): PropertyDescriptor<number>);
|
|
descriptor.enumerable = true;
|
|
Object.defineProperty(CustomData.prototype, 'number', descriptor);
|
|
|
|
const Example = ({data}) => null;
|
|
|
|
act(() =>
|
|
ReactDOM.render(
|
|
<Example data={new CustomData()} />,
|
|
document.createElement('div'),
|
|
),
|
|
);
|
|
|
|
const id = ((store.getElementIDAtIndex(0): any): number);
|
|
const inspectedElement = await read(id);
|
|
|
|
expect(inspectedElement).toMatchInlineSnapshot(`
|
|
{
|
|
"context": {},
|
|
"events": undefined,
|
|
"hooks": null,
|
|
"id": 2,
|
|
"owners": null,
|
|
"props": {
|
|
"data": {
|
|
"_number": 42,
|
|
"number": 42,
|
|
},
|
|
},
|
|
"rootType": null,
|
|
"state": null,
|
|
}
|
|
`);
|
|
});
|
|
|
|
// @reactVersion >= 16.0
|
|
it('should support objects with inherited keys', async () => {
|
|
const Example = () => null;
|
|
|
|
const base = Object.create(Object.prototype, {
|
|
enumerableStringBase: {
|
|
value: 1,
|
|
writable: true,
|
|
enumerable: true,
|
|
configurable: true,
|
|
},
|
|
[Symbol('enumerableSymbolBase')]: {
|
|
value: 1,
|
|
writable: true,
|
|
enumerable: true,
|
|
configurable: true,
|
|
},
|
|
nonEnumerableStringBase: {
|
|
value: 1,
|
|
writable: true,
|
|
enumerable: false,
|
|
configurable: true,
|
|
},
|
|
[Symbol('nonEnumerableSymbolBase')]: {
|
|
value: 1,
|
|
writable: true,
|
|
enumerable: false,
|
|
configurable: true,
|
|
},
|
|
});
|
|
|
|
const object = Object.create(base, {
|
|
enumerableString: {
|
|
value: 2,
|
|
writable: true,
|
|
enumerable: true,
|
|
configurable: true,
|
|
},
|
|
nonEnumerableString: {
|
|
value: 3,
|
|
writable: true,
|
|
enumerable: false,
|
|
configurable: true,
|
|
},
|
|
123: {
|
|
value: 3,
|
|
writable: true,
|
|
enumerable: true,
|
|
configurable: true,
|
|
},
|
|
[Symbol('nonEnumerableSymbol')]: {
|
|
value: 2,
|
|
writable: true,
|
|
enumerable: false,
|
|
configurable: true,
|
|
},
|
|
[Symbol('enumerableSymbol')]: {
|
|
value: 3,
|
|
writable: true,
|
|
enumerable: true,
|
|
configurable: true,
|
|
},
|
|
});
|
|
|
|
act(() =>
|
|
ReactDOM.render(<Example data={object} />, document.createElement('div')),
|
|
);
|
|
|
|
const id = ((store.getElementIDAtIndex(0): any): number);
|
|
const inspectedElement = await read(id);
|
|
|
|
expect(inspectedElement).toMatchInlineSnapshot(`
|
|
{
|
|
"context": {},
|
|
"events": undefined,
|
|
"hooks": null,
|
|
"id": 2,
|
|
"owners": null,
|
|
"props": {
|
|
"data": {
|
|
"123": 3,
|
|
"Symbol(enumerableSymbol)": 3,
|
|
"Symbol(enumerableSymbolBase)": 1,
|
|
"enumerableString": 2,
|
|
"enumerableStringBase": 1,
|
|
},
|
|
},
|
|
"rootType": null,
|
|
"state": null,
|
|
}
|
|
`);
|
|
});
|
|
|
|
// @reactVersion >= 16.0
|
|
it('should allow component prop value and value`s prototype has same name params.', async () => {
|
|
const testData = Object.create(
|
|
{
|
|
a: undefined,
|
|
b: Infinity,
|
|
c: NaN,
|
|
d: 'normal',
|
|
},
|
|
{
|
|
a: {
|
|
value: undefined,
|
|
writable: true,
|
|
enumerable: true,
|
|
configurable: true,
|
|
},
|
|
b: {
|
|
value: Infinity,
|
|
writable: true,
|
|
enumerable: true,
|
|
configurable: true,
|
|
},
|
|
c: {
|
|
value: NaN,
|
|
writable: true,
|
|
enumerable: true,
|
|
configurable: true,
|
|
},
|
|
d: {
|
|
value: 'normal',
|
|
writable: true,
|
|
enumerable: true,
|
|
configurable: true,
|
|
},
|
|
},
|
|
);
|
|
|
|
const Example = ({data}) => null;
|
|
act(() =>
|
|
ReactDOM.render(
|
|
<Example data={testData} />,
|
|
document.createElement('div'),
|
|
),
|
|
);
|
|
|
|
const id = ((store.getElementIDAtIndex(0): any): number);
|
|
const inspectedElement = await read(id);
|
|
|
|
expect(inspectedElement.props).toMatchInlineSnapshot(`
|
|
{
|
|
"data": {
|
|
"a": undefined,
|
|
"b": Infinity,
|
|
"c": NaN,
|
|
"d": "normal",
|
|
},
|
|
}
|
|
`);
|
|
});
|
|
|
|
// @reactVersion >= 16.0
|
|
it('should not dehydrate nested values until explicitly requested', async () => {
|
|
const Example = () => null;
|
|
|
|
act(() =>
|
|
ReactDOM.render(
|
|
<Example
|
|
nestedObject={{
|
|
a: {
|
|
b: {
|
|
c: [
|
|
{
|
|
d: {
|
|
e: {},
|
|
},
|
|
},
|
|
],
|
|
},
|
|
},
|
|
}}
|
|
/>,
|
|
document.createElement('div'),
|
|
),
|
|
);
|
|
|
|
const id = ((store.getElementIDAtIndex(0): any): number);
|
|
|
|
let inspectedElement = await read(id);
|
|
expect(inspectedElement.props).toMatchInlineSnapshot(`
|
|
{
|
|
"nestedObject": {
|
|
"a": Dehydrated {
|
|
"preview_short": {…},
|
|
"preview_long": {b: {…}},
|
|
},
|
|
},
|
|
}
|
|
`);
|
|
|
|
inspectedElement = await read(id, ['props', 'nestedObject', 'a']);
|
|
expect(inspectedElement.props).toMatchInlineSnapshot(`
|
|
{
|
|
"nestedObject": {
|
|
"a": {
|
|
"b": {
|
|
"c": Dehydrated {
|
|
"preview_short": Array(1),
|
|
"preview_long": [{…}],
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
`);
|
|
|
|
inspectedElement = await read(id, ['props', 'nestedObject', 'a', 'b', 'c']);
|
|
expect(inspectedElement.props).toMatchInlineSnapshot(`
|
|
{
|
|
"nestedObject": {
|
|
"a": {
|
|
"b": {
|
|
"c": [
|
|
{
|
|
"d": Dehydrated {
|
|
"preview_short": {…},
|
|
"preview_long": {e: {…}},
|
|
},
|
|
},
|
|
],
|
|
},
|
|
},
|
|
},
|
|
}
|
|
`);
|
|
|
|
inspectedElement = await read(id, [
|
|
'props',
|
|
'nestedObject',
|
|
'a',
|
|
'b',
|
|
'c',
|
|
0,
|
|
'd',
|
|
]);
|
|
expect(inspectedElement.props).toMatchInlineSnapshot(`
|
|
{
|
|
"nestedObject": {
|
|
"a": {
|
|
"b": {
|
|
"c": [
|
|
{
|
|
"d": {
|
|
"e": {},
|
|
},
|
|
},
|
|
],
|
|
},
|
|
},
|
|
},
|
|
}
|
|
`);
|
|
});
|
|
|
|
// @reactVersion >= 16.0
|
|
it('should enable inspected values to be stored as global variables', () => {
|
|
const Example = () => null;
|
|
|
|
const nestedObject = {
|
|
a: {
|
|
value: 1,
|
|
b: {
|
|
value: 1,
|
|
c: {
|
|
value: 1,
|
|
},
|
|
},
|
|
},
|
|
};
|
|
|
|
act(() =>
|
|
ReactDOM.render(
|
|
<Example nestedObject={nestedObject} />,
|
|
document.createElement('div'),
|
|
),
|
|
);
|
|
|
|
const id = ((store.getElementIDAtIndex(0): any): number);
|
|
const rendererID = ((store.getRendererIDForElement(id): any): number);
|
|
|
|
const logSpy = jest.fn();
|
|
jest.spyOn(console, 'log').mockImplementation(logSpy);
|
|
|
|
// Should store the whole value (not just the hydrated parts)
|
|
backendAPI.storeAsGlobal({
|
|
bridge,
|
|
id,
|
|
path: ['props', 'nestedObject'],
|
|
rendererID,
|
|
});
|
|
|
|
jest.runOnlyPendingTimers();
|
|
expect(logSpy).toHaveBeenCalledWith('$reactTemp0');
|
|
expect(global.$reactTemp0).toBe(nestedObject);
|
|
|
|
logSpy.mockReset();
|
|
|
|
// Should store the nested property specified (not just the outer value)
|
|
backendAPI.storeAsGlobal({
|
|
bridge,
|
|
id,
|
|
path: ['props', 'nestedObject', 'a', 'b'],
|
|
rendererID,
|
|
});
|
|
|
|
jest.runOnlyPendingTimers();
|
|
expect(logSpy).toHaveBeenCalledWith('$reactTemp1');
|
|
expect(global.$reactTemp1).toBe(nestedObject.a.b);
|
|
});
|
|
|
|
// @reactVersion >= 16.0
|
|
it('should enable inspected values to be copied to the clipboard', () => {
|
|
const Example = () => null;
|
|
|
|
const nestedObject = {
|
|
a: {
|
|
value: 1,
|
|
b: {
|
|
value: 1,
|
|
c: {
|
|
value: 1,
|
|
},
|
|
},
|
|
},
|
|
};
|
|
|
|
act(() =>
|
|
ReactDOM.render(
|
|
<Example nestedObject={nestedObject} />,
|
|
document.createElement('div'),
|
|
),
|
|
);
|
|
|
|
const id = ((store.getElementIDAtIndex(0): any): number);
|
|
const rendererID = ((store.getRendererIDForElement(id): any): number);
|
|
|
|
// Should copy the whole value (not just the hydrated parts)
|
|
backendAPI.copyInspectedElementPath({
|
|
bridge,
|
|
id,
|
|
path: ['props', 'nestedObject'],
|
|
rendererID,
|
|
});
|
|
|
|
jest.runOnlyPendingTimers();
|
|
expect(global.mockClipboardCopy).toHaveBeenCalledTimes(1);
|
|
expect(global.mockClipboardCopy).toHaveBeenCalledWith(
|
|
JSON.stringify(nestedObject),
|
|
);
|
|
|
|
global.mockClipboardCopy.mockReset();
|
|
|
|
// Should copy the nested property specified (not just the outer value)
|
|
backendAPI.copyInspectedElementPath({
|
|
bridge,
|
|
id,
|
|
path: ['props', 'nestedObject', 'a', 'b'],
|
|
rendererID,
|
|
});
|
|
|
|
jest.runOnlyPendingTimers();
|
|
expect(global.mockClipboardCopy).toHaveBeenCalledTimes(1);
|
|
expect(global.mockClipboardCopy).toHaveBeenCalledWith(
|
|
JSON.stringify(nestedObject.a.b),
|
|
);
|
|
});
|
|
|
|
// @reactVersion >= 16.0
|
|
it('should enable complex values to be copied to the clipboard', () => {
|
|
const Immutable = require('immutable');
|
|
|
|
const Example = () => null;
|
|
|
|
const set = new Set(['abc', 123]);
|
|
const map = new Map([
|
|
['name', 'Brian'],
|
|
['food', 'sushi'],
|
|
]);
|
|
const setOfSets = new Set([new Set(['a', 'b', 'c']), new Set([1, 2, 3])]);
|
|
const mapOfMaps = new Map([
|
|
['first', map],
|
|
['second', map],
|
|
]);
|
|
const typedArray = Int8Array.from([100, -100, 0]);
|
|
const arrayBuffer = typedArray.buffer;
|
|
const dataView = new DataView(arrayBuffer);
|
|
const immutable = Immutable.fromJS({
|
|
a: [{hello: 'there'}, 'fixed', true],
|
|
b: 123,
|
|
c: {
|
|
'1': 'xyz',
|
|
xyz: 1,
|
|
},
|
|
});
|
|
const bigInt = BigInt(123); // eslint-disable-line no-undef
|
|
|
|
act(() =>
|
|
ReactDOM.render(
|
|
<Example
|
|
arrayBuffer={arrayBuffer}
|
|
dataView={dataView}
|
|
map={map}
|
|
set={set}
|
|
mapOfMaps={mapOfMaps}
|
|
setOfSets={setOfSets}
|
|
typedArray={typedArray}
|
|
immutable={immutable}
|
|
bigInt={bigInt}
|
|
/>,
|
|
document.createElement('div'),
|
|
),
|
|
);
|
|
|
|
const id = ((store.getElementIDAtIndex(0): any): number);
|
|
const rendererID = ((store.getRendererIDForElement(id): any): number);
|
|
|
|
// Should copy the whole value (not just the hydrated parts)
|
|
backendAPI.copyInspectedElementPath({
|
|
bridge,
|
|
id,
|
|
path: ['props'],
|
|
rendererID,
|
|
});
|
|
jest.runOnlyPendingTimers();
|
|
// Should not error despite lots of unserialized values.
|
|
|
|
global.mockClipboardCopy.mockReset();
|
|
|
|
// Should copy the nested property specified (not just the outer value)
|
|
backendAPI.copyInspectedElementPath({
|
|
bridge,
|
|
id,
|
|
path: ['props', 'bigInt'],
|
|
rendererID,
|
|
});
|
|
jest.runOnlyPendingTimers();
|
|
expect(global.mockClipboardCopy).toHaveBeenCalledTimes(1);
|
|
expect(global.mockClipboardCopy).toHaveBeenCalledWith(
|
|
JSON.stringify('123n'),
|
|
);
|
|
|
|
global.mockClipboardCopy.mockReset();
|
|
|
|
// Should copy the nested property specified (not just the outer value)
|
|
backendAPI.copyInspectedElementPath({
|
|
bridge,
|
|
id,
|
|
path: ['props', 'typedArray'],
|
|
rendererID,
|
|
});
|
|
jest.runOnlyPendingTimers();
|
|
expect(global.mockClipboardCopy).toHaveBeenCalledTimes(1);
|
|
expect(global.mockClipboardCopy).toHaveBeenCalledWith(
|
|
JSON.stringify({0: 100, 1: -100, 2: 0}),
|
|
);
|
|
});
|
|
});
|