| /** | 
|  * @file        S&OP Dialog component to wrap common methods the team encounter during development | 
|  * @description Dialog class extending libappbase's DialogBase. | 
|  * All S&OP page objects inherit from our own class (inheriting e2e/libappbase), but we can propose common methods to them. | 
|  * @author      Clarence (clarence.chan@3ds.com) | 
|  * @copyright   Dassault Systèmes | 
|  */ | 
| import { DialogBase } from '../libappbase/dialogbase'; | 
| import { LogMessage } from '../libappbase/logmessage'; | 
| import { Timeout } from '../libmp/appmp'; | 
| import { ButtonSOP } from './buttonsop'; | 
| import { UIActionSOP } from './objectsop'; | 
| import { UtilSOP } from './utilsop'; | 
| import { browser } from '../e2elib/node_modules/protractor'; | 
|   | 
| export enum DialogBatchEditActionLink { | 
|   EnableBatchEdit = 'Edit', | 
|   DisableBatchEdit = 'Revert', | 
| } | 
|   | 
| /** | 
|  * Dialog key value pairs of UI name and its value. Created to mimic the generic type DialogFieldsType. | 
|  */ | 
| export interface DialogUIKeyValues { | 
|   [propName: string]: string; | 
| } | 
|   | 
| export class DialogSOP<DialogFieldsType> extends DialogBase { | 
|   protected readonly _uiMap = new Map<string, UIActionSOP>(); // Tracks UI element to set value and verify value (e.g key = Start , mapped to a DateTimeSelector object in dialog) | 
|   private readonly _btnOK: ButtonSOP; | 
|   | 
|   /** | 
|    * Replace all values in object entries (e.g overwrite 'false' to all interface elements). | 
|    * | 
|    * @param fields Key-value pair of values to enter per UI element. Key = the UI element to update. | 
|    * @param replaceValue Value to replace in all object entries. | 
|    * @returns The reconstructed object entries with the new replaced value. | 
|    */ | 
|   public static replaceDialogFieldsTypeValues(fields: any, replaceValue: string): DialogUIKeyValues { | 
|     let result = {}; // Rebuild the interface to overwrite value to all entries | 
|     // Not interested in value from Object.entries, thus put underscore to prevent lint error | 
|     for (const [key, _value] of Object.entries(fields)) { | 
|       result = {...result, [key]: replaceValue}; | 
|     } | 
|   | 
|     return result; | 
|   } | 
|   | 
|   /** | 
|    * Create dialog by defining name, optionally providing OK and Cancel button names. | 
|    * Since most dialogs OK and Cancel buttons have same name, this makes the script cleaner without need to define the names. | 
|    * | 
|    * @param dialogName Name of dialog to initialize | 
|    * @param btnOkName Button name of positive action to save changes | 
|    * @param btnCancelName Button name of negative action to cancel changes | 
|    */ | 
|   public constructor(dialogName: string, btnOkName: string = 'btnOk', btnCancelName: string = 'btnCancel') { | 
|     super(dialogName, btnOkName, btnCancelName); | 
|     this._btnOK = new ButtonSOP(btnOkName); // ABSAPI: duplicate ok button since S&OP we need buttons to be of ButtonSOP types (remove once migrate to QDialog) | 
|   } | 
|   | 
|   /** | 
|    * Click on action link text of UI element in dialog (e.g action link text of an edit field). | 
|    * | 
|    * @param actionLinkText Text defined on action link | 
|    * @param values Key-value pair of values to enter per UI element. | 
|    */ | 
|   public async clickActionLinkText(values: DialogFieldsType): Promise<void> { | 
|     for (const [k, v] of Object.entries(values)) { | 
|       const uiObj = this._uiMap.get(k); | 
|       if (uiObj) { | 
|         await uiObj.clickActionLinkText(v); | 
|       } else { | 
|         throw Error(`Dialog ${await this.getTitle()}: Object mapping not defined for UI element '${k}'. Define mapping in dialog constructor.`); | 
|       } | 
|     } | 
|   } | 
|   | 
|   /** | 
|    * Override libappbase clickOK to use browser.wait with timeout. | 
|    */ | 
|   public async clickOK(waitTime?: number | undefined): Promise<boolean> { | 
|     return browser.wait(async () => { | 
|       return super.clickOK(waitTime); | 
|     }, Timeout.Short, 'Unable to click OK button. Timeout waiting for button to be enabled.'); | 
|   } | 
|   | 
|   public async getDialogValues(values: DialogFieldsType): Promise<DialogUIKeyValues> { | 
|     let getValues: DialogUIKeyValues = {}; | 
|   | 
|     // "v" value not used, thus underscore to prevent compile warning | 
|     for (const [k, _v] of Object.entries(values)) { | 
|       const uiObj = this._uiMap.get(k); | 
|       if (uiObj) { | 
|         const value = await uiObj.getValueString(); | 
|         getValues = {...getValues, [k]: value}; // Overwrite the UI value passed in via argument | 
|       } else { | 
|         throw Error(`Dialog ${await this.getTitle()}: Object mapping not defined for UI element '${k}'. Define mapping in dialog constructor.`); | 
|       } | 
|     } | 
|   | 
|     return getValues; | 
|   } | 
|   | 
|   public async hoverElementVerifyTooltip(values: DialogFieldsType): Promise<void> { | 
|     for (const [k, v] of Object.entries(values)) { | 
|       const uiObj = this._uiMap.get(k); | 
|       if (uiObj) { | 
|         await uiObj.verifyTooltip(v); | 
|       } else { | 
|         throw Error(`Dialog ${await this.getTitle()}: Object mapping not defined for UI element '${k}'. Define mapping in dialog constructor.`); | 
|       } | 
|     } | 
|   } | 
|   | 
|   /** | 
|    * Set UI element values in dialog (e.g set value to an edit field). | 
|    * | 
|    * @param values Key-value pair of values to enter per UI element. Key = the UI element to update, Value = the value to update. | 
|    * Derived dialog to provide the interface to ensure scriptor is guided to what keys/UI element | 
|    * can be updated else a typo by scriptor in spec file goes undetected and waste times debugging what goes wrong. | 
|    * | 
|    * @example export interface DialogCurrencyFields { | 
|    *            Currency?: string; | 
|    *            Start?: string; | 
|    *            Rate?: string; | 
|    *            RateLabel?: string; | 
|    *          } | 
|    */ | 
|   public async updateDialogValues(values: DialogFieldsType, randomSetValue: boolean = false): Promise<void> { | 
|     for (const [k, v] of Object.entries(values)) { | 
|       const uiObj = this._uiMap.get(k); | 
|       if (uiObj) { | 
|         // console.log(`@@@@@@@@@ ${k} --------------- ${v}`) | 
|         if (randomSetValue) { | 
|           await uiObj.toggleValue!(); | 
|         } else { | 
|           await uiObj.setValue(v); | 
|         } | 
|       } else { | 
|         throw Error(`Dialog ${await this.getTitle()}: Object mapping not defined for UI element '${k}'. Define mapping in dialog constructor.`); | 
|       } | 
|     } | 
|   } | 
|   | 
|   public async updateDialogValuesBatchEdit(batchEditAction: DialogBatchEditActionLink, values: DialogFieldsType): Promise<void> { | 
|     for (const [k, v] of Object.entries(values)) { | 
|       const uiObj = this._uiMap.get(k); | 
|       if (uiObj) { | 
|         // Click action link regardless enable or disable batch edit (the component to throw error if action link text does not match) | 
|         await uiObj.clickActionLinkText(batchEditAction); | 
|   | 
|         // If enable batch edit, follow-up with setting value to dialog field | 
|         if (batchEditAction === DialogBatchEditActionLink.EnableBatchEdit) { | 
|           await uiObj.setValue(v); | 
|         } | 
|       } else { | 
|         throw Error(`Dialog ${await this.getTitle()}: Object mapping not defined for UI element '${k}'. Define mapping in dialog constructor.`); | 
|       } | 
|     } | 
|   } | 
|   | 
|   /** | 
|    * Verify dialog fields support batch edit by checking if action link present. | 
|    * If disabled, the action link should show Edit for user to click to input value. | 
|    * If enabled, the action link should show Revert for user to toggle off batch edit. | 
|    * | 
|    * @param values Key-value pair of values to enter per UI element. Key = the UI element to update, Value = the value to update. | 
|    */ | 
|   public async verifyBatchEditEnabled(values: DialogFieldsType): Promise<void> { | 
|     for (const [k, v] of Object.entries(values)) { | 
|       const uiObj = this._uiMap.get(k); | 
|       if (uiObj) { | 
|         const expectedEnabled = v === 'true'; | 
|         // If enabled, expect action link text to be Revert for user to click to disable it (vice versa) | 
|         const expectedActionLinkText = expectedEnabled ? DialogBatchEditActionLink.DisableBatchEdit : DialogBatchEditActionLink.EnableBatchEdit; | 
|   | 
|         await uiObj.verifyBatchEditEnabled(expectedEnabled, expectedActionLinkText); | 
|       } else { | 
|         throw Error(`Dialog ${await this.getTitle()}: Object mapping not defined for UI element '${k}'. Define mapping in dialog constructor.`); | 
|       } | 
|     } | 
|   } | 
|   | 
|   /** | 
|    * Verify button disabled and optionally providing tooltip to verify. | 
|    * | 
|    * @param button Button to verify disabled. | 
|    * @param tooltip Disabled tooltip to verify. | 
|    * @param fullMatch By default false = partial comparison when verifying tooltip. | 
|    */ | 
|   public async verifyButtonDisabled(button: ButtonSOP, tooltip?: string, fullMatch: boolean = false): Promise<void> { | 
|     const [isBtnClickable, disabledTooltip] = await button.getIsClickable(true, Timeout.ButtonState, false); | 
|   | 
|     expect(isBtnClickable).toBe(false, LogMessage.btn_isEnabled(await button.getComponentLabel())); | 
|     // Only if tooltip passed in, we check if it matches | 
|     if (tooltip) { | 
|       if (fullMatch) { | 
|         expect(disabledTooltip).toBe(tooltip, LogMessage.tooltip_notMatched(tooltip, disabledTooltip)); | 
|       } else { | 
|         expect(disabledTooltip).toContain(tooltip, LogMessage.tooltip_notMatched(tooltip, disabledTooltip)); | 
|       } | 
|     } | 
|   } | 
|   | 
|   /** | 
|    * Verify button enabled. If disabled, fail by mentioning the disabled tooltip if any. | 
|    */ | 
|   public async verifyButtonEnabled(button: ButtonSOP): Promise<void> { | 
|     const [isBtnClickable, disabledTooltip] = await button.getIsClickable(true, Timeout.ButtonState); | 
|     const label = await button.getComponentLabel(); | 
|   | 
|     expect(isBtnClickable).toBe(true, LogMessage.btn_notClickable(label, disabledTooltip)); | 
|   } | 
|   | 
|   /** | 
|    * Verify UI element enabled/disabled. | 
|    * | 
|    * @param values Key-value pair of values to verify per UI element. Key = the UI element to verify, Value = the expected value. | 
|    */ | 
|   public async verifyDialogEnabled(values: DialogFieldsType): Promise<void> { | 
|     for (const [k, v] of Object.entries(values)) { | 
|       const uiObj = this._uiMap.get(k); | 
|       if (uiObj) { | 
|         await uiObj.verifyEnabled(v === 'true'); | 
|       } else { | 
|         throw Error(`Dialog ${await this.getTitle()}: Object mapping not defined for UI element '${k}'. Define mapping in dialog constructor.`); | 
|       } | 
|     } | 
|   } | 
|   | 
|   /** | 
|    * Verify UI element values in dialog (e.g verify value of an edit field). | 
|    * | 
|    * @param values Key-value pair of values to verify per UI element. Key = the UI element to verify, Value = the expected value. | 
|    * Derived dialog to provide the interface to ensure scriptor is guided to what keys/UI element | 
|    * can be verified else a typo by scriptor in spec file goes undetected and waste times debugging what goes wrong. | 
|    * | 
|    * @example export interface DialogCurrencyFields { | 
|    *            Currency?: string; | 
|    *            Start?: string; | 
|    *            Rate?: string; | 
|    *            RateLabel?: string; | 
|    *          } | 
|    */ | 
|   public async verifyDialogValues(values: DialogFieldsType): Promise<void> { | 
|     for (const [k, v] of Object.entries(values)) { | 
|       const uiObj = this._uiMap.get(k); | 
|       if (uiObj) { | 
|         await uiObj.verifyValue(v); | 
|       } else { | 
|         throw Error(`Dialog ${await this.getTitle()}: Object mapping not defined for UI element '${k}'. Define mapping in dialog constructor.`); | 
|       } | 
|     } | 
|   } | 
|   | 
|   /** | 
|    * Verify UI element visibility in dialog. | 
|    * | 
|    * @param values Key-value pair of values to verify per UI element. Key = the UI element to verify, Value = the expected value. | 
|    */ | 
|   public async verifyDialogUIVisible(values: DialogFieldsType): Promise<void> { | 
|     for (const [k, v] of Object.entries(values)) { | 
|       const uiObj = this._uiMap.get(k); | 
|       if (uiObj) { | 
|         await uiObj.verifyVisible(v); | 
|       } else { | 
|         throw Error(`Dialog ${await this.getTitle()}: Object mapping not defined for UI element '${k}'. Define mapping in dialog constructor.`); | 
|       } | 
|     } | 
|   } | 
|   | 
|   /** | 
|    * Verify UI element if has mask error. | 
|    * | 
|    * @param values Key-value pair of values to verify per UI element. Key = the UI element to verify, Value = if has mask error. | 
|    */ | 
|   public async verifyHasMaskError(values: DialogFieldsType): Promise<void> { | 
|     for (const [k, v] of Object.entries(values)) { | 
|       const uiObj = this._uiMap.get(k); | 
|       if (uiObj) { | 
|         await uiObj.verifyHasMaskError(v === 'true'); | 
|       } else { | 
|         throw Error(`Dialog ${await this.getTitle()}: Object mapping not defined for UI element '${k}'. Define mapping in dialog constructor.`); | 
|       } | 
|     } | 
|   } | 
|   | 
|   /** | 
|    * Verify OK button disabled and optionally providing tooltip to verify. | 
|    * | 
|    * @param tooltip Disabled tooltip to verify. | 
|    * @param fullMatch By default false = partial comparison when verifying tooltip. | 
|    */ | 
|   public async verifyOKDisabled(tooltip?: string, fullMatch: boolean = false): Promise<void> { | 
|     return this.verifyButtonDisabled(this._btnOK, tooltip, fullMatch); | 
|   } | 
|   | 
|   /** | 
|    * Verify OK button enabled. If disabled, fail by mentioning the disabled tooltip if any. | 
|    */ | 
|   public async verifyOKEnabled(): Promise<void> { | 
|     return this.verifyButtonEnabled(this._btnOK); | 
|   } | 
|   | 
|    /** | 
|     * Returns the name of fields defined in each subclass DialogType. Usually used when passing UI name into step description method for formatting. | 
|     * Prevent hardcoding field name in scripts as this method ensures compile time error if any field names changes. | 
|     * | 
|     * @param name Field name to reference. | 
|     * @returns Convert to string and return the field name. | 
|     */ | 
|   public fieldName(name: keyof DialogFieldsType): string { | 
|     return name.toString(); | 
|   } | 
| } | 
|   | 
| // Step description to re-use in spec file to prevent scriptor re-write each time | 
| const stepDialog = { | 
|   clickActionLinkText: (fields: any): string => `Click action link for fields ${UtilSOP.getInterfaceObjectAsKeyValueArray(fields)}.`, | 
|   clickCancel: (): string => 'Click Cancel to dismiss dialog.', | 
|   clickButton: (buttonName: string): string => `Click button ${buttonName}.`, | 
|   clickOK: (buttonName: string = 'OK'): string => stepDialog.clickButton(buttonName), | 
|   clickTab: (tabTitle: string): string => `Click tab "${tabTitle}".`, | 
|   | 
|   updateDialogValues: (dialogName: string, fields: any): string => { | 
|     const arr: string[] = UtilSOP.getInterfaceObjectAsKeyValueArray(fields); | 
|     return `In dialog ${dialogName}, set ${arr.join(', ')}.`; | 
|   }, | 
|   updateDialogValuesBatchEdit: (dialogName: string, actionLinkText: DialogBatchEditActionLink, fields: any): string => { | 
|     const arr: string[] = UtilSOP.getInterfaceObjectAsKeyValueArray(fields); | 
|     return `In dialog ${dialogName}, click each field's action link ${actionLinkText} and set value as such: ${arr.join(', ')}.`; | 
|   }, | 
|   verifyAvailableValues: (dialogName: string, uiName: string, values: string[]): string => `In dialog ${dialogName}, verify values '${values.join(', ')}' exist in field ${uiName}.`, | 
|   verifyBatchEditEnabled: (dialogName: string, fields: any): string => { | 
|     const arr: string[] = UtilSOP.getInterfaceObjectAsKeyValueArray(fields); | 
|     return `In dialog ${dialogName}, verify fields are on batch edit mode and enabled state as such: ${arr.join(', ')}.`; | 
|   }, | 
|   verifyDialogEnabled: (dialogName: string, fields: any): string => { | 
|     const arr: string[] = UtilSOP.getInterfaceObjectAsKeyValueArray(fields); | 
|     return `In dialog ${dialogName}, verify fields enabled ${arr.join(', ')}.`; | 
|   }, | 
|   verifyDialogUIVisible: (dialogName: string, fields: any): string => { | 
|     const arr: string[] = UtilSOP.getInterfaceObjectAsKeyValueArray(fields); | 
|     return `In dialog ${dialogName}, verify fields visibility ${arr.join(', ')}.`; | 
|   }, | 
|   verifyDialogValues: (dialogName: string, fields: any): string => { | 
|     const arr: string[] = UtilSOP.getInterfaceObjectAsKeyValueArray(fields); | 
|     return `In dialog ${dialogName}, verify ${arr.join(', ')}.`; | 
|   }, | 
|   verifyHasMaskError: (dialogName: string, fields: any): string => { | 
|     const arr: string[] = UtilSOP.getInterfaceObjectAsKeyValueArray(fields); | 
|     return `In dialog ${dialogName}, verify has mask error for ${arr.join(', ')}.`; | 
|   }, | 
|   verifyValueExists: (dialogName: string, uiName: string, value: string): string => `In dialog ${dialogName}, verify value '${value}' exist in field ${uiName}.`, | 
|   verifyValueNotExist: (dialogName: string, uiName: string, value: string): string => `In dialog ${dialogName}, verify value '${value}' not exist in field ${uiName}.`, | 
|   | 
|   verifyButtonDisabled: (buttonName: string, preconditionText?: string): string => { | 
|     const displayPreconditionText = preconditionText ? ` with precondition = ${preconditionText}` : ''; | 
|     return `Verify ${buttonName} disabled${displayPreconditionText}.`; | 
|   }, | 
|   verifyButtonEnabled: (buttonName: string): string => `Verify ${buttonName} enabled.`, | 
|   | 
|   verifyOKDisabled: (buttonName: string = 'OK', preconditionText?: string): string => stepDialog.verifyButtonDisabled(buttonName, preconditionText), | 
|   verifyOKEnabled: (buttonName: string = 'OK'): string => stepDialog.verifyButtonEnabled(buttonName), | 
| }; | 
|   | 
| export { stepDialog as StepDialog }; |