@memlab/lens 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +63 -0
  2. package/dist/index.js +112 -0
  3. package/explainer.md +54 -0
  4. package/package.json +34 -0
  5. package/playwright.config.ts +21 -0
  6. package/src/config/config.ts +32 -0
  7. package/src/core/dom-observer.ts +189 -0
  8. package/src/core/event-listener-tracker.ts +171 -0
  9. package/src/core/react-fiber-analysis.ts +123 -0
  10. package/src/core/react-memory-scan.ts +366 -0
  11. package/src/core/types.ts +180 -0
  12. package/src/core/valid-component-name.ts +17 -0
  13. package/src/extensions/basic-extension.ts +41 -0
  14. package/src/extensions/dom-visualization-extension.ts +42 -0
  15. package/src/index.ts +16 -0
  16. package/src/memlens.lib.js.flow +75 -0
  17. package/src/memlens.lib.ts +22 -0
  18. package/src/memlens.run.ts +21 -0
  19. package/src/tests/bundle/lib.bundle.test.ts +31 -0
  20. package/src/tests/bundle/run.bundle.start.head.test.ts +48 -0
  21. package/src/tests/bundle/run.bundle.start.test.ts +48 -0
  22. package/src/tests/bundle/run.bundle.test.ts +51 -0
  23. package/src/tests/fiber/dev.fiber.complex.dev.test.ts +92 -0
  24. package/src/tests/fiber/dev.fiber.complex.list.dev.test.ts +118 -0
  25. package/src/tests/fiber/dev.fiber.complex.prod.test.ts +92 -0
  26. package/src/tests/fiber/dev.fiber.name.dev.test.ts +77 -0
  27. package/src/tests/fiber/dev.fiber.name.prod.test.ts +79 -0
  28. package/src/tests/lib/babel.prod.js +4 -0
  29. package/src/tests/lib/react-dom-v18.dev.js +29926 -0
  30. package/src/tests/lib/react-dom-v18.prod.js +269 -0
  31. package/src/tests/lib/react-v18.dev.js +3345 -0
  32. package/src/tests/lib/react-v18.prod.js +33 -0
  33. package/src/tests/manual/playwright-open-manual.js +40 -0
  34. package/src/tests/manual/todo-list/todo-with-run.bundle.html +80 -0
  35. package/src/tests/utils/test-utils.ts +28 -0
  36. package/src/utils/intersection-observer.ts +65 -0
  37. package/src/utils/react-fiber-utils.ts +212 -0
  38. package/src/utils/utils.ts +201 -0
  39. package/src/utils/weak-ref-utils.ts +308 -0
  40. package/src/visual/components/component-stack-panel.ts +85 -0
  41. package/src/visual/components/control-widget.ts +96 -0
  42. package/src/visual/components/overlay-rectangle.ts +167 -0
  43. package/src/visual/components/status-text.ts +53 -0
  44. package/src/visual/components/toggle-button.ts +57 -0
  45. package/src/visual/components/visual-overlay.ts +19 -0
  46. package/src/visual/dom-element-visualizer-interactive.ts +358 -0
  47. package/src/visual/dom-element-visualizer.ts +130 -0
  48. package/src/visual/visual-utils.ts +89 -0
  49. package/tsconfig.json +18 -0
  50. package/webpack.config.js +105 -0
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @format
8
+ * @oncall memory_lab
9
+ */
10
+ import type ReactMemoryScan from '../core/react-memory-scan';
11
+ import type {AnalysisResult} from '../core/types';
12
+
13
+ /**
14
+ * Base class for React Memory Scanner extensions.
15
+ * Extensions can hook into the scanning process before and after analysis.
16
+ */
17
+ export abstract class BasicExtension {
18
+ protected readonly scanner: ReactMemoryScan;
19
+
20
+ constructor(scanner: ReactMemoryScan) {
21
+ this.scanner = scanner;
22
+ }
23
+
24
+ /**
25
+ * Hook that runs before the memory scan starts.
26
+ * Override this method to perform any setup or pre-scan operations.
27
+ */
28
+ beforeScan(): void {
29
+ // to be overridden
30
+ }
31
+
32
+ /**
33
+ * Hook that runs after the memory scan completes.
34
+ * Override this method to process or modify the analysis results.
35
+ * @param analysisResult - The results from the memory scan
36
+ */
37
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
38
+ afterScan(_analysisResult: AnalysisResult): void {
39
+ // to be overridden
40
+ }
41
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @format
8
+ * @oncall memory_lab
9
+ */
10
+ import type ReactMemoryScan from '../core/react-memory-scan';
11
+ import type {AnalysisResult} from '../core/types';
12
+
13
+ import DOMElementVisualizer from '../visual/dom-element-visualizer';
14
+ import DOMElementVisualizerInteractive from '../visual/dom-element-visualizer-interactive';
15
+ import {BasicExtension} from './basic-extension';
16
+
17
+ const USE_INTERACTIVE_VISUALIZER = true;
18
+
19
+ export class DOMVisualizationExtension extends BasicExtension {
20
+ #domVirtualizer: DOMElementVisualizer;
21
+
22
+ constructor(scanner: ReactMemoryScan) {
23
+ super(scanner);
24
+ if (USE_INTERACTIVE_VISUALIZER) {
25
+ this.#domVirtualizer = new DOMElementVisualizerInteractive();
26
+ } else {
27
+ this.#domVirtualizer = new DOMElementVisualizer();
28
+ }
29
+ }
30
+
31
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
32
+ afterScan(_analysisResult: AnalysisResult): void {
33
+ // const start = Date.now();
34
+ const scanner = this.scanner;
35
+ if (scanner.isDevMode()) {
36
+ const detachedDOMInfo = scanner.getDetachedDOMInfo();
37
+ this.#domVirtualizer.repaint(detachedDOMInfo);
38
+ }
39
+ // const end = Date.now();
40
+ // console.log(`repaint took ${end - start}ms`);
41
+ }
42
+ }
package/src/index.ts ADDED
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @format
8
+ * @oncall memory_lab
9
+ */
10
+ import * as fs from 'fs';
11
+ import * as path from 'path';
12
+
13
+ export function getBundleContent(): string {
14
+ const bundlePath = path.join(__dirname, 'memlens.run.bundle.min.js');
15
+ return fs.readFileSync(bundlePath, 'utf-8');
16
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow strict-local
8
+ * @format
9
+ * @oncall memory_lab
10
+ */
11
+ // Core types
12
+ export type BoundingRect = {
13
+ bottom: number,
14
+ height: number,
15
+ left: number,
16
+ right: number,
17
+ top: number,
18
+ width: number,
19
+ x: number,
20
+ y: number,
21
+ };
22
+
23
+ export type DOMElementInfo = {
24
+ boundingRect: BoundingRect,
25
+ };
26
+
27
+ export type Nullable<T> = T | null;
28
+
29
+ // [lib-index.ts]
30
+ export type ReactMemoryScan = {
31
+ constructor: (options: CreateOptions) => void,
32
+ start: () => void,
33
+ stop: () => void,
34
+ scan: () => void,
35
+ visualize: () => void,
36
+ subscribe: (callback: AnalysisResultCallback) => () => void,
37
+ unsubscribe: (callback: AnalysisResultCallback) => void,
38
+ isDevMode: () => boolean,
39
+ };
40
+
41
+ declare export function createReactMemoryScan(
42
+ options: CreateOptions,
43
+ ): ReactMemoryScan;
44
+
45
+ export type BasicExtension = {
46
+ beforeScan: () => void,
47
+ afterScan: (result: AnalysisResult) => void,
48
+ };
49
+
50
+ export type CreateOptions = {
51
+ isDevMode?: boolean,
52
+ subscribers?: Array<AnalysisResultCallback>,
53
+ extensions?: Array<BasicExtension>,
54
+ scanIntervalMs?: number,
55
+ };
56
+
57
+ export type ScanResult = {
58
+ components: Set<string>,
59
+ componentToFiberNodeCount: Map<string, number>,
60
+ totalElements: number,
61
+ totalDetachedElements: number,
62
+ detachedComponentToFiberNodeCount: Map<string, number>,
63
+ fiberNodes: Array<WeakRef<Fiber>>,
64
+ leakedFibers: Array<WeakRef<Fiber>>,
65
+ };
66
+
67
+ export type AnalysisResult = {
68
+ ...ScanResult,
69
+ start: number,
70
+ end: number,
71
+ };
72
+
73
+ export type AnalysisResultCallback = (result: AnalysisResult) => void;
74
+
75
+ export type DOMObserveCallback = (list: Array<WeakRef<Element>>) => void;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @format
8
+ * @oncall memory_lab
9
+ */
10
+ import ReactMemoryScan from './core/react-memory-scan';
11
+ import {CreateOptions} from './core/types';
12
+ // import { DOMVisualizationExtension } from './extensions/dom-visualization-extension';
13
+
14
+ export function createReactMemoryScan(
15
+ options: CreateOptions = {},
16
+ ): ReactMemoryScan {
17
+ return new ReactMemoryScan(options);
18
+ // const memoryScan = new ReactMemoryScan(options);
19
+ // const domVisualizer = new DOMVisualizationExtension(memoryScan);
20
+ // memoryScan.registerExtension(domVisualizer);
21
+ // return memoryScan;
22
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @format
8
+ * @oncall memory_lab
9
+ */
10
+ import ReactMemoryScan from './core/react-memory-scan';
11
+ import {DOMVisualizationExtension} from './extensions/dom-visualization-extension';
12
+ import {hasRunInSession, setRunInSession} from './utils/utils';
13
+
14
+ if (!hasRunInSession()) {
15
+ const memoryScan = new ReactMemoryScan({isDevMode: true});
16
+ const domVisualizer = new DOMVisualizationExtension(memoryScan);
17
+ memoryScan.registerExtension(domVisualizer);
18
+
19
+ memoryScan.start();
20
+ setRunInSession();
21
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @format
8
+ * @oncall memory_lab
9
+ */
10
+ import type {AnyValue} from '../../core/types';
11
+ import {test, expect} from '@playwright/test';
12
+ import {libBundleFilePath} from '../utils/test-utils';
13
+
14
+ test('test library in browser via addScriptTag', async ({page}) => {
15
+ // Navigate to an empty page (or a test page)
16
+ await page.goto('about:blank');
17
+
18
+ // Inject the lib bundle file (UMD bundle of the library) into the page
19
+ await page.addScriptTag({path: libBundleFilePath});
20
+
21
+ // Now the global `MemLens` should be available in the page
22
+ const libraryLoaded = await page.evaluate(() => {
23
+ const createReactMemoryScan = (window as AnyValue).MemLens
24
+ .createReactMemoryScan;
25
+ const instance = createReactMemoryScan();
26
+ const analysisResult = instance.scan();
27
+ return typeof analysisResult.totalElements === 'number';
28
+ });
29
+
30
+ expect(libraryLoaded).toBe(true);
31
+ });
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @format
8
+ * @oncall memory_lab
9
+ */
10
+ import {test, expect} from '@playwright/test';
11
+ import fs from 'fs';
12
+ import {wait} from '../utils/test-utils';
13
+ import {runBundleFilePath} from '../utils/test-utils';
14
+
15
+ test.describe('run.bundle.js functionality', () => {
16
+ test('should load and execute correctly', async ({page}) => {
17
+ const bundleCode = fs.readFileSync(runBundleFilePath, 'utf8');
18
+
19
+ let bundleLoaded = false;
20
+ // Listen for console messages
21
+ page.on('console', message => {
22
+ // add console.log(message) if you would like to debug
23
+ const msgText = message.text();
24
+ if (msgText.includes('Tracking React and DOM memory')) {
25
+ bundleLoaded = true;
26
+ }
27
+ });
28
+
29
+ await page.setContent(`
30
+ <!DOCTYPE html>
31
+ <html>
32
+ <head>
33
+ <script>
34
+ ${bundleCode}
35
+ </script>
36
+ </head>
37
+ <body>
38
+ <div id="test-container"></div>
39
+ </body>
40
+ </html>
41
+ `);
42
+
43
+ await wait(2000);
44
+
45
+ // check if the bundle loaded successfully
46
+ expect(bundleLoaded).toBe(true);
47
+ });
48
+ });
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @format
8
+ * @oncall memory_lab
9
+ */
10
+ import {test, expect} from '@playwright/test';
11
+ import fs from 'fs';
12
+ import {wait} from '../utils/test-utils';
13
+ import {runBundleFilePath} from '../utils/test-utils';
14
+
15
+ test.describe('run.bundle.js functionality', () => {
16
+ test('should load and execute correctly', async ({page}) => {
17
+ const bundleCode = fs.readFileSync(runBundleFilePath, 'utf8');
18
+
19
+ let bundleLoaded = false;
20
+ // Listen for console messages
21
+ page.on('console', message => {
22
+ // add console.log(message) if you would like to debug
23
+ const msgText = message.text();
24
+ if (msgText.includes('Tracking React and DOM memory')) {
25
+ bundleLoaded = true;
26
+ }
27
+ });
28
+
29
+ await page.setContent(`
30
+ <!DOCTYPE html>
31
+ <html>
32
+ <head>
33
+ </head>
34
+ <body>
35
+ <div id="test-container"></div>
36
+ <script>
37
+ ${bundleCode}
38
+ </script>
39
+ </body>
40
+ </html>
41
+ `);
42
+
43
+ await wait(1000);
44
+
45
+ // check if the bundle loaded successfully
46
+ expect(bundleLoaded).toBe(true);
47
+ });
48
+ });
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @format
8
+ * @oncall memory_lab
9
+ */
10
+ import {test, expect} from '@playwright/test';
11
+ import fs from 'fs';
12
+ import {wait} from '../utils/test-utils';
13
+ import {runBundleFilePath} from '../utils/test-utils';
14
+
15
+ test.describe('run.bundle.js functionality', () => {
16
+ test('should load and execute correctly', async ({page}) => {
17
+ const bundleCode = fs.readFileSync(runBundleFilePath, 'utf8');
18
+
19
+ let bundleLoaded = false;
20
+ // Listen for console messages
21
+ page.on('console', message => {
22
+ // add console.log(message) if you would like to debug
23
+ const msgText = message.text();
24
+ if (msgText.includes('duration:')) {
25
+ bundleLoaded = true;
26
+ }
27
+ });
28
+
29
+ await page.setContent(`
30
+ <!DOCTYPE html>
31
+ <html>
32
+ <head>
33
+ <script>
34
+ window.TEST_MEMORY_SCAN = true;
35
+ </script>
36
+ </head>
37
+ <body>
38
+ <div id="test-container"></div>
39
+ <script>
40
+ ${bundleCode}
41
+ </script>
42
+ </body>
43
+ </html>
44
+ `);
45
+
46
+ await wait(2000);
47
+
48
+ // check if the bundle loaded successfully
49
+ expect(bundleLoaded).toBe(true);
50
+ });
51
+ });
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @format
8
+ * @oncall memory_lab
9
+ */
10
+ import type {AnyValue} from '../../core/types';
11
+
12
+ import {test, expect} from '@playwright/test';
13
+ import path from 'path';
14
+ import fs from 'fs';
15
+ import {srcPath} from '../utils/test-utils';
16
+ import {libBundleFilePath} from '../utils/test-utils';
17
+
18
+ test('scan should identify react components in a complex fiber tree (React 18 Dev)', async ({
19
+ page,
20
+ }) => {
21
+ const bundleCode = fs.readFileSync(libBundleFilePath, 'utf8');
22
+
23
+ const reactDevPath = path.join(srcPath, 'tests', 'lib', 'react-v18.dev.js');
24
+ const reactDevCode = fs.readFileSync(reactDevPath, 'utf8');
25
+
26
+ const reactDOMDevPath = path.join(
27
+ srcPath,
28
+ 'tests',
29
+ 'lib',
30
+ 'react-dom-v18.dev.js',
31
+ );
32
+ const reactDOMDevCode = fs.readFileSync(reactDOMDevPath, 'utf8');
33
+
34
+ // Create a simple test page with React and a custom component
35
+ await page.setContent(`
36
+ <html>
37
+ <body>
38
+ <div id="root"></div>
39
+ <script>${reactDevCode}</script>
40
+ <script>${reactDOMDevCode}</script>
41
+ <script>${bundleCode}</script>
42
+ <script>
43
+ function TopContainer() {
44
+ return React.createElement(
45
+ 'div',
46
+ null,
47
+ React.createElement(TestContainer),
48
+ React.createElement(TestContainer),
49
+ React.createElement(TestContainer),
50
+ );
51
+ }
52
+ function TestContainer() {
53
+ return React.createElement(
54
+ 'div',
55
+ null,
56
+ React.createElement(TestComponent),
57
+ React.createElement(TestComponent)
58
+ );
59
+ }
60
+
61
+ function TestComponent() {
62
+ return React.createElement('div', null, 'Test Component');
63
+ }
64
+
65
+ ReactDOM.render(
66
+ React.createElement(TopContainer),
67
+ document.getElementById('root')
68
+ );
69
+ </script>
70
+ </body>
71
+ </html>
72
+ `);
73
+
74
+ // Now the global `MemLens` should be available in the page
75
+ const componentIdentified = await page.evaluate(() => {
76
+ const createReactMemoryScan = (window as AnyValue).MemLens
77
+ .createReactMemoryScan;
78
+ const instance = createReactMemoryScan();
79
+ const analysisResult = instance.scan();
80
+ const componentCountCorrect =
81
+ analysisResult.componentToFiberNodeCount.get('TestComponent') === 6;
82
+ const containerCountCorrect =
83
+ analysisResult.componentToFiberNodeCount.get('TestContainer') === 3;
84
+ const topContainerCountCorrect =
85
+ analysisResult.componentToFiberNodeCount.get('TopContainer') === 1;
86
+ return (
87
+ componentCountCorrect && containerCountCorrect && topContainerCountCorrect
88
+ );
89
+ });
90
+
91
+ expect(componentIdentified).toBe(true);
92
+ });
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @format
8
+ * @oncall memory_lab
9
+ */
10
+ import {AnyValue} from '../../core/types';
11
+ import {test, expect} from '@playwright/test';
12
+ import path from 'path';
13
+ import fs from 'fs';
14
+ import {srcPath} from '../utils/test-utils';
15
+ import {libBundleFilePath} from '../utils/test-utils';
16
+
17
+ test('scan should identify react components in a complex list fiber tree (React 18 Dev)', async ({
18
+ page,
19
+ }) => {
20
+ const bundleCode = fs.readFileSync(libBundleFilePath, 'utf8');
21
+
22
+ const reactDevPath = path.join(srcPath, 'tests', 'lib', 'react-v18.dev.js');
23
+ const reactDevCode = fs.readFileSync(reactDevPath, 'utf8');
24
+
25
+ const reactDOMDevPath = path.join(
26
+ srcPath,
27
+ 'tests',
28
+ 'lib',
29
+ 'react-dom-v18.dev.js',
30
+ );
31
+ const reactDOMDevCode = fs.readFileSync(reactDOMDevPath, 'utf8');
32
+
33
+ // Create a test page with React and some list components
34
+ await page.setContent(`
35
+ <html>
36
+ <body>
37
+ <div id="root"></div>
38
+ <script>${reactDevCode}</script>
39
+ <script>${reactDOMDevCode}</script>
40
+ <script>${bundleCode}</script>
41
+ <script>
42
+ function ListItem({ text, count }) {
43
+ return React.createElement('li', null, \`$\{text} ($\{count})\`);
44
+ }
45
+
46
+ function List({ items }) {
47
+ return React.createElement(
48
+ 'ul',
49
+ { className: 'list-container' },
50
+ items.map((item, index) =>
51
+ React.createElement(ListItem, {
52
+ key: index,
53
+ text: item.text,
54
+ count: item.count
55
+ })
56
+ )
57
+ );
58
+ }
59
+
60
+ function Counter({ initialCount }) {
61
+ const [count, setCount] = React.useState(initialCount);
62
+
63
+ return React.createElement(
64
+ 'div',
65
+ { className: 'counter' },
66
+ React.createElement('span', null, \`Count: $\{count}\`),
67
+ React.createElement(
68
+ 'button',
69
+ { onClick: () => setCount(count + 1) },
70
+ 'Increment'
71
+ )
72
+ );
73
+ }
74
+
75
+ function TestContainer() {
76
+ const listSetup = [];
77
+ for (let i = 0; i < 1000; ++i) {
78
+ listSetup.push({ text: 'Item ' + i, count: i });
79
+ }
80
+ const [items] = React.useState(listSetup);
81
+
82
+ return React.createElement(
83
+ 'div',
84
+ { className: 'container' },
85
+ React.createElement('h1', null, 'Test Application'),
86
+ React.createElement(Counter, { initialCount: 0 }),
87
+ React.createElement(Counter, { initialCount: 10 }),
88
+ React.createElement(List, { items: items }),
89
+ React.createElement(List, { items: items.slice(0, 500) })
90
+ );
91
+ }
92
+
93
+ ReactDOM.render(
94
+ React.createElement(TestContainer),
95
+ document.getElementById('root')
96
+ );
97
+ </script>
98
+ </body>
99
+ </html>
100
+ `);
101
+
102
+ // Now the global `MemLens` should be available in the page
103
+ const componentIdentified = await page.evaluate(() => {
104
+ const createReactMemoryScan = (window as AnyValue).MemLens
105
+ .createReactMemoryScan;
106
+ const instance = createReactMemoryScan();
107
+ const analysisResult = instance.scan();
108
+ const counterCorrect =
109
+ analysisResult.componentToFiberNodeCount.get('Counter') === 2;
110
+ const listCorrect =
111
+ analysisResult.componentToFiberNodeCount.get('List') === 2;
112
+ const listItemCorrect =
113
+ analysisResult.componentToFiberNodeCount.get('ListItem') === 1500;
114
+ return counterCorrect && listCorrect && listItemCorrect;
115
+ });
116
+
117
+ expect(componentIdentified).toBe(true);
118
+ });