/** 
 | 
 * @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 }; 
 |