import { ColumnMenuName, FilterMenuName, SortDirection } from '../e2elib/lib/src/helper/enumhelper'; 
 | 
import { ContextMenu } from '../e2elib/lib/src/pageobjects/contextmenu/contextmenu.component'; 
 | 
import { List, QCellAdditionAttribute } from '../e2elib/lib/src/pageobjects/list/list.component'; 
 | 
import { ListCell } from '../e2elib/lib/src/pageobjects/list/listcell.component'; 
 | 
import { ListColumn } from '../e2elib/lib/src/pageobjects/list/listcolumn.component'; 
 | 
import { ListRow } from '../e2elib/lib/src/pageobjects/list/listrow.component'; 
 | 
import { protractor, by } from '../e2elib/node_modules/protractor'; 
 | 
import { FilterManager } from './filtermanager'; 
 | 
import { EnumLike, getEnumKeyEntries, getHtmlContent, lowercaseFirstLetter } from './utils'; 
 | 
import { promise } from '../e2elib/node_modules/@types/selenium-webdriver/'; 
 | 
import { QUtils } from '../e2elib/lib/src/main/qutils.class'; 
 | 
  
 | 
export class ListBase extends List { 
 | 
  private readonly _dlgFilterManager = new FilterManager(); 
 | 
  
 | 
  /** 
 | 
   * Configure the list to show specified columns. 
 | 
   * 
 | 
   * @param actionBarName action bar name for the list. 
 | 
   * @param columnMenu column which shall be toggled to show in the list 
 | 
   * @param columnMenus columns which shall be toggled to show in the list 
 | 
   * @example 
 | 
   * await list.configureColumns('abpList', 'OrderID'); 
 | 
   * await list2.configureColumns('abpList2', 'MachineID', 'MachineName', 'Speed', 'MachineType'); 
 | 
   */ 
 | 
  public async configureColumns(actionBarName: string, columnMenu?: ColumnMenuName | string, ...columnMenus: ColumnMenuName[] | string[]): Promise<void> { 
 | 
    await this.leftClickOnWhiteSpace(); 
 | 
    if (columnMenu) { 
 | 
      await super.configureColumns(actionBarName, columnMenu); 
 | 
    } 
 | 
    for (const menu of columnMenus) { 
 | 
      await super.configureColumns(actionBarName, menu); 
 | 
    } 
 | 
  } 
 | 
  
 | 
  /** 
 | 
   * Simulate dragging a list row and drop it on another list row 
 | 
   * 
 | 
   * @param sourceRow The list row(s) to be dragged 
 | 
   * @param targetRow The list row to be dropped 
 | 
   * @param hasTooltips retrieve the info tooltip while drop is not allow 
 | 
   * 
 | 
   * @returns Promise<string> 
 | 
   */ 
 | 
  public async dropRowOnTargetRow(sourceRow: ListRow | ListRow[], targetRow: ListRow, hasTooltips: boolean = false): Promise<string> { 
 | 
    await this.selectRows(Array.isArray(sourceRow) ? sourceRow : [sourceRow]); 
 | 
    if (Array.isArray(sourceRow)) { 
 | 
      return QUtils.emitDragAndDropEvent(sourceRow[0].element, targetRow.element, hasTooltips); 
 | 
    } 
 | 
    return QUtils.emitDragAndDropEvent(sourceRow.element, targetRow.element, hasTooltips); 
 | 
  } 
 | 
  
 | 
  /** 
 | 
   * Simulate dragging a list row and drop it on the white space of the list 
 | 
   * 
 | 
   * @param sourceRow Source row to drag 
 | 
   * @param hasTooltips retrieve the info tooltip while drop is not allow 
 | 
   */ 
 | 
  public async dropRowOnWhiteSpace(sourceRow: ListRow | ListRow[], hasTooltips: boolean = false): Promise<string> { 
 | 
    const dropTarget = this.element.element(by.css('q-scrollarea')); 
 | 
    await this.selectRows(Array.isArray(sourceRow) ? sourceRow : [sourceRow]); 
 | 
  
 | 
    if (Array.isArray(sourceRow)) { 
 | 
      return QUtils.emitDragAndDropEvent(sourceRow[0].element, dropTarget, hasTooltips); 
 | 
    } 
 | 
    return QUtils.emitDragAndDropEvent(sourceRow.element, dropTarget, hasTooltips); 
 | 
  } 
 | 
  
 | 
  /** 
 | 
   * Finds a row (by typing the specified search value) from the list, then gets the row. 
 | 
   * 
 | 
   * This method allows auto-navigation to the specified row. 
 | 
   * 
 | 
   * @param target target row to be navigated 
 | 
   * @param parents parent rows of the targeted row (optional: only should be specified for hierarchy list) 
 | 
   * 
 | 
   * @return List row found 
 | 
   */ 
 | 
  public async findRowByValue(target: ColumnValueSearch, parents?: ColumnValueSearch[]): Promise<ListRow> { 
 | 
    // Search through the list to ensure the row is visible to avoid undefined row when using getRowByValue() 
 | 
    if (parents) { 
 | 
      await this.searchListInHierarchy(target, parents); 
 | 
    } else { 
 | 
      await this.searchList(target.columnID, target.searchValue); 
 | 
    } 
 | 
    return this.getRowByValue([{ columnID: target.columnID, value: target.searchValue }]); 
 | 
  } 
 | 
  
 | 
  /** 
 | 
   * Get all the cells within a list row 
 | 
   * 
 | 
   * @param row The row to retrieve it cells 
 | 
   * 
 | 
   * @return A Column to Cell Map object. 
 | 
   */ 
 | 
  private async getAllCellsInRow(row: ListRow): Promise<Map<string, ListCell>> { 
 | 
    const cellCount = await row.getCellCount(); 
 | 
    let columnName = ''; 
 | 
    const columnCellMap: Map<string, ListCell> = new Map<string, ListCell>(); 
 | 
  
 | 
    for (let i = 0; i < cellCount; i++) { 
 | 
      const cell = await row.getCell(i); 
 | 
      const column = await this.getColumnByIndex(i); 
 | 
  
 | 
      try { 
 | 
        columnName = await column.getValue(); 
 | 
      } catch { 
 | 
        await column.resizeColumn(50); 
 | 
        columnName = await column.getValue(); 
 | 
      } finally { 
 | 
        columnName = columnName === '' ? `column${i + 1}` : columnName; 
 | 
      } 
 | 
      columnCellMap.set(columnName, cell); 
 | 
    } 
 | 
  
 | 
    return columnCellMap; 
 | 
  } 
 | 
  
 | 
  /** 
 | 
   * Get the cells by columns of a list row 
 | 
   * 
 | 
   * @param row The row to retrieve it cells 
 | 
   * @param columnsIndexName The columns' name/index of the cells to retreive 
 | 
   */ 
 | 
  public async getCellsFromRow(row: ListRow, columnsIndexName?: string[] | number[] | EnumLike): Promise<Map<string | number, ListCell>> { 
 | 
    let columnCellMap: Map<string | number, ListCell> = new Map(); 
 | 
  
 | 
    if (columnsIndexName) { 
 | 
      // type guarder that checks if columnsIndexName is array or EnumLike, true if it's EnumLike 
 | 
      const enumLike = (e: string[] | number[] | EnumLike): e is EnumLike => !Array.isArray(e); 
 | 
  
 | 
      if (enumLike(columnsIndexName)) { 
 | 
        const keys = getEnumKeyEntries(columnsIndexName); 
 | 
        for (const key of keys) { 
 | 
          let cell: ListCell; 
 | 
          try { 
 | 
            cell = await row.getCell(columnsIndexName[key]); 
 | 
          } catch { 
 | 
            await this.resizeColumnWithCoveredName(columnsIndexName[key], 50); 
 | 
            cell = await row.getCell(columnsIndexName[key]); 
 | 
          } 
 | 
          columnCellMap.set(key, cell); 
 | 
        } 
 | 
      } else { 
 | 
        for (const column of columnsIndexName as string[] | number[]) { 
 | 
          let cell: ListCell; 
 | 
          try { 
 | 
            cell = await row.getCell(column); 
 | 
          } catch { 
 | 
            if (typeof column === 'string') { 
 | 
              await this.resizeColumnWithCoveredName(column, 50); 
 | 
            } 
 | 
            cell = await row.getCell(column); 
 | 
          } 
 | 
          columnCellMap.set(column, cell); 
 | 
        } 
 | 
      } 
 | 
    } else { 
 | 
      columnCellMap = await this.getAllCellsInRow(row); 
 | 
    } 
 | 
  
 | 
    return columnCellMap; 
 | 
  } 
 | 
  
 | 
  /** 
 | 
   * Get the cell value of the row by column 
 | 
   * 
 | 
   * @param columnIndexName Column name of the cell value 
 | 
   * @param row Row to retrieve the cell value 
 | 
   * @param isImgAttribute To determine whether pass-in column name is image attribute column 
 | 
   * @returns the cell value of the row 
 | 
   */ 
 | 
  public async getCellValueFromRow(columnIndexName: string | number, rows: ListRow, isImgAttribute?: boolean): Promise<string>; 
 | 
  
 | 
  /** 
 | 
   * Loop through all the rows to get the cell value by column 
 | 
   * 
 | 
   * @param columnIndexName Column name of the cell value 
 | 
   * @param rows Rows to retrieve the cell value 
 | 
   * @param isImgAttribute To determine whether pass-in column name is image attribute column 
 | 
   * @returns Array of cell values 
 | 
   */ 
 | 
  public async getCellValueFromRow(columnIndexName: string | number, rows: ListRow[], isImgAttribute?: boolean): Promise<string[]>; 
 | 
  
 | 
  public async getCellValueFromRow(columnIndexName: string | number, rows: ListRow | ListRow[], isImgAttribute: boolean = false): Promise<string | string[]> { 
 | 
    if (rows instanceof ListRow) { 
 | 
      const cell = await rows.getCell(columnIndexName); 
 | 
      const value = isImgAttribute ? await cell.getImageAttribute() : await cell.getValue(); 
 | 
      return value; 
 | 
    } else { 
 | 
      const promises: promise.Promise<string>[] = []; 
 | 
      for (const row of rows) { 
 | 
        const value = row.getCell(columnIndexName).then((cell: ListCell) => (isImgAttribute ? cell.getImageAttribute() : cell.getValue())); 
 | 
        promises.push(value); 
 | 
      } 
 | 
      const values = await protractor.promise.all(promises); 
 | 
      return values; 
 | 
    } 
 | 
  } 
 | 
  
 | 
  /** 
 | 
   * Get all cell values from a given row. 
 | 
   * 
 | 
   * @description The function will obtain all cell values from the given row.\ 
 | 
   * If `columnIDs` is defined, the function will obtain cell values from the given columns, in the given row. 
 | 
   * 
 | 
   * @template T Generic type T which extends the properties of either `EnumLike`, `string[]` or `number[]`. 
 | 
   * @template K Generic type K which stands for key, which extends the properties from the key of type `T` only when it's enumerator-like. 
 | 
   * @param {ListRow} row `ListRow` The row to retrive its cell values 
 | 
   * @param {T} columnIDs `T` (Optional) Columns to determine the values to be retrieved. Accepts an enumerator, `string[]` or `number[]`. 
 | 
   * 
 | 
   * @return `Promise<Record<K, string>` or `Promise<Record<string, string>` -- depends on the parameter `columnIDs`.\ 
 | 
   * IF it's enumerator-like then former return type will be used, ELSE IF it's `string[]` or `number[]` then the latter return type will be used. 
 | 
   * 
 | 
   * @example |   ...  | Order ID |   Name  | ...and other columns 
 | 
   *          | imgYes |  12345   | Order 1 | ...and other values 
 | 
   * // assume row is illustrated like above. 
 | 
   * // first column is sized shorter than the column name resulting in '...'. Assume the column name as 'ImgPlanned' 
 | 
   * 
 | 
   * ----- example usage (most recommended usage) ----- 
 | 
   * enum OrderColumn { 
 | 
   * IMGPLANNED = 'imgPlanned'; 
 | 
   * ORDER_ID = 'Order ID'; 
 | 
   * NAME = 'Name'; 
 | 
   * } 
 | 
   * // type-safe call by passing the entire OrderColumn enum into the method. 
 | 
   * const {IMGPLANNED, ORDER_ID, NAME} = await getCellValuesInRow(row, OrderColumn); // safe, obtains value from column imgPlanned, Order ID and Name. 
 | 
   * const {NAME} = await getCellValuesInRow(row, OrderColumn); // safe, obtains value from column Name. 
 | 
   * const {IMGPLANNED, random1} = await getCellValuesInRow(row, OrderColumn) // ERROR, typescript will auto highlight 'random1' in red. 
 | 
   * 
 | 
   * // you can also call such as the line of code below, but TypeScript will not tell you whether the names are correct or not. 
 | 
   * const {imgPlanned, orderID} = await getCellValuesInRow(row, ['ImgPlanned', 'Order ID']); 
 | 
   * // thus this is not type-safe and we only recommend to perform type-safe calls. 
 | 
   */ 
 | 
  public async getCellValuesFromRow<T extends CellValue, K extends keyof T>( 
 | 
    row: ListRow, 
 | 
    columnIDs?: T, 
 | 
  ): Promise<Record<T extends undefined ? string : T extends any[] ? string : K, string>> { 
 | 
    const values = {} as any; // type assertion 
 | 
    const columnCellMap = await this.getCellsFromRow(row, columnIDs); 
 | 
    // type guarder 
 | 
    const enumLike = (e: any): e is EnumLike => !Array.isArray(e); 
 | 
    // if columnsIndexName exists and it's not enum-like, or doesn't exists at all (to prevent lowercasing if columnsIndexName is passed in as enumerator) 
 | 
    const isNotEnumLike = (columnIDs && !enumLike(columnIDs)) || !columnIDs; 
 | 
  
 | 
    for (const [column, cell] of columnCellMap) { 
 | 
      let value = await cell.isImageAttributeCell() ? await cell.getImageAttribute() : await cell.getValue(); // Clarence added to check img attribute, create RfC for Abstraction API 
 | 
      let columnName = column; 
 | 
      if (isNotEnumLike) { 
 | 
        if (typeof column === 'string') { 
 | 
          columnName = column.replace(/\s/, ''); 
 | 
          columnName = lowercaseFirstLetter(columnName); 
 | 
        } else if (typeof column === 'number') { 
 | 
          columnName = column.toString(10); 
 | 
        } 
 | 
      } 
 | 
  
 | 
      if (value === '') { 
 | 
        value = await cell.getImageAttribute(); 
 | 
      } 
 | 
  
 | 
      values[columnName] = value; 
 | 
    } 
 | 
  
 | 
    return values; 
 | 
  } 
 | 
  
 | 
  /** 
 | 
   * Get the column by the title of the tooltip of the column 
 | 
   * 
 | 
   * @param title The title of the tooltip of the column 
 | 
   */ 
 | 
  public async getColumnByToolipTitle(title: string): Promise<ListColumn | undefined> { 
 | 
    const columnCount = await this.getColumnCount(); 
 | 
    let column: ListColumn; 
 | 
    let titleFound: string; 
 | 
  
 | 
    for (let i = 0; i < columnCount; i++) { 
 | 
      column = await this.getColumnByIndex(i); 
 | 
      const tooltip = await column.getTooltip(true, false, true); 
 | 
      titleFound = getHtmlContent(tooltip)[0]; // Tooltip title will be at the first array item 
 | 
  
 | 
      if (titleFound === title) { 
 | 
        return column; 
 | 
      } 
 | 
    } 
 | 
    return undefined; 
 | 
  } 
 | 
  
 | 
  /** 
 | 
   * Get all highlighted rows component in the list 
 | 
   * 
 | 
   * @returns array of highlighted rows 
 | 
   */ 
 | 
  public async getHighlightedRows(): Promise<ListRow[]> { 
 | 
    const highlightedRows: ListRow[] = []; 
 | 
    const rows = await this.getAllRows(); 
 | 
    for (const row of rows) { 
 | 
      const isHighlighted = await row.isHighlighted(); 
 | 
  
 | 
      if (isHighlighted) { 
 | 
        highlightedRows.push(row); 
 | 
      } 
 | 
    } 
 | 
  
 | 
    return highlightedRows; 
 | 
  } 
 | 
  
 | 
  /** 
 | 
   * Get value of inline chart in the cell 
 | 
   * 
 | 
   * @param cell List cell to get inline chart value 
 | 
   * @returns Key value map of chart 
 | 
   */ 
 | 
  public async getInlineChartValue(cell: ListCell): Promise<Map<string, string>> { 
 | 
    const valuePair: Map<string, string> = new Map(); 
 | 
    const cellValue = await cell.getValue(); 
 | 
    const dataKeyValues = cellValue.split(';'); 
 | 
    dataKeyValues.pop(); 
 | 
    for (const dataKeyValue of dataKeyValues) { 
 | 
      const pair = dataKeyValue.split(', '); 
 | 
      const dataKey = pair[0].split('Data-Key: ')[1]; 
 | 
      const dataValue = pair[1].split('Data-Value: ')[1]; 
 | 
      valuePair.set(dataKey, dataValue); 
 | 
    } 
 | 
    return valuePair; 
 | 
  } 
 | 
  
 | 
  /** 
 | 
   * Get last row of availble rows in list 
 | 
   */ 
 | 
  public async getLastRow(): Promise<ListRow> { 
 | 
    const count = await this.getRowCount(); 
 | 
    const lastRow = await this.getRowByIndex(count - 1); 
 | 
  
 | 
    return lastRow; 
 | 
  } 
 | 
  
 | 
  /** 
 | 
   * Get row by passing name of the column, target value, and all parent cell values. Parent cell values and target cell value will be based on columnName. 
 | 
   * 
 | 
   * @param columnName Column name to get from 
 | 
   * @param targetValue Target cell value based on the column name defined 
 | 
   * @param parentValues All parent cell values, based on the column name defined, to expand 
 | 
   */ 
 | 
  public async getRowByCellValue(columnName: string, targetValue: string, parentValues?: string[]): Promise<ListRow> { 
 | 
    const targetRow: ColumnIDValueMap[] = [{ columnID: columnName, value: targetValue }]; 
 | 
    if (parentValues) { 
 | 
      const parents: ColumnIDValueMap[][] = []; 
 | 
      for (const parentValue of parentValues) { 
 | 
        const parentRow: ColumnIDValueMap[] = [{ columnID: columnName, value: parentValue }]; 
 | 
        parents.push(parentRow); 
 | 
      } 
 | 
      return this.getRowByValueInHierarchy(targetRow, parents); 
 | 
    } 
 | 
    return this.getRowByValue(targetRow); 
 | 
  } 
 | 
  
 | 
  /** 
 | 
   * Get row by passing targetRow's ColumnIDValueMap and all parentRows' ColumnIDValueMap. 
 | 
   * 
 | 
   * This method can get row that is immediately visible in the current view. If the row is not visible in the current view, it won't able to get the row. 
 | 
   * 
 | 
   * @param targetRow target row's column header and cell value mapping 
 | 
   * @param parents parent rows' column header and cell value mapping 
 | 
   * @returns target list row 
 | 
   */ 
 | 
  public async getRowByValueInHierarchy(targetRow: ColumnIDValueMap[], parents: ColumnIDValueMap[][]): Promise<ListRow> { 
 | 
    // get first element and remove in array 
 | 
    const firstParent = parents.shift(); 
 | 
    if (firstParent === undefined) { 
 | 
      throw Error('No parent is found in parents parameter.'); 
 | 
    } 
 | 
    let parentRow = await this.getRowByValue(firstParent, true); 
 | 
    await parentRow.expandRow(); 
 | 
    // If there are still parent remaining, we'll loop through parent array and expand parent row 
 | 
    if (parents.length > 0) { 
 | 
      for (const parent of parents) { 
 | 
        parentRow = await this.getRowByValue(parent, false, parentRow); 
 | 
        await parentRow.expandRow(); 
 | 
      } 
 | 
    } 
 | 
  
 | 
    return this.getRowByValue(targetRow, false, parentRow); 
 | 
  } 
 | 
  
 | 
  /** 
 | 
   * Get Multiple Rows based on multiple Values for a specific column 
 | 
   * 
 | 
   * @param columnName Column name 
 | 
   * @param columnValues Values of the column 
 | 
   * @returns Array of ListRows 
 | 
   */ 
 | 
  public async getRowsByCellValuesFromOneColumn(columnName: string, columnValues: string[]): Promise<ListRow[]> { 
 | 
    const promises: promise.Promise<ListRow>[] = []; 
 | 
    for (const columnValue of columnValues) { 
 | 
      promises.push(this.getRowByValue([{ columnID: columnName, value: columnValue }])); 
 | 
    } 
 | 
    const values = await protractor.promise.all(promises); 
 | 
    return values; 
 | 
  } 
 | 
  
 | 
  /** 
 | 
   * Check whether a row exists in the list 
 | 
   * 
 | 
   * @param targetRow Cell value for the target row 
 | 
   * @param parentRows All parent cell values, based on the column name defined, to expand 
 | 
   * 
 | 
   * @return a boolean (true = exists, false = not exists) indicating whether the row with the passed-in 
 | 
   * value exists in the list 
 | 
   */ 
 | 
  public async hasRow(targetRow: ColumnIDValueMap[], parentRows?: ColumnIDValueMap[][]): Promise<boolean> { 
 | 
    try { 
 | 
      if (parentRows) { 
 | 
        await this.getRowByValueInHierarchy(targetRow, parentRows); 
 | 
      } else { 
 | 
        await this.getRowByValue(targetRow); 
 | 
      } 
 | 
      return true; 
 | 
    } catch { 
 | 
      return false; 
 | 
    } 
 | 
  } 
 | 
  
 | 
  /** 
 | 
   * Perform left click on the empty space in the list. 
 | 
   */ 
 | 
  public async leftClickOnWhiteSpace(): Promise<void> { 
 | 
    const scrollArea = this.element.element(by.css('q-scrollarea')); 
 | 
    await QUtils.leftClickElement(scrollArea); 
 | 
  } 
 | 
  
 | 
  /** 
 | 
   * Open context menu on column 
 | 
   * 
 | 
   * @param columnID Target column name / index to open context menu 
 | 
   */ 
 | 
  public async openColumnMenu(columnID: string | number): Promise<void> { 
 | 
    const column = typeof columnID === 'string' ? await this.getColumnByValue(columnID) : await this.getColumnByIndex(columnID); 
 | 
    if (column) { 
 | 
      await column.columnMenu(); //  open context menu on the column 
 | 
    } 
 | 
  } 
 | 
  
 | 
  /** 
 | 
   * Trigger filter dialog on column 
 | 
   * 
 | 
   * @param columnNameIndex Target column name or index 
 | 
   * @param contextMenu Context menu triggered 
 | 
   * @returns Filter manager dialog 
 | 
   */ 
 | 
  public async openFilterDialogOnColumn(columnNameIndex: string | number, contextMenu: ContextMenu): Promise<FilterManager> { 
 | 
    await this.openColumnMenu(columnNameIndex); 
 | 
    await contextMenu.selectFilterMenu(FilterMenuName.EditFilters); 
 | 
    await this._dlgFilterManager.waitUntilPresent(); 
 | 
    return this._dlgFilterManager; 
 | 
  } 
 | 
  
 | 
  /** 
 | 
   * Find and resize covered column in list based on pass-in column name 
 | 
   * 
 | 
   * @param columnName Target column that need to resize 
 | 
   * @param size Pixel size to move 
 | 
   */ 
 | 
  public async resizeColumnWithCoveredName(columnName: string, size: number): Promise<void> { 
 | 
    const count = await this.getColumnCount(); 
 | 
    let isFound = false; 
 | 
    for (let i = 0; i < count; i++) { 
 | 
      const column = await this.getColumnByIndex(i); 
 | 
      try { 
 | 
        const columnHeader = await column.getValue(); 
 | 
        if (columnHeader === '…') { 
 | 
          throw Error; 
 | 
        } 
 | 
      } catch { 
 | 
        // Error will be thrown when getting value (...) in img attribut column 
 | 
        // Resize column and check whether resized column's name is matched with pass-in column name 
 | 
        await column.resizeColumn(size); 
 | 
        const columnNameToCompare = await column.getValue(); 
 | 
        if (columnNameToCompare !== columnName) { 
 | 
          await column.resizeColumn(size, false); 
 | 
        } else { 
 | 
          isFound = true; 
 | 
        } 
 | 
      } 
 | 
  
 | 
      if (isFound) { 
 | 
        break; 
 | 
      } 
 | 
    } 
 | 
  } 
 | 
  
 | 
  /** 
 | 
   * Check whether the cell value in rows matches the expected value 
 | 
   * 
 | 
   * @param rows rows to verify it cell value 
 | 
   * @param columnName name of column to identify the cell of the rows 
 | 
   * @param expectedValue expected value of the cell value 
 | 
   * @param isImgAttribute — [Default=false] To determine whether cell value is image attribute column 
 | 
   * 
 | 
   * @returns boolean indicating valid or invalid 
 | 
   */ 
 | 
  public async rowHasValidValue(rows: ListRow[], columnName: string | string[], expectedValue: string | string[], isImgAttribute: boolean = false): Promise<boolean> { 
 | 
    const columnNames = typeof columnName === 'string' ? [columnName] : columnName; 
 | 
    let areAllValid = false; 
 | 
  
 | 
    for (const name of columnNames) { 
 | 
      const cellValues = await this.getCellValueFromRow(name, rows, isImgAttribute); 
 | 
      areAllValid = Array.isArray(expectedValue) 
 | 
        ? cellValues.every((cellValue: string) => expectedValue.includes(cellValue)) 
 | 
        : cellValues.every((cellValue: string) => cellValue === expectedValue); 
 | 
  
 | 
      if (!areAllValid) { 
 | 
        break; 
 | 
      } 
 | 
    } 
 | 
  
 | 
    return areAllValid; 
 | 
  } 
 | 
  
 | 
  /** 
 | 
   * Perform search action on list 
 | 
   * 
 | 
   * @param columnIndexName Target column name / index 
 | 
   * @param searchValue Value to search 
 | 
   * @param defaultRowClick reset the list and scroll to top for re-search a new value 
 | 
   */ 
 | 
  public async searchList(columnIndexName: string | number, searchValue: string, defaultRowClick: boolean = true): Promise<void> { 
 | 
    let index: number; 
 | 
  
 | 
    if (typeof columnIndexName === 'string') { 
 | 
      const column = await this.getColumnByValue(columnIndexName); 
 | 
      index = await column.getIndex(); 
 | 
    } else { 
 | 
      index = columnIndexName; 
 | 
    } 
 | 
    await super.searchList(index, searchValue, defaultRowClick); 
 | 
  } 
 | 
  
 | 
  /** 
 | 
   * Perform search action on hierarchy list 
 | 
   * 
 | 
   * @param target target row to be navigated 
 | 
   * @param parents parent rows of the targeted row 
 | 
   */ 
 | 
  public async searchListInHierarchy(target: ColumnValueSearch, parents: ColumnValueSearch[]): Promise<void> { 
 | 
    if (parents.length > 0) { 
 | 
      for (let i = 0; i < parents.length; i++) { 
 | 
        const parent = parents[i]; 
 | 
        await this.searchList(parent.columnID, parent.searchValue); 
 | 
        const parentRow = (await this.getInboundSelectedRows())[0]; 
 | 
        await parentRow.expandRow(); 
 | 
        // click on its first child to update the list column 
 | 
        const firstChildRow = await parentRow.getChildRow(0); 
 | 
        if (firstChildRow) { 
 | 
          await firstChildRow.leftClick(); 
 | 
          const cellSearch = i === parents.length - 1 ? await firstChildRow.getCell(target.columnID) : await firstChildRow.getCell(parents[i + 1].columnID); 
 | 
          await cellSearch.leftClick(); 
 | 
        } 
 | 
      } 
 | 
    } 
 | 
    await this.searchList(target.columnID, target.searchValue, false); 
 | 
  } 
 | 
  
 | 
  /** 
 | 
   * Select multiple rows 
 | 
   * 
 | 
   * @param rows The rows to be selected 
 | 
   */ 
 | 
  public async selectRows(rows: ListRow[]): Promise<void> { 
 | 
    await rows[0].leftClick(); 
 | 
    for (let i = 1; i < rows.length; i++) { 
 | 
      await rows[i].leftClick({ control: true }); 
 | 
    } 
 | 
  } 
 | 
  
 | 
  /** 
 | 
   * Sort column in list 
 | 
   * 
 | 
   * @param columnName Target column that need to sort 
 | 
   * @param isAscending To sort column in ascending order (default = true, false for descending order) 
 | 
   * @param isImageAttribute Boolean value indicate whether the column is an image attribute column 
 | 
   * @param ctrl Ctrl modifier key when click (default = false) 
 | 
   */ 
 | 
  public async sortColumn(columnName: string, isAscending: boolean = true, isImageAttribute: boolean = false, ctrl: boolean = false): Promise<void> { 
 | 
    if (isImageAttribute) { 
 | 
      await this.resizeColumnWithCoveredName(columnName, 50); 
 | 
    } 
 | 
    const idColumn = await this.getColumnByValue(columnName); 
 | 
    const sortDirection = isAscending ? SortDirection.ASC : SortDirection.DESC; 
 | 
    await idColumn.setSortDirection(sortDirection, ctrl); 
 | 
  } 
 | 
  
 | 
  /** 
 | 
   * Toggle ON/OFF the checkbox of a list row 
 | 
   * 
 | 
   * @param row Target row to toggle on/off the checkbox 
 | 
   * @param expectedState Boolean indicate the expected checkbox state (true = checked, false = unchecked) 
 | 
   * 
 | 
   * @return the checkbox state after it's toggled 
 | 
   */ 
 | 
  public async toggleRowCheckbox(row: ListRow, expectedState: boolean): Promise<boolean> { 
 | 
    const currentState = await row.isChecked(); 
 | 
  
 | 
    if (currentState !== expectedState) { 
 | 
      await this.scrollToRow(row); // S&OP added to scroll to view before click 
 | 
      await row.checkboxClick(); 
 | 
    } 
 | 
  
 | 
    return row.isChecked(); 
 | 
  } 
 | 
} 
 | 
  
 | 
/** 
 | 
 * An interface that consists of a map of columnID and value. 
 | 
 * ``` 
 | 
 * { 
 | 
 *   columnID: string | number; 
 | 
 *   value: string; 
 | 
 * } 
 | 
 * ``` 
 | 
 */ 
 | 
export interface ColumnIDValueMap { 
 | 
  columnID: string | number; 
 | 
  value: string; 
 | 
  additionAttribute?: QCellAdditionAttribute[]; // S&OP added as e2elib able distinguish 2 rows with same name by ondrawimagename 
 | 
} 
 | 
  
 | 
/** 
 | 
 * An interface used for searching in row. Contains a map of columnID and search value. 
 | 
 * ``` 
 | 
 * { 
 | 
 *   columnID: string | number; 
 | 
 *   searchValue: string; 
 | 
 * } 
 | 
 * ``` 
 | 
 */ 
 | 
export interface ColumnValueSearch { 
 | 
  columnID: string | number; 
 | 
  searchValue: string; 
 | 
} 
 | 
  
 | 
export type CellValue = EnumLike | string[] | number[] | undefined; 
 |