@react-native-windows/automation-commands 0.81.37 → 0.81.40

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.
package/README.md CHANGED
@@ -27,5 +27,9 @@ test('Widget', async () => {
27
27
  });
28
28
  ```
29
29
 
30
+ Fabric image sources use a stable `<localOrBundlerUri>` prefix instead of the
31
+ local bundle path, for both legacy underscore and Metro `@@/` asset paths. Set
32
+ `removeGuidsFromImageSources: false` to keep the original image URIs.
33
+
30
34
  #### Options
31
35
  **TBD**
@@ -53,15 +53,12 @@ function removeGuidsFromImageSourcesHelper(node) {
53
53
  'https://raw.githubusercontent.com/microsoft/react-native-windows/main/packages/@react-native/tester/js/assets/flowers.png?hash=<some_hash_here>';
54
54
  }
55
55
  else {
56
- // When getting files from a prebuilt bundle the uri is going to include a local path, which would make snapshots inconsistent,
57
- // This logic replaces the local path so that we get consistent results.
58
- // file://E:\\repos\\react-native-windows\\packages\\e2e-test-app-fabric\\windows\\RNTesterApp-Fabric.Package\\bin\\x64\\Release\\AppX\\RNTesterApp-Fabric\\Bundle\\@react-native-windows/tester/js/assets/uie_thumb_normal@2x.png
59
- // becomes
60
- // <localOrBundlerUri>@react-native-windows/tester/js/assets/uie_thumb_normal@2x.png
56
+ // Strip machine-specific bundle paths, supporting both legacy underscores
57
+ // and Metro's escaped parent-directory markers.
61
58
  const packagesPath = require('path')
62
59
  .resolve(__dirname, '../../..')
63
60
  .replace(/\\/g, '\\\\');
64
- source.Uri = source.Uri.replace(new RegExp(`file://${packagesPath}.*\\\\Bundle\\\\assets/_+`), '<localOrBundlerUri>');
61
+ source.Uri = source.Uri.replace(new RegExp(`file://${packagesPath}.*\\\\Bundle\\\\assets/(?:_+|(?:@@/)+)`), '<localOrBundlerUri>');
65
62
  // When loading the bundle from metro local paths will be replaced with paths to localhost, which will not align with snapshots made with prebuilt bundles.
66
63
  // This logic replaces the localhost uri, with the same uri that we would have gotten from a prebuild bundle. This makes it easier to debug without breaking snapshots
67
64
  // http://localhost:8081/assets/@@/@react-native-windows/tester/js/assets/uie_thumb_normal@2x.png?platform=windows&hash=c6f5aec4d9e0aa47c0887e4266796224
@@ -1 +1 @@
1
- {"version":3,"file":"dumpVisualTree.js","sourceRoot":"","sources":["../src/dumpVisualTree.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Copyright (c) Microsoft Corporation.\n * Licensed under the MIT License.\n *\n * @format\n */\n\nimport {AutomationClient} from '@react-native-windows/automation-channel';\n\n/**\n * Schema of tree dumped node\n */\nexport type UIElement = {\n XamlType: string;\n Foreground?: string | null;\n Background?: string | null;\n Padding?: string | null;\n Margin?: string | null;\n RenderSize?: number[] | null;\n Visibility?: 'Collapsed' | 'Visible' | null;\n CornerRadius?: string | null;\n BorderThickness?: string | null;\n Width?: number | null;\n Height?: number | null;\n BorderBrush?: string | null;\n VerticalAlignment?: string | null;\n HorizontalAlignment?: string | null;\n Clip?: string | null;\n FlowDirection?: string | null;\n Name?: string | null;\n Text?: string | null;\n children?: UIElement[];\n [index: string]: unknown;\n};\n\nexport type AutomationNode = {\n AutomationId?: string;\n ControlType?: number;\n LocalizedControlType?: string;\n Name?: string;\n __Children?: [AutomationNode];\n};\n\nexport type ComponentNode = {\n Type: string;\n _Props?: {\n TestId?: string;\n Sources?: [{Uri?: string}];\n };\n __Children?: [ComponentNode];\n};\n\nexport type VisualNode = {\n Comment?: string;\n Offset?: `${number} ${number} ${number}`;\n Size?: `${number} ${number}`;\n 'Visual Type'?: string;\n __Children?: [VisualNode];\n};\n\nexport type VisualTree = {\n 'Automation Tree': AutomationNode;\n 'Component Tree': ComponentNode;\n 'Visual Tree': VisualNode;\n};\n\ndeclare global {\n const automationClient: AutomationClient | undefined;\n}\n\n/**\n * Dump a section of the native visual tree.\n */\nexport default async function dumpVisualTree(\n accessibilityId: string,\n opts?: {\n pruneCollapsed?: boolean;\n deterministicOnly?: boolean;\n removeDefaultProps?: boolean;\n removeGuidsFromImageSources?: boolean;\n additionalProperties?: string[];\n },\n): Promise<UIElement | VisualTree> {\n if (!automationClient) {\n throw new Error('RPC client is not enabled');\n }\n\n const dumpResponse = await automationClient.invoke('DumpVisualTree', {\n accessibilityId,\n ...opts,\n });\n\n if (dumpResponse.type === 'error') {\n throw new Error(dumpResponse.message);\n }\n\n const element: UIElement | VisualTree = dumpResponse.result;\n\n if ('XamlType' in element && opts?.pruneCollapsed !== false) {\n pruneCollapsedElements(element);\n }\n\n if ('XamlType' in element && opts?.deterministicOnly !== false) {\n removeNonDeterministicProps(element);\n }\n\n if ('XamlType' in element && opts?.removeDefaultProps !== false) {\n removeDefaultProps(element);\n }\n\n if (!('XamlType' in element) && opts?.removeGuidsFromImageSources !== false) {\n removeGuidsFromImageSources(element);\n }\n\n return element;\n}\n\nfunction removeGuidsFromImageSourcesHelper(node: ComponentNode) {\n if (node._Props && node._Props.Sources) {\n node._Props.Sources.forEach((source: any) => {\n if (source.Uri) {\n if (source.Uri.startsWith('blob:')) {\n source.Uri = source.Uri.replace(\n /blob:[a-f0-9]+-[a-f0-9]+-[a-f0-9]+-[a-f0-9]+-[a-f0-9]+/,\n 'blob:<some_guid_here>',\n );\n source.Uri = source.Uri.replace(/size=\\d+/, 'size=<size>');\n } else if (\n source.Uri.startsWith(\n 'https://raw.githubusercontent.com/microsoft/react-native-windows/main/packages/@react-native/tester/js/assets/flux@3x.png?r=1&t=',\n )\n ) {\n source.Uri =\n 'https://raw.githubusercontent.com/microsoft/react-native-windows/main/packages/@react-native/tester/js/assets/flux@3x.png?r=1&t=<some_hash_here>';\n } else if (\n source.Uri.startsWith(\n 'https://raw.githubusercontent.com/microsoft/react-native-windows/main/packages/@react-native/tester/js/assets/flowers.png?hash=',\n )\n ) {\n source.Uri =\n 'https://raw.githubusercontent.com/microsoft/react-native-windows/main/packages/@react-native/tester/js/assets/flowers.png?hash=<some_hash_here>';\n } else {\n // When getting files from a prebuilt bundle the uri is going to include a local path, which would make snapshots inconsistent,\n // This logic replaces the local path so that we get consistent results.\n // file://E:\\\\repos\\\\react-native-windows\\\\packages\\\\e2e-test-app-fabric\\\\windows\\\\RNTesterApp-Fabric.Package\\\\bin\\\\x64\\\\Release\\\\AppX\\\\RNTesterApp-Fabric\\\\Bundle\\\\@react-native-windows/tester/js/assets/uie_thumb_normal@2x.png\n // becomes\n // <localOrBundlerUri>@react-native-windows/tester/js/assets/uie_thumb_normal@2x.png\n const packagesPath = require('path')\n .resolve(__dirname, '../../..')\n .replace(/\\\\/g, '\\\\\\\\');\n source.Uri = source.Uri.replace(\n new RegExp(`file://${packagesPath}.*\\\\\\\\Bundle\\\\\\\\assets/_+`),\n '<localOrBundlerUri>',\n );\n\n // When loading the bundle from metro local paths will be replaced with paths to localhost, which will not align with snapshots made with prebuilt bundles.\n // This logic replaces the localhost uri, with the same uri that we would have gotten from a prebuild bundle. This makes it easier to debug without breaking snapshots\n // http://localhost:8081/assets/@@/@react-native-windows/tester/js/assets/uie_thumb_normal@2x.png?platform=windows&hash=c6f5aec4d9e0aa47c0887e4266796224\n // becomes\n // \"<localOrBundlerUri>@react-native-windows/tester/js/assets/uie_thumb_normal@2x.png\"\n source.Uri = source.Uri.replace(\n /http:\\/\\/localhost:8081\\/assets\\/(@@\\/)+/,\n '<localOrBundlerUri>',\n );\n source.Uri = source.Uri.replace(/\\?platform=windows&hash=[^=]$/, '');\n }\n }\n });\n }\n if (node.__Children) {\n node.__Children.forEach((child: any) =>\n removeGuidsFromImageSourcesHelper(child),\n );\n }\n}\n\nfunction removeGuidsFromImageSources(visualTree: VisualTree) {\n removeGuidsFromImageSourcesHelper(visualTree['Component Tree']);\n}\n\n/**\n * Removes trees of XAML that are not visible.\n */\nfunction pruneCollapsedElements(element: UIElement) {\n if (!element.children) {\n return;\n }\n\n element.children = element.children.filter(\n child => child.Visibility !== 'Collapsed',\n );\n\n element.children.forEach(pruneCollapsedElements);\n}\n\n/**\n * Removes trees of properties that are not deterministic\n */\nfunction removeNonDeterministicProps(element: UIElement) {\n if (element.RenderSize) {\n // RenderSize is subject to rounding, etc and should mostly be derived from\n // other deterministic properties in the tree.\n delete element.RenderSize;\n }\n\n if (element.children) {\n element.children.forEach(removeNonDeterministicProps);\n }\n}\n\n/**\n * Removes noise from snapshot by removing properties with the default value\n */\nfunction removeDefaultProps(element: UIElement) {\n const defaultValues: [string, unknown][] = [['Tooltip', null]];\n\n defaultValues.forEach(([propname, defaultValue]) => {\n if (element[propname] === defaultValue) {\n delete element[propname];\n }\n });\n\n if (element.children) {\n element.children.forEach(removeDefaultProps);\n }\n}\n"]}
1
+ {"version":3,"file":"dumpVisualTree.js","sourceRoot":"","sources":["../src/dumpVisualTree.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Copyright (c) Microsoft Corporation.\n * Licensed under the MIT License.\n *\n * @format\n */\n\nimport {AutomationClient} from '@react-native-windows/automation-channel';\n\n/**\n * Schema of tree dumped node\n */\nexport type UIElement = {\n XamlType: string;\n Foreground?: string | null;\n Background?: string | null;\n Padding?: string | null;\n Margin?: string | null;\n RenderSize?: number[] | null;\n Visibility?: 'Collapsed' | 'Visible' | null;\n CornerRadius?: string | null;\n BorderThickness?: string | null;\n Width?: number | null;\n Height?: number | null;\n BorderBrush?: string | null;\n VerticalAlignment?: string | null;\n HorizontalAlignment?: string | null;\n Clip?: string | null;\n FlowDirection?: string | null;\n Name?: string | null;\n Text?: string | null;\n children?: UIElement[];\n [index: string]: unknown;\n};\n\nexport type AutomationNode = {\n AutomationId?: string;\n ControlType?: number;\n LocalizedControlType?: string;\n Name?: string;\n __Children?: [AutomationNode];\n};\n\nexport type ComponentNode = {\n Type: string;\n _Props?: {\n TestId?: string;\n Sources?: [{Uri?: string}];\n };\n __Children?: [ComponentNode];\n};\n\nexport type VisualNode = {\n Comment?: string;\n Offset?: `${number} ${number} ${number}`;\n Size?: `${number} ${number}`;\n 'Visual Type'?: string;\n __Children?: [VisualNode];\n};\n\nexport type VisualTree = {\n 'Automation Tree': AutomationNode;\n 'Component Tree': ComponentNode;\n 'Visual Tree': VisualNode;\n};\n\ndeclare global {\n const automationClient: AutomationClient | undefined;\n}\n\n/**\n * Dump a section of the native visual tree.\n */\nexport default async function dumpVisualTree(\n accessibilityId: string,\n opts?: {\n pruneCollapsed?: boolean;\n deterministicOnly?: boolean;\n removeDefaultProps?: boolean;\n removeGuidsFromImageSources?: boolean;\n additionalProperties?: string[];\n },\n): Promise<UIElement | VisualTree> {\n if (!automationClient) {\n throw new Error('RPC client is not enabled');\n }\n\n const dumpResponse = await automationClient.invoke('DumpVisualTree', {\n accessibilityId,\n ...opts,\n });\n\n if (dumpResponse.type === 'error') {\n throw new Error(dumpResponse.message);\n }\n\n const element: UIElement | VisualTree = dumpResponse.result;\n\n if ('XamlType' in element && opts?.pruneCollapsed !== false) {\n pruneCollapsedElements(element);\n }\n\n if ('XamlType' in element && opts?.deterministicOnly !== false) {\n removeNonDeterministicProps(element);\n }\n\n if ('XamlType' in element && opts?.removeDefaultProps !== false) {\n removeDefaultProps(element);\n }\n\n if (!('XamlType' in element) && opts?.removeGuidsFromImageSources !== false) {\n removeGuidsFromImageSources(element);\n }\n\n return element;\n}\n\nfunction removeGuidsFromImageSourcesHelper(node: ComponentNode) {\n if (node._Props && node._Props.Sources) {\n node._Props.Sources.forEach((source: any) => {\n if (source.Uri) {\n if (source.Uri.startsWith('blob:')) {\n source.Uri = source.Uri.replace(\n /blob:[a-f0-9]+-[a-f0-9]+-[a-f0-9]+-[a-f0-9]+-[a-f0-9]+/,\n 'blob:<some_guid_here>',\n );\n source.Uri = source.Uri.replace(/size=\\d+/, 'size=<size>');\n } else if (\n source.Uri.startsWith(\n 'https://raw.githubusercontent.com/microsoft/react-native-windows/main/packages/@react-native/tester/js/assets/flux@3x.png?r=1&t=',\n )\n ) {\n source.Uri =\n 'https://raw.githubusercontent.com/microsoft/react-native-windows/main/packages/@react-native/tester/js/assets/flux@3x.png?r=1&t=<some_hash_here>';\n } else if (\n source.Uri.startsWith(\n 'https://raw.githubusercontent.com/microsoft/react-native-windows/main/packages/@react-native/tester/js/assets/flowers.png?hash=',\n )\n ) {\n source.Uri =\n 'https://raw.githubusercontent.com/microsoft/react-native-windows/main/packages/@react-native/tester/js/assets/flowers.png?hash=<some_hash_here>';\n } else {\n // Strip machine-specific bundle paths, supporting both legacy underscores\n // and Metro's escaped parent-directory markers.\n const packagesPath = require('path')\n .resolve(__dirname, '../../..')\n .replace(/\\\\/g, '\\\\\\\\');\n source.Uri = source.Uri.replace(\n new RegExp(\n `file://${packagesPath}.*\\\\\\\\Bundle\\\\\\\\assets/(?:_+|(?:@@/)+)`,\n ),\n '<localOrBundlerUri>',\n );\n\n // When loading the bundle from metro local paths will be replaced with paths to localhost, which will not align with snapshots made with prebuilt bundles.\n // This logic replaces the localhost uri, with the same uri that we would have gotten from a prebuild bundle. This makes it easier to debug without breaking snapshots\n // http://localhost:8081/assets/@@/@react-native-windows/tester/js/assets/uie_thumb_normal@2x.png?platform=windows&hash=c6f5aec4d9e0aa47c0887e4266796224\n // becomes\n // \"<localOrBundlerUri>@react-native-windows/tester/js/assets/uie_thumb_normal@2x.png\"\n source.Uri = source.Uri.replace(\n /http:\\/\\/localhost:8081\\/assets\\/(@@\\/)+/,\n '<localOrBundlerUri>',\n );\n source.Uri = source.Uri.replace(/\\?platform=windows&hash=[^=]$/, '');\n }\n }\n });\n }\n if (node.__Children) {\n node.__Children.forEach((child: any) =>\n removeGuidsFromImageSourcesHelper(child),\n );\n }\n}\n\nfunction removeGuidsFromImageSources(visualTree: VisualTree) {\n removeGuidsFromImageSourcesHelper(visualTree['Component Tree']);\n}\n\n/**\n * Removes trees of XAML that are not visible.\n */\nfunction pruneCollapsedElements(element: UIElement) {\n if (!element.children) {\n return;\n }\n\n element.children = element.children.filter(\n child => child.Visibility !== 'Collapsed',\n );\n\n element.children.forEach(pruneCollapsedElements);\n}\n\n/**\n * Removes trees of properties that are not deterministic\n */\nfunction removeNonDeterministicProps(element: UIElement) {\n if (element.RenderSize) {\n // RenderSize is subject to rounding, etc and should mostly be derived from\n // other deterministic properties in the tree.\n delete element.RenderSize;\n }\n\n if (element.children) {\n element.children.forEach(removeNonDeterministicProps);\n }\n}\n\n/**\n * Removes noise from snapshot by removing properties with the default value\n */\nfunction removeDefaultProps(element: UIElement) {\n const defaultValues: [string, unknown][] = [['Tooltip', null]];\n\n defaultValues.forEach(([propname, defaultValue]) => {\n if (element[propname] === defaultValue) {\n delete element[propname];\n }\n });\n\n if (element.children) {\n element.children.forEach(removeDefaultProps);\n }\n}\n"]}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Copyright (c) Microsoft Corporation.
3
+ * Licensed under the MIT License.
4
+ *
5
+ * @format
6
+ */
7
+ export {};
@@ -0,0 +1,96 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright (c) Microsoft Corporation.
4
+ * Licensed under the MIT License.
5
+ *
6
+ * @format
7
+ */
8
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
9
+ if (k2 === undefined) k2 = k;
10
+ var desc = Object.getOwnPropertyDescriptor(m, k);
11
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
12
+ desc = { enumerable: true, get: function() { return m[k]; } };
13
+ }
14
+ Object.defineProperty(o, k2, desc);
15
+ }) : (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ o[k2] = m[k];
18
+ }));
19
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
20
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
21
+ }) : function(o, v) {
22
+ o["default"] = v;
23
+ });
24
+ var __importStar = (this && this.__importStar) || function (mod) {
25
+ if (mod && mod.__esModule) return mod;
26
+ var result = {};
27
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
28
+ __setModuleDefault(result, mod);
29
+ return result;
30
+ };
31
+ var __importDefault = (this && this.__importDefault) || function (mod) {
32
+ return (mod && mod.__esModule) ? mod : { "default": mod };
33
+ };
34
+ Object.defineProperty(exports, "__esModule", { value: true });
35
+ const path = __importStar(require("path"));
36
+ const dumpVisualTree_1 = __importDefault(require("../dumpVisualTree"));
37
+ const assetPath = '@react-native-windows/tester/js/assets/flux@3x.png';
38
+ const packagesPath = path.resolve(__dirname, '../../../..');
39
+ function bundledImageUri(platform, marker) {
40
+ return `file://${packagesPath}\\e2e-test-app-fabric\\windows\\RNTesterApp-Fabric.Package\\bin\\${platform}\\Release\\AppX\\RNTesterApp-Fabric\\Bundle\\assets/${marker}${assetPath}`;
41
+ }
42
+ function mockVisualTree(tree) {
43
+ Object.defineProperty(global, 'automationClient', {
44
+ configurable: true,
45
+ value: {
46
+ invoke: async () => ({ type: 'success', result: tree }),
47
+ },
48
+ });
49
+ }
50
+ async function dumpImageSource(uri, options) {
51
+ const tree = {
52
+ 'Automation Tree': {},
53
+ 'Component Tree': { Type: 'Image', _Props: { Sources: [{ Uri: uri }] } },
54
+ 'Visual Tree': {},
55
+ };
56
+ mockVisualTree(tree);
57
+ await (0, dumpVisualTree_1.default)('image', options);
58
+ return tree['Component Tree']._Props?.Sources?.[0].Uri;
59
+ }
60
+ afterEach(() => {
61
+ Reflect.deleteProperty(global, 'automationClient');
62
+ });
63
+ describe.each(['x64', 'x86'])('bundled image snapshots (%s)', platform => {
64
+ test.each(['@@/', '@@/@@/', '_', '___'])('normalizes packaged image paths with the %s marker', async (marker) => {
65
+ expect(await dumpImageSource(bundledImageUri(platform, marker))).toBe(`<localOrBundlerUri>${assetPath}`);
66
+ });
67
+ });
68
+ test('normalizes image sources in nested component trees', async () => {
69
+ const tree = {
70
+ 'Automation Tree': {},
71
+ 'Component Tree': {
72
+ Type: 'View',
73
+ __Children: [
74
+ {
75
+ Type: 'Image',
76
+ _Props: { Sources: [{ Uri: bundledImageUri('x64', '@@/') }] },
77
+ },
78
+ ],
79
+ },
80
+ 'Visual Tree': {},
81
+ };
82
+ mockVisualTree(tree);
83
+ await (0, dumpVisualTree_1.default)('view');
84
+ expect(tree['Component Tree'].__Children?.[0]._Props?.Sources?.[0].Uri).toBe(`<localOrBundlerUri>${assetPath}`);
85
+ });
86
+ test('preserves bundled image paths when normalization is disabled', async () => {
87
+ const uri = bundledImageUri('x64', '@@/');
88
+ expect(await dumpImageSource(uri, { removeGuidsFromImageSources: false })).toBe(uri);
89
+ });
90
+ test.each([
91
+ `file://C:\\other-app\\Bundle\\assets/@@/${assetPath}`,
92
+ `https://example.com/assets/@@/${assetPath}`,
93
+ ])('preserves unrelated image URI %s', async (uri) => {
94
+ expect(await dumpImageSource(uri)).toBe(uri);
95
+ });
96
+ //# sourceMappingURL=dumpVisualTree.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dumpVisualTree.test.js","sourceRoot":"","sources":["../../src/test/dumpVisualTree.test.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Copyright (c) Microsoft Corporation.\n * Licensed under the MIT License.\n *\n * @format\n */\n\nimport * as path from 'path';\nimport dumpVisualTree, {VisualTree} from '../dumpVisualTree';\n\nconst assetPath = '@react-native-windows/tester/js/assets/flux@3x.png';\nconst packagesPath = path.resolve(__dirname, '../../../..');\n\nfunction bundledImageUri(platform: string, marker: string): string {\n return `file://${packagesPath}\\\\e2e-test-app-fabric\\\\windows\\\\RNTesterApp-Fabric.Package\\\\bin\\\\${platform}\\\\Release\\\\AppX\\\\RNTesterApp-Fabric\\\\Bundle\\\\assets/${marker}${assetPath}`;\n}\n\nfunction mockVisualTree(tree: VisualTree) {\n Object.defineProperty(global, 'automationClient', {\n configurable: true,\n value: {\n invoke: async () => ({type: 'success', result: tree}),\n },\n });\n}\n\nasync function dumpImageSource(\n uri: string,\n options?: Parameters<typeof dumpVisualTree>[1],\n): Promise<string | undefined> {\n const tree: VisualTree = {\n 'Automation Tree': {},\n 'Component Tree': {Type: 'Image', _Props: {Sources: [{Uri: uri}]}},\n 'Visual Tree': {},\n };\n mockVisualTree(tree);\n await dumpVisualTree('image', options);\n return tree['Component Tree']._Props?.Sources?.[0].Uri;\n}\n\nafterEach(() => {\n Reflect.deleteProperty(global, 'automationClient');\n});\n\ndescribe.each(['x64', 'x86'])('bundled image snapshots (%s)', platform => {\n test.each(['@@/', '@@/@@/', '_', '___'])(\n 'normalizes packaged image paths with the %s marker',\n async marker => {\n expect(await dumpImageSource(bundledImageUri(platform, marker))).toBe(\n `<localOrBundlerUri>${assetPath}`,\n );\n },\n );\n});\n\ntest('normalizes image sources in nested component trees', async () => {\n const tree: VisualTree = {\n 'Automation Tree': {},\n 'Component Tree': {\n Type: 'View',\n __Children: [\n {\n Type: 'Image',\n _Props: {Sources: [{Uri: bundledImageUri('x64', '@@/')}]},\n },\n ],\n },\n 'Visual Tree': {},\n };\n mockVisualTree(tree);\n\n await dumpVisualTree('view');\n\n expect(tree['Component Tree'].__Children?.[0]._Props?.Sources?.[0].Uri).toBe(\n `<localOrBundlerUri>${assetPath}`,\n );\n});\n\ntest('preserves bundled image paths when normalization is disabled', async () => {\n const uri = bundledImageUri('x64', '@@/');\n expect(await dumpImageSource(uri, {removeGuidsFromImageSources: false})).toBe(\n uri,\n );\n});\n\ntest.each([\n `file://C:\\\\other-app\\\\Bundle\\\\assets/@@/${assetPath}`,\n `https://example.com/assets/@@/${assetPath}`,\n])('preserves unrelated image URI %s', async uri => {\n expect(await dumpImageSource(uri)).toBe(uri);\n});\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react-native-windows/automation-commands",
3
- "version": "0.81.37",
3
+ "version": "0.81.40",
4
4
  "description": "Allows controlling your react-native-windows application",
5
5
  "main": "lib-commonjs/index.js",
6
6
  "license": "MIT",
@@ -14,21 +14,26 @@
14
14
  "clean": "rnw-scripts clean",
15
15
  "lint": "rnw-scripts lint",
16
16
  "lint:fix": "rnw-scripts lint:fix",
17
+ "test": "rnw-scripts test",
17
18
  "watch": "rnw-scripts watch"
18
19
  },
19
20
  "dependencies": {
20
- "@react-native-windows/automation-channel": "0.81.37",
21
+ "@react-native-windows/automation-channel": "0.81.40",
21
22
  "@typescript-eslint/eslint-plugin": "^8.36.0",
22
23
  "@typescript-eslint/parser": "^8.36.0"
23
24
  },
24
25
  "devDependencies": {
26
+ "@babel/core": "^7.25.2",
25
27
  "@jest/types": "^29.2.1",
26
28
  "@rnw-scripts/eslint-config": "1.2.38",
29
+ "@rnw-scripts/jest-unittest-config": "1.5.12",
27
30
  "@rnw-scripts/just-task": "2.3.56",
28
31
  "@rnw-scripts/ts-config": "2.0.6",
29
32
  "@types/jest": "^29.2.2",
30
33
  "@types/node": "^22.14.0",
34
+ "babel-jest": "^29.6.3",
31
35
  "eslint": "^8.19.0",
36
+ "jest": "^29.7.0",
32
37
  "prettier": "2.8.8",
33
38
  "typescript": "5.0.4"
34
39
  },