From 4d270c5ee6eb9c1acdb507eb07e990aac5bee352 Mon Sep 17 00:00:00 2001
From: Anna Iustus <ajustusa@gmail.com>
Date: Wed, 13 Dec 2023 21:36:36 +0100
Subject: [PATCH 1/5] refactor: code style, types

---
 src/features/locators/locators.slice.ts       | 16 ++++-----
 src/features/locators/types/locator.types.ts  | 24 ++++++-------
 .../locators/utils/locatorValidation.ts       | 23 ++----------
 src/features/locators/utils/utils.ts          | 35 +++++++++++++++++--
 4 files changed, 54 insertions(+), 44 deletions(-)

diff --git a/src/features/locators/locators.slice.ts b/src/features/locators/locators.slice.ts
index c14b6c2b..cfb4fff5 100644
--- a/src/features/locators/locators.slice.ts
+++ b/src/features/locators/locators.slice.ts
@@ -70,13 +70,14 @@ const locatorsSlice = createSlice({
 
       const newValue: ILocator = { ...currentLocator, locator: { ...currentLocator.locator }, locatorType, ...rest };
       const isStandardLocator: boolean =
-        locatorType === LocatorType.cssSelector ||
-        locatorType === LocatorType.className ||
-        locatorType === LocatorType.id ||
-        locatorType === LocatorType.linkText ||
-        locatorType === LocatorType.name ||
-        locatorType === LocatorType.tagName ||
-        locatorType.startsWith('data-');
+        [
+          LocatorType.cssSelector,
+          LocatorType.className,
+          LocatorType.id,
+          LocatorType.linkText,
+          LocatorType.name,
+          LocatorType.tagName,
+        ].includes(locatorType) || locatorType.startsWith('data-');
 
       if (locatorType === LocatorType.cssSelector) {
         newValue.locator.cssSelector = locator;
@@ -102,7 +103,6 @@ const locatorsSlice = createSlice({
       if (rest.message === LocatorValidationWarnings.NotFound) {
         newValue.jdnHash = '';
       }
-
       locatorsAdapter.upsertOne(state, newValue);
     },
     changeIdentificationStatus(state, { payload }: PayloadAction<IdentificationStatus>) {
diff --git a/src/features/locators/types/locator.types.ts b/src/features/locators/types/locator.types.ts
index 4d3e4799..93f142f4 100644
--- a/src/features/locators/types/locator.types.ts
+++ b/src/features/locators/types/locator.types.ts
@@ -54,18 +54,6 @@ export interface LocatorsState {
 
 export type ElementId = string;
 
-export interface LocatorValue {
-  fullXpath?: string;
-  xPath: string | null;
-  attributes: ElementAttributes;
-  cssSelector: string | null;
-  cssSelectorStatus?: LocatorTaskStatus; // status of cssSelector calculation
-  xPathStatus?: LocatorTaskStatus; // status of xPath calculation
-  taskStatus?: LocatorTaskStatus; // status of both calculations
-  errorMessage?: string; // comes during the locator generation
-  output?: string;
-}
-
 export enum LocatorValidationErrors {
   DuplicatedName = 'This name already exists in the page object.',
   DuplicatedLocator = 'The locator for this element already exists.',
@@ -82,6 +70,18 @@ export enum LocatorValidationWarnings {
 export type LocatorValidationErrorType = LocatorValidationErrors | LocatorValidationWarnings | '';
 export type JDNHash = string;
 
+export interface LocatorValue {
+  fullXpath?: string;
+  xPath: string | null;
+  attributes: ElementAttributes;
+  cssSelector: string | null;
+  cssSelectorStatus?: LocatorTaskStatus; // status of cssSelector calculation
+  xPathStatus?: LocatorTaskStatus; // status of xPath calculation
+  taskStatus?: LocatorTaskStatus; // status of both calculations
+  errorMessage?: string; // comes during the locator generation
+  output?: string;
+}
+
 export interface ILocator extends PredictedEntity {
   children?: ElementId[];
   deleted?: boolean;
diff --git a/src/features/locators/utils/locatorValidation.ts b/src/features/locators/utils/locatorValidation.ts
index d838d4b9..2719bf10 100644
--- a/src/features/locators/utils/locatorValidation.ts
+++ b/src/features/locators/utils/locatorValidation.ts
@@ -8,27 +8,8 @@ import {
   JDNHash,
   ElementId,
 } from '../types/locator.types';
-import { checkDuplicates, evaluateStandardLocator, evaluateXpath } from './utils';
+import { checkDuplicates, evaluateLocator } from './utils';
 
-const prepareLocatorStringForEvaluation = (type: LocatorType, string: string): string => {
-  if (type === LocatorType.id) return `#${string}`;
-  if (type === LocatorType.className) return `.${string}`;
-  if (type === LocatorType.name) return `[name="${string}"]`;
-  return string;
-};
-
-const evaluateLocator = async (
-  locatorString: string,
-  elementId: ElementId,
-  jdnHash: string,
-  locatorType: LocatorType,
-) => {
-  if (locatorType === LocatorType.xPath) return evaluateXpath(locatorString, elementId, jdnHash);
-  else {
-    const preparedValue = prepareLocatorStringForEvaluation(locatorType, locatorString);
-    return evaluateStandardLocator(preparedValue, locatorType, elementId, jdnHash);
-  }
-};
 // ToDo: logic refactoring needed
 export const validateLocator = async (
   locatorString: string,
@@ -44,7 +25,7 @@ export const validateLocator = async (
   let validatedJdnHash;
   let validationMessage: LocatorValidationErrorType = '';
 
-  const locatorValue = await evaluateLocator(locatorString, element_id, jdnHash, locatorType);
+  const locatorValue = await evaluateLocator(locatorString, locatorType, element_id, jdnHash);
 
   if (locatorValue === LocatorValidationWarnings.NotFound || !locatorValue) {
     validationMessage = LocatorValidationWarnings.NotFound; //validationStatus: WARNING
diff --git a/src/features/locators/utils/utils.ts b/src/features/locators/utils/utils.ts
index 7fa6c0dc..62cbb103 100644
--- a/src/features/locators/utils/utils.ts
+++ b/src/features/locators/utils/utils.ts
@@ -41,6 +41,26 @@ export const evaluateStandardLocator = (
   originJdnHash?: string,
 ) => sendMessage.evaluateStandardLocator({ selector, locatorType, element_id, originJdnHash });
 
+const prepareLocatorStringForEvaluation = (type: LocatorType, string: string): string => {
+  if (type === LocatorType.id) return `#${string}`;
+  if (type === LocatorType.className) return `.${string}`;
+  if (type === LocatorType.name) return `[name="${string}"]`;
+  return string;
+};
+
+export const evaluateLocator = async (
+  locatorString: string,
+  locatorType: LocatorType,
+  elementId: ElementId,
+  jdnHash?: string,
+) => {
+  if (locatorType === LocatorType.xPath) return evaluateXpath(locatorString, elementId, jdnHash);
+  else {
+    const preparedValue = prepareLocatorStringForEvaluation(locatorType, locatorString);
+    return evaluateStandardLocator(preparedValue, locatorType, elementId, jdnHash);
+  }
+};
+
 export const generateSelectorByHash = (element_id: ElementId, jdnHash: string) =>
   sendMessage.generateSelectorByHash({ element_id, jdnHash });
 
@@ -86,6 +106,7 @@ export const setIndents = (ref: React.RefObject<HTMLDivElement>, depth: number)
   }
 };
 
+// used in the coverage panel in the Copy option of the Context Menu:
 export const copyLocator =
   (framework: FrameworkType, locatorsForCopy: ILocator[], option?: LocatorOption) => (): void => {
     const isVividusFramework = framework === FrameworkType.Vividus;
@@ -98,7 +119,9 @@ export const copyLocator =
         value = locatorsForCopy.map(({ locator }) => getLocatorWithSelenium(locator.xPath ?? '', 'xpath'));
         break;
       case LocatorOption.XpathAndJDI:
-        value = locatorsForCopy.map(({ locator }) => getLocatorWithJDIAnnotation(locator.xPath ?? ''));
+        value = locatorsForCopy.map(({ locator }) =>
+          getLocatorWithJDIAnnotation(locator.xPath ?? '', LocatorType.xPath),
+        );
         break;
       case LocatorOption.CSSSelector:
         value = locatorsForCopy.map(({ locator }) => `"${locator.cssSelector}"`);
@@ -107,7 +130,9 @@ export const copyLocator =
         value = locatorsForCopy.map(({ locator }) => getLocatorWithSelenium(locator.cssSelector ?? '', 'css'));
         break;
       case LocatorOption.CSSAndJDI:
-        value = locatorsForCopy.map(({ locator }) => getLocatorWithJDIAnnotation(locator.cssSelector ?? ''));
+        value = locatorsForCopy.map(({ locator }) =>
+          getLocatorWithJDIAnnotation(locator.cssSelector ?? '', LocatorType.cssSelector),
+        );
         break;
       default:
         value = locatorsForCopy.map((element) => {
@@ -184,7 +209,11 @@ export const getLocatorValueOnTypeSwitch = async (
       ({ cssSelector: newLocatorValue } = await generateSelectorByHash(element_id, foundHash));
     } else {
       if (newLocatorType === LocatorType.cssSelector) newLocatorValue = locatorValue.cssSelector;
-      newLocatorValue = getLocatorValueByType(locatorValue, newLocatorType);
+      try {
+        newLocatorValue = getLocatorValueByType(locatorValue, newLocatorType);
+      } catch (error) {
+        console.log('error: ', error);
+      }
     }
   } else {
     if (isLocatorLeadsToNewElement || !locatorValue.xPath) {

From d747d902e20338e76e13525d903259ec1f77ba8e Mon Sep 17 00:00:00 2001
From: Anna Iustus <ajustusa@gmail.com>
Date: Wed, 13 Dec 2023 21:41:15 +0100
Subject: [PATCH 2/5] fix: update handle Create Custom Locator & add Custom
 Locator Thunk

---
 .../locators/components/LocatorEditDialog.tsx | 38 ++++-----
 .../reducers/addCustomLocator.thunk.ts        | 78 +++++++++----------
 2 files changed, 58 insertions(+), 58 deletions(-)

diff --git a/src/features/locators/components/LocatorEditDialog.tsx b/src/features/locators/components/LocatorEditDialog.tsx
index d52bf573..db60e3ea 100644
--- a/src/features/locators/components/LocatorEditDialog.tsx
+++ b/src/features/locators/components/LocatorEditDialog.tsx
@@ -142,32 +142,32 @@ export const LocatorEditDialog: React.FC<Props> = ({
   };
 
   const handleCreateCustomLocator = async () => {
-    let newLocator: ILocator = {
-      ...newLocatorStub,
-      pageObj: pageObjectId,
-      isCustomName: isEditedName,
-    };
-
     const isLocatorFieldTouched = form.isFieldTouched('locator');
     // in case if user didn't touch locator field to avoid forceUpdate
     const locatorMessage = isLocatorFieldTouched ? validationMessage : LocatorValidationWarnings.NotFound;
-    // ToDo: fix legacy:
-    // eslint-disable-next-line @typescript-eslint/no-shadow
-    const { name, type, locator, locatorType, annotationType } = await form.validateFields();
-    const isCSSLocator = locatorType === LocatorType.cssSelector;
 
-    newLocator = {
-      ...newLocator,
-      locator: { ...newLocator.locator, ...{ [isCSSLocator ? 'cssSelector' : 'xPath']: locator } },
+    const {
+      name: locatorFormName,
+      type: locatorFormElementClass,
+      locator: locatorFormValue,
+      locatorType: locatorFormType,
+      annotationType: locatorFormAnnotationType,
+    } = await form.validateFields();
+
+    const newLocatorData: ILocator & { locatorFormValue: string } = {
+      ...newLocatorStub,
+      pageObj: pageObjectId,
+      isCustomName: isEditedName,
       predicted_label: type.toLowerCase(),
-      annotationType,
-      locatorType,
+      annotationType: locatorFormAnnotationType,
+      locatorType: locatorFormType,
       message: locatorMessage,
-      name,
-      type,
+      name: locatorFormName,
+      type: locatorFormElementClass,
+      locatorFormValue,
     };
 
-    dispatch(addCustomLocator({ newLocator, pageObjectId }));
+    await dispatch(addCustomLocator({ newLocatorData }));
     closeDialog();
   };
 
@@ -190,7 +190,7 @@ export const LocatorEditDialog: React.FC<Props> = ({
 
     // we need this check for the case when user edits custom invalid locator, that doesn't have jdnHash
     if ((!validationMessage.length && !jdnHash) || validationMessage === LocatorValidationWarnings.NewElement) {
-      dispatch(changeLocatorElement(updatedLocator));
+      await dispatch(changeLocatorElement(updatedLocator));
     } else {
       dispatch(changeLocatorAttributes(updatedLocator));
     }
diff --git a/src/features/locators/reducers/addCustomLocator.thunk.ts b/src/features/locators/reducers/addCustomLocator.thunk.ts
index 7262f5ac..bee8bf35 100644
--- a/src/features/locators/reducers/addCustomLocator.thunk.ts
+++ b/src/features/locators/reducers/addCustomLocator.thunk.ts
@@ -3,25 +3,27 @@ import { ILocator, LocatorsState, ValidationStatus } from '../types/locator.type
 import { generateId, getElementFullXpath } from '../../../common/utils/helpers';
 import { addLocatorToPageObj } from '../../pageObjects/pageObject.slice';
 import { addLocators, setScrollToLocator, setActiveSingle } from '../locators.slice';
-import {
-  getLocatorValidationStatus,
-  evaluateXpath,
-  generateSelectorByHash,
-  evaluateStandardLocator,
-} from '../utils/utils';
-import { PageObjectId } from '../../pageObjects/types/pageObjectSlice.types';
+import { getLocatorValidationStatus, evaluateXpath, generateSelectorByHash, evaluateLocator } from '../utils/utils';
 import { sendMessage } from '../../../pageServices/connector';
 import { locatorsAdapter } from '../selectors/locators.selectors';
 import { LocatorType } from '../../../common/types/common';
 
 export const addCustomLocator = createAsyncThunk(
   'locators/addCustomLocator',
-  async (payload: { newLocator: ILocator; pageObjectId: PageObjectId }, thunkAPI) => {
-    let { newLocator } = payload;
-    const { pageObjectId } = payload;
-    const { message, locator, locatorType } = newLocator;
+  async (payload: { newLocatorData: ILocator & { locatorFormValue: string } }, thunkAPI) => {
+    const { newLocatorData } = payload;
+    const { message, locator, locatorType, pageObj: pageObjectId, locatorFormValue: locatorValue } = newLocatorData;
 
-    const isCSSLocator = locatorType === LocatorType.cssSelector;
+    const isXPathLocator = locatorType === LocatorType.xPath;
+    const isStandardLocator: boolean =
+      [
+        LocatorType.cssSelector,
+        LocatorType.className,
+        LocatorType.id,
+        LocatorType.linkText,
+        LocatorType.name,
+        LocatorType.tagName,
+      ].includes(locatorType) || locatorType.startsWith('data-');
 
     let foundHash;
     let foundElementText;
@@ -31,21 +33,14 @@ export const addCustomLocator = createAsyncThunk(
     // eslint-disable-next-line @typescript-eslint/naming-convention
     const element_id = `${generateId()}_${pageObjectId}`;
 
-    newLocator = {
-      ...newLocator,
-      element_id,
-    };
-
     if (getLocatorValidationStatus(message) === ValidationStatus.SUCCESS) {
       try {
-        if (isCSSLocator && locator.cssSelector) {
-          ({ foundHash, foundElementText } = JSON.parse(
-            await evaluateStandardLocator(locator.cssSelector, LocatorType.cssSelector, element_id),
-          ));
-        } else {
-          if (locator.xPath) {
-            ({ foundHash, foundElementText } = JSON.parse(await evaluateXpath(locator.xPath, element_id)));
-          }
+        if ((isXPathLocator && locator.xPath) || (isStandardLocator && locatorValue)) {
+          const locatorData = await (isXPathLocator && locator.xPath
+            ? evaluateXpath(locator.xPath, element_id)
+            : evaluateLocator(locatorValue, locatorType, element_id));
+
+          ({ foundHash, foundElementText } = JSON.parse(locatorData));
         }
 
         if (!foundHash) {
@@ -53,8 +48,8 @@ export const addCustomLocator = createAsyncThunk(
           await sendMessage
             .assignJdnHash({
               jdnHash: foundHash,
-              ...{ locator: isCSSLocator ? locator.cssSelector ?? '' : locator.xPath ?? '' },
-              isCSSLocator,
+              ...{ locator: isStandardLocator ? locatorValue : locator.xPath ?? '' },
+              isCSSLocator: isStandardLocator,
             })
             .then((res) => {
               if (res === 'success') return res;
@@ -65,24 +60,29 @@ export const addCustomLocator = createAsyncThunk(
             });
         }
 
-        isCSSLocator
-          ? (fullXpath = await getElementFullXpath(foundHash))
-          : ({ cssSelector } = await generateSelectorByHash(element_id, foundHash));
+        if (isXPathLocator) {
+          ({ cssSelector } = await generateSelectorByHash(element_id, foundHash));
+        }
 
-        newLocator = {
-          ...newLocator,
-          locator: {
-            ...newLocator.locator,
-            ...(isCSSLocator ? { xPath: fullXpath } : { cssSelector }),
-          },
-          elemText: foundElementText || '',
-          jdnHash: foundHash,
-        };
+        if (isStandardLocator) {
+          fullXpath = await getElementFullXpath(foundHash);
+        }
       } catch (err) {
         console.warn(err);
       }
     }
 
+    const newLocator: ILocator = {
+      ...newLocatorData,
+      element_id,
+      locator: {
+        ...newLocatorData.locator,
+        ...(isStandardLocator ? { xPath: fullXpath } : { cssSelector }),
+      },
+      elemText: foundElementText || '',
+      jdnHash: foundHash,
+    };
+
     const dispatch = thunkAPI.dispatch;
     dispatch(addLocators([newLocator]));
     dispatch(addLocatorToPageObj({ pageObjId: pageObjectId, locatorId: newLocator.element_id }));

From 81b44d7ddee899b0c1278e30f74d1548e7ebfb97 Mon Sep 17 00:00:00 2001
From: Anna Iustus <ajustusa@gmail.com>
Date: Wed, 13 Dec 2023 21:41:57 +0100
Subject: [PATCH 3/5] feat: rewrite change locator element thunk

---
 .../reducers/changeLocatorElement.thunk.ts    | 98 ++++++++++---------
 1 file changed, 54 insertions(+), 44 deletions(-)

diff --git a/src/features/locators/reducers/changeLocatorElement.thunk.ts b/src/features/locators/reducers/changeLocatorElement.thunk.ts
index 97cd44eb..65580931 100644
--- a/src/features/locators/reducers/changeLocatorElement.thunk.ts
+++ b/src/features/locators/reducers/changeLocatorElement.thunk.ts
@@ -1,9 +1,9 @@
 import { ActionReducerMapBuilder, createAsyncThunk } from '@reduxjs/toolkit';
-import { LocatorTaskStatus, LocatorsState } from '../types/locator.types';
+import { ILocator, LocatorTaskStatus, LocatorsState } from '../types/locator.types';
 import { locatorsAdapter, selectLocatorById } from '../selectors/locators.selectors';
 import { RootState } from '../../../app/store/store';
 import { ChangeLocatorAttributesPayload } from '../locators.slice';
-import { evaluateXpath, generateSelectorByHash, evaluateStandardLocator } from '../utils/utils';
+import { generateSelectorByHash, evaluateLocator } from '../utils/utils';
 import { generateId, getElementFullXpath } from '../../../common/utils/helpers';
 import { sendMessage } from '../../../pageServices/connector';
 import { LocatorType } from '../../../common/types/common';
@@ -16,71 +16,81 @@ export const changeLocatorElement = createAsyncThunk(
     // ToDo: fix legacy naming
     // eslint-disable-next-line @typescript-eslint/naming-convention
     const { locator, element_id, locatorType, ...rest } = payload;
-    const isCSSLocator = locatorType === LocatorType.cssSelector;
-    let foundHash;
-    let foundElementText;
-    let cssSelector;
-    let fullXpath;
 
-    const isStandardLocator: boolean =
-      locatorType === LocatorType.cssSelector ||
-      locatorType === LocatorType.className ||
-      locatorType === LocatorType.id ||
-      locatorType === LocatorType.linkText ||
-      locatorType === LocatorType.name ||
-      locatorType === LocatorType.tagName ||
-      locatorType.startsWith('data-');
+    const state = thunkAPI.getState() as RootState;
+    const currentLocator = selectLocatorById(state, element_id);
 
-    if (isStandardLocator) {
-      ({ foundHash, foundElementText } = JSON.parse(await evaluateStandardLocator(locator, locatorType, element_id)));
-    } else {
-      ({ foundHash, foundElementText } = JSON.parse(await evaluateXpath(locator, element_id)));
-    }
+    if (!currentLocator) return;
 
-    const state = thunkAPI.getState() as RootState;
-    // ToDo: fix legacy naming
-    // eslint-disable-next-line @typescript-eslint/naming-convention
-    const _locator = selectLocatorById(state, element_id);
+    const isDataAttributeLocator = locatorType === LocatorType.dataAttributes || locatorType.startsWith('data-');
+    const isCSSLocator = locatorType === LocatorType.cssSelector;
+    const isXPathLocator = locatorType === LocatorType.xPath;
+    const isStandardLocator =
+      [
+        LocatorType.cssSelector,
+        LocatorType.className,
+        LocatorType.id,
+        LocatorType.linkText,
+        LocatorType.name,
+        LocatorType.tagName,
+        LocatorType.dataAttributes,
+      ].includes(locatorType) || locatorType.startsWith('data-');
 
-    if (!_locator) return;
+    let foundHash, foundElementText, cssSelector, fullXpath;
+
+    ({ foundHash, foundElementText } = JSON.parse(await await evaluateLocator(locator, locatorType, element_id)));
 
     if (!foundHash) {
       foundHash = generateId();
       await sendMessage
         .assignJdnHash({ jdnHash: foundHash, locator, isCSSLocator })
         .then((res) => {
-          if (res === 'success') return res;
-          else throw new Error('Failed to assign jdnHash');
+          if (res !== 'success') throw new Error('Failed to assign jdnHash');
         })
-        .catch((err) => {
-          console.log(err);
-        });
+        .catch((err) => console.log(err));
     }
 
-    isCSSLocator
-      ? (fullXpath = await getElementFullXpath(foundHash))
-      : ({ cssSelector } = await generateSelectorByHash(element_id, foundHash));
-
-    const newValue = {
-      ..._locator,
-      ...rest,
+    const newValue: ILocator = {
+      ...currentLocator,
+      locator: { ...currentLocator.locator, output: locator },
       locatorType,
+      ...rest,
       elemText: foundElementText || '',
       jdnHash: foundHash,
-      locator: {
-        ...(isCSSLocator ? { cssSelector: locator, xPath: fullXpath } : { xPath: locator, cssSelector }),
-        ...(isCSSLocator
-          ? { cssSelectorStatus: LocatorTaskStatus.SUCCESS }
-          : { xPathStatus: LocatorTaskStatus.SUCCESS }),
-      },
     };
 
+    if (isCSSLocator) {
+      fullXpath = await getElementFullXpath(foundHash);
+      newValue.locator.cssSelector = locator;
+      newValue.locator.xPath = fullXpath;
+      newValue.locator.cssSelectorStatus = LocatorTaskStatus.SUCCESS;
+    } else if (isXPathLocator) {
+      cssSelector = (await generateSelectorByHash(element_id, foundHash)).cssSelector;
+      newValue.locator.cssSelector = cssSelector;
+      newValue.locator.xPath = locator;
+      newValue.locator.xPathStatus = LocatorTaskStatus.SUCCESS;
+    } else if (isStandardLocator) {
+      if (isDataAttributeLocator) {
+        newValue.locator.attributes = {
+          ...newValue.locator.attributes,
+          dataAttributes: { ...(newValue.locator.attributes?.dataAttributes || {}) },
+        }; // unfreeze object
+
+        if (newValue.locator.attributes.dataAttributes) {
+          newValue.locator.attributes.dataAttributes[locatorType] = locator;
+        }
+      } else {
+        const attributes = { ...newValue.locator.attributes }; // unfreeze object
+        attributes[locatorType] = locator;
+        newValue.locator.attributes = attributes;
+      }
+    }
     return newValue;
   },
 );
 
 export const changeLocatorElementReducer = (builder: ActionReducerMapBuilder<LocatorsState>) => {
-  return builder
+  builder
     .addCase(changeLocatorElement.fulfilled, (state, { payload }) => {
       // @ts-ignore
       locatorsAdapter.upsertOne(state, payload);

From 9f6fc4879539819d73f0d9b333ab1cc656a4e038 Mon Sep 17 00:00:00 2001
From: Anna Iustus <ajustusa@gmail.com>
Date: Wed, 13 Dec 2023 21:43:39 +0100
Subject: [PATCH 4/5] fix: update getLocator fn, fix getting locator output
 string

---
 .../locators/selectors/locators.selectors.ts  |  4 +-
 .../selectors/locatorsByPO.selectors.ts       | 16 +++----
 src/features/locators/utils/locatorOutput.tsx | 48 +++++++++++--------
 src/pageServices/contentScripts/utils.ts      |  4 +-
 4 files changed, 39 insertions(+), 33 deletions(-)

diff --git a/src/features/locators/selectors/locators.selectors.ts b/src/features/locators/selectors/locators.selectors.ts
index 7f11ae3b..cac25f00 100644
--- a/src/features/locators/selectors/locators.selectors.ts
+++ b/src/features/locators/selectors/locators.selectors.ts
@@ -18,7 +18,7 @@ export const selectLocatorById = createSelector(selectById, (_item?: ILocator) =
       ..._item,
       locator: {
         ..._item.locator,
-        output: getLocator(_item.annotationType, _item.locator, _item.locatorType),
+        output: getLocator(_item.locator, _item.locatorType),
         taskStatus: getTaskStatus(_item.locator),
       },
     };
@@ -32,7 +32,7 @@ export const selectLocators = createSelector(selectAll, (items: ILocator[]) =>
       ..._item,
       locator: {
         ..._item.locator,
-        output: getLocator(_item.annotationType, _item.locator, _item.locatorType),
+        output: getLocator(_item.locator, _item.locatorType),
         taskStatus: getTaskStatus(_item.locator),
       },
     };
diff --git a/src/features/locators/selectors/locatorsByPO.selectors.ts b/src/features/locators/selectors/locatorsByPO.selectors.ts
index 04baf1ec..2128cb81 100644
--- a/src/features/locators/selectors/locatorsByPO.selectors.ts
+++ b/src/features/locators/selectors/locatorsByPO.selectors.ts
@@ -33,21 +33,21 @@ export const selectPresentLocatorsByPO = createSelector(
   (locators, pageObject) => {
     const locByPageObj = pageObject?.locators || [];
     return locators
-      .filter((loc) => locByPageObj.includes(loc.element_id))
-      .map((loc) => {
-        const annotationType = loc.annotationType || pageObject?.annotationType;
-        const locatorType = loc.locatorType || pageObject?.locatorType || LocatorType.xPath;
+      .filter((locator) => locByPageObj.includes(locator.element_id))
+      .map((locator) => {
+        const annotationType = locator.annotationType || pageObject?.annotationType;
+        const locatorType = locator.locatorType || pageObject?.locatorType || LocatorType.xPath;
         // ToDo: isDefaultLocatorType ???
-        const isDefaultLocatorType = () => !loc.locatorType && pageObject?.locatorType === LocatorType.cssSelector;
+        const isDefaultLocatorType = () => !locator.locatorType && pageObject?.locatorType === LocatorType.cssSelector;
 
         return {
-          ...loc,
+          ...locator,
           ...(annotationType && { annotationType }),
           ...(locatorType && { locatorType }),
           ...(isDefaultLocatorType() && {
             locator: {
-              ...loc.locator,
-              output: getLocator(loc.annotationType, loc.locator, pageObject?.locatorType),
+              ...locator.locator,
+              output: getLocator(locator.locator, pageObject?.locatorType),
             },
           }),
         };
diff --git a/src/features/locators/utils/locatorOutput.tsx b/src/features/locators/utils/locatorOutput.tsx
index 0eed2ff4..ece2e397 100644
--- a/src/features/locators/utils/locatorOutput.tsx
+++ b/src/features/locators/utils/locatorOutput.tsx
@@ -7,18 +7,27 @@ import { camelCase } from 'lodash';
 
 const getLocatorAnnotationStringByType = (value: string, locatorType: LocatorType, annotationType: AnnotationType) => {
   if (annotationType === AnnotationType.FindBy) return value;
+  const isDataAttributes = locatorType.startsWith('data-');
+
   // else return annotation string for UI Annotation Type:
   const annotations = {
-    'CSS Selector': value,
     xPath: value,
+    'CSS Selector': value,
     id: `#${value}`,
-    name: `[name='${value}']`,
+    name: `[name="${value}"]`,
     tagName: value,
-    className: `.${value}`,
+    className: value
+      .split(/\s+/)
+      .map((className) => `.${className}`)
+      .join(''),
     linkText: value,
-    dataAttributes: value,
+    dataAttributes: `[${locatorType}='${value}']`,
   };
 
+  if (isDataAttributes) {
+    return annotations.dataAttributes;
+  }
+
   return annotations[locatorType];
 };
 
@@ -50,23 +59,14 @@ export const getLocatorValueByType = (locatorValue: LocatorValue, type: LocatorT
   }
 };
 
-export const getLocator = (
-  annotationType: AnnotationType,
-  locatorValue: LocatorValue,
-  locatorType: LocatorType = LocatorType.xPath,
-) => {
-  const preparedLocatorType = locatorType.startsWith('data-') ? LocatorType.dataAttributes : locatorType;
-  const annotationString: string = getLocatorAnnotationStringByType(
-    getLocatorValueByType(locatorValue, locatorType),
-    preparedLocatorType,
-    annotationType,
-  );
+export const getLocator = (locatorValue: LocatorValue, locatorType: LocatorType = LocatorType.xPath) => {
+  const locatorValueByType = getLocatorValueByType(locatorValue, locatorType);
 
   if (locatorType === LocatorType.cssSelector) {
-    return annotationString || CALCULATING;
+    return locatorValueByType || CALCULATING;
   }
 
-  return annotationString;
+  return locatorValueByType;
 };
 
 export const getLocatorPrefix = (annotationType: AnnotationType, locatorType: LocatorType): string => {
@@ -113,7 +113,12 @@ export const renderColorizedJdiString = (
       <span>
         {annotationType}({getLocatorPrefix(annotationType, locatorType)}
       </span>
-      <span className="jdn__locator__output-string">{`"${locatorOutput}"`}</span>)
+      <span className="jdn__locator__output-string">{`"${getLocatorAnnotationStringByType(
+        locatorOutput,
+        locatorType,
+        annotationType,
+      )}"`}</span>
+      )
       <br />
       <span className="jdn__locator_item-type">public</span>
       <span>&nbsp;{type}&nbsp;</span>
@@ -121,9 +126,10 @@ export const renderColorizedJdiString = (
     </>
   );
 };
-
-export const getLocatorWithJDIAnnotation = (locator: string): string => `${AnnotationType.UI}("${locator}")`;
-
+// used in the coverage panel in the Copy option of the Context Menu:
+export const getLocatorWithJDIAnnotation = (locator: string, locatorType: LocatorType): string =>
+  `${AnnotationType.UI}("${getLocatorAnnotationStringByType(locator, locatorType, AnnotationType.UI)}")`;
+// used in the coverage panel in the Copy option of the Context Menu:
 export const getLocatorWithSelenium = (locator: string, option: string): string =>
   `${AnnotationType.FindBy}(${option} = "${locator}")`;
 
diff --git a/src/pageServices/contentScripts/utils.ts b/src/pageServices/contentScripts/utils.ts
index 528d7c3d..0ed03227 100644
--- a/src/pageServices/contentScripts/utils.ts
+++ b/src/pageServices/contentScripts/utils.ts
@@ -56,8 +56,8 @@ export const evaluateStandardLocator = ({
     }
 
     const length = foundElements.length;
-    const foundHash = foundElements && foundElements[0].getAttribute('jdn-hash');
-    const foundElementText = foundElements && foundElements[0].textContent;
+    const foundHash = foundElements[0] && foundElements[0].getAttribute('jdn-hash');
+    const foundElementText = foundElements[0] && foundElements[0].textContent;
     return JSON.stringify({ length, foundHash, elementId, foundElementText, originJdnHash });
   } catch (error) {
     console.error('error: ', error);

From 8e0ce163f82843cdef04ed5eca01c5e6d3e6a24d Mon Sep 17 00:00:00 2001
From: Anna Iustus <ajustusa@gmail.com>
Date: Wed, 13 Dec 2023 21:44:47 +0100
Subject: [PATCH 5/5] fix: update tests for getLocator

---
 src/__tests__/getLocator.test.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/__tests__/getLocator.test.js b/src/__tests__/getLocator.test.js
index 8f430826..7746c278 100644
--- a/src/__tests__/getLocator.test.js
+++ b/src/__tests__/getLocator.test.js
@@ -35,10 +35,10 @@ const data = [
 describe('locator presentation by getLocator()', () => {
   data.forEach((_data) => {
     test(`converts ${JSON.stringify(_data.input, LocatorType.xPath)} to ${_data.xpathOutput}`, () => {
-      expect(getLocator(AnnotationType.UI, _data.input)).toBe(_data.xpathOutput);
+      expect(getLocator(_data.input)).toBe(_data.xpathOutput);
     });
     test(`converts ${JSON.stringify(_data.input, LocatorType.cssSelector)} to ${_data.cssOutput}`, () => {
-      expect(getLocator(AnnotationType.UI, _data.input, LocatorType.cssSelector)).toBe(_data.cssOutput);
+      expect(getLocator(_data.input, LocatorType.cssSelector)).toBe(_data.cssOutput);
     });
   });
 });