Repository: gravity-ui/uikit
Pull request #1117 (2023-11-13 08:36:23)

feat: add id converter for getXpath


Patch:
diff --git a/src/components/utils/xpath.ts b/src/components/utils/xpath.ts
index 2bc58684..4b8ff4c1 100644
--- a/src/components/utils/xpath.ts
+++ b/src/components/utils/xpath.ts
@@ -12,9 +12,13 @@ export type XpathClassConverter = (
     strClass: string,
 ) => ElementClass | undefined;
 
+export type XpathIdConverter = (id: string) => string | undefined;
+
 export interface XpathOptions {
     /** Function for converting and filtering classes */
     classConverter?: XpathClassConverter;
+    /** Function for converting and filtering ids */
+    idConverter?: XpathIdConverter;
     /** Flag for managing replaces from tag[@class='...'] to tag[@id='...'] if id is exist */
     withoutId?: boolean;
 }
@@ -39,8 +43,10 @@ function getXpathByNode(node: Node | null, options: InternalXpathOptions): strin
     const tag = node.tagName.toLowerCase();
 
     let token = `/${tag}`;
-    if (node.id && !options.withoutId) {
-        token += `[@id='${node.id}']`;
+
+    const convertedId = node.id && !options.withoutId ? options.idConverter(node.id) : undefined;
+    if (convertedId) {
+        token += `[@id='${convertedId}']`;
     } else {
         const classes: string[] = [];
         node.classList.forEach((className) => {
@@ -59,6 +65,7 @@ function getXpathByNode(node: Node | null, options: InternalXpathOptions): strin
 
 const defaultXpathOptions: InternalXpathOptions = {
     classConverter: (arg) => arg,
+    idConverter: (arg) => arg,
     withoutId: false,
 };
 


Test patch:
diff --git a/src/components/utils/__tests__/xpath.test.tsx b/src/components/utils/__tests__/xpath.test.tsx
index 4eeb3869..d17b2656 100644
--- a/src/components/utils/__tests__/xpath.test.tsx
+++ b/src/components/utils/__tests__/xpath.test.tsx
@@ -313,5 +313,51 @@ describe('getXpath', () => {
             expect(xpath).toBe(rootBuilder.append({className: 'class__div'}).xpath);
             expect(hash).toMatchInlineSnapshot(`"bf5138ce0f335973b588b4c3029e1335"`);
         });
+
+        it('should remove id if it was converted to undefined', () => {
+            const onClick = jest.fn();
+            render(
+                <div onClick={onClick} id={'remove-too'}>
+                    <div id={'keep-this'} className={'class-2'}>
+                        <div id={'remove'} className={'target class-3'}>
+                            {clickText}
+                        </div>
+                    </div>
+                </div>,
+            );
+            screen.getByText(clickText).click();
+
+            const {xpath, hash} = getXpath(onClick.mock.calls[0][0], {
+                idConverter: (id) => (id.startsWith('remove') ? undefined : id),
+            });
+            expect(xpath).toBe(
+                rootBuilder.append().append({id: 'keep-this'}).append({className: 'target class-3'})
+                    .xpath,
+            );
+            expect(hash).toMatchInlineSnapshot(`"21df5cf9017ae106f30a9a6608a9cb4a"`);
+        });
+
+        it('should convert some ids', () => {
+            const onClick = jest.fn();
+            render(
+                <div onClick={onClick} className={'class-1'} id={'convert-too'}>
+                    <div id={'keep-this'} className={'class-2'}>
+                        <div id={'convert'} className={'target class-3'}>
+                            {clickText}
+                        </div>
+                    </div>
+                </div>,
+            );
+            screen.getByText(clickText).click();
+
+            const {xpath, hash} = getXpath(onClick.mock.calls[0][0], {
+                idConverter: (id) => id.replace('convert', 'keep'),
+            });
+            expect(xpath).toBe(
+                rootBuilder.append({id: 'keep-too'}).append({id: 'keep-this'}).append({id: 'keep'})
+                    .xpath,
+            );
+            expect(hash).toMatchInlineSnapshot(`"2ac575742aa22271a17b4e1efb375330"`);
+        });
     });
 });