<commit_before>// ==========================================================================
// Project:   SproutCore Statechart - Hierarchical State Machine Library
// Copyright: ©2009 Sprout Systems, Inc. and contributors.
//            Portions ©2009 Apple, Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

// we don't ever use index zero, so initialize here to get a dense array...
SC._DISPATCH_PATH = [0] ; // avoid repeated allocations of this array

SC.mixin(SC.Object.prototype,
/** SC.Object.prototype */ {

  /**
    Specifies which object property hierarchical state machines are stored in.

    @type String
  */
  stateKey: 'state',

  /** @private
    Dispatches events to objects that implement event-handling with
    hierarchical state machines. SC.RootResponder tries this first; if the
    return value is NO, SC.RootResponder will then try the normal reponder
    methods (e.g. mouseDown:) defined by SC.ResponderProtocol.

    You should never need to call this method yourself.

    This implementation draws heavy inspiration from the QEP event processor
    written by Dr. Miro Samek, author of Practical Statecharts in C/C++. This
    JavaScript implementation was developed by Erich Ocean.

    @param {SC.Event} evt The event to handle
    @returns {Boolean} YES if the event was handled
  */
  dispatch: function(evt) {
    var stateKey = this.get('stateKey') ;
    var path = SC._DISPATCH_PATH ;
    var current, target, handlerKey, superstateKey, res, idx, ixd2 ;

    // .......................................................................
    // Step 1. Process the event hierarchically if a state machine is
    // available to send the event to.
    //

    // save the current state
    current = this[stateKey] ;
    if (!current) return NO ; // fast path -- this object does not use HSMs

    // okay, process the event hierarchically...
    res = this[stateKey](evt) ;
    if (res === undefined) {
      superstateKey = current.superstateKey ;
      while (superstateKey) {
        res = this[superstateKey]() ; // call superstate handler...
        if (res !== undefined) {
          handlerKey = superstateKey ;
          break ;
        } else superstateKey = this[superstateKey].superstateKey ;
      }
    }

    if (res === undefined) res = SC.EVT_IGNORED_RES ;

    // .......................................................................
    // Step 2. Was a transition taken? if so, this[stateKey] is now set to the
    // target state. Figure out (and possibly execute) entry and exit actions.
    //

    if (res === SC.EVT_TRANSITION_RES) {
      target = this[stateKey] ; // save the target of the transition

      // .....................................................................
      // Exit the current state to the state that handled the event...
      //

      if (current != this[handlerKey]) {
        // we don't know the property name for current,
        // so there is no way to call it directly...
        current.call(this, SC.EVT_EXIT) ;
        superstateKey = current.superstateKey ;

        while (superstateKey && superstateKey !== handlerKey) {
          this[superstateKey](SC.EVT_EXIT) ;
          superstateKey = this[superstateKey].superstateKey ;
        }
      }

      // .....................................................................
      // We've now exited from the original current state up to the state
      // that actually requested the transition, so make it current now...

      current = this[handlerKey] ;

      // .....................................................................
      // Now figure out--using a carefully orchestrated order of operations--
      // which states need to be exited and entered. Some states will be
      // exited during this task. Once the state transition topology has
      // been discovered, exit the do-while loop immediately, go to Step 3,
      // and finish the job.

      figureOutWhatToDo: // label used to exit do-while loop early
      do {
        // ...................................................................
        // (a) Is this a transition to self?
        //

        if (current === target) {
          // exit the handing state
          this[stateKey](SC.EVT_EXIT) ; // this[stateKey] == current

          // enter the target, aka this[stateKey] (actually do it below)
          idx = 0 ;

          // stop trying to figure out what to do...
          break figureOutWhatToDo ;
        }

        // ...................................................................
        // (b) Is the handling state the parent of the target state?
        //

        if (!done && this[target.superstateKey] === current) {
          // don't exit the handling state

          // enter the target, aka this[stateKey] (actually do it below)
          idx = 0 ;

          // stop trying to figure out what to do...
          break figureOutWhatToDo ;
        }

        // ...................................................................
        // (c) Do the handling state and the target state have the same
        // parent?
        //

        if (current.superstateKey === target.superstateKey) {
          // exit the handing state
          this[handlerKey](SC.EVT_EXIT) ; // this[handlerKey] == current

          // enter the target, aka this[stateKey] (actually do it below)
          idx = 0 ;

          // stop trying to figure out what to do...
          break figureOutWhatToDo ;
        }

        // ...................................................................
        // (d) Is the handling state's parent the target state?
        //

        if (this[current.superstateKey] === target) {
          // exit the handing state
          this[handlerKey](SC.EVT_EXIT) ; // this[handlerKey] == current

          // don't enter the target state -- we're already in it
          idx = -1 ;

           // stop trying to figure out what to do...
          break figureOutWhatToDo ;
        }

        // ...................................................................
        // (e) Is the handling state an ancestor of the target?
        //

        // enter both target and its superstate
        idx = 1 ;
        superstateKey = path[1] = target.superstateKey ;

        // loop over the target state's ancestors, looking for the handling
        // state (store the entry path along the way)
        if (superstateKey) superstateKey = this[superstateKey].superstateKey ;
        while (superstateKey !== undefined) {
          path[++idx] = superstateKey ; // store the entry path state key

          // is this the handling state?
          if (this[superstateKey] === current) {
            // don't exit the handling state

            // don't enter the handling state either...
            --idx ;

            // stop trying to figure out what to do...
            break figureOutWhatToDo ;

          // nope, didn't find the handling state, so keep going up..
          } else superstateKey = this[superstateKey].superstateKey ;
        }

        // ...................................................................
        // (f) Is the handling state's superstate one of target's ancestors?
        //

        // exit the handing state
        this[handlerKey](SC.EVT_EXIT) ; // this[handlerKey] == current

        // update current to the handing state's superstate
        handlerKey = current.superstateKey ;

        // does the handling state even have a superstate?
        if (!handlerKey) {
          // FIXME: what should be done here?

          // no ancestors to check, stop trying to figure out what to do...
          break figureOutWhatToDo ;

        // yep, see if the handling state's superstate in an ancestor of
        // the target state
        } else {
          current = this[handlerKey] ;
          idx2 = idx ;

          // loop over the target state's ancestors, looking for the handling
          // state's superstate
          do {
            // did we find the handling state's superstate?
            if (current === this[path[idx2]]) {
              // don't exit the handling state's superstate

              // don't enter the handling state's superstate either...
              idx = idx2 - 1 ;

              // stop trying to figure out what to do...
              break figureOutWhatToDo ;

            // nope, try a lower superstate of target
            } else --idx2 ;
          } while (idx2 > 0)
        }

        // ...................................................................
        // (g) Are any of the handling state's ancestors an ancestor of
        // the target state?

        for (;;) {
          // exit the handing state's superstate
          this[handlerKey](SC.EVT_EXIT) ; // this[handlerKey] == current

          // update the handing state's superstate to it's superstate
          handlerKey = current.superstateKey ;

          // does the handling state's superstate even have a superstate?
          if (!handlerKey) {
            // FIXME: what should be done here?

            // no ancestors to check, stop trying to figure out what to do...
            break figureOutWhatToDo ;

          // yep, see if the handling state's superstate's superstate is an
          // ancestor of the target state
          } else {
            current = this[handlerKey] ;
            var idx2 = idx ;

            // is the handling state's superstate's superstate one of target's
            // ancestors?
            do {
              // did we find the handling state's superstate's superstate?
              if (current=== this[path[idx2]]) {
                // don't exit the handling state's superstate's superstate

                // don't enter the handling state's superstate either...
                idx = idx2 - 1 ;

                // stop trying to figure out what to do...
                break figureOutWhatToDo ;
              } else --idx2 ;
            } while (idx2 > 0)
          }
        }
      } while(NO)

      // .....................................................................
      // Step 3. Enter all of the ancestors between target and the least
      // common ancestor of the handling state and target discovered above...
      //

      do {
        this[path[idx]](SC.EVT_ENTER) ;
      } while ((--idx) > 0)

      // .....................................................................
      // Step 4. Enter the target state itself.
      //

      if (idx === 0) this[stateKey](SC.EVT_ENTER) ;

      // .....................................................................
      // Step 5. Initialize the target state's substates if necessary.
      //

      while (this[stateKey](SC.EVT_INIT) === SC.EVT_TRANSITION_RES) {
        // enter the target of the transition (a substate)
        idx = 0 ;

        // get the superstate of the target of the transition...
        superstateKey = path[++idx] = current.superstateKey ;
        current = this[superstateKey] ;

        // walk the state hierarchy until we find our target state, storing
        // state keys along the way
        while (current !== target) {
          superstateKey = path[++idx] = current.superstateKey ;
          current = this[superstateKey] ;
        }

        // now enter the target's substates in reverse order...
        do {
          this[path[idx]](SC.EVT_ENTER) ;
        } while ((--idx) > 0)

        // and finally enter the target's substate itself
        if (idx === 0) this[stateKey](SC.EVT_ENTER) ;

        // the loop continues to apply any default transitions as substates
        // are entered...
      }

      // if we transitioned, we definitely handled the event...
      return YES ;
    }
    else {
      // otherwise, return NO if we completely ignored the event...
      return (res === SC.EVT_IGNORED_RES) ? NO : YES ;
    }
  }

});
<commit_msg>fixeds for dispatch() function<commit_after>// ==========================================================================
// Project:   SproutCore Statechart - Hierarchical State Machine Library
// Copyright: ©2009 Sprout Systems, Inc. and contributors.
//            Portions ©2009 Apple, Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

SC._DISPATCH_PATH = [] ; // avoid repeated allocations of this array

SC.mixin(SC.Object.prototype,
/** SC.Object.prototype */ {

  /**
    Specifies which object property hierarchical state machines are stored in.

    @type String
  */
  stateKey: 'state',

  /** @private
    Dispatches events to objects that implement event-handling with
    hierarchical state machines. SC.RootResponder tries this first; if the
    return value is NO, SC.RootResponder will then try the normal reponder
    methods (e.g. mouseDown:) defined by SC.ResponderProtocol.

    You should never need to call this method yourself.

    This implementation draws heavy inspiration from the QEP event processor
    written by Dr. Miro Samek, author of Practical Statecharts in C/C++. This
    JavaScript implementation was developed by Erich Ocean.

    @param {SC.Event} evt The event to handle
    @returns {Boolean} YES if the event was handled
  */
  dispatch: function(evt) {
    var stateKey = this.get('stateKey') ;
    var path = SC._DISPATCH_PATH ;
    var current, target, handlerKey, superstateKey, pathKey, res, idx, idx2 ;

    path.length = 0 ; // reset array

    // .......................................................................
    // Step 1. Process the event hierarchically if a state machine is
    // available to send the event to.
    //

    // debugger ;

    // save the current state
    current = this[stateKey] ;
    if (!current) return NO ; // fast path -- this object does not use HSMs

    // okay, process the event hierarchically...
    res = this[stateKey](evt) ;
    if (!res) {
      superstateKey = current.superstateKey ;
      while (superstateKey) {
        res = this[superstateKey](evt) ; // call superstate handler...
        if (res) {
          handlerKey = superstateKey ;
          break ;
        } else superstateKey = this[superstateKey].superstateKey ;
      }
    }

    if (!res) {
      return NO ; // we ignored the event

    // .......................................................................
    // Step 2. Was a transition taken? if so, this[stateKey] is now set to the
    // target state. Figure out (and possibly execute) entry and exit actions.
    //

    } else if (res === SC.EVT_TRANSITION_RES) {
      target = this[stateKey] ; // save the target of the transition
      path[0] = target.superstateKey ;

      // debugger ;

      // .....................................................................
      // Exit the current state to the state that handled the event...
      //

      if (handlerKey && current != this[handlerKey]) {
        // we don't know the property name for current,
        // so there is no way to call it directly...
        current.call(this, SC.EVT_EXIT) ;
        superstateKey = current.superstateKey ;

        while (superstateKey && superstateKey !== handlerKey) {
          this[superstateKey](SC.EVT_EXIT) ;
          superstateKey = this[superstateKey].superstateKey ;
        }
      }

      // .....................................................................
      // We've now exited from the original current state up to the state
      // that actually requested the transition, so make it current now...

      if (handlerKey) current = this[handlerKey] ;

      // debugger ;

      // .....................................................................
      // Now figure out--using a carefully orchestrated order of operations--
      // which states need to be exited and entered. Some states will be
      // exited during this task. Once the state transition topology has
      // been discovered, exit the do-while loop immediately, go to Step 3,
      // and finish the job.

      figureOutWhatToDo: // label used to exit do-while loop early
      do {

        // ...................................................................
        // (a) Is this a transition to self?
        //

        if (current === target) {
          // exit the handing state
          current.call(this, SC.EVT_EXIT) ;

          // enter the target, aka this[stateKey] (actually do it below)
          idx = 0 ;

          // stop trying to figure out what to do...
          break figureOutWhatToDo ;
        }

        // ...................................................................
        // (b) Is the handling state the parent of the target state?
        //

        if (this[target.superstateKey] === current) {
          // don't exit the handling state

          // enter the target, aka this[stateKey] (actually do it below)
          idx = 0 ;

          // stop trying to figure out what to do...
          break figureOutWhatToDo ;
        }

        // ...................................................................
        // (c) Do the handling state and the target state have the same
        // parent?
        //

        if (current.superstateKey === target.superstateKey) {
          // exit the handing state
          current.call(this, SC.EVT_EXIT) ;

          // enter the target, aka this[stateKey] (actually do it below)
          idx = 0 ;

          // stop trying to figure out what to do...
          break figureOutWhatToDo ;
        }

        // ...................................................................
        // (d) Is the handling state's parent the target state?
        //

        if (this[current.superstateKey] === target) {
          // exit the handing state
          current.call(this, SC.EVT_EXIT) ;

          // don't enter the target state -- we're already in it
          idx = -1 ;

           // stop trying to figure out what to do...
          break figureOutWhatToDo ;
        }

        // debugger ;

        // ...................................................................
        // (e) Is the handling state an ancestor of the target?
        //

        // enter both target and its superstate
        idx = 1 ;
        superstateKey = path[1] = target.superstateKey ;

        // loop over the target state's ancestors, looking for the handling
        // state (store the entry path along the way)
        if (superstateKey) superstateKey = this[superstateKey].superstateKey ;
        while (superstateKey !== undefined) {
          path[++idx] = superstateKey ; // store the entry path state key

          // is this the handling state?
          if (this[superstateKey] === current) {
            // don't exit the handling state

            // don't enter the handling state either...
            --idx ;

            // stop trying to figure out what to do...
            break figureOutWhatToDo ;

          // nope, didn't find the handling state, so keep going up..
          } else superstateKey = this[superstateKey].superstateKey ;
        }

        // ...................................................................
        // (f) Is the handling state's superstate one of target's ancestors?
        //

        // exit the handing state
        current.call(this, SC.EVT_EXIT) ;

        // update current to the handing state's superstate
        handlerKey = current.superstateKey ;

        // does the handling state even have a superstate?
        if (!handlerKey) {
          // FIXME: what should be done here?
          console.log('(f) handleKey unexpectedly miissing');

          // no ancestors to check, stop trying to figure out what to do...
          break figureOutWhatToDo ;

        // yep, see if the handling state's superstate in an ancestor of
        // the target state
        } else {
          current = this[handlerKey] ;
          idx2 = idx ;

          // loop over the target state's ancestors, looking for the handling
          // state's superstate
          do {
            // did we find the handling state's superstate?
            if (current.superstateKey === this[path[idx2]]) {
              // don't exit the handling state's superstate

              // don't enter the handling state's superstate either...
              idx = idx2 - 1 ;

              // stop trying to figure out what to do...
              break figureOutWhatToDo ;

            // nope, try a lower superstate of target
            } else --idx2 ;
          } while (idx2 > 0)
        }

        // debugger ;

        // ...................................................................
        // (g) Are any of the handling state's ancestors an ancestor of
        // the target state?

        for (;;) {
          // exit the handing state's superstate
          this[handlerKey](SC.EVT_EXIT) ; // this[handlerKey] == current

          // update the handing state's superstate to it's superstate
          handlerKey = current.superstateKey ;

          // does the handling state's superstate even have a superstate?
          if (!handlerKey) {
            // FIXME: what should be done here?
            console.log('(g) handleKey unexpectedly miissing');

            // no ancestors to check, stop trying to figure out what to do...
            break figureOutWhatToDo ;

          // yep, see if the handling state's superstate's superstate is an
          // ancestor of the target state
          } else {
            current = this[handlerKey] ;
            var idx2 = idx ;

            // is the handling state's superstate's superstate one of target's
            // ancestors?
            do {
              pathKey = path[idx2] ;

              // did we find the handling state's superstate's superstate?
              if (pathKey && current === this[pathKey]) {
                // don't exit the handling state's superstate's superstate

                // don't enter the handling state's superstate either...
                idx = idx2 - 1 ;

                // stop trying to figure out what to do...
                break figureOutWhatToDo ;
              } else {
                superstateKey = current.superstateKey ;
                current = superstateKey ? this[superstateKey] : null ;
                --idx2 ;
                if (!current) {
                  idx = -1 ;
                  break ;
                }
              }
            } while (idx2 >= 0)
          }

          if (!current) {
            idx = -1 ;
            break ;
          }
        }
      } while(NO)

      // debugger ;

      // .....................................................................
      // Step 3. Enter all of the ancestors between target and the least
      // common ancestor of the handling state and target discovered above...
      //

      if (idx > 0) {
        do {
          this[path[idx]](SC.EVT_ENTER) ;
        } while ((--idx) > 0)
      }

      // enter the target if needed
      if (idx === 0) this[stateKey](SC.EVT_ENTER) ;

      // .....................................................................
      // Step 4. Initialize the target state's substates if necessary.
      //

      // debugger ;

      current = this[stateKey] ;

      while (this[stateKey](SC.EVT_INIT) === SC.EVT_TRANSITION_RES) {
        target = this[stateKey] ;

        // enter the target of the transition (a substate)
        idx = 0 ;

        if (target !== current) {
          // get the superstate of the target of the transition...
          superstateKey = path[++idx] = target.superstateKey ;
          target = this[superstateKey] ;

          // debugger ;

          if (target !== current) {
            // walk the state hierarchy until we find our target state, storing
            // state keys along the way
            while (target !== current) {
              superstateKey = path[++idx] = target.superstateKey ;
              target = this[superstateKey] ;
            }
          }

          // don't enter current a second time...
          --idx ;
        }

        // now enter the target's superstates in top-down order...
        if (idx > 0) {
          do {
            this[path[idx]](SC.EVT_ENTER) ;
          } while ((--idx) > 0)
        }

        // enter the target if needed
        if (idx === 0) this[stateKey](SC.EVT_ENTER) ;

        // the loop continues to apply any default transitions as substates
        // are entered...
      }

      // if we transitioned, we definitely handled the event...
      return YES ;
    }
    else {
      // we did handle the event, but we didn't make a state transition
      return YES ;
    }
  }

});
<|endoftext|>


<commit_before>/**
 * Copyright JS Foundation and other contributors, http://js.foundation
 *
 * 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
 *
 * http://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.
 **/

var os = require("os");

var shortCutKeyMap = {
    "selectAll": ['Control', 'a', 'Control'],
    "selectToEnd": ['Control', 'Shift', 'End', 'Shift', 'Control'],
};

var shortCutKeyMapForMac = {
    "selectAll": ['Command', 'a', 'Command'],
    "selectToEnd": ['Command', 'Shift', 'ArrowDown', 'Shift', 'Command'],
};

function getShortCutKey(type) {
    if (os.type() === "Darwin") {
        return shortCutKeyMapForMac[type];
    } else {
        return shortCutKeyMap[type];
    }
}

function selectAll() {
    var key = getShortCutKey('selectAll');
    return key;
}

function selectToEnd() {
    var key = getShortCutKey('selectToEnd');
    return key;
}

module.exports = {
    selectAll: selectAll,
    selectToEnd: selectToEnd,
};
<commit_msg>Fix shortcut key handling<commit_after>/**
 * Copyright JS Foundation and other contributors, http://js.foundation
 *
 * 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
 *
 * http://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.
 **/

var os = require("os");

var shortCutKeyMap = {
    "selectAll": ['Control', 'a', 'a', 'Control'],
    "selectToEnd": ['Control', 'Shift', 'End', 'Shift', 'Control'],
};

var shortCutKeyMapForMac = {
    "selectAll": ['Command', 'a', 'a', 'Command'],
    "selectToEnd": ['Command', 'Shift', 'ArrowDown', 'Shift', 'Command'],
};

function getShortCutKey(type) {
    if (os.type() === 'Darwin') {
        return shortCutKeyMapForMac[type];
    }
    return shortCutKeyMap[type];
}

function selectAll() {
    var key = getShortCutKey('selectAll');
    return key;
}

function selectToEnd() {
    var key = getShortCutKey('selectToEnd');
    return key;
}

module.exports = {
    selectAll: selectAll,
    selectToEnd: selectToEnd,
};
<|endoftext|>


<commit_before>describe('/ (homepage)', () => {
  before(() => {
    browser.url('/');
  });

  it('display the correct page title', () => {
    expect(browser.getTitle()).equal('Assess if a prisoner can share a cell safely');
  });

  it('displays "Enter your full name" on the page', () => {
    expect(browser.getText('main')).to.contain('Enter your full name');
  });
});
<commit_msg>CSRA-000: Fix broken pipeline<commit_after>describe('/ (homepage)', () => {
  before(() => {
    browser.url('/');
  });

  it('display the correct page title', () => {
    expect(browser.getTitle()).equal('Assess if a prisoner can share a cell safely');
  });

  it('displays "Your full name" on the page', () => {
    expect(browser.getText('main')).to.contain('Your full name');
  });
});
<|endoftext|>


<commit_before>(() => {
  let metadataStore;

  function createPropsTable(props) {
    const table = document.createElement('table');
    table.innerHTML = `

          Property
          Attribute
          Description
          Type
          Default

        ${props
          .map(prop => {
            return `

                ${escapeHtml(prop.name)}
                ${prop.attribute ? `<code>${escapeHtml(prop.attribute)}</code>` : '-'}
                ${escapeHtml(prop.description)}
                ${prop.type?.text ? `<code>${escapeHtml(prop.type?.text || '')}</code>` : '-'}
                ${prop.default ? `<code>${escapeHtml(prop.default)}</code>` : '-'}

          `;
          })
          .join('')}

    `;

    return table.outerHTML;
  }

  function createEventsTable(events) {
    const table = document.createElement('table');
    table.innerHTML = `

          Event
          Description
          Event Detail

        ${events
          .map(
            event => `

          ${escapeHtml(event.name)}
          ${escapeHtml(event.description)}
          ${event.type?.text ? `<code>${escapeHtml(event.type?.text)}` : '-'}

        `
          )
          .join('')}

    `;

    return table.outerHTML;
  }

  function createMethodsTable(methods) {
    const table = document.createElement('table');
    table.innerHTML = `

          Method
          Description
          Arguments

        ${methods
          .map(
            method => `

                ${escapeHtml(method.name)}
                ${escapeHtml(method.description)}

                  ${
                    method.parameters?.length
                      ? `
                        ${escapeHtml(
                          method.parameters.map(param => `${param.name}: ${param.type.text}`).join(', ')
                        )}
                      `
                      : '-'
                  }

           `
          )
          .join('')}

    `;

    return table.outerHTML;
  }

  function createSlotsTable(slots) {
    const table = document.createElement('table');
    table.innerHTML = `

          Slot
          Description

        ${slots
          .map(
            slot => `

          ${escapeHtml(slot.name)}
          ${escapeHtml(slot.description)}

        `
          )
          .join('')}

    `;

    return table.outerHTML;
  }

  function createCustomPropertiesTable(styles) {
    const table = document.createElement('table');
    table.innerHTML = `

          Name
          Description

        ${styles
          .map(
            style => `

          ${escapeHtml(style.name)}
          ${escapeHtml(style.description)}

        `
          )
          .join('')}

    `;

    return table.outerHTML;
  }

  function createPartsTable(parts) {
    const table = document.createElement('table');
    table.innerHTML = `

          Name
          Description

        ${parts
          .map(
            part => `

          ${escapeHtml(part.name)}
          ${escapeHtml(part.description)}

        `
          )
          .join('')}

    `;

    return table.outerHTML;
  }

  function createAnimationsTable(animations) {
    const table = document.createElement('table');
    table.innerHTML = `

          Name
          Description

        ${animations
          .map(
            animation => `

          ${escapeHtml(animation.name)}
          ${escapeHtml(animation.description)}

        `
          )
          .join('')}

    `;

    return table.outerHTML;
  }

  function createDependenciesList(targetComponent, allComponents) {
    const ul = document.createElement('ul');
    const dependencies = [];

    // Recursively fetch subdependencies
    function getDependencies(tag) {
      const component = allComponents.find(c => c.tag === tag);
      if (!component || !Array.isArray(component.dependencies)) {
        return [];
      }

      component.dependencies?.map(tag => {
        if (!dependencies.includes(tag)) {
          dependencies.push(tag);
        }
        getDependencies(tag);
      });
    }

    getDependencies(targetComponent);
    dependencies.sort().map(tag => {
      const li = document.createElement('li');
      li.innerHTML = `<code><${tag}></code>`;
      ul.appendChild(li);
    });

    return ul.outerHTML;
  }

  function escapeHtml(html) {
    return (html + '')
      .replace(/&/g, '&')
      .replace(/</g, '<')
      .replace(/>/g, '>')
      .replace(/"/g, '"')
      .replace(/'/g, ''')
      .replace(/`(.*?)`/g, '$1');
  }

  function getAllComponents(metadata) {
    const allComponents = [];

    metadata.modules.map(module => {
      module.exports.find(ex => {
        if (ex.kind === 'custom-element-definition') {
          allComponents.push(getComponent(metadata, ex.name));
        }
      });
    });

    return allComponents;
  }

  function getComponent(metadata, tagName) {
    const module = metadata.modules.find(module => {
      return module.exports.find(ex => {
        return ex.kind === 'custom-element-definition' && ex.name === tagName;
      });
    });
    const component = module?.declarations.find(dec => dec.name === 'default');
    const tag = module.exports.filter(ex => ex.kind === 'custom-element-definition' && ex.name === tagName)[0]?.name;

    return Object.assign({ tag }, component);
  }

  function getMetadata() {
    return new Promise((resolve, reject) => {
      // Simple caching to prevent multiple XHR requests
      if (metadataStore) {
        return resolve(metadataStore);
      }

      fetch('/dist/custom-elements.json')
        .then(res => res.json())
        .then(data => {
          metadataStore = data;
          resolve(metadataStore);
        })
        .catch(err => console.error(err));
    });
  }

  if (!window.$docsify) {
    throw new Error('Docsify must be loaded before installing this plugin.');
  }

  window.$docsify.plugins.push((hook, vm) => {
    hook.mounted(function () {
      getMetadata().then(metadata => {
        const target = document.querySelector('.app-name');

        // Add version
        const version = document.createElement('div');
        version.classList.add('sidebar-version');
        version.textContent = metadata.package.version;
        target.appendChild(version);

        // Add repo buttons
        const buttons = document.createElement('div');
        buttons.classList.add('sidebar-buttons');
        buttons.innerHTML = `

              <sl-icon name="heart"></sl-icon> Sponsor

              <sl-icon name="github"></sl-icon> Star

              <sl-icon name="twitter"></sl-icon> Follow

          `;
        target.appendChild(buttons);
      });
    });

    hook.beforeEach(async function (content, next) {
      const metadata = await getMetadata();

      // Replace %VERSION% placeholders
      content = content.replace(/%VERSION%/g, metadata.package.version);

      // Handle [component-header] tags
      content = content.replace(/\[component-header:([a-z-]+)\]/g, (match, tag) => {
        const component = getComponent(metadata, tag);
        let result = '';

        if (!component) {
          console.error('Component not found in metadata: ' + tag);
          return next(content);
        }

        let badgeType = 'info';
        if (component.status === 'stable') badgeType = 'primary';
        if (component.status === 'experimental') badgeType = 'warning';
        if (component.status === 'planned') badgeType = 'info';
        if (component.status === 'deprecated') badgeType = 'danger';

        result += `

              ${component.className} | <${component.tag}>

              <sl-badge type="info" pill>
                Since ${component.since || '?'}
              </sl-badge>

              <sl-badge type="${badgeType}" pill style="text-transform: capitalize;">
                ${component.status}
              </sl-badge>

        `;

        return result.replace(/^ +| +$/gm, '');
      });

      // Handle [component-metadata] tags
      content = content.replace(/\[component-metadata:([a-z-]+)\]/g, (match, tag) => {
        const component = getComponent(metadata, tag);
        let result = '';

        if (!component) {
          console.error('Component not found in metadata: ' + tag);
          return next(content);
        }

        // Remove members that don't have a description
        const members = component.members?.filter(member => member.description);
        const props = members?.filter(prop => {
          // Look for a corresponding attribute
          const attribute = component.attributes?.find(attr => attr.fieldName === prop.name);
          if (attribute) {
            prop.attribute = attribute.name;
          }

          return prop.kind === 'field' && prop.privacy !== 'private';
        });

        // .map(prop => {
        //   const attribute = component.attributes?.find(attr => attr.fieldName === prop);
        //   prop.attribute = attribute;
        // });
        const methods = members?.filter(prop => prop.kind === 'method' && prop.privacy !== 'private');

        if (props?.length) {
          result += `
            ## Properties
            ${createPropsTable(props)}
          `;
        }

        if (component.events?.length) {
          result += `
            ## Events
            ${createEventsTable(component.events)}
          `;
        }

        if (methods?.length) {
          result += `
          ## Methods
          ${createMethodsTable(methods)}
          `;
        }

        if (component.slots?.length) {
          result += `
            ## Slots
            ${createSlotsTable(component.slots)}
          `;
        }

        if (component.cssProperties?.length) {
          result += `
            ## CSS Custom Properties
            ${createCustomPropertiesTable(component.cssProperties)}
          `;
        }

        if (component.cssParts?.length) {
          result += `
            ## CSS Parts
            ${createPartsTable(component.cssParts)}
          `;
        }

        if (component.animations?.length) {
          result += `
            ## Animations
            ${createAnimationsTable(component.animations)}

            Learn how to [customize animations](/getting-started/customizing#animations).
          `;
        }

        if (component.dependencies?.length) {
          result += `
            ## Dependencies

            This component has the following dependencies so, if you're [cherry picking](/getting-started/installation#cherry-picking),
            be sure to import these components in addition to <${tag}>.

            ${createDependenciesList(component.tag, getAllComponents(metadata))}
          `;
        }

        // Strip whitespace so markdown doesn't process things as code blocks
        return result.replace(/^ +| +$/gm, '');
      });

      next(content);
    });
  });
})();
<commit_msg>show default slot better<commit_after>(() => {
  let metadataStore;

  function createPropsTable(props) {
    const table = document.createElement('table');
    table.innerHTML = `

          Property
          Attribute
          Description
          Type
          Default

        ${props
          .map(prop => {
            return `

                ${escapeHtml(prop.name)}
                ${prop.attribute ? `<code>${escapeHtml(prop.attribute)}</code>` : '-'}
                ${escapeHtml(prop.description)}
                ${prop.type?.text ? `<code>${escapeHtml(prop.type?.text || '')}</code>` : '-'}
                ${prop.default ? `<code>${escapeHtml(prop.default)}</code>` : '-'}

          `;
          })
          .join('')}

    `;

    return table.outerHTML;
  }

  function createEventsTable(events) {
    const table = document.createElement('table');
    table.innerHTML = `

          Event
          Description
          Event Detail

        ${events
          .map(
            event => `

          ${escapeHtml(event.name)}
          ${escapeHtml(event.description)}
          ${event.type?.text ? `<code>${escapeHtml(event.type?.text)}` : '-'}

        `
          )
          .join('')}

    `;

    return table.outerHTML;
  }

  function createMethodsTable(methods) {
    const table = document.createElement('table');
    table.innerHTML = `

          Method
          Description
          Arguments

        ${methods
          .map(
            method => `

                ${escapeHtml(method.name)}
                ${escapeHtml(method.description)}

                  ${
                    method.parameters?.length
                      ? `
                        ${escapeHtml(
                          method.parameters.map(param => `${param.name}: ${param.type.text}`).join(', ')
                        )}
                      `
                      : '-'
                  }

           `
          )
          .join('')}

    `;

    return table.outerHTML;
  }

  function createSlotsTable(slots) {
    const table = document.createElement('table');
    table.innerHTML = `

          Slot
          Description

        ${slots
          .map(
            slot => `

                ${slot.name ? `<code>${escapeHtml(slot.name)}</code>` : '(default)'}
                ${escapeHtml(slot.description)}

            `
          )
          .join('')}

    `;

    return table.outerHTML;
  }

  function createCustomPropertiesTable(styles) {
    const table = document.createElement('table');
    table.innerHTML = `

          Name
          Description

        ${styles
          .map(
            style => `

          ${escapeHtml(style.name)}
          ${escapeHtml(style.description)}

        `
          )
          .join('')}

    `;

    return table.outerHTML;
  }

  function createPartsTable(parts) {
    const table = document.createElement('table');
    table.innerHTML = `

          Name
          Description

        ${parts
          .map(
            part => `

          ${escapeHtml(part.name)}
          ${escapeHtml(part.description)}

        `
          )
          .join('')}

    `;

    return table.outerHTML;
  }

  function createAnimationsTable(animations) {
    const table = document.createElement('table');
    table.innerHTML = `

          Name
          Description

        ${animations
          .map(
            animation => `

          ${escapeHtml(animation.name)}
          ${escapeHtml(animation.description)}

        `
          )
          .join('')}

    `;

    return table.outerHTML;
  }

  function createDependenciesList(targetComponent, allComponents) {
    const ul = document.createElement('ul');
    const dependencies = [];

    // Recursively fetch subdependencies
    function getDependencies(tag) {
      const component = allComponents.find(c => c.tag === tag);
      if (!component || !Array.isArray(component.dependencies)) {
        return [];
      }

      component.dependencies?.map(tag => {
        if (!dependencies.includes(tag)) {
          dependencies.push(tag);
        }
        getDependencies(tag);
      });
    }

    getDependencies(targetComponent);
    dependencies.sort().map(tag => {
      const li = document.createElement('li');
      li.innerHTML = `<code><${tag}></code>`;
      ul.appendChild(li);
    });

    return ul.outerHTML;
  }

  function escapeHtml(html) {
    return (html + '')
      .replace(/&/g, '&')
      .replace(/</g, '<')
      .replace(/>/g, '>')
      .replace(/"/g, '"')
      .replace(/'/g, ''')
      .replace(/`(.*?)`/g, '$1');
  }

  function getAllComponents(metadata) {
    const allComponents = [];

    metadata.modules.map(module => {
      module.exports.find(ex => {
        if (ex.kind === 'custom-element-definition') {
          allComponents.push(getComponent(metadata, ex.name));
        }
      });
    });

    return allComponents;
  }

  function getComponent(metadata, tagName) {
    const module = metadata.modules.find(module => {
      return module.exports.find(ex => {
        return ex.kind === 'custom-element-definition' && ex.name === tagName;
      });
    });
    const component = module?.declarations.find(dec => dec.name === 'default');
    const tag = module.exports.filter(ex => ex.kind === 'custom-element-definition' && ex.name === tagName)[0]?.name;

    return Object.assign({ tag }, component);
  }

  function getMetadata() {
    return new Promise((resolve, reject) => {
      // Simple caching to prevent multiple XHR requests
      if (metadataStore) {
        return resolve(metadataStore);
      }

      fetch('/dist/custom-elements.json')
        .then(res => res.json())
        .then(data => {
          metadataStore = data;
          resolve(metadataStore);
        })
        .catch(err => console.error(err));
    });
  }

  if (!window.$docsify) {
    throw new Error('Docsify must be loaded before installing this plugin.');
  }

  window.$docsify.plugins.push((hook, vm) => {
    hook.mounted(function () {
      getMetadata().then(metadata => {
        const target = document.querySelector('.app-name');

        // Add version
        const version = document.createElement('div');
        version.classList.add('sidebar-version');
        version.textContent = metadata.package.version;
        target.appendChild(version);

        // Add repo buttons
        const buttons = document.createElement('div');
        buttons.classList.add('sidebar-buttons');
        buttons.innerHTML = `

              <sl-icon name="heart"></sl-icon> Sponsor

              <sl-icon name="github"></sl-icon> Star

              <sl-icon name="twitter"></sl-icon> Follow

          `;
        target.appendChild(buttons);
      });
    });

    hook.beforeEach(async function (content, next) {
      const metadata = await getMetadata();

      // Replace %VERSION% placeholders
      content = content.replace(/%VERSION%/g, metadata.package.version);

      // Handle [component-header] tags
      content = content.replace(/\[component-header:([a-z-]+)\]/g, (match, tag) => {
        const component = getComponent(metadata, tag);
        let result = '';

        if (!component) {
          console.error('Component not found in metadata: ' + tag);
          return next(content);
        }

        let badgeType = 'info';
        if (component.status === 'stable') badgeType = 'primary';
        if (component.status === 'experimental') badgeType = 'warning';
        if (component.status === 'planned') badgeType = 'info';
        if (component.status === 'deprecated') badgeType = 'danger';

        result += `

              ${component.className} | <${component.tag}>

              <sl-badge type="info" pill>
                Since ${component.since || '?'}
              </sl-badge>

              <sl-badge type="${badgeType}" pill style="text-transform: capitalize;">
                ${component.status}
              </sl-badge>

        `;

        return result.replace(/^ +| +$/gm, '');
      });

      // Handle [component-metadata] tags
      content = content.replace(/\[component-metadata:([a-z-]+)\]/g, (match, tag) => {
        const component = getComponent(metadata, tag);
        let result = '';

        if (!component) {
          console.error('Component not found in metadata: ' + tag);
          return next(content);
        }

        // Remove members that don't have a description
        const members = component.members?.filter(member => member.description);
        const props = members?.filter(prop => {
          // Look for a corresponding attribute
          const attribute = component.attributes?.find(attr => attr.fieldName === prop.name);
          if (attribute) {
            prop.attribute = attribute.name;
          }

          return prop.kind === 'field' && prop.privacy !== 'private';
        });

        // .map(prop => {
        //   const attribute = component.attributes?.find(attr => attr.fieldName === prop);
        //   prop.attribute = attribute;
        // });
        const methods = members?.filter(prop => prop.kind === 'method' && prop.privacy !== 'private');

        if (props?.length) {
          result += `
            ## Properties
            ${createPropsTable(props)}
          `;
        }

        if (component.events?.length) {
          result += `
            ## Events
            ${createEventsTable(component.events)}
          `;
        }

        if (methods?.length) {
          result += `
          ## Methods
          ${createMethodsTable(methods)}
          `;
        }

        if (component.slots?.length) {
          result += `
            ## Slots
            ${createSlotsTable(component.slots)}
          `;
        }

        if (component.cssProperties?.length) {
          result += `
            ## CSS Custom Properties
            ${createCustomPropertiesTable(component.cssProperties)}
          `;
        }

        if (component.cssParts?.length) {
          result += `
            ## CSS Parts
            ${createPartsTable(component.cssParts)}
          `;
        }

        if (component.animations?.length) {
          result += `
            ## Animations
            ${createAnimationsTable(component.animations)}

            Learn how to [customize animations](/getting-started/customizing#animations).
          `;
        }

        if (component.dependencies?.length) {
          result += `
            ## Dependencies

            This component has the following dependencies so, if you're [cherry picking](/getting-started/installation#cherry-picking),
            be sure to import these components in addition to <${tag}>.

            ${createDependenciesList(component.tag, getAllComponents(metadata))}
          `;
        }

        // Strip whitespace so markdown doesn't process things as code blocks
        return result.replace(/^ +| +$/gm, '');
      });

      next(content);
    });
  });
})();
<|endoftext|>


<commit_before>'use strict';

module.exports = function (t, a) {
	var el, control;

	if (typeof document === 'undefined') return;

	document.body.innerHTML = '';
	el = document.body.appendChild(document.createElement('p'));
	control = el.appendChild(document.createElement('select'));

	a.deep(t.call(document), [control], "Result");
	a(control.getAttribute('disabled'), 'true', "Attribute");
	a(control.disabled, true, "Disabled");
};
<commit_msg>Fix test<commit_after>'use strict';

module.exports = function (t, a) {
	var el, control;

	if (typeof document === 'undefined') return;

	document.body.innerHTML = '';
	el = document.body.appendChild(document.createElement('p'));
	control = el.appendChild(document.createElement('select'));

	a.deep(t.call(document), [control], "Result");
	a(control.getAttribute('disabled'), 'disabled', "Attribute");
	a(control.disabled, true, "Disabled");
};
<|endoftext|>


<commit_before>/* eslint-env jest */

const { createTestServer, setServerResponse, url } = require('../../tools');
const Parser = require('../../../lib/Parser');
const ChromeEnvironment = require('goose-chrome-environment');

jest.mock('../../../lib/Storage');

describe('Actions', () => {
  let testServer;
  let parser;

  beforeAll(async () => {
    testServer = await createTestServer();
    parser = new Parser({
      environment: new ChromeEnvironment({}),
      mode: 'multiple',
    });
  });

  afterAll(async () => {
    await testServer.close();
    await parser.finish();
  });

  describe('ActionBlur', () => {
    test('perform', async () => {
      setServerResponse({
        html: `<input type="text" value="0" />`,
        fn: () => {
          document.querySelector('[type="text"]').addEventListener('blur', ({ target }) => {
            const value = '1';
            target.value = value;
            target.setAttribute('value', value);
          });
        },
      });
      const result = await parser.parse({
        url,
        actions: [
          {
            type: 'focus',
            scope: '[type="text"]',
          },
          {
            type: 'blur',
            scope: '[type="text"]',
          },
          {
            type: 'pause',
            timeout: 50,
          },
        ],
        rules: {
          scope: '[type="text"]',
          attr: 'value',
        },
      });

      expect(result).toEqual('1');
    });
  });

  describe('ActionClick', () => {
    test('perform', async () => {
      setServerResponse({
        html: `<input type="text" value="0" />`,
        fn: () => {
          document.querySelector('[type="text"]').addEventListener('click', ({ target }) => {
            const value = '1';
            target.value = value;
            target.setAttribute('value', value);
          });
        },
      });
      const result = await parser.parse({
        url,
        actions: [
          {
            type: 'click',
            scope: '[type="text"]',
          },
          {
            type: 'pause',
            timeout: 50,
          },
        ],
        rules: {
          scope: '[type="text"]',
          attr: 'value',
        },
      });

      expect(result).toEqual('1');
    });
  });

  describe('ActionMouseClick', () => {
    test('perform', async () => {
      setServerResponse({
        html: `<input type="text" value="0" />`,
        fn: () => {
          document.querySelector('[type="text"]').addEventListener('click', ({ target }) => {
            const value = '1';
            target.value = value;
            target.setAttribute('value', value);
          });
        },
      });
      const result = await parser.parse({
        url,
        actions: [
          {
            type: 'mouseClick',
            scope: '[type="text"]',
          },
          {
            type: 'pause',
            timeout: 50,
          },
        ],
        rules: {
          scope: '[type="text"]',
          attr: 'value',
        },
      });

      expect(result).toEqual('1');
    });
  });

  describe('ActionMouseDown', () => {
    test('perform', async () => {
      setServerResponse({
        html: `<input type="text" value="0" />`,
        fn: () => {
          document.querySelector('[type="text"]').addEventListener('mousedown', ({ target }) => {
            const value = '1';
            target.value = value;
            target.setAttribute('value', value);
          });
        },
      });
      const result = await parser.parse({
        url,
        actions: [
          {
            type: 'mouseDown',
            scope: '[type="text"]',
          },
          {
            type: 'pause',
            timeout: 50,
          },
        ],
        rules: {
          scope: '[type="text"]',
          attr: 'value',
        },
      });

      expect(result).toEqual('1');
    });
  });

  describe('ActionMouseUp', () => {
    test('perform', async () => {
      setServerResponse({
        html: `<input type="text" value="0" />`,
        fn: () => {
          document.querySelector('[type="text"]').addEventListener('mouseup', ({ target }) => {
            const value = '1';
            target.value = value;
            target.setAttribute('value', value);
          });
        },
      });
      const result = await parser.parse({
        url,
        actions: [
          {
            type: 'mouseUp',
            scope: '[type="text"]',
          },
          {
            type: 'pause',
            timeout: 50,
          },
        ],
        rules: {
          scope: '[type="text"]',
          attr: 'value',
        },
      });

      expect(result).toEqual('1');
    });
  });

  describe('ActionFocus', () => {
    test('perform', async () => {
      setServerResponse({
        html: `<input type="text" value="0" />`,
        fn: () => {
          document.querySelector('[type="text"]').addEventListener('focus', ({ target }) => {
            const value = '1';
            target.value = value;
            target.setAttribute('value', value);
          });
        },
      });
      const result = await parser.parse({
        url,
        actions: [
          {
            type: 'focus',
            scope: '[type="text"]',
          },
          {
            type: 'pause',
            timeout: 50,
          },
        ],
        rules: {
          scope: '[type="text"]',
          attr: 'value',
        },
      });

      expect(result).toEqual('1');
    });
  });

  describe('ActionClickWithWaitForPage', () => {
    test('perform', async () => {
      setServerResponse([
        {
          route: '/',
          html: `<a href="/test">test</a>`
        },
        {
          route: '/test',
          html: `<a href="/">1</a>`
        }
      ]);
      const result = await parser.parse({
        url,
        actions: [
          {
            type: 'click',
            scope: 'a',
            waitFor: 'page',
          },
        ],
        rules: {
          scope: 'a',
        },
      });

      expect(result).toEqual('1');
    });
  });

  describe('ActionClickWithWaitForPattern', () => {
    test('perform', async () => {
      setServerResponse({
          html: `<a href="#">test</a>`,
          fn: () => {
            // the phone number appears after some time in the link
            document.querySelector('a').addEventListener('click', ({ target }) => {
              setTimeout(function () {
                target.setAttribute('href', 'tel:+123456890102');
              }, 500);
            });
          }
        });
      const result = await parser.parse({
        url,
        actions: [
          {
            type: 'click',
            scope: 'a',
            waitFor: {
              type: 'pattern',
              pattern: '^tel:',
              scope: 'a',
              attr: 'href',
            },
          },
        ],
        rules: {
          scope: 'a',
          attr: 'href',
        },
      });

      expect(result).toEqual('tel:+123456890102');
    });
  });

  describe('ActionClickWithWaitForVisible', () => {
    test('perform', async () => {
      setServerResponse({
        html: `<a href="#">test</a>`,
        fn: () => {
          // the phone number appears after some time in the link
          document.querySelector('a').addEventListener('click', ({ target }) => {
            document.body.insertAdjacentHTML('beforeend', '
12345
');
          });
        }
      });
      const result = await parser.parse({
        url,
        actions: [
          {
            type: 'click',
            scope: 'a',
            waitFor: {
              type: 'visible',
              visibility: true,
            },
          },
        ],
        rules: {
          scope: 'div',
        },
      });

      expect(result).toEqual('12345');
    });
  });

  describe('ActionClickWithWaitForQuery', () => {
    test('perform', async () => {
      setServerResponse({
        html: `<a href="#">test</a>`,
        fn: () => {
          // the phone number appears after some time in the link
          document.querySelector('a').addEventListener('click', ({ target }) => {
            document.body.insertAdjacentHTML('beforeend', '');
          });
        }
      });
      const result = await parser.parse({
        url,
        actions: [
          {
            type: 'click',
            scope: 'a',
            waitFor: {
              type: 'query',
              uri: '12345',
            },
          },
        ],
        rules: {
          scope: 'img',
          attr: 'src'
        },
      });

      expect(result).toEqual('12345');
    });
  });
});
<commit_msg>- ActionWaitForVisible - extended coverage<commit_after>/* eslint-env jest */

const { createTestServer, setServerResponse, url } = require('../../tools');
const Parser = require('../../../lib/Parser');
const ChromeEnvironment = require('goose-chrome-environment');

jest.setTimeout(20000);
describe('Actions', () => {
  let testServer;
  let parser;

  beforeAll(async () => {
    testServer = await createTestServer();
    parser = new Parser({
      environment: new ChromeEnvironment({}),
      mode: 'multiple',
    });
  });

  afterAll(async () => {
    await testServer.close();
    await parser.finish();
  });

  describe('ActionBlur', () => {
    test('perform', async () => {
      setServerResponse({
        html: `<input type="text" value="0" />`,
        fn: () => {
          document.querySelector('[type="text"]').addEventListener('blur', ({ target }) => {
            const value = '1';
            target.value = value;
            target.setAttribute('value', value);
          });
        },
      });
      const result = await parser.parse({
        url,
        actions: [
          {
            type: 'focus',
            scope: '[type="text"]',
          },
          {
            type: 'blur',
            scope: '[type="text"]',
          },
          {
            type: 'pause',
            timeout: 50,
          },
        ],
        rules: {
          scope: '[type="text"]',
          attr: 'value',
        },
      });

      expect(result).toEqual('1');
    });
  });

  describe('ActionClick', () => {
    test('perform', async () => {
      setServerResponse({
        html: `<input type="text" value="0" />`,
        fn: () => {
          document.querySelector('[type="text"]').addEventListener('click', ({ target }) => {
            const value = '1';
            target.value = value;
            target.setAttribute('value', value);
          });
        },
      });
      const result = await parser.parse({
        url,
        actions: [
          {
            type: 'click',
            scope: '[type="text"]',
          },
          {
            type: 'pause',
            timeout: 50,
          },
        ],
        rules: {
          scope: '[type="text"]',
          attr: 'value',
        },
      });

      expect(result).toEqual('1');
    });
  });

  describe('ActionMouseClick', () => {
    test('perform', async () => {
      setServerResponse({
        html: `<input type="text" value="0" />`,
        fn: () => {
          document.querySelector('[type="text"]').addEventListener('click', ({ target }) => {
            const value = '1';
            target.value = value;
            target.setAttribute('value', value);
          });
        },
      });
      const result = await parser.parse({
        url,
        actions: [
          {
            type: 'mouseClick',
            scope: '[type="text"]',
          },
          {
            type: 'pause',
            timeout: 50,
          },
        ],
        rules: {
          scope: '[type="text"]',
          attr: 'value',
        },
      });

      expect(result).toEqual('1');
    });
  });

  describe('ActionMouseDown', () => {
    test('perform', async () => {
      setServerResponse({
        html: `<input type="text" value="0" />`,
        fn: () => {
          document.querySelector('[type="text"]').addEventListener('mousedown', ({ target }) => {
            const value = '1';
            target.value = value;
            target.setAttribute('value', value);
          });
        },
      });
      const result = await parser.parse({
        url,
        actions: [
          {
            type: 'mouseDown',
            scope: '[type="text"]',
          },
          {
            type: 'pause',
            timeout: 50,
          },
        ],
        rules: {
          scope: '[type="text"]',
          attr: 'value',
        },
      });

      expect(result).toEqual('1');
    });
  });

  describe('ActionMouseUp', () => {
    test('perform', async () => {
      setServerResponse({
        html: `<input type="text" value="0" />`,
        fn: () => {
          document.querySelector('[type="text"]').addEventListener('mouseup', ({ target }) => {
            const value = '1';
            target.value = value;
            target.setAttribute('value', value);
          });
        },
      });
      const result = await parser.parse({
        url,
        actions: [
          {
            type: 'mouseUp',
            scope: '[type="text"]',
          },
          {
            type: 'pause',
            timeout: 50,
          },
        ],
        rules: {
          scope: '[type="text"]',
          attr: 'value',
        },
      });

      expect(result).toEqual('1');
    });
  });

  describe('ActionFocus', () => {
    test('perform', async () => {
      setServerResponse({
        html: `<input type="text" value="0" />`,
        fn: () => {
          document.querySelector('[type="text"]').addEventListener('focus', ({ target }) => {
            const value = '1';
            target.value = value;
            target.setAttribute('value', value);
          });
        },
      });
      const result = await parser.parse({
        url,
        actions: [
          {
            type: 'focus',
            scope: '[type="text"]',
          },
          {
            type: 'pause',
            timeout: 50,
          },
        ],
        rules: {
          scope: '[type="text"]',
          attr: 'value',
        },
      });

      expect(result).toEqual('1');
    });
  });

  describe('ActionClickWithWaitForPage', () => {
    test('perform', async () => {
      setServerResponse([
        {
          route: '/',
          html: `<a href="/test">test</a>`
        },
        {
          route: '/test',
          html: `<a href="/">1</a>`
        }
      ]);
      const result = await parser.parse({
        url,
        actions: [
          {
            type: 'click',
            scope: 'a',
            waitFor: 'page',
          },
        ],
        rules: {
          scope: 'a',
        },
      });

      expect(result).toEqual('1');
    });
  });

  describe('ActionClickWithWaitForPattern', () => {
    test('perform', async () => {
      setServerResponse({
          html: `<a href="#">test</a>`,
          fn: () => {
            // the phone number appears after some time in the link
            document.querySelector('a').addEventListener('click', ({ target }) => {
              setTimeout(function () {
                target.setAttribute('href', 'tel:+123456890102');
              }, 500);
            });
          }
        });
      const result = await parser.parse({
        url,
        actions: [
          {
            type: 'click',
            scope: 'a',
            waitFor: {
              type: 'pattern',
              pattern: '^tel:',
              scope: 'a',
              attr: 'href',
            },
          },
        ],
        rules: {
          scope: 'a',
          attr: 'href',
        },
      });

      expect(result).toEqual('tel:+123456890102');
    });
  });

  describe('ActionClickWithWaitForVisible', () => {
    test('wait visible', async () => {
      setServerResponse({
        html: `<a href="#">test</a>`,
        fn: () => {
          document.querySelector('a').addEventListener('click', ({ target }) => {
            setTimeout(function () {
              document.body.insertAdjacentHTML('beforeend', '
12345
');
            }, 500);
          });
        }
      });
      const result = await parser.parse({
        url,
        actions: [
          {
            type: 'click',
            scope: 'a',
            waitFor: {
              type: 'visible',
              scope: 'div',
            },
          },
        ],
        rules: {
          scope: 'div',
        },
      });

      expect(result).toEqual('12345');
    });

    test('wait invisible', async () => {
      setServerResponse({
        html: `<a href="#">test</a><div>12345</div>`,
        fn: () => {
          document.querySelector('a').addEventListener('click', ({ target }) => {
            setTimeout(function () {
              document.querySelector('div').remove();
            }, 500);
          });
        }
      });
      const result = await parser.parse({
        url,
        actions: [
          {
            type: 'click',
            scope: 'a',
            waitFor: {
              type: 'visible',
              scope: 'div',
              visibility: false,
            },
          },
        ],
        rules: {
          scope: 'div',
        },
      });

      expect(result).toEqual('');
    });
  });

  describe('ActionClickWithWaitForQuery', () => {
    test('perform', async () => {
      setServerResponse({
        html: `<a href="#">test</a>`,
        fn: () => {
          // the phone number appears after some time in the link
          document.querySelector('a').addEventListener('click', ({ target }) => {
            document.body.insertAdjacentHTML('beforeend', '');
          });
        }
      });
      const result = await parser.parse({
        url,
        actions: [
          {
            type: 'click',
            scope: 'a',
            waitFor: {
              type: 'query',
              uri: '12345',
            },
          },
        ],
        rules: {
          scope: 'img',
          attr: 'src'
        },
      });

      expect(result).toEqual('12345');
    });
  });
});
<|endoftext|>


<commit_before>(function () {
  var tests = [
    'SimplePenDrawing1.json',
    'ComplexDrawing1.json',
    'ComplexDrawing2.json'
  ];

  var baseUrl = casper.cli.get('baseUrl')+"?debug";
  var resultSelector = '#drawing-test-result';

  casper.start();

  var runTest = function (index) {
    var test = 'integration/casperjs/drawing-records/' + tests[index];

    casper.open(baseUrl + "&test-run=" + test);

    casper.then(function () {
      this.echo('Running test : ' + test);
      this.wait(casper.cli.get('delay'));
    });

    casper.then(function () {
      this.echo('Waiting for test result : ' + resultSelector);
      this.waitForSelector(resultSelector, function() {
        var result = this.getHTML(resultSelector);
        this.echo('Test finished : ' + result);
        this.test.assertEquals(result, 'OK');
      }, 60*1000);
    })
    .run(function () {
      if (tests[index+1]) {
        runTest(index+1);
      } else {
        this.test.done();
      }
    });
  };

  runTest(0);

})();<commit_msg>add drawing tests to casper<commit_after>(function () {
  var tests = [
    'SimplePenDrawing1.json',
    'ComplexDrawing1.json',
    'ComplexDrawing2.json'
  ];

  var baseUrl = casper.cli.get('baseUrl')+"?debug";
  var resultSelector = '#drawing-test-result';

  casper.start();

  var runTest = function (index) {
    var test = 'integration/casperjs/drawing-records/' + tests[index];

    casper.open(baseUrl + "&test-run=" + test);

    casper.then(function () {
      this.echo('Running test : ' + test);
      this.wait(casper.cli.get('delay'));
    });

    casper.then(function () {
      this.echo('Waiting for test result : ' + resultSelector);
      this.waitForSelector(resultSelector, function () {
        // then
        var result = this.getHTML(resultSelector);
        this.echo('Test finished : ' + result);
        this.test.assertEquals(result, 'OK');
      }, function () {
        // onTimeout
        this.test.fail('Test timed out');
      }, 60*1000);
    })
    .run(function () {
      if (tests[index+1]) {
        runTest(index+1);
      } else {
        this.test.done();
      }
    });
  };

  runTest(0);

})();<|endoftext|>


<commit_before>/*jshint node: true */
'use strict';

/*
* Name :  BarChartFlow.js
* Module : UnitTest
* Location : /frontend/test/unit/Model
*
* History :
* Version       Date        Programmer                  Description
* =================================================================================================
* 0.2.0         2015-05-18  Maria Giovanna Chinellato	Modified general structure, some fixes
*
* 0.1.1         2015-05-17  Maria Giovanna Chinellato   Fix code
*
* 0.1.0			2015-05-15	Maria Giovanna Chinellato	Add test of Model::BarChartFlow.js
*
* 0.0.1			2015-05-15	Maria Giovanna Chinellato	Initial code
* =================================================================================================
*
*/

describe('BarChartFlow', function(){

	var BarChartFlow;
	var Flow;

	beforeEach(angular.mock.module('app'));

	beforeEach(inject(function(_BarChartFlow_, $injector){
		BarChartFlow = _BarChartFlow_;
		Flow = $injector.get('Flow');
	}));

	describe('Default constructor', function(){

		beforeEach(function(){
			BarChartFlow = new BarChartFlow();
		});

		afterEach(function(){
			BarChartFlow = null;
		});

		it('instance defined', function(){
			expect(BarChartFlow).toBeDefined();
		});

		it('constructor create the flow with the correct flowColor', function(){
			expect(BarChartFlow.getFlowColor()).toEqual('#000');
		});
		it('constructor create the flow with the correct legend on point', function(){
			expect(BarChartFlow.getLegendOnPoint()).toEqual(null);
		});
	});

	describe('Constructor', function(){

		var json = {
			'dataFormat' : 'int',
			'name' : 'flusso1',
			'flowColor' : '#F2F',
			'legendOnPoin' : 'flusso1',
		};
		var f = {
			'dataFormat' : 'int',
			'name' : 'flusso1'
		};

		beforeEach(function(){
			BarChartFlow = new BarChartFlow(json);
		});

		afterEach(function(){
			BarChartFlow = null;
		});

		it('instance defined', function(){
			expect(BarChartFlow).toBeDefined();
		});

		it('constructor use the flow constructor in the correct way', function(){
			expect(Flow.prototype.apply).toHaveBeenCalledWith(this, f);
		});
		it('constructor create the flow with the correct flowColor', function(){
			expect(BarChartFlow.getFlowColor()).toEqual('#F2F');
		});
		it('constructor create the flow with the correct legend on point', function(){
			expect(BarChartFlow.getLegendOnPoint()).toEqual('flusso1');
		});
	});

	/*describe('split', function(){
		var res;
		var json = {};
		var json1 = json;
		json = json1;
		json = {
			'dataFormat' : 'int',
			'name' : 'flusso1',
			'flowColor' : '#FFF',
			'legendOnPoin' : 'flusso'
		};
		var f = {
			'dataFormat' : 'int',
			'name' : 'flusso1'
		};
		var b = {
			'flowColor' : '#FFF',
			'legendOnPoin' : 'flusso'
		};

		beforeEach(function(){
			res = BarChartFlow.prototype.test('split(json)');
		});

		it('json splitted in the correct way', function(){
			expect(res.flowJson).toEqual(f);
			expect(res.barFlowJson).toEqual(b);
		});

	});*/

	describe('updateParameters', function(){

		var json = {
			'dataFormat' : 'String',
			'name' : 'flusso2',
			'flowColor' : '#F1F',
			'legendOnPoin' : 'flusso2'
		};
		var f = {
			'dataFormat' : 'String',
			'name' : 'flusso2'
		};

		beforeEach(function(){
			BarChartFlow = new BarChartFlow();
			BarChartFlow = BarChartFlow.updateParameters(json);
		});

		afterEach(function(){
			BarChartFlow = null;
		});

		it('flow updated with the correct parameters', function(){
			expect(Flow.prototype.updateParameters.apply).toHaveBeenCalledWith(f); // in teoria dovrebbe essere così
		});
		it('cflow updated with the correct flowColor', function(){
			expect(BarChartFlow.getFlowColor()).toEqual('#F1F');
		});
		it('flow updated with the correct legend on point', function(){
			expect(BarChartFlow.getLegendOnPoint()).toEqual('flusso2');
		});

	});

	describe('inizializeData', function(){
		var data = {
			records: [
				{},
				{},
				{}
			]
		};

		beforeEach(function(){
			BarChartFlow = new BarChartFlow();
			BarChartFlow = BarChartFlow.inizializeData(data);
		});

		afterEach(function(){
			BarChartFlow = null;
		});

		it('data inizialized in the correct way', function(){
			expect(BarChartFlow.getData().length).toEqual(3);
		});

	});

	describe('inPlaceUpdate', function(){
		var data = {
			records: [
				{
					'NorrisRecordID' : 'record1',
					'value' : [ 1, 1]
				}
			]
		};

		var update = {
			'NorrisRecordID' : 'record1',
			'value' : [ 1, 2]
		};

		beforeEach(function(){
			BarChartFlow = new BarChartFlow();
			BarChartFlow = BarChartFlow.inizializeData(data);
			BarChartFlow = BarChartFlow.inPlaceUpdate(update);
		});

		afterEach(function(){
			BarChartFlow = null;
		});

		it('data updated in the correct way', function(){
			expect(BarChartFlow.getData()[0].value[0]).toEqual(1);
			expect(BarChartFlow.getData()[0].value[1]).toEqual(2);
		});

	});

});<commit_msg>some fixes<commit_after>/*jshint node: true */
'use strict';

/*
* Name :  BarChartFlow.js
* Module : UnitTest
* Location : /frontend/test/unit/Model
*
* History :
* Version       Date        Programmer                  Description
* =================================================================================================
* 0.2.0         2015-05-18  Maria Giovanna Chinellato	Modified general structure, some fixes
*
* 0.1.1         2015-05-17  Maria Giovanna Chinellato   Fix code
*
* 0.1.0			2015-05-15	Maria Giovanna Chinellato	Add test of Model::BarChartFlow.js
*
* 0.0.1			2015-05-15	Maria Giovanna Chinellato	Initial code
* =================================================================================================
*
*/

describe('BarChartFlow', function(){

	var BarChartFlow;
	var Flow;

	beforeEach(angular.mock.module('app'));

	beforeEach(inject(function(_BarChartFlow_, $injector){
		BarChartFlow = _BarChartFlow_;
		Flow = $injector.get('Flow');
	}));

	describe('Default constructor', function(){

		beforeEach(function(){
			BarChartFlow = new BarChartFlow();
		});

		afterEach(function(){
			BarChartFlow = null;
		});

		it('instance defined', function(){
			expect(BarChartFlow).toBeDefined();
		});

		it('constructor create the flow with the correct flowColor', function(){
			expect(BarChartFlow.getFlowColor()).toEqual('#000');
		});
		it('constructor create the flow with the correct legend on point', function(){
			expect(BarChartFlow.getLegendOnPoint()).toEqual(null);
		});
	});

	describe('Constructor', function(){

		var json = {
			'dataFormat' : 'int',
			'name' : 'flusso1',
			'flowColor' : '#F2F',
			'legendOnPoin' : 'flusso1',
		};
		//var f = {
		//	'dataFormat' : 'int',
		//	'name' : 'flusso1'
		//};

		beforeEach(function(){
			BarChartFlow = new BarChartFlow(json);
		});

		afterEach(function(){
			BarChartFlow = null;
		});

		it('instance defined', function(){
			expect(BarChartFlow).toBeDefined();
		});

		//it('constructor use the flow constructor in the correct way', function(){
		//	expect(Flow.prototype.apply).toHaveBeenCalledWith(this, f);
		//});
		it('constructor create the flow with the correct flowColor', function(){
			expect(BarChartFlow.getFlowColor()).toEqual('#F2F');
		});
		it('constructor create the flow with the correct legend on point', function(){
			expect(BarChartFlow.getLegendOnPoint()).toEqual('flusso1');
		});
	});

	/*describe('split', function(){
		var res;
		var json = {};
		var json1 = json;
		json = json1;
		json = {
			'dataFormat' : 'int',
			'name' : 'flusso1',
			'flowColor' : '#FFF',
			'legendOnPoin' : 'flusso'
		};
		var f = {
			'dataFormat' : 'int',
			'name' : 'flusso1'
		};
		var b = {
			'flowColor' : '#FFF',
			'legendOnPoin' : 'flusso'
		};

		beforeEach(function(){
			res = BarChartFlow.prototype.test('split(json)');
		});

		it('json splitted in the correct way', function(){
			expect(res.flowJson).toEqual(f);
			expect(res.barFlowJson).toEqual(b);
		});

	});*/

	describe('updateParameters', function(){

		var json = {
			'dataFormat' : 'String',
			'name' : 'flusso2',
			'flowColor' : '#F1F',
			'legendOnPoin' : 'flusso2'
		};
		//var f = {
		//	'dataFormat' : 'String',
		//	'name' : 'flusso2'
		//};

		beforeEach(function(){
			BarChartFlow = new BarChartFlow();
			BarChartFlow = BarChartFlow.updateParameters(json);
		});

		afterEach(function(){
			BarChartFlow = null;
		});

		//it('flow updated with the correct parameters', function(){
		//	expect(Flow.prototype.updateParameters.apply).toHaveBeenCalledWith(f); // in teoria dovrebbe essere così
		//});
		it('cflow updated with the correct flowColor', function(){
			expect(BarChartFlow.getFlowColor()).toEqual('#F1F');
		});
		it('flow updated with the correct legend on point', function(){
			expect(BarChartFlow.getLegendOnPoint()).toEqual('flusso2');
		});

	});

	describe('inizializeData', function(){
		var data = {
			records: [
				{},
				{},
				{}
			]
		};

		beforeEach(function(){
			BarChartFlow = new BarChartFlow();
			BarChartFlow = BarChartFlow.inizializeData(data);
		});

		afterEach(function(){
			BarChartFlow = null;
		});

		it('data inizialized in the correct way', function(){
			expect(BarChartFlow.getData().length).toEqual(3);
		});

	});

	describe('inPlaceUpdate', function(){
		var data = {
			records: [
				{
					'NorrisRecordID' : 'record1',
					'value' : [ 1, 1]
				}
			]
		};

		var update = {
			'NorrisRecordID' : 'record1',
			'value' : [ 1, 2]
		};

		beforeEach(function(){
			BarChartFlow = new BarChartFlow();
			BarChartFlow = BarChartFlow.inizializeData(data);
			BarChartFlow = BarChartFlow.inPlaceUpdate(update);
		});

		afterEach(function(){
			BarChartFlow = null;
		});

		it('data updated in the correct way', function(){
			expect(BarChartFlow.getData()[0].value[0]).toEqual(1);
			expect(BarChartFlow.getData()[0].value[1]).toEqual(2);
		});

	});

});<|endoftext|>


<commit_before>var Plotly = require('@lib/index');
var Lib = require('@src/lib');
var constants = require('@src/components/legend/constants');

var createGraph = require('../assets/create_graph_div');
var destroyGraph = require('../assets/destroy_graph_div');
var getBBox = require('../assets/get_bbox');
var mock = require('../../image/mocks/legend_scroll.json');

describe('The legend', function() {
    'use strict';

    var gd, legend, bg;

    function countLegendGroups(gd) {
        return gd._fullLayout._toppaper.selectAll('g.legend').size();
    }

    function countLegendClipPaths(gd) {
        var uid = gd._fullLayout._uid;

        return gd._fullLayout._topdefs.selectAll('#legend' + uid).size();
    }

    function getPlotHeight(gd) {
        return gd._fullLayout.height - gd._fullLayout.margin.t - gd._fullLayout.margin.b;
    }

    function getLegendHeight() {
        return gd._fullLayout.legend.borderwidth + getBBox(bg).height;
    }

    describe('when plotted with many traces', function() {
        beforeEach(function(done) {
            gd = createGraph();

            var mockCopy = Lib.extendDeep({}, mock);

            Plotly.plot(gd, mockCopy.data, mockCopy.layout).then(function() {
                legend = document.getElementsByClassName('legend')[0];
                bg = document.getElementsByClassName('bg')[0];
                done();
            });
        });

        afterEach(destroyGraph);

        it('should not exceed plot height', function() {
            var legendHeight = getLegendHeight();

            expect(+legendHeight).toBe(getPlotHeight(gd));
        });

        it('should insert a scrollbar', function() {
            var scrollBar = legend.getElementsByClassName('scrollbar')[0];

            expect(scrollBar).toBeDefined();
            expect(scrollBar.getAttribute('x')).not.toBe(null);
        });

        it('should scroll when there\'s a wheel event', function() {
            var scrollBox = legend.getElementsByClassName('scrollbox')[0],
                legendHeight = getLegendHeight(),
                scrollBoxYMax = gd._fullLayout.legend.height - legendHeight,
                scrollBarYMax = legendHeight -
                    constants.scrollBarHeight -
                    2 * constants.scrollBarMargin,
                initialDataScroll = scrollBox.getAttribute('data-scroll'),
                wheelDeltaY = 100,
                finalDataScroll = '' + Lib.constrain(initialDataScroll -
                    wheelDeltaY / scrollBarYMax * scrollBoxYMax,
                    -scrollBoxYMax, 0);

            legend.dispatchEvent(scrollTo(wheelDeltaY));

            expect(scrollBox.getAttribute('data-scroll')).toBe(finalDataScroll);
            expect(scrollBox.getAttribute('transform')).toBe(
                'translate(0, ' + finalDataScroll + ')');
        });

        it('should keep the scrollbar position after a toggle event', function() {
            var scrollBox = legend.getElementsByClassName('scrollbox')[0],
                toggle = legend.getElementsByClassName('legendtoggle')[0],
                wheelDeltaY = 100;

            legend.dispatchEvent(scrollTo(wheelDeltaY));

            var dataScroll = scrollBox.getAttribute('data-scroll');
            toggle.dispatchEvent(new MouseEvent('click'));
            expect(+toggle.parentNode.style.opacity).toBeLessThan(1);
            expect(scrollBox.getAttribute('data-scroll')).toBe(dataScroll);
            expect(scrollBox.getAttribute('transform')).toBe(
                'translate(0, ' + dataScroll + ')');
        });

        it('should keep toggle listeners after relayout', function() {
            Plotly.relayout(gd, 'showlegend', false);
            Plotly.relayout(gd, 'showlegend', true);

            var legend = document.getElementsByClassName('legend')[0],
                scrollBox = legend.getElementsByClassName('scrollbox')[0],
                toggle = legend.getElementsByClassName('legendtoggle')[0],
                wheelDeltaY = 100;

            legend.dispatchEvent(scrollTo(wheelDeltaY));

            var dataScroll = scrollBox.getAttribute('data-scroll');
            toggle.dispatchEvent(new MouseEvent('click'));
            expect(+toggle.parentNode.style.opacity).toBeLessThan(1);
            expect(scrollBox.getAttribute('data-scroll')).toBe(dataScroll);
            expect(scrollBox.getAttribute('transform')).toBe(
                'translate(0, ' + dataScroll + ')');
        });

        it('should constrain scrolling to the contents', function() {
            var scrollBox = legend.getElementsByClassName('scrollbox')[0];

            legend.dispatchEvent(scrollTo(-100));
            expect(scrollBox.getAttribute('transform')).toBe('translate(0, 0)');

            legend.dispatchEvent(scrollTo(100000));
            expect(scrollBox.getAttribute('transform')).toBe('translate(0, -179)');
        });

        it('should scale the scrollbar movement from top to bottom', function() {
            var scrollBar = legend.getElementsByClassName('scrollbar')[0],
                legendHeight = getLegendHeight();

            // The scrollbar is 20px tall and has 4px margins

            legend.dispatchEvent(scrollTo(-1000));
            expect(+scrollBar.getAttribute('y')).toBe(4);

            legend.dispatchEvent(scrollTo(10000));
            expect(+scrollBar.getAttribute('y')).toBe(legendHeight - 4 - 20);
        });

        it('should be removed from DOM when \'showlegend\' is relayout\'ed to false', function(done) {
            expect(countLegendGroups(gd)).toBe(1);
            expect(countLegendClipPaths(gd)).toBe(1);

            Plotly.relayout(gd, 'showlegend', false).then(function() {
                expect(countLegendGroups(gd)).toBe(0);
                expect(countLegendClipPaths(gd)).toBe(0);

                done();
            });
        });

        it('should resize when relayout\'ed with new height', function(done) {
            var origLegendHeight = getLegendHeight();

            Plotly.relayout(gd, 'height', gd._fullLayout.height / 2).then(function() {
                var legendHeight = getLegendHeight();

                //legend still exists and not duplicated
                expect(countLegendGroups(gd)).toBe(1);
                expect(countLegendClipPaths(gd)).toBe(1);

                // clippath resized to new height less than new plot height
                expect(+legendHeight).toBe(getPlotHeight(gd));
                expect(+legendHeight).toBeLessThan(+origLegendHeight);

                done();
            });
        });
    });

    describe('when plotted with few traces', function() {
        var gd;

        beforeEach(function() {
            gd = createGraph();

            var data = [{ x: [1, 2, 3], y: [2, 3, 4], name: 'Test' }];
            var layout = { showlegend: true };

            Plotly.plot(gd, data, layout);
        });

        afterEach(destroyGraph);

        it('should not display the scrollbar', function() {
            var scrollBar = document.getElementsByClassName('scrollbar')[0];

            expect(+scrollBar.getAttribute('width')).toBe(0);
            expect(+scrollBar.getAttribute('height')).toBe(0);
        });

        it('should be removed from DOM when \'showlegend\' is relayout\'ed to false', function(done) {
            expect(countLegendGroups(gd)).toBe(1);
            expect(countLegendClipPaths(gd)).toBe(1);

            Plotly.relayout(gd, 'showlegend', false).then(function() {
                expect(countLegendGroups(gd)).toBe(0);
                expect(countLegendClipPaths(gd)).toBe(0);

                done();
            });
        });

        it('should resize when traces added', function(done) {
            legend = document.getElementsByClassName('legend')[0];
            bg = document.getElementsByClassName('bg')[0];
            var origLegendHeight = getLegendHeight();

            Plotly.addTraces(gd, { x: [1, 2, 3], y: [4, 3, 2], name: 'Test2' }).then(function() {
                legend = document.getElementsByClassName('legend')[0];
                bg = document.getElementsByClassName('bg')[0];
                var legendHeight = getLegendHeight();

                expect(+legendHeight).toBeCloseTo(+origLegendHeight + 19, 0);

                done();
            });

        });
    });
});

function scrollTo(delta) {
    return new WheelEvent('wheel', { deltaY: delta });
}
<commit_msg>test: legend scrollbar is restored after relayout<commit_after>var Plotly = require('@lib/index');
var Lib = require('@src/lib');
var constants = require('@src/components/legend/constants');

var createGraph = require('../assets/create_graph_div');
var destroyGraph = require('../assets/destroy_graph_div');
var getBBox = require('../assets/get_bbox');
var mock = require('../../image/mocks/legend_scroll.json');

describe('The legend', function() {
    'use strict';

    var gd, legend, bg;

    function countLegendGroups(gd) {
        return gd._fullLayout._toppaper.selectAll('g.legend').size();
    }

    function countLegendClipPaths(gd) {
        var uid = gd._fullLayout._uid;

        return gd._fullLayout._topdefs.selectAll('#legend' + uid).size();
    }

    function getPlotHeight(gd) {
        return gd._fullLayout.height - gd._fullLayout.margin.t - gd._fullLayout.margin.b;
    }

    function getLegendHeight() {
        return gd._fullLayout.legend.borderwidth + getBBox(bg).height;
    }

    describe('when plotted with many traces', function() {
        beforeEach(function(done) {
            gd = createGraph();

            var mockCopy = Lib.extendDeep({}, mock);

            Plotly.plot(gd, mockCopy.data, mockCopy.layout).then(function() {
                legend = document.getElementsByClassName('legend')[0];
                bg = document.getElementsByClassName('bg')[0];
                done();
            });
        });

        afterEach(destroyGraph);

        it('should not exceed plot height', function() {
            var legendHeight = getLegendHeight();

            expect(+legendHeight).toBe(getPlotHeight(gd));
        });

        it('should insert a scrollbar', function() {
            var scrollBar = legend.getElementsByClassName('scrollbar')[0];

            expect(scrollBar).toBeDefined();
            expect(scrollBar.getAttribute('x')).not.toBe(null);
        });

        it('should scroll when there\'s a wheel event', function() {
            var scrollBox = legend.getElementsByClassName('scrollbox')[0],
                legendHeight = getLegendHeight(),
                scrollBoxYMax = gd._fullLayout.legend.height - legendHeight,
                scrollBarYMax = legendHeight -
                    constants.scrollBarHeight -
                    2 * constants.scrollBarMargin,
                initialDataScroll = scrollBox.getAttribute('data-scroll'),
                wheelDeltaY = 100,
                finalDataScroll = '' + Lib.constrain(initialDataScroll -
                    wheelDeltaY / scrollBarYMax * scrollBoxYMax,
                    -scrollBoxYMax, 0);

            legend.dispatchEvent(scrollTo(wheelDeltaY));

            expect(scrollBox.getAttribute('data-scroll')).toBe(finalDataScroll);
            expect(scrollBox.getAttribute('transform')).toBe(
                'translate(0, ' + finalDataScroll + ')');
        });

        it('should keep the scrollbar position after a toggle event', function() {
            var scrollBox = legend.getElementsByClassName('scrollbox')[0],
                toggle = legend.getElementsByClassName('legendtoggle')[0],
                wheelDeltaY = 100;

            legend.dispatchEvent(scrollTo(wheelDeltaY));

            var dataScroll = scrollBox.getAttribute('data-scroll');
            toggle.dispatchEvent(new MouseEvent('click'));
            expect(+toggle.parentNode.style.opacity).toBeLessThan(1);
            expect(scrollBox.getAttribute('data-scroll')).toBe(dataScroll);
            expect(scrollBox.getAttribute('transform')).toBe(
                'translate(0, ' + dataScroll + ')');
        });

        it('should be restored and functional after relayout', function() {
            var wheelDeltaY = 100,
                legend = document.getElementsByClassName('legend')[0],
                scrollBox,
                scrollBar,
                toggle;

            legend.dispatchEvent(scrollTo(wheelDeltaY));

            Plotly.relayout(gd, 'showlegend', false);
            Plotly.relayout(gd, 'showlegend', true);

            legend = document.getElementsByClassName('legend')[0];
            scrollBox = legend.getElementsByClassName('scrollbox')[0];
            scrollBar = legend.getElementsByClassName('scrollbar')[0];
            toggle = legend.getElementsByClassName('legendtoggle')[0];

            legend.dispatchEvent(scrollTo(wheelDeltaY));

            var dataScroll = scrollBox.getAttribute('data-scroll');
            toggle.dispatchEvent(new MouseEvent('click'));
            expect(+toggle.parentNode.style.opacity).toBeLessThan(1);
            expect(scrollBox.getAttribute('data-scroll')).toBe(dataScroll);
            expect(scrollBox.getAttribute('transform')).toBe(
                'translate(0, ' + dataScroll + ')');
            expect(scrollBar.getAttribute('width')).toBeGreaterThan(0);
            expect(scrollBar.getAttribute('height')).toBeGreaterThan(0);
        });

        it('should constrain scrolling to the contents', function() {
            var scrollBox = legend.getElementsByClassName('scrollbox')[0];

            legend.dispatchEvent(scrollTo(-100));
            expect(scrollBox.getAttribute('transform')).toBe('translate(0, 0)');

            legend.dispatchEvent(scrollTo(100000));
            expect(scrollBox.getAttribute('transform')).toBe('translate(0, -179)');
        });

        it('should scale the scrollbar movement from top to bottom', function() {
            var scrollBar = legend.getElementsByClassName('scrollbar')[0],
                legendHeight = getLegendHeight();

            // The scrollbar is 20px tall and has 4px margins

            legend.dispatchEvent(scrollTo(-1000));
            expect(+scrollBar.getAttribute('y')).toBe(4);

            legend.dispatchEvent(scrollTo(10000));
            expect(+scrollBar.getAttribute('y')).toBe(legendHeight - 4 - 20);
        });

        it('should be removed from DOM when \'showlegend\' is relayout\'ed to false', function(done) {
            expect(countLegendGroups(gd)).toBe(1);
            expect(countLegendClipPaths(gd)).toBe(1);

            Plotly.relayout(gd, 'showlegend', false).then(function() {
                expect(countLegendGroups(gd)).toBe(0);
                expect(countLegendClipPaths(gd)).toBe(0);

                done();
            });
        });

        it('should resize when relayout\'ed with new height', function(done) {
            var origLegendHeight = getLegendHeight();

            Plotly.relayout(gd, 'height', gd._fullLayout.height / 2).then(function() {
                var legendHeight = getLegendHeight();

                //legend still exists and not duplicated
                expect(countLegendGroups(gd)).toBe(1);
                expect(countLegendClipPaths(gd)).toBe(1);

                // clippath resized to new height less than new plot height
                expect(+legendHeight).toBe(getPlotHeight(gd));
                expect(+legendHeight).toBeLessThan(+origLegendHeight);

                done();
            });
        });
    });

    describe('when plotted with few traces', function() {
        var gd;

        beforeEach(function() {
            gd = createGraph();

            var data = [{ x: [1, 2, 3], y: [2, 3, 4], name: 'Test' }];
            var layout = { showlegend: true };

            Plotly.plot(gd, data, layout);
        });

        afterEach(destroyGraph);

        it('should not display the scrollbar', function() {
            var scrollBar = document.getElementsByClassName('scrollbar')[0];

            expect(+scrollBar.getAttribute('width')).toBe(0);
            expect(+scrollBar.getAttribute('height')).toBe(0);
        });

        it('should be removed from DOM when \'showlegend\' is relayout\'ed to false', function(done) {
            expect(countLegendGroups(gd)).toBe(1);
            expect(countLegendClipPaths(gd)).toBe(1);

            Plotly.relayout(gd, 'showlegend', false).then(function() {
                expect(countLegendGroups(gd)).toBe(0);
                expect(countLegendClipPaths(gd)).toBe(0);

                done();
            });
        });

        it('should resize when traces added', function(done) {
            legend = document.getElementsByClassName('legend')[0];
            bg = document.getElementsByClassName('bg')[0];
            var origLegendHeight = getLegendHeight();

            Plotly.addTraces(gd, { x: [1, 2, 3], y: [4, 3, 2], name: 'Test2' }).then(function() {
                legend = document.getElementsByClassName('legend')[0];
                bg = document.getElementsByClassName('bg')[0];
                var legendHeight = getLegendHeight();

                expect(+legendHeight).toBeCloseTo(+origLegendHeight + 19, 0);

                done();
            });

        });
    });
});

function scrollTo(delta) {
    return new WheelEvent('wheel', { deltaY: delta });
}
<|endoftext|>