<commit_before>/*
 * Copyright 2020 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * External dependencies
 */
import PropTypes from 'prop-types';
import { useRef, useEffect, useState } from 'react';

/**
 * Internal dependencies
 */
import Movable from '../movable';
import { useStory, useDropTargets } from '../../app';
import objectWithout from '../../utils/objectWithout';
import { useTransform } from '../transform';
import { useUnits } from '../../units';
import { getDefinitionForType } from '../../elements';
import { useGlobalIsKeyPressed } from '../keyboard';
import isMouseUpAClick from '../../utils/isMouseUpAClick';
import isTargetOutOfContainer from '../../utils/isTargetOutOfContainer';
import useCanvas from './useCanvas';

const CORNER_HANDLES = ['nw', 'ne', 'sw', 'se'];

function MultiSelectionMovable({ selectedElements }) {
  const moveable = useRef();

  const eventTracker = useRef({});

  const { updateElementsById, deleteElementsById } = useStory((state) => ({
    updateElementsById: state.actions.updateElementsById,
    deleteElementsById: state.actions.deleteElementsById,
  }));
  const {
    canvasWidth,
    canvasHeight,
    nodesById,
    handleSelectElement,
    fullbleedContainer,
  } = useCanvas(
    ({
      state: {
        pageSize: { width: canvasWidth, height: canvasHeight },
        nodesById,
        fullbleedContainer,
      },
      actions: { handleSelectElement },
    }) => ({
      canvasWidth,
      canvasHeight,
      fullbleedContainer,
      nodesById,
      handleSelectElement,
    })
  );
  const { editorToDataX, editorToDataY, dataToEditorY } = useUnits((state) => ({
    editorToDataX: state.actions.editorToDataX,
    editorToDataY: state.actions.editorToDataY,
    dataToEditorY: state.actions.dataToEditorY,
  }));

  const {
    actions: { pushTransform },
  } = useTransform();
  const {
    state: { draggingResource },
  } = useDropTargets();

  const [isDragging, setIsDragging] = useState(false);

  // Update moveable with whatever properties could be updated outside moveable
  // itself.
  useEffect(() => {
    if (moveable.current) {
      moveable.current.updateRect();
    }
  }, [selectedElements, moveable, nodesById]);

  // ⌘ key disables snapping
  const canSnap = !useGlobalIsKeyPressed('meta');

  // ⇧ key rotates the element 30 degrees at a time
  const throttleRotation = useGlobalIsKeyPressed('shift');

  // Create targets list including nodes and also necessary attributes.
  const targetList = selectedElements.map((element) => ({
    element,
    node: nodesById[element.id],
    updateForResizeEvent: getDefinitionForType(element.type)
      .updateForResizeEvent,
  }));
  // Not all targets have been defined yet.
  if (targetList.some(({ node }) => node === undefined)) {
    return null;
  }
  const otherNodes = Object.values(
    objectWithout(
      nodesById,
      selectedElements.map((element) => element.id)
    )
  );

  /**
   * Set style to the element.
   *
   * @param {string} id Target element's id.
   * @param {Object} target Target element to update.
   * @param {Object} frame Properties from the frame for that specific element.
   */
  const setTransformStyle = (id, target, frame) => {
    target.style.transform = `translate(${frame.translate[0]}px, ${frame.translate[1]}px) rotate(${frame.rotate}deg)`;
    pushTransform(id, frame);
  };

  const frames = targetList
    ? targetList.map(({ element }) => ({
        translate: [0, 0],
        rotate: element.rotationAngle,
        direction: [0, 0],
        resize: [0, 0],
        updates: null,
      }))
    : [];

  /**
   * Resets Movable once the action is done, sets the initial values.
   */
  const resetMoveable = () => {
    targetList.forEach(({ element, node }, i) => {
      frames[i].direction = [0, 0];
      frames[i].translate = [0, 0];
      frames[i].resize = [0, 0];
      frames[i].updates = null;
      node.style.transform = '';
      node.style.width = '';
      node.style.height = '';
      pushTransform(element.id, null);
    });
    if (moveable.current) {
      moveable.current.updateRect();
    }
  };

  const onGroupEventStart = ({ events, isDrag, isRotate }) => {
    events.forEach((ev, i) => {
      const sFrame = frames[i];
      if (isDrag) {
        ev.set(sFrame.translate);
      } else if (isRotate) {
        ev.set(sFrame.rotate);
      }
    });
  };

  // Update elements once the event has ended.
  const onGroupEventEnd = ({ targets, isRotate, isResize }) => {
    const updates = {};
    const toRemove = [];
    targets.forEach((target, i) => {
      const { element, updateForResizeEvent } = targetList[i];
      if (isTargetOutOfContainer(target, fullbleedContainer)) {
        toRemove.push(element.id);
        return;
      }
      // Update position in all cases.
      const frame = frames[i];
      const { direction } = frame;
      const properties = {
        x: element.x + editorToDataX(frame.translate[0]),
        y: element.y + editorToDataY(frame.translate[1]),
      };
      if (isRotate) {
        properties.rotationAngle = frame.rotate;
      }
      const [editorWidth, editorHeight] = frame.resize;
      const didResize = editorWidth !== 0 && editorHeight !== 0;
      if (isResize && didResize) {
        const newWidth = editorToDataX(editorWidth);
        const newHeight = editorToDataY(editorHeight);
        properties.width = newWidth;
        properties.height = newHeight;
        if (updateForResizeEvent) {
          Object.assign(
            properties,
            updateForResizeEvent(element, direction, newWidth, newHeight)
          );
        }
      }
      updates[element.id] = properties;
    });
    updateElementsById({
      elementIds: Object.keys(updates),
      properties: (currentProperties) => updates[currentProperties.id],
    });
    if (toRemove.length > 0) {
      deleteElementsById({ elementIds: toRemove });
    }
    resetMoveable();
  };

  const startEventTracking = (evt) => {
    const { timeStamp, clientX, clientY } = evt;
    eventTracker.current = {
      timeStamp,
      clientX,
      clientY,
    };
  };

  // Let's check if we consider this a drag or a click, In case of a click handle click instead.
  // We are doing this here in Moveable selection since it takes over the mouseup event
  // and it can be captured here and not in the frame element.
  // @todo Add integration test for this!
  const clickHandled = (inputEvent) => {
    if (isMouseUpAClick(inputEvent, eventTracker.current)) {
      const clickedElement = Object.keys(nodesById).find((id) =>
        nodesById[id].contains(inputEvent.target)
      );
      if (clickedElement) {
        handleSelectElement(clickedElement, inputEvent);
      }
      // Click was handled.
      return true;
    }
    // No click was found/handled.
    return false;
  };

  const hideHandles = isDragging || Boolean(draggingResource);
  return (
    <Movable
      className={`default-movable ${hideHandles ? 'hide-handles' : ''}`}
      ref={moveable}
      zIndex={0}
      target={targetList.map(({ node }) => node)}
      draggable={true}
      resizable={!hideHandles}
      rotatable={!hideHandles}
      onDragGroup={({ events }) => {
        events.forEach(({ target, beforeTranslate }, i) => {
          const sFrame = frames[i];
          const { element } = targetList[i];
          sFrame.translate = beforeTranslate;
          setTransformStyle(element.id, target, sFrame);
        });
      }}
      onDragGroupStart={({ events, inputEvent }) => {
        startEventTracking(inputEvent);
        if (!isDragging) {
          setIsDragging(true);
        }
        onGroupEventStart({ events, isDrag: true });
      }}
      onDragGroupEnd={({ targets, inputEvent }) => {
        setIsDragging(false);
        if (!clickHandled(inputEvent)) {
          onGroupEventEnd({ targets });
        }
      }}
      onRotateGroupStart={({ events }) => {
        onGroupEventStart({ events, isRotate: true });
      }}
      onRotateGroup={({ events }) => {
        events.forEach(({ target, beforeRotate, drag }, i) => {
          const sFrame = frames[i];
          const { element } = targetList[i];
          sFrame.rotate = ((beforeRotate % 360) + 360) % 360;
          sFrame.translate = drag.beforeTranslate;
          setTransformStyle(element.id, target, sFrame);
        });
      }}
      onRotateGroupEnd={({ targets }) => {
        onGroupEventEnd({ targets, isRotate: true });
      }}
      throttleRotate={throttleRotation ? 30 : 0}
      onResizeGroupStart={({ events }) => {
        events.forEach((ev, i) => {
          const frame = frames[i];
          ev.setOrigin(['%', '%']);
          if (ev.dragStart) {
            ev.dragStart.set(frame.translate);
          }
        });
      }}
      onResizeGroup={({ events }) => {
        events.forEach(({ target, direction, width, height, drag }, i) => {
          const sFrame = frames[i];
          const { element, updateForResizeEvent } = targetList[i];
          let newHeight = height;
          const newWidth = width;
          let updates = null;
          if (updateForResizeEvent) {
            updates = updateForResizeEvent(
              element,
              direction,
              editorToDataX(newWidth),
              editorToDataY(newHeight)
            );
          }
          if (updates && updates.height) {
            newHeight = dataToEditorY(updates.height);
          }
          target.style.width = `${newWidth}px`;
          target.style.height = `${newHeight}px`;
          sFrame.direction = direction;
          sFrame.resize = [newWidth, newHeight];
          sFrame.translate = drag.beforeTranslate;
          sFrame.updates = updates;
          setTransformStyle(element.id, target, sFrame);
        });
      }}
      onResizeGroupEnd={({ targets }) => {
        onGroupEventEnd({ targets, isResize: true });
      }}
      renderDirections={CORNER_HANDLES}
      snappable={canSnap}
      snapElement={canSnap}
      snapHorizontal={canSnap}
      snapVertical={canSnap}
      snapCenter={canSnap}
      horizontalGuidelines={canSnap ? [0, canvasHeight / 2, canvasHeight] : []}
      verticalGuidelines={canSnap ? [0, canvasWidth / 2, canvasWidth] : []}
      elementGuidelines={otherNodes}
      snapGap={canSnap}
      isDisplaySnapDigit={false}
    />
  );
}

MultiSelectionMovable.propTypes = {
  selectedElements: PropTypes.arrayOf(PropTypes.object).isRequired,
};

export default MultiSelectionMovable;
<commit_msg>Add logic for multi-selection moveable.<commit_after>/*
 * Copyright 2020 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * External dependencies
 */
import PropTypes from 'prop-types';
import { useRef, useEffect, useState } from 'react';

/**
 * Internal dependencies
 */
import Movable from '../movable';
import { useStory, useDropTargets } from '../../app';
import objectWithout from '../../utils/objectWithout';
import { useTransform } from '../transform';
import { useUnits } from '../../units';
import { getDefinitionForType } from '../../elements';
import { useGlobalIsKeyPressed } from '../keyboard';
import isMouseUpAClick from '../../utils/isMouseUpAClick';
import isTargetOutOfContainer from '../../utils/isTargetOutOfContainer';
import useElementsWithLinks from '../../utils/useElementsWithLinks';
import useCanvas from './useCanvas';

const CORNER_HANDLES = ['nw', 'ne', 'sw', 'se'];

function MultiSelectionMovable({ selectedElements }) {
  const moveable = useRef();

  const eventTracker = useRef({});

  const { updateElementsById, deleteElementsById, currentPage } = useStory(
    (state) => ({
      updateElementsById: state.actions.updateElementsById,
      deleteElementsById: state.actions.deleteElementsById,
      currentPage: state.state.currentPage,
    })
  );
  const {
    canvasWidth,
    canvasHeight,
    nodesById,
    handleSelectElement,
    fullbleedContainer,
    setShowAttachmentBorder,
  } = useCanvas(
    ({
      state: {
        pageSize: { width: canvasWidth, height: canvasHeight },
        nodesById,
        fullbleedContainer,
      },
      actions: { handleSelectElement, setShowAttachmentBorder },
    }) => ({
      canvasWidth,
      canvasHeight,
      fullbleedContainer,
      nodesById,
      handleSelectElement,
      setShowAttachmentBorder,
    })
  );
  const { editorToDataX, editorToDataY, dataToEditorY } = useUnits((state) => ({
    editorToDataX: state.actions.editorToDataX,
    editorToDataY: state.actions.editorToDataY,
    dataToEditorY: state.actions.dataToEditorY,
  }));

  const {
    actions: { pushTransform },
  } = useTransform();
  const {
    state: { draggingResource },
  } = useDropTargets();

  const { isLinkInAttachmentArea } = useElementsWithLinks();

  const [isDragging, setIsDragging] = useState(false);

  // Update moveable with whatever properties could be updated outside moveable
  // itself.
  useEffect(() => {
    if (moveable.current) {
      moveable.current.updateRect();
    }
  }, [selectedElements, moveable, nodesById]);

  // ⌘ key disables snapping
  const canSnap = !useGlobalIsKeyPressed('meta');

  // ⇧ key rotates the element 30 degrees at a time
  const throttleRotation = useGlobalIsKeyPressed('shift');

  // Create targets list including nodes and also necessary attributes.
  const targetList = selectedElements.map((element) => ({
    element,
    node: nodesById[element.id],
    updateForResizeEvent: getDefinitionForType(element.type)
      .updateForResizeEvent,
  }));
  // Not all targets have been defined yet.
  if (targetList.some(({ node }) => node === undefined)) {
    return null;
  }
  const otherNodes = Object.values(
    objectWithout(
      nodesById,
      selectedElements.map((element) => element.id)
    )
  );

  /**
   * Set style to the element.
   *
   * @param {string} id Target element's id.
   * @param {Object} target Target element to update.
   * @param {Object} frame Properties from the frame for that specific element.
   */
  const setTransformStyle = (id, target, frame) => {
    target.style.transform = `translate(${frame.translate[0]}px, ${frame.translate[1]}px) rotate(${frame.rotate}deg)`;
    pushTransform(id, frame);
  };

  const frames = targetList
    ? targetList.map(({ element }) => ({
        translate: [0, 0],
        rotate: element.rotationAngle,
        direction: [0, 0],
        resize: [0, 0],
        updates: null,
      }))
    : [];

  /**
   * Resets Movable once the action is done, sets the initial values.
   */
  const resetMoveable = () => {
    targetList.forEach(({ element, node }, i) => {
      frames[i].direction = [0, 0];
      frames[i].translate = [0, 0];
      frames[i].resize = [0, 0];
      frames[i].updates = null;
      node.style.transform = '';
      node.style.width = '';
      node.style.height = '';
      pushTransform(element.id, null);
    });
    if (moveable.current) {
      moveable.current.updateRect();
    }
  };

  const onGroupEventStart = ({ events, isDrag, isRotate }) => {
    events.forEach((ev, i) => {
      const sFrame = frames[i];
      if (isDrag) {
        ev.set(sFrame.translate);
      } else if (isRotate) {
        ev.set(sFrame.rotate);
      }
    });
  };

  // Update elements once the event has ended.
  const onGroupEventEnd = ({ targets, isRotate, isResize }) => {
    const updates = {};
    const toRemove = [];
    let hasLinkInAttachmentArea = false;
    targets.forEach((target, i) => {
      if (hasLinkInAttachmentArea) {
        return;
      }
      const { element, updateForResizeEvent } = targetList[i];
      if (element.link?.url && isLinkInAttachmentArea(target)) {
        hasLinkInAttachmentArea = true;
      }
      if (isTargetOutOfContainer(target, fullbleedContainer)) {
        toRemove.push(element.id);
        return;
      }
      // Update position in all cases.
      const frame = frames[i];
      const { direction } = frame;
      const properties = {
        x: element.x + editorToDataX(frame.translate[0]),
        y: element.y + editorToDataY(frame.translate[1]),
      };
      if (isRotate) {
        properties.rotationAngle = frame.rotate;
      }
      const [editorWidth, editorHeight] = frame.resize;
      const didResize = editorWidth !== 0 && editorHeight !== 0;
      if (isResize && didResize) {
        const newWidth = editorToDataX(editorWidth);
        const newHeight = editorToDataY(editorHeight);
        properties.width = newWidth;
        properties.height = newHeight;
        if (updateForResizeEvent) {
          Object.assign(
            properties,
            updateForResizeEvent(element, direction, newWidth, newHeight)
          );
        }
      }
      updates[element.id] = properties;
    });
    if (hasLinkInAttachmentArea) {
      resetMoveable();
      return;
    }
    updateElementsById({
      elementIds: Object.keys(updates),
      properties: (currentProperties) => updates[currentProperties.id],
    });
    if (toRemove.length > 0) {
      deleteElementsById({ elementIds: toRemove });
    }
    resetMoveable();
  };

  const startEventTracking = (evt) => {
    const { timeStamp, clientX, clientY } = evt;
    eventTracker.current = {
      timeStamp,
      clientX,
      clientY,
    };
  };

  // Let's check if we consider this a drag or a click, In case of a click handle click instead.
  // We are doing this here in Moveable selection since it takes over the mouseup event
  // and it can be captured here and not in the frame element.
  // @todo Add integration test for this!
  const clickHandled = (inputEvent) => {
    if (isMouseUpAClick(inputEvent, eventTracker.current)) {
      const clickedElement = Object.keys(nodesById).find((id) =>
        nodesById[id].contains(inputEvent.target)
      );
      if (clickedElement) {
        handleSelectElement(clickedElement, inputEvent);
      }
      // Click was handled.
      return true;
    }
    // No click was found/handled.
    return false;
  };

  const pageHasAttachment = Boolean(currentPage.pageAttachment?.url);
  const hideHandles = isDragging || Boolean(draggingResource);
  return (
    <Movable
      className={`default-movable ${hideHandles ? 'hide-handles' : ''}`}
      ref={moveable}
      zIndex={0}
      target={targetList.map(({ node }) => node)}
      draggable={true}
      resizable={!hideHandles}
      rotatable={!hideHandles}
      onDragGroup={({ events }) => {
        events.forEach(({ target, beforeTranslate }, i) => {
          const sFrame = frames[i];
          const { element } = targetList[i];
          sFrame.translate = beforeTranslate;
          setTransformStyle(element.id, target, sFrame);
          if (pageHasAttachment) {
            setShowAttachmentBorder(
              element.link?.url && isLinkInAttachmentArea(target)
            );
          }
        });
      }}
      onDragGroupStart={({ events, inputEvent }) => {
        startEventTracking(inputEvent);
        if (!isDragging) {
          setIsDragging(true);
        }
        onGroupEventStart({ events, isDrag: true });
      }}
      onDragGroupEnd={({ targets, inputEvent }) => {
        setIsDragging(false);
        if (!clickHandled(inputEvent)) {
          onGroupEventEnd({ targets });
        }
      }}
      onRotateGroupStart={({ events }) => {
        onGroupEventStart({ events, isRotate: true });
      }}
      onRotateGroup={({ events }) => {
        events.forEach(({ target, beforeRotate, drag }, i) => {
          const sFrame = frames[i];
          const { element } = targetList[i];
          sFrame.rotate = ((beforeRotate % 360) + 360) % 360;
          sFrame.translate = drag.beforeTranslate;
          setTransformStyle(element.id, target, sFrame);
          if (pageHasAttachment) {
            setShowAttachmentBorder(
              element.link?.url && isLinkInAttachmentArea(target)
            );
          }
        });
      }}
      onRotateGroupEnd={({ targets }) => {
        onGroupEventEnd({ targets, isRotate: true });
      }}
      throttleRotate={throttleRotation ? 30 : 0}
      onResizeGroupStart={({ events }) => {
        events.forEach((ev, i) => {
          const frame = frames[i];
          ev.setOrigin(['%', '%']);
          if (ev.dragStart) {
            ev.dragStart.set(frame.translate);
          }
        });
      }}
      onResizeGroup={({ events }) => {
        events.forEach(({ target, direction, width, height, drag }, i) => {
          const sFrame = frames[i];
          const { element, updateForResizeEvent } = targetList[i];
          let newHeight = height;
          const newWidth = width;
          let updates = null;
          if (updateForResizeEvent) {
            updates = updateForResizeEvent(
              element,
              direction,
              editorToDataX(newWidth),
              editorToDataY(newHeight)
            );
          }
          if (updates && updates.height) {
            newHeight = dataToEditorY(updates.height);
          }
          target.style.width = `${newWidth}px`;
          target.style.height = `${newHeight}px`;
          sFrame.direction = direction;
          sFrame.resize = [newWidth, newHeight];
          sFrame.translate = drag.beforeTranslate;
          sFrame.updates = updates;
          setTransformStyle(element.id, target, sFrame);
          if (pageHasAttachment) {
            setShowAttachmentBorder(
              element.link?.url && isLinkInAttachmentArea(target)
            );
          }
        });
      }}
      onResizeGroupEnd={({ targets }) => {
        onGroupEventEnd({ targets, isResize: true });
      }}
      renderDirections={CORNER_HANDLES}
      snappable={canSnap}
      snapElement={canSnap}
      snapHorizontal={canSnap}
      snapVertical={canSnap}
      snapCenter={canSnap}
      horizontalGuidelines={canSnap ? [0, canvasHeight / 2, canvasHeight] : []}
      verticalGuidelines={canSnap ? [0, canvasWidth / 2, canvasWidth] : []}
      elementGuidelines={otherNodes}
      snapGap={canSnap}
      isDisplaySnapDigit={false}
    />
  );
}

MultiSelectionMovable.propTypes = {
  selectedElements: PropTypes.arrayOf(PropTypes.object).isRequired,
};

export default MultiSelectionMovable;
<|endoftext|>


<commit_before><% if (projectstylelint === 'strict') { %>const font = {
  properties: ['font', 'font-family', 'font-size', 'line-height', 'font-weight', 'font-style']
}

const text = {
  properties: [
    'break',
    'column',
    'columns',
    'hyphens',
    'letter-spacing',
    'tab-size',
    'text',
    'white-space',
    'word-spacing',
    'word-wrap'
  ]
}

const color = {
  properties: [
    'opacity',
    'background',
    'box-decoration-break',
    'box-shadow',
    'color',
    'filter',
    'layer',
    'mask',
    'mix-blend-mode'
  ]
}

const scrollbar = {
  properties: ['scrollbar']
}

const outline = {
  properties: ['outline']
}

const list = {
  properties: ['list-style', 'marker-offset']
}

const tables = {
  properties: [
    'border-collapse',
    'border-spacing',
    'caption-side',
    'empty-cells',
    'speak-header',
    'table-layout'
  ]
}

const classification = {
  properties: ['content', 'clear', 'display', 'float', 'isolation', 'visibility']
}

const dimensions = {
  properties: [
    'block-size',
    'box-sizing',
    'size',
    'width',
    'min-width',
    'max-width',
    'height',
    'min-height',
    'max-height',
    'inline-size',
    'object'
  ]
}

const positioning = {
  properties: [
    'position',
    'top',
    'right',
    'bottom',
    'left',
    'z-index',
    'flex',
    'flex-wrap',
    'flex-direction',
    'justify-content',
    'align-content',
    'align-items',
    'align-self',
    'grid',
    'offset',
    'order',
    'overflow',
    'text-overflow',
    'clip',
    'vertical-align'
  ]
}

const margins = {
  properties: ['margin', 'margin-top', 'margin-right', 'margin-bottom', 'margin-left']
}

const padding = {
  properties: ['padding', 'padding-top', 'padding-right', 'padding-bottom', 'padding-left']
}

const border = {
  properties: ['border-color', 'border-style', 'border-width', 'border']
}

const dynamic = {
  properties: [
    'accelerator',
    'behavior',
    'caret-color',
    'cursor',
    'filter',
    'pointer-events',
    'resize',
    'touch-action',
    'zoom'
  ]
}

const generated = {
  properties: ['counter', 'fallback', 'include', 'quotes']
}

const international = {
  properties: [
    'direction',
    'ime-mode',
    'layout',
    'line-break',
    'ruby',
    'spacing',
    'text-autospace',
    'text-justify',
    'text-kashida-space',
    'unicode-bidi',
    'word-break',
    'writing-mode'
  ]
}

const print = {
  properties: ['marks', 'orphans', 'page-break', 'page', 'size', 'widows']
}

const aural = {
  properties: [
    'azimut',
    'cue',
    'elevation',
    'pause',
    'pitch-range',
    'play-during',
    'richness',
    'speak',
    'speech',
    'stress',
    'voice-family',
    'volume'
  ]
}

const animation = {
  properties: ['animation', 'transition', 'will-change']
}

const transform = {
  properties: ['transform', 'backface-visibility', 'perspective']
}

<% } %>module.exports = {
  'plugins': [
    'stylelint-order',
    'stylelint-scss',
    'stylelint-selector-bem-pattern'
  ],
  'extends': 'stylelint-config-sass-guidelines',
  'rules': {
    'at-rule-empty-line-before': [
      'never',
      {
        'except': [
          'inside-block'
        ],
        'ignore': [
          'after-comment',
          'inside-block'
        ]
      }
    ],
    'at-rule-name-case': 'lower',
    'at-rule-name-newline-after': 'always-multi-line',
    'at-rule-name-space-after': 'always',
    'at-rule-no-unknown': null,
    'at-rule-no-vendor-prefix': true,
    'block-closing-brace-empty-line-before': 'never',
    'block-closing-brace-newline-after': [
      'always',
      {
        'ignoreAtRules': [
          'if',
          'else'
        ]
      }
    ],
    'block-closing-brace-newline-before': 'always-multi-line',
    'block-closing-brace-space-after': 'always-single-line',
    'block-closing-brace-space-before': 'always-single-line',
    'block-no-empty': true,
    'block-opening-brace-newline-after': 'always',
    'block-opening-brace-newline-before': 'never-single-line',
    'block-opening-brace-space-after': 'always-single-line',
    'block-opening-brace-space-before': 'always-single-line',
    'color-hex-case': 'lower',
    'color-hex-length': 'short',
    'color-named': 'never',
    'color-no-invalid-hex': true,
    'comment-empty-line-before': [
      'always',
      {
        'except': [
          'first-nested'
        ],
        'ignore': [
          'after-comment',
          'stylelint-commands'
        ]
      }
    ],
    'comment-no-empty': true,
    'comment-whitespace-inside': 'always',
    'custom-property-empty-line-before': 'never',
    'declaration-bang-space-after': 'never',
    'declaration-bang-space-before': 'always',
    'declaration-block-no-duplicate-properties': [
      true,
      {
        'ignore': [
          'consecutive-duplicates-with-different-values'
        ]
      }
    ],
    'declaration-block-no-shorthand-property-overrides': true,
    'declaration-block-semicolon-newline-after': 'always',
    'declaration-block-semicolon-newline-before': 'never-multi-line',
    'declaration-block-semicolon-space-after': 'always-single-line',
    'declaration-block-semicolon-space-before': 'never',
    'declaration-block-single-line-max-declarations': 1,
    'declaration-block-trailing-semicolon': 'always',
    'declaration-colon-newline-after': 'always-multi-line',
    'declaration-colon-space-after': 'always-single-line',
    'declaration-colon-space-before': 'never',
    'declaration-empty-line-before': 'never',
    'font-family-name-quotes': 'always-where-recommended',
    'font-family-no-duplicate-names': true,
    'font-weight-notation': 'numeric',
    'function-blacklist': [
      'rgb'
    ],
    'function-calc-no-unspaced-operator': true,
    'function-comma-newline-after': 'never-multi-line',
    'function-comma-newline-before': 'never-multi-line',
    'function-comma-space-after': 'always',
    'function-comma-space-before': 'never',
    'function-linear-gradient-no-nonstandard-direction': true,
    'function-max-empty-lines': 0,
    'function-parentheses-newline-inside': 'never-multi-line',
    'function-parentheses-space-inside': 'never',
    'function-url-quotes': 'always',
    'function-whitespace-after': 'always',
    'indentation': 2,
    'keyframe-declaration-no-important': true,
    'length-zero-no-unit': true,
    'max-empty-lines': 2,
    'max-nesting-depth': 5,
    'media-feature-colon-space-after': 'always',
    'media-feature-colon-space-before': 'never',
    'media-feature-name-case': 'lower',
    'media-feature-name-no-unknown': true,
    'media-feature-name-no-vendor-prefix': true,
    'media-feature-parentheses-space-inside': 'never',
    'media-feature-range-operator-space-after': 'always',
    'media-feature-range-operator-space-before': 'always',
    'media-query-list-comma-newline-after': 'always',
    'media-query-list-comma-newline-before': 'never-multi-line',
    'media-query-list-comma-space-after': 'always-single-line',
    'media-query-list-comma-space-before': 'never',
    'no-duplicate-selectors': true,
    'no-empty-source': true,
    'no-eol-whitespace': true,
    'no-extra-semicolons': true,
    'no-invalid-double-slash-comments': true,
    'no-missing-end-of-source-newline': true,
    'no-unknown-animations': true,
    'number-leading-zero': <% if ( projectprettier === true ) { %>'always'<% } else { %>'never'<% } %>,
    'number-max-precision': 6,
    'number-no-trailing-zeros': true,<% if (projectstylelint === 'strict') { %>

    'order/order': [
      'dollar-variables',
      'at-variables',
      // @include rule
      {
        type: 'at-rule',
        name: 'include'
      },
      'declarations',
      'custom-properties',
      'less-mixins',
      'rules',
      'at-rules'
    ],
    'order/properties-order': [
      classification,
      positioning,
      dimensions,
      padding,
      border,
      margins,
      list,
      tables,
      color,
      font,
      text,
      outline,
      transform,
      animation,
      dynamic,
      generated,
      international,
      print,
      aural,
      scrollbar
    ],<% } %>
    'order/properties-alphabetical-order': null,

    'property-case': 'lower',
    'property-no-unknown': true,
    'property-no-vendor-prefix': true,
    'rule-empty-line-before': [
      'always',
      {
        'except': [
          'after-single-line-comment',
          'first-nested'
        ],
        'ignore': [
          'after-comment'
        ]
      }
    ],
    'selector-attribute-brackets-space-inside': 'never',
    'selector-attribute-operator-space-after': 'never',
    'selector-attribute-operator-space-before': 'never',
    'selector-attribute-quotes': 'always',
    'selector-combinator-space-after': 'always',
    'selector-combinator-space-before': 'always',
    'selector-descendant-combinator-no-non-space': true,
    'selector-id-pattern': '^[a-zA-Z]+$',
    'selector-max-compound-selectors': 5,
    'selector-max-specificity': '0,4,0',
    'selector-max-id': 0,
    'selector-no-qualifying-type': [
      true,
      {
        'ignore': [
          'attribute'
        ]
      }
    ],
    'selector-pseudo-class-case': 'lower',
    'selector-pseudo-class-no-unknown': true,
    'selector-pseudo-class-parentheses-space-inside': 'never',
    'selector-pseudo-element-case': 'lower',
    'selector-pseudo-element-colon-notation': 'double',
    'selector-pseudo-element-no-unknown': true,
    'selector-type-case': 'lower',
    'selector-type-no-unknown': [true, {
      ignore: ['custom-elements', 'default-namespace']
    }],
    'selector-max-empty-lines': 0,
    'selector-list-comma-newline-after': 'always-multi-line',
    'selector-list-comma-newline-before': 'never-multi-line',
    'selector-list-comma-space-after': 'always-single-line',
    'selector-list-comma-space-before': 'never',
    'shorthand-property-no-redundant-values': true,
    'string-quotes': 'single',
    'unit-blacklist': [],
    'unit-case': 'lower',
    'unit-no-unknown': true,
    'value-no-vendor-prefix': true,
    'value-list-comma-newline-after': 'always-multi-line',
    'value-list-comma-newline-before': 'always-multi-line',
    'value-list-comma-space-after': 'always',
    'value-list-comma-space-before': 'never',
    'value-list-max-empty-lines': 0
  }
}
<commit_msg>Adjust Stylelint-Config<commit_after><% if (projectstylelint === 'strict') { %>const font = {
  properties: ['font', 'font-family', 'font-size', 'line-height', 'font-weight', 'font-style']
}

const text = {
  properties: [
    'break',
    'column',
    'columns',
    'hyphens',
    'letter-spacing',
    'tab-size',
    'text',
    'white-space',
    'word-spacing',
    'word-wrap'
  ]
}

const color = {
  properties: [
    'opacity',
    'background',
    'box-decoration-break',
    'box-shadow',
    'color',
    'filter',
    'layer',
    'mask',
    'mix-blend-mode'
  ]
}

const scrollbar = {
  properties: ['scrollbar']
}

const outline = {
  properties: ['outline']
}

const list = {
  properties: ['list-style', 'marker-offset']
}

const tables = {
  properties: [
    'border-collapse',
    'border-spacing',
    'caption-side',
    'empty-cells',
    'speak-header',
    'table-layout'
  ]
}

const classification = {
  properties: ['content', 'clear', 'display', 'float', 'isolation', 'visibility']
}

const dimensions = {
  properties: [
    'block-size',
    'box-sizing',
    'size',
    'width',
    'min-width',
    'max-width',
    'height',
    'min-height',
    'max-height',
    'inline-size',
    'object'
  ]
}

const positioning = {
  properties: [
    'position',
    'top',
    'right',
    'bottom',
    'left',
    'z-index',
    'flex',
    'flex-wrap',
    'flex-direction',
    'justify-content',
    'align-content',
    'align-items',
    'align-self',
    'grid',
    'offset',
    'order',
    'overflow',
    'text-overflow',
    'clip',
    'vertical-align'
  ]
}

const margins = {
  properties: ['margin', 'margin-top', 'margin-right', 'margin-bottom', 'margin-left']
}

const padding = {
  properties: ['padding', 'padding-top', 'padding-right', 'padding-bottom', 'padding-left']
}

const border = {
  properties: ['border-color', 'border-style', 'border-width', 'border']
}

const dynamic = {
  properties: [
    'accelerator',
    'behavior',
    'caret-color',
    'cursor',
    'filter',
    'pointer-events',
    'resize',
    'touch-action',
    'zoom'
  ]
}

const generated = {
  properties: ['counter', 'fallback', 'include', 'quotes']
}

const international = {
  properties: [
    'direction',
    'ime-mode',
    'layout',
    'line-break',
    'ruby',
    'spacing',
    'text-autospace',
    'text-justify',
    'text-kashida-space',
    'unicode-bidi',
    'word-break',
    'writing-mode'
  ]
}

const print = {
  properties: ['marks', 'orphans', 'page-break', 'page', 'size', 'widows']
}

const aural = {
  properties: [
    'azimut',
    'cue',
    'elevation',
    'pause',
    'pitch-range',
    'play-during',
    'richness',
    'speak',
    'speech',
    'stress',
    'voice-family',
    'volume'
  ]
}

const animation = {
  properties: ['animation', 'transition', 'will-change']
}

const transform = {
  properties: ['transform', 'backface-visibility', 'perspective']
}

<% } %>module.exports = {
  'plugins': [
    'stylelint-order',
    'stylelint-scss',
    'stylelint-selector-bem-pattern'
  ],
  'extends': 'stylelint-config-sass-guidelines',
  'rules': {
    'at-rule-empty-line-before': [
      'never',
      {
        'except': [
          'inside-block'
        ],
        'ignore': [
          'after-comment',
          'inside-block'
        ]
      }
    ],
    'at-rule-name-case': 'lower',
    'at-rule-name-newline-after': 'always-multi-line',
    'at-rule-name-space-after': 'always',
    'at-rule-no-unknown': null,
    'at-rule-no-vendor-prefix': true,
    'block-closing-brace-empty-line-before': 'never',
    'block-closing-brace-newline-after': [
      'always',
      {
        'ignoreAtRules': [
          'if',
          'else'
        ]
      }
    ],
    'block-closing-brace-newline-before': 'always-multi-line',
    'block-closing-brace-space-after': 'always-single-line',
    'block-closing-brace-space-before': 'always-single-line',
    'block-no-empty': true,
    'block-opening-brace-newline-after': 'always',
    'block-opening-brace-newline-before': 'never-single-line',
    'block-opening-brace-space-after': 'always-single-line',
    'block-opening-brace-space-before': 'always-single-line',
    'color-hex-case': 'lower',
    'color-hex-length': 'short',
    'color-named': 'never',
    'color-no-invalid-hex': true,
    'comment-empty-line-before': [
      'always',
      {
        'except': [
          'first-nested'
        ],
        'ignore': [
          'after-comment',
          'stylelint-commands'
        ]
      }
    ],
    'comment-no-empty': true,
    'comment-whitespace-inside': 'always',
    'custom-property-empty-line-before': 'never',
    'declaration-bang-space-after': 'never',
    'declaration-bang-space-before': 'always',
    'declaration-block-no-duplicate-properties': [
      true,
      {
        'ignore': [
          'consecutive-duplicates-with-different-values'
        ]
      }
    ],
    'declaration-block-no-shorthand-property-overrides': true,
    'declaration-block-semicolon-newline-after': 'always',
    'declaration-block-semicolon-newline-before': 'never-multi-line',
    'declaration-block-semicolon-space-after': 'always-single-line',
    'declaration-block-semicolon-space-before': 'never',
    'declaration-block-single-line-max-declarations': 1,
    'declaration-block-trailing-semicolon': 'always',
    'declaration-colon-newline-after': 'always-multi-line',
    'declaration-colon-space-after': 'always-single-line',
    'declaration-colon-space-before': 'never',
    'declaration-empty-line-before': 'never',
    'font-family-name-quotes': 'always-where-recommended',
    'font-family-no-duplicate-names': true,
    'font-weight-notation': 'numeric',
    'function-blacklist': [
      'rgb'
    ],
    'function-calc-no-unspaced-operator': true,
    'function-comma-newline-after': 'never-multi-line',
    'function-comma-newline-before': 'never-multi-line',
    'function-comma-space-after': 'always',
    'function-comma-space-before': 'never',
    'function-linear-gradient-no-nonstandard-direction': true,
    'function-max-empty-lines': 0,
    'function-parentheses-newline-inside': 'never-multi-line',
    'function-parentheses-space-inside': 'never',
    'function-url-quotes': 'always',
    'function-whitespace-after': 'always',
    'indentation': 2,
    'keyframe-declaration-no-important': true,
    'length-zero-no-unit': true,
    'max-empty-lines': 2,
    'max-nesting-depth': 5,
    'media-feature-colon-space-after': 'always',
    'media-feature-colon-space-before': 'never',
    'media-feature-name-case': 'lower',
    'media-feature-name-no-unknown': true,
    'media-feature-name-no-vendor-prefix': true,
    'media-feature-parentheses-space-inside': 'never',
    'media-feature-range-operator-space-after': 'always',
    'media-feature-range-operator-space-before': 'always',
    'media-query-list-comma-newline-after': 'always',
    'media-query-list-comma-newline-before': 'never-multi-line',
    'media-query-list-comma-space-after': 'always-single-line',
    'media-query-list-comma-space-before': 'never',
    'no-duplicate-selectors': true,
    'no-empty-source': true,
    'no-eol-whitespace': true,
    'no-extra-semicolons': true,
    'no-invalid-double-slash-comments': true,
    'no-missing-end-of-source-newline': true,
    'no-unknown-animations': true,
    'number-leading-zero': <% if ( projectprettier === true ) { %>'always'<% } else { %>'never'<% } %>,
    'number-max-precision': 6,
    'number-no-trailing-zeros': true,<% if (projectstylelint === 'strict') { %>

    'order/order': [
      'custom-properties',
      'dollar-variables',
      'at-variables',
      {
        type: 'at-rule',
        name: 'include',
        hasBlock: false
      },
      'declarations',
      {
        type: 'at-rule',
        name: 'include',
        hasBlock: true
      },
      {
        type: 'at-rule',
        name: 'media',
        hasBlock: true
      },
      'rules',
      'at-rules'
    ],
    'order/properties-order': [
      classification,
      positioning,
      dimensions,
      padding,
      border,
      margins,
      list,
      tables,
      color,
      font,
      text,
      outline,
      transform,
      animation,
      dynamic,
      generated,
      international,
      print,
      aural,
      scrollbar
    ],<% } %>
    'order/properties-alphabetical-order': null,

    'property-case': 'lower',
    'property-no-unknown': true,
    'property-no-vendor-prefix': true,
    'rule-empty-line-before': [
      'always',
      {
        'except': [
          'after-single-line-comment',
          'first-nested'
        ],
        'ignore': [
          'after-comment'
        ]
      }
    ],
    'selector-attribute-brackets-space-inside': 'never',
    'selector-attribute-operator-space-after': 'never',
    'selector-attribute-operator-space-before': 'never',
    'selector-attribute-quotes': 'always',
    'selector-combinator-space-after': 'always',
    'selector-combinator-space-before': 'always',
    'selector-descendant-combinator-no-non-space': true,
    'selector-id-pattern': '^[a-zA-Z]+$',
    'selector-max-compound-selectors': 5,
    'selector-max-specificity': '0,4,0',
    'selector-max-id': 0,
    'selector-no-qualifying-type': [
      true,
      {
        'ignore': [
          'attribute'
        ]
      }
    ],
    'selector-pseudo-class-case': 'lower',
    'selector-pseudo-class-no-unknown': true,
    'selector-pseudo-class-parentheses-space-inside': 'never',
    'selector-pseudo-element-case': 'lower',
    'selector-pseudo-element-colon-notation': 'double',
    'selector-pseudo-element-no-unknown': true,
    'selector-type-case': 'lower',
    'selector-type-no-unknown': [true, {
      ignore: ['custom-elements', 'default-namespace']
    }],
    'selector-max-empty-lines': 0,
    'selector-list-comma-newline-after': 'always-multi-line',
    'selector-list-comma-newline-before': 'never-multi-line',
    'selector-list-comma-space-after': 'always-single-line',
    'selector-list-comma-space-before': 'never',
    'shorthand-property-no-redundant-values': true,
    'string-quotes': 'single',
    'unit-blacklist': [],
    'unit-case': 'lower',
    'unit-no-unknown': true,
    'value-no-vendor-prefix': true,
    'value-list-comma-newline-after': 'always-multi-line',
    'value-list-comma-newline-before': 'always-multi-line',
    'value-list-comma-space-after': 'always',
    'value-list-comma-space-before': 'never',
    'value-list-max-empty-lines': 0
  }
}
<|endoftext|>


<commit_before>/*eslint camelcase: ["error", {properties: "never"}]*/

import { flatten } from 'lodash';
import CBTConnector from 'cbt_tunnels';
import request from 'request-promise';
import wd from 'wd';

var openedBrowsers = {};
var webDriver;

const MAX_DURATION     = process.env['CBT_MAX_DURATION'];

const AUTH_FAILED_ERROR = 'Authentication failed. Please assign the correct username and access key ' +
    'to the CBT_TUNNELS_USERNAME and CBT_TUNNELS_AUTHKEY environment variables.';

const CBT_API_PATHS = {
    browserList: {
        url: 'https://crossbrowsertesting.com/api/v3/selenium/browsers?format=json'
    },
    tunnelInfo: {
        url: 'https://crossbrowsertesting.com/api/v3/tunnels?active=true'
    },
    deleteTunnel: id => ({
        url:    `https://crossbrowsertesting.com/api/v3/tunnels/${id}`,
        method: 'DELETE'
    }),
    seleniumTestHistory: {
        url: 'https://crossbrowsertesting.com/api/v3/selenium?active=true'
    },
    deleteBrowser: id => ({
        url:    `https://crossbrowsertesting.com/api/v3/selenium/${id}`,
        method: 'DELETE'
    })
};

function doRequest (apiPath) {
    var url = apiPath.url;

    var opts = {
        auth: {
            user: process.env['CBT_TUNNELS_USERNAME'],
            pass: process.env['CBT_TUNNELS_AUTHKEY'],
        },

        method: apiPath.method || 'GET'
    };

    return request(url, opts)
        .catch(error => {
            throw error;
        });
}

async function startBrowser (id, url, capabilities) {
    webDriver = wd.promiseChainRemote('hub.crossbrowsertesting.com', 80, process.env['CBT_TUNNELS_USERNAME'], process.env['CBT_TUNNELS_AUTHKEY']);
    openedBrowsers[id] = webDriver;

    await webDriver
        .init(capabilities)
        .get(url);

}

export default {
    // Multiple browsers support
    isMultiBrowser: true,
    platformsInfo:  [],
    browserNames:   [],

    async _getDeviceList () {
        this.myArr = JSON.parse(await doRequest(CBT_API_PATHS.browserList));

        this.browserNames = this.myArr
            .map(info => {
                if (info['device'] === 'mobile') {
                    this.name = info['browsers'][0]['type'];
                    this.version = info['caps'].platformVersion;
                    this.OS = info['caps'].deviceName;
                    return `${this.name}@${this.version}:${this.OS}`;
                }

                this.arrList = [];
                this.OS = info['name'];

                for (var i = 0; i < info['browsers'].length; i++) {
                    this.name = info['browsers'][i]['type'];
                    this.version = info['browsers'][i]['version'];
                    this.arrList.push(`${this.name}@${this.version}:${this.OS}`);
                }
                return this.arrList;
            });

        this.browserNames = flatten( this.browserNames);
    },

    // Required - must be implemented
    // Browser control
    async openBrowser (id, pageUrl, browserName) {
        if (!process.env['CBT_TUNNELS_USERNAME'] || !process.env['CBT_TUNNELS_AUTHKEY'])
            throw new Error(AUTH_FAILED_ERROR);

        await CBTConnector.start({ 'username': process.env['CBT_TUNNELS_USERNAME'], 'authkey': process.env['CBT_TUNNELS_AUTHKEY'] }, async function (err) {
            if (!err) {

                var colon = browserName.indexOf(':');

                if (colon > -1) {
                    var platform = browserName.substr(colon + 1);

                    browserName = browserName.substr(0, colon);
                }
                var at = browserName.indexOf('@');

                if (at > -1) {
                    var version = browserName.substr(at + 1);

                    browserName = browserName.substr(0, at);
                }

                var capabilities;

                if (browserName !== 'Chrome Mobile' && browserName !== 'Mobile Safari') {
                    capabilities = {
                        browserName: browserName,
                        version:     version,
                        platform:    platform
                    };
                }
                else {
                    capabilities = {
                        browserName:     browserName,
                        platformVersion: version,
                        deviceName:      platform
                    };
                }

                // CrossBrowserTesting-Specific Capabilities
                if (MAX_DURATION)
                    capabilities.max_duration = MAX_DURATION;

                capabilities.name = `TestCafe test run ${id}`;
                capabilities.build = process.env.CBT_TUNNELS_BUILD || null;
                capabilities.record_video = (process.env.CBT_TUNNELS_RECORD_VIDEO || '').match(/true/i); // eslint-disable-line camelcase

                await startBrowser(id, pageUrl, capabilities);
            }

        });
    },

    async closeBrowser (id) {
        await openedBrowsers[id].quit();
    },

    // Optional - implement methods you need, remove other methods
    // Initialization
    async init () {
        await this._getDeviceList();
    },

    async dispose () {
        this.seleniumHistoryList = JSON.parse(await doRequest(CBT_API_PATHS.seleniumTestHistory));
        if (this.seleniumHistoryList.meta.record_count >= 1) {
            for (let i = 0; i < this.seleniumHistoryList.meta.record_count; i++) {
                this.seleniumTestID = this.seleniumHistoryList.selenium[i].selenium_test_id;
                await doRequest(CBT_API_PATHS.deleteBrowser(this.seleniumTestID));
            }
        }

        this.tunnelList = JSON.parse(await doRequest(CBT_API_PATHS.tunnelInfo));
        if (this.tunnelList.meta.record_count >= 1) {
            for (let i = 0; i < this.tunnelList.meta.record_count; i++) {
                this.tunnelID = this.tunnelList.tunnels[i].tunnel_id;
                await doRequest(CBT_API_PATHS.deleteTunnel(this.tunnelID));
            }
        }
    },

    // Browser names handling
    async getBrowserList () {
        return this.browserNames;
    },

    async isValidBrowserName (/* browserName */) {
        return true;
    },

    // Extra methods
    async resizeWindow (/* id, width, height, currentWidth, currentHeight */) {
        this.reportWarning('The window resize functionality is not supported by the "crossbrowsertesting" browser provider.');
    },

    async takeScreenshot (/* id, screenshotPath, pageWidth, pageHeight */) {
        this.reportWarning('The screenshot functionality is not supported by the "crossbrowsertesting" browser provider.');
    }
};
<commit_msg>update to crossbrowsertesting capabilities<commit_after>/*eslint camelcase: ["error", {properties: "never"}]*/

import { flatten } from 'lodash';
import CBTConnector from 'cbt_tunnels';
import request from 'request-promise';
import wd from 'wd';

var openedBrowsers = {};
var webDriver;

const AUTH_FAILED_ERROR = 'Authentication failed. Please assign the correct username and access key ' +
    'to the CBT_TUNNELS_USERNAME and CBT_TUNNELS_AUTHKEY environment variables.';

const CBT_API_PATHS = {
    browserList: {
        url: 'https://crossbrowsertesting.com/api/v3/selenium/browsers?format=json'
    },
    tunnelInfo: {
        url: 'https://crossbrowsertesting.com/api/v3/tunnels?active=true'
    },
    deleteTunnel: id => ({
        url:    `https://crossbrowsertesting.com/api/v3/tunnels/${id}`,
        method: 'DELETE'
    }),
    seleniumTestHistory: {
        url: 'https://crossbrowsertesting.com/api/v3/selenium?active=true'
    },
    deleteBrowser: id => ({
        url:    `https://crossbrowsertesting.com/api/v3/selenium/${id}`,
        method: 'DELETE'
    })
};

function doRequest (apiPath) {
    var url = apiPath.url;

    var opts = {
        auth: {
            user: process.env['CBT_TUNNELS_USERNAME'],
            pass: process.env['CBT_TUNNELS_AUTHKEY'],
        },

        method: apiPath.method || 'GET'
    };

    return request(url, opts)
        .catch(error => {
            throw error;
        });
}

async function startBrowser (id, url, capabilities) {
    webDriver = wd.promiseChainRemote('hub.crossbrowsertesting.com', 80, process.env['CBT_TUNNELS_USERNAME'], process.env['CBT_TUNNELS_AUTHKEY']);
    openedBrowsers[id] = webDriver;

    await webDriver
        .init(capabilities)
        .get(url);

}

export default {
    // Multiple browsers support
    isMultiBrowser: true,
    platformsInfo:  [],
    browserNames:   [],

    async _getDeviceList () {
        this.myArr = JSON.parse(await doRequest(CBT_API_PATHS.browserList));

        this.browserNames = this.myArr
            .map(info => {
                if (info['device'] === 'mobile') {
                    this.name = info['browsers'][0]['type'];
                    this.version = info['caps'].platformVersion;
                    this.OS = info['caps'].deviceName;
                    return `${this.name}@${this.version}:${this.OS}`;
                }

                this.arrList = [];
                this.OS = info['name'];

                for (var i = 0; i < info['browsers'].length; i++) {
                    this.name = info['browsers'][i]['type'];
                    this.version = info['browsers'][i]['version'];
                    this.arrList.push(`${this.name}@${this.version}:${this.OS}`);
                }
                return this.arrList;
            });

        this.browserNames = flatten( this.browserNames);
    },

    // Required - must be implemented
    // Browser control
    async openBrowser (id, pageUrl, browserName) {
        if (!process.env['CBT_TUNNELS_USERNAME'] || !process.env['CBT_TUNNELS_AUTHKEY'])
            throw new Error(AUTH_FAILED_ERROR);

        await CBTConnector.start({ 'username': process.env['CBT_TUNNELS_USERNAME'], 'authkey': process.env['CBT_TUNNELS_AUTHKEY'] }, async function (err) {
            if (!err) {

                var colon = browserName.indexOf(':');

                if (colon > -1) {
                    var platform = browserName.substr(colon + 1);

                    browserName = browserName.substr(0, colon);
                }
                var at = browserName.indexOf('@');

                if (at > -1) {
                    var version = browserName.substr(at + 1);

                    browserName = browserName.substr(0, at);
                }

                var capabilities;

                if (browserName !== 'Chrome Mobile' && browserName !== 'Mobile Safari') {
                    capabilities = {
                        browserName: browserName,
                        version:     version,
                        platform:    platform
                    };
                }
                else {
                    capabilities = {
                        browserName:     browserName,
                        platformVersion: version,
                        deviceName:      platform
                    };
                }

                // CrossBrowserTesting-Specific Capabilities
                capabilities.name = `TestCafe test run ${id}`;
                if (process.env.CBT_BUILD)
                    capabilities.build = process.env.CBT_BUILD;
                if (process.env.CBT_RECORD_VIDEO)
                    capabilities.record_video = process.env.CBT_RECORD_VIDEO.match(/true/i);
                if (process.env.CBT_MAX_DURATION)
                    capabilities.max_duration = process.env.CBT_MAX_DURATION;

                await startBrowser(id, pageUrl, capabilities);
            }

        });
    },

    async closeBrowser (id) {
        await openedBrowsers[id].quit();
    },

    // Optional - implement methods you need, remove other methods
    // Initialization
    async init () {
        await this._getDeviceList();
    },

    async dispose () {
        this.seleniumHistoryList = JSON.parse(await doRequest(CBT_API_PATHS.seleniumTestHistory));
        if (this.seleniumHistoryList.meta.record_count >= 1) {
            for (let i = 0; i < this.seleniumHistoryList.meta.record_count; i++) {
                this.seleniumTestID = this.seleniumHistoryList.selenium[i].selenium_test_id;
                await doRequest(CBT_API_PATHS.deleteBrowser(this.seleniumTestID));
            }
        }

        this.tunnelList = JSON.parse(await doRequest(CBT_API_PATHS.tunnelInfo));
        if (this.tunnelList.meta.record_count >= 1) {
            for (let i = 0; i < this.tunnelList.meta.record_count; i++) {
                this.tunnelID = this.tunnelList.tunnels[i].tunnel_id;
                await doRequest(CBT_API_PATHS.deleteTunnel(this.tunnelID));
            }
        }
    },

    // Browser names handling
    async getBrowserList () {
        return this.browserNames;
    },

    async isValidBrowserName (/* browserName */) {
        return true;
    },

    // Extra methods
    async resizeWindow (/* id, width, height, currentWidth, currentHeight */) {
        this.reportWarning('The window resize functionality is not supported by the "crossbrowsertesting" browser provider.');
    },

    async takeScreenshot (/* id, screenshotPath, pageWidth, pageHeight */) {
        this.reportWarning('The screenshot functionality is not supported by the "crossbrowsertesting" browser provider.');
    }
};
<|endoftext|>


<commit_before>module.exports = {
    name: 'v-image',

    template: '
\

\
            \

\
            Select Image\
            <input type="file" v-bind:name="name" style="display:none;" accept="image/*" v-on:change="onFileChange">\

\

\
            \

\
            <button v-bind:class="buttonStyle" v-on:click="removeImage">Remove image</button>\

\

',

    props: {
        image: {
            type: String,
            default: null,
            required: false,
        },

        name: {
            type: String,
            default: 'name',
            required: true,
        },

        alt: {
            type: String,
            default: 'Very Interesting Image',
            required: false,
        },

        imgStyle: {
            type: Object,
            required: false
        },

        buttonStyle: {
            type: Object,
            required: false
        }
    },
    data: function() {
        return {
            internal_image: null,
        }
    },
    methods: {
        onFileChange(e) {
            let files = e.target.files || e.dataTransfer.files;
            if (!files.length)
                return;
            this.createImage(files[0]);
        },

        createImage(file) {
            let image = new Image();
            let reader = new FileReader();
            let t = this;

            reader.onload = (e) => {
                t.internal_image = e.target.result;
                t.$emit('loadImage', e.target.result);
            };
            reader.readAsDataURL(file);
        },

        removeImage() {
            this.image = null;
            this.internal_image = null;
        }

    }
};<commit_msg>:arrow_right_hook: Fixed the `id` attribute for input type=file<commit_after>module.exports = {
    name: 'v-image',

    template: '
\

\
            \

\
            Select Image\
            <input type="file" v-bind:name="name" v-bind:id="name" style="display:none;" accept="image/*" v-on:change="onFileChange">\

\

\
            \

\
            <button v-bind:class="buttonStyle" v-on:click="removeImage">Remove image</button>\

\

',

    props: {
        image: {
            type: String,
            default: null,
            required: false,
        },

        name: {
            type: String,
            default: 'name',
            required: true,
        },

        alt: {
            type: String,
            default: 'Very Interesting Image',
            required: false,
        },

        imgStyle: {
            type: Object,
            required: false
        },

        buttonStyle: {
            type: Object,
            required: false
        }
    },
    data: function() {
        return {
            internal_image: null,
        }
    },
    methods: {
        onFileChange(e) {
            let files = e.target.files || e.dataTransfer.files;
            if (!files.length)
                return;
            this.createImage(files[0]);
        },

        createImage(file) {
            let image = new Image();
            let reader = new FileReader();
            let t = this;

            reader.onload = (e) => {
                t.internal_image = e.target.result;
                t.$emit('loadImage', e.target.result);
            };
            reader.readAsDataURL(file);
        },

        removeImage() {
            this.image = null;
            this.internal_image = null;
        }

    }
};<|endoftext|>


<commit_before>const filter = document.querySelector('#filter');
const order = document.querySelector('#order');

const output = document.querySelector('#output');

const countries = [
  {
    country: "United Kingdom",
    continent: "europe",
    flag: "flags/1465051433_United-Kingdom_flat.png",
    capital: "London",
    population: 64100000
  },
  {
    country: "France",
    continent: "europe",
    flag: "flags/1465051447_France_flat.png",
    capital: "Paris",
    population: 66300000
  },
  {
    country: "Germany",
    continent: "europe",
    flag: "flags/1465051462_Germany_flat.png",
    capital: "Berlin",
    population: 80620000
  },
  {
    country: "United States of America",
    continent: "north-america",
    flag: "flags/1465051472_United-States_flat.png",
    capital: "Washington D.C.",
    population: 318900000
  },
  {
    country: "Italy",
    continent: "europe",
    flag: "flags/1465051483_Italy_flat.png",
    capital: "Rome",
    population: 59830000
  },
  {
    country: "China",
    continent: "asia",
    flag: "flags/1465051492_China_flat.png",
    capital: "Beijing",
    population: 1357000000
  },
  {
    country: "Canada",
    continent: "north-america",
    flag: "flags/1465051500_Canada_flat.png",
    capital: "Ottawa",
    population: 35160000
  },
  {
    country: "South Korea",
    continent: "asia",
    flag: "flags/1465051555_South-Korea_flat.png",
    capital: "Seoul",
    population: 50220000
  }
];

const identifier = (type, value)=> ({type, value});

const checkCriteria = (options, country)=> (
  country.continent === options.filter
);

const filter$ = Rx.Observable.fromEvent(filter, 'change')
  .map((x)=> identifier('filter', x.target.value));

const order$ = Rx.Observable.fromEvent(order, 'change')
  .map((x)=> identifier('order', x.target.value))
  .startWith(identifier('order', 'a-z'));

const renderResults = (result) => {
  let child = `

${result.country}

${result.capital}

${result.population}

`;
  output.insertAdjacentHTML('beforeend', child);
};

// http://stackoverflow.com/a/8175221
function sortByKey(array, key) {
    return array.sort(function(a, b) {
        var x = a[key]; var y = b[key];
        return ((x < y) ? -1 : ((x > y) ? 1 : 0));
    });
}

const sortCountry = (order, countries) => {
  switch(order) {
    case 'a-z':
      return sortByKey(countries, 'country');
    case 'z-a':
      return sortByKey(countries, 'country').reverse();
    case 'large':
      return sortByKey(countries, 'population').reverse();
    case 'small':
      return sortByKey(countries, 'population');
    default:
      return countries;
  }
}

const filterOrder = (options, countries)=> {

  let sorted = sortCountry(options.order, countries);
  let countres$ = Rx.Observable.from(sorted);

  countres$.filter((country)=> checkCriteria(options, country))
  .subscribe((country)=> renderResults(country));
};

const countriesFilterOrder$ = Rx.Observable.combineLatest(
  filter$,
  order$,
  (filter, order)=> ({filter: filter.value, order: order.value}))
  .do((x)=> output.innerHTML = "")
  .map((x)=> filterOrder(x, countries))
  .subscribe();
<commit_msg>Added a simple number with commas formatter.<commit_after>const filter = document.querySelector('#filter');
const order = document.querySelector('#order');

const output = document.querySelector('#output');

const countries = [
  {
    country: "United Kingdom",
    continent: "europe",
    flag: "flags/1465051433_United-Kingdom_flat.png",
    capital: "London",
    population: 64100000
  },
  {
    country: "France",
    continent: "europe",
    flag: "flags/1465051447_France_flat.png",
    capital: "Paris",
    population: 66300000
  },
  {
    country: "Germany",
    continent: "europe",
    flag: "flags/1465051462_Germany_flat.png",
    capital: "Berlin",
    population: 80620000
  },
  {
    country: "United States of America",
    continent: "north-america",
    flag: "flags/1465051472_United-States_flat.png",
    capital: "Washington D.C.",
    population: 318900000
  },
  {
    country: "Italy",
    continent: "europe",
    flag: "flags/1465051483_Italy_flat.png",
    capital: "Rome",
    population: 59830000
  },
  {
    country: "China",
    continent: "asia",
    flag: "flags/1465051492_China_flat.png",
    capital: "Beijing",
    population: 1357000000
  },
  {
    country: "Canada",
    continent: "north-america",
    flag: "flags/1465051500_Canada_flat.png",
    capital: "Ottawa",
    population: 35160000
  },
  {
    country: "South Korea",
    continent: "asia",
    flag: "flags/1465051555_South-Korea_flat.png",
    capital: "Seoul",
    population: 50220000
  }
];

const identifier = (type, value)=> ({type, value});

const checkCriteria = (options, country)=> (
  country.continent === options.filter
);

const filter$ = Rx.Observable.fromEvent(filter, 'change')
  .map((x)=> identifier('filter', x.target.value));

const order$ = Rx.Observable.fromEvent(order, 'change')
  .map((x)=> identifier('order', x.target.value))
  .startWith(identifier('order', 'a-z'));

// http://stackoverflow.com/a/2901298
const numberWithCommas = (number) =>
  number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");

const renderResults = (result) => {
  let child = `

${result.country}

${result.capital}

${numberWithCommas(result.population)}

`;
  output.insertAdjacentHTML('beforeend', child);
};

// http://stackoverflow.com/a/8175221
function sortByKey(array, key) {
    return array.sort(function(a, b) {
        var x = a[key]; var y = b[key];
        return ((x < y) ? -1 : ((x > y) ? 1 : 0));
    });
}

const sortCountry = (order, countries) => {
  switch(order) {
    case 'a-z':
      return sortByKey(countries, 'country');
    case 'z-a':
      return sortByKey(countries, 'country').reverse();
    case 'large':
      return sortByKey(countries, 'population').reverse();
    case 'small':
      return sortByKey(countries, 'population');
    default:
      return countries;
  }
}

const filterOrder = (options, countries)=> {

  let sorted = sortCountry(options.order, countries);
  let countres$ = Rx.Observable.from(sorted);

  countres$.filter((country)=> checkCriteria(options, country))
  .subscribe((country)=> renderResults(country));
};

const countriesFilterOrder$ = Rx.Observable.combineLatest(
  filter$,
  order$,
  (filter, order)=> ({filter: filter.value, order: order.value}))
  .do((x)=> output.innerHTML = "")
  .map((x)=> filterOrder(x, countries))
  .subscribe();
<|endoftext|>


<commit_before>	//var time_val = 1;
	//var time_handle;
	var onecallbackFired = false;
	var twocallbackFired = false;
	var threecallbackFired = false;

describe("Timer JS API Test", function() {

	beforeEach(function() {
		//startTestTimer();
		onecallbackFired = false;
		twocallbackFired = false;
		threecallbackFired = false;
		//displayResult("Output: ","");
		//one.stop();
	});

    afterEach(function () {
		//stopTestTimer();
    });

	var one_callback = function() {
	    //displayResult("Output: First Timer callback fired");
	    //first.innerHTML="First Timer callback fired";
		onecallbackFired = true;
	}

	var two_callback = function() {
	    //displayResult("Output: Second Timer callback fired");
		//second.innerHTML="Second Timer callback fired";
		twocallbackFired = true;
	}

	var three_callback = function() {
		//displayResult("Output: Third Timer callback fired");
		//third.innerHTML="Third Timer callback fired";
	    threecallbackFired = true;
	}

	it("VTXX-0001 | call isAlive  before timeout seconds |", function() {
			var one = Rho.Timer.create();
			one.start(1000, one_callback);
			setTimeout(function() {
					expect(one.isAlive()).toEqual(true);
				}, 500);

            waits(1100);
			runs(function(){
    			expect(onecallbackFired).toEqual(true);
			});

	});

	it("VTXX-0003 | call isAlive after timeout seconds |", function() {
			var one = Rho.Timer.create();
			one.start(500, one_callback);
			setTimeout(function() {
					expect(one.isAlive()).toEqual(false);
				}, 600);
			waits(1000);
			runs(function(){
			    expect(onecallbackFired).toEqual(true);
			});

	});

	it("VTXX-0004 | call isAlive for different timers (1) |", function() {
			var one = Rho.Timer.create();
			var two = Rho.Timer.create();
			var three = Rho.Timer.create();
			one.start(200, one_callback);
			two.start(250, two_callback);
			three.start(400, three_callback);
			setTimeout(function() {
					expect(one.isAlive()).toEqual(false);
					expect(two.isAlive()).toEqual(true);
					expect(three.isAlive()).toEqual(true);
				}, 210);
			waits(500);
			runs(function(){
    			expect(onecallbackFired).toEqual(true);
	    		expect(twocallbackFired).toEqual(true);
		    	expect(threecallbackFired).toEqual(true);
			});

	});

	it("VTXX-0005 | call isAlive for different timers (2) |", function() {
			var one = Rho.Timer.create();
			var two = Rho.Timer.create();
			var three = Rho.Timer.create();
			one.start(200, one_callback);
			two.start(250, two_callback);
			three.start(400, three_callback);
			setTimeout(function() {
					expect(one.isAlive()).toEqual(false);
					expect(two.isAlive()).toEqual(false);
					expect(three.isAlive()).toEqual(false);
				}, 500);
			waits(550);
			runs(function(){
    			expect(onecallbackFired).toEqual(true);
	    		expect(twocallbackFired).toEqual(true);
		    	expect(threecallbackFired).toEqual(true);
			});

	});

	it("VTXX-0006 | call stop before timeout |", function() {
			var one = Rho.Timer.create();
			one.start(500, one_callback);
			setTimeout(function() {
					one.stop();
					expect(one.isAlive()).toEqual(false);
				}, 200);
			waits(550);
			runs(function(){
			    expect(onecallbackFired).toEqual(false);
			});

	});

	it("VTXX-0008 | call stop after timeout |", function() {
			var one = Rho.Timer.create();
			one.start(200, one_callback);
			setTimeout(function() {
					one.stop();
				}, 210);
			waits(220);
			runs(function(){
    			expect(one.isAlive()).toEqual(false);
	    		expect(onecallbackFired).toEqual(true);
			});

	});

	it("VTXX-0009 | call stop for one of three timers|", function() {
			var one = Rho.Timer.create();
			var two = Rho.Timer.create();
			var three = Rho.Timer.create();
			one.start(500, one_callback);
			two.start(300, two_callback);
			three.start(600, three_callback);
			setTimeout(function() {
					two.stop();
					expect(one.isAlive()).toEqual(true);
					expect(two.isAlive()).toEqual(false);
					expect(three.isAlive()).toEqual(true);
				}, 200);
			waits(650);
			runs(function(){
    			expect(onecallbackFired).toEqual(true);
	    		expect(twocallbackFired).toEqual(false);
		    	expect(threecallbackFired).toEqual(true);
			});

	});

	it("VTXX-0010 | call stop for all timers |", function() {
			var one = Rho.Timer.create();
			var two = Rho.Timer.create();
			var three = Rho.Timer.create();
			one.start(250, one_callback);
			two.start(300, two_callback);
			three.start(350, three_callback);
			setTimeout(function() {
					one.stop();
					two.stop();
					three.stop();
					expect(one.isAlive()).toEqual(false);
					expect(two.isAlive()).toEqual(false);
					expect(three.isAlive()).toEqual(false);
				}, 160);

			waits(400);
			runs(function(){
			    expect(onecallbackFired).toEqual(false);
			    expect(twocallbackFired).toEqual(false);
			    expect(threecallbackFired).toEqual(false);
			});

	});

	it("VTXX-0011 | call isAlive without calling start |", function() {
			var one = Rho.Timer.create();
    		expect(one.isAlive()).toEqual(false);

	});

	it("VTXX-0012 | call stop without calling start |", function() {
			var one = Rho.Timer.create();
			setTimeout(function() {
					one.stop();
					expect(one.isAlive()).toEqual(false);
				}, 150);
			waits(300);
			runs(function(){
    			expect(onecallbackFired).toEqual(false);
			});

	});

	it("VTXX-0013 | call start with 100 ms |", function() {
			var one = Rho.Timer.create();
			one.start(100, one_callback);
			setTimeout(function() {
					expect(one.isAlive()).toEqual(false);
				}, 200);
			waits(210);
			runs(function(){
    			expect(onecallbackFired).toEqual(true);
			});
	});

	it("VTXX-0014 | call start with 0 ms |", function() {
		var one = Rho.Timer.create();
		var res = null;
		runs(function() {
    		one.start(0, function(arg, err) {
    		  res = err;
		    });
		});

		waitsFor(function() { return null != res; }, 'Callback should be called', 10000);
		runs(function() {
		    expect(res).not.toBeNull();
		});
	});

	it("VTXX-0015 | call start with -10000 ms |", function() {
		var one = Rho.Timer.create();
		var res = null;
		runs(function() {
    		one.start(-1000, function(arg, err) {
    		  res = err;
		    });
		});

		waitsFor(function() { return null != res; }, 'Callback should be called', 10000);
		runs(function() {
		    expect(res).not.toBeNull();
		});
	});

	it("VTXX-0016 | call start without param and callback |", function() {
			var one = Rho.Timer.create();
			expect(function() {
			    var res = one.start();
			 }).toThrow();
	});

	it("VTXX-0017 | call start only with callback |", function() {
			var one = Rho.Timer.create();
			expect(function() { one.start(one_callback); }).toThrow();
	});

	it("VTXX-0018 | call start only with interval |", function() {
			var one = Rho.Timer.create();
			expect(function() { one.start(10000); }).toThrow();
	});

});
<commit_msg>reduce fragility for Timer specs<commit_after>	//var time_val = 1;
	//var time_handle;
	var onecallbackFired = false;
	var twocallbackFired = false;
	var threecallbackFired = false;

describe("Timer JS API Test", function() {

	beforeEach(function() {
		//startTestTimer();
		onecallbackFired = false;
		twocallbackFired = false;
		threecallbackFired = false;
		//displayResult("Output: ","");
		//one.stop();
	});

    afterEach(function () {
		//stopTestTimer();
    });

	var one_callback = function() {
	    //displayResult("Output: First Timer callback fired");
	    //first.innerHTML="First Timer callback fired";
		onecallbackFired = true;
	}

	var two_callback = function() {
	    //displayResult("Output: Second Timer callback fired");
		//second.innerHTML="Second Timer callback fired";
		twocallbackFired = true;
	}

	var three_callback = function() {
		//displayResult("Output: Third Timer callback fired");
		//third.innerHTML="Third Timer callback fired";
	    threecallbackFired = true;
	}

	it("VTXX-0001 | call isAlive  before timeout seconds |", function() {
			var one = Rho.Timer.create();
			one.start(1000, one_callback);
			setTimeout(function() {
					expect(one.isAlive()).toEqual(true);
				}, 500);

            waits(1100);
			runs(function(){
    			expect(onecallbackFired).toEqual(true);
			});

	});

	it("VTXX-0003 | call isAlive after timeout seconds |", function() {
			var one = Rho.Timer.create();
			one.start(500, one_callback);
			setTimeout(function() {
					expect(one.isAlive()).toEqual(false);
				}, 600);
			waits(1000);
			runs(function(){
			    expect(onecallbackFired).toEqual(true);
			});

	});

	it("VTXX-0004 | call isAlive for different timers (1) |", function() {
			var one = Rho.Timer.create();
			var two = Rho.Timer.create();
			var three = Rho.Timer.create();
			one.start(2000, one_callback);
			two.start(2500, two_callback);
			three.start(4000, three_callback);
			setTimeout(function() {
					expect(one.isAlive()).toEqual(false);
					expect(two.isAlive()).toEqual(true);
					expect(three.isAlive()).toEqual(true);
				}, 2100);
			waits(5000);
			runs(function(){
    			expect(onecallbackFired).toEqual(true);
	    		expect(twocallbackFired).toEqual(true);
		    	expect(threecallbackFired).toEqual(true);
			});

	});

	it("VTXX-0005 | call isAlive for different timers (2) |", function() {
			var one = Rho.Timer.create();
			var two = Rho.Timer.create();
			var three = Rho.Timer.create();
			one.start(2000, one_callback);
			two.start(2500, two_callback);
			three.start(4000, three_callback);
			setTimeout(function() {
					expect(one.isAlive()).toEqual(false);
					expect(two.isAlive()).toEqual(false);
					expect(three.isAlive()).toEqual(false);
				}, 5000);
			waits(5500);
			runs(function(){
    			expect(onecallbackFired).toEqual(true);
	    		expect(twocallbackFired).toEqual(true);
		    	expect(threecallbackFired).toEqual(true);
			});

	});

	it("VTXX-0006 | call stop before timeout |", function() {
			var one = Rho.Timer.create();
			one.start(500, one_callback);
			setTimeout(function() {
					one.stop();
					expect(one.isAlive()).toEqual(false);
				}, 200);
			waits(550);
			runs(function(){
			    expect(onecallbackFired).toEqual(false);
			});

	});

	it("VTXX-0008 | call stop after timeout |", function() {
			var one = Rho.Timer.create();
			one.start(200, one_callback);
			setTimeout(function() {
					one.stop();
				}, 210);
			waits(220);
			runs(function(){
    			expect(one.isAlive()).toEqual(false);
	    		expect(onecallbackFired).toEqual(true);
			});

	});

	it("VTXX-0009 | call stop for one of three timers|", function() {
			var one = Rho.Timer.create();
			var two = Rho.Timer.create();
			var three = Rho.Timer.create();
			one.start(500, one_callback);
			two.start(300, two_callback);
			three.start(600, three_callback);
			setTimeout(function() {
					two.stop();
					expect(one.isAlive()).toEqual(true);
					expect(two.isAlive()).toEqual(false);
					expect(three.isAlive()).toEqual(true);
				}, 200);
			waits(650);
			runs(function(){
    			expect(onecallbackFired).toEqual(true);
	    		expect(twocallbackFired).toEqual(false);
		    	expect(threecallbackFired).toEqual(true);
			});

	});

	it("VTXX-0010 | call stop for all timers |", function() {
			var one = Rho.Timer.create();
			var two = Rho.Timer.create();
			var three = Rho.Timer.create();
			one.start(250, one_callback);
			two.start(300, two_callback);
			three.start(350, three_callback);
			setTimeout(function() {
					one.stop();
					two.stop();
					three.stop();
					expect(one.isAlive()).toEqual(false);
					expect(two.isAlive()).toEqual(false);
					expect(three.isAlive()).toEqual(false);
				}, 160);

			waits(400);
			runs(function(){
			    expect(onecallbackFired).toEqual(false);
			    expect(twocallbackFired).toEqual(false);
			    expect(threecallbackFired).toEqual(false);
			});

	});

	it("VTXX-0011 | call isAlive without calling start |", function() {
			var one = Rho.Timer.create();
    		expect(one.isAlive()).toEqual(false);

	});

	it("VTXX-0012 | call stop without calling start |", function() {
			var one = Rho.Timer.create();
			setTimeout(function() {
					one.stop();
					expect(one.isAlive()).toEqual(false);
				}, 150);
			waits(300);
			runs(function(){
    			expect(onecallbackFired).toEqual(false);
			});

	});

	it("VTXX-0013 | call start with 100 ms |", function() {
			var one = Rho.Timer.create();
			one.start(100, one_callback);
			setTimeout(function() {
					expect(one.isAlive()).toEqual(false);
				}, 200);
			waits(210);
			runs(function(){
    			expect(onecallbackFired).toEqual(true);
			});
	});

	it("VTXX-0014 | call start with 0 ms |", function() {
		var one = Rho.Timer.create();
		var res = null;
		runs(function() {
    		one.start(0, function(arg, err) {
    		  res = err;
		    });
		});

		waitsFor(function() { return null != res; }, 'Callback should be called', 10000);
		runs(function() {
		    expect(res).not.toBeNull();
		});
	});

	it("VTXX-0015 | call start with -10000 ms |", function() {
		var one = Rho.Timer.create();
		var res = null;
		runs(function() {
    		one.start(-1000, function(arg, err) {
    		  res = err;
		    });
		});

		waitsFor(function() { return null != res; }, 'Callback should be called', 10000);
		runs(function() {
		    expect(res).not.toBeNull();
		});
	});

	it("VTXX-0016 | call start without param and callback |", function() {
			var one = Rho.Timer.create();
			expect(function() {
			    var res = one.start();
			 }).toThrow();
	});

	it("VTXX-0017 | call start only with callback |", function() {
			var one = Rho.Timer.create();
			expect(function() { one.start(one_callback); }).toThrow();
	});

	it("VTXX-0018 | call start only with interval |", function() {
			var one = Rho.Timer.create();
			expect(function() { one.start(10000); }).toThrow();
	});

});
<|endoftext|>