import '../stylus/components/_overlay.styl'

// Utils
import { keyCodes } from '../util/helpers'

/* @vue/component */
export default {
  name: 'overlayable',

  props: {
    hideOverlay: Boolean
  },

  data () {
    return {
      overlay: null,
      overlayOffset: 0,
      overlayTimeout: null,
      overlayTransitionDuration: 500 + 150 // transition + delay
    }
  },

  beforeDestroy () {
    this.removeOverlay()
  },

  methods: {
    genOverlay () {
      // If fn is called and timeout is active
      // or overlay already exists
      // cancel removal of overlay and re-add active
      if ((!this.isActive || this.hideOverlay) ||
        (this.isActive && this.overlayTimeout) ||
        this.overlay
      ) {
        clearTimeout(this.overlayTimeout)

        return this.overlay &&
          this.overlay.classList.add('v-overlay--active')
      }

      this.overlay = document.createElement('div')
      this.overlay.className = 'v-overlay'

      if (this.absolute) this.overlay.className += ' v-overlay--absolute'

      this.hideScroll()

      const parent = this.absolute
        ? this.$el.parentNode
        : document.querySelector('[data-app]')

      parent && parent.insertBefore(this.overlay, parent.firstChild)

      // eslint-disable-next-line no-unused-expressions
      this.overlay.clientHeight // Force repaint
      requestAnimationFrame(() => {
        // https://github.com/vuetifyjs/vuetify/issues/4678
        if (!this.overlay) return

        this.overlay.className += ' v-overlay--active'

        if (this.activeZIndex !== undefined) {
          this.overlay.style.zIndex = this.activeZIndex - 1
        }
      })

      return true
    },
    removeOverlay () {
      if (!this.overlay) {
        return this.showScroll()
      }

      this.overlay.classList.remove('v-overlay--active')

      this.overlayTimeout = setTimeout(() => {
        // IE11 Fix
        try {
          if (this.overlay && this.overlay.parentNode) {
            this.overlay.parentNode.removeChild(this.overlay)
          }
          this.overlay = null
          this.showScroll()
        } catch (e) { console.log(e) }

        clearTimeout(this.overlayTimeout)
        this.overlayTimeout = null
      }, this.overlayTransitionDuration)
    },
    /**
     * @param {Event} e
     * @returns void
     */
    scrollListener (e) {
      if (e.type === 'keydown') {
        if (
          ['INPUT', 'TEXTAREA', 'SELECT'].includes(e.target.tagName) ||
          // https://github.com/vuetifyjs/vuetify/issues/4715
          e.target.isContentEditable
        ) return

        const up = [keyCodes.up, keyCodes.pageup]
        const down = [keyCodes.down, keyCodes.pagedown]

        if (up.includes(e.keyCode)) {
          e.deltaY = -1
        } else if (down.includes(e.keyCode)) {
          e.deltaY = 1
        } else {
          return
        }
      }

      if (e.target === this.overlay ||
        (e.type !== 'keydown' && e.target === document.body) ||
        this.checkPath(e)) e.preventDefault()
    },
    hasScrollbar (el) {
      if (!el || el.nodeType !== Node.ELEMENT_NODE) return false

      const style = window.getComputedStyle(el)
      return ['auto', 'scroll'].includes(style['overflow-y']) && el.scrollHeight > el.clientHeight
    },
    shouldScroll (el, delta) {
      if (el.scrollTop === 0 && delta < 0) return true
      return el.scrollTop + el.clientHeight === el.scrollHeight && delta > 0
    },
    isInside (el, parent) {
      if (el === parent) {
        return true
      } else if (el === null || el === document.body) {
        return false
      } else {
        return this.isInside(el.parentNode, parent)
      }
    },
    /**
     * @param {Event} e
     * @returns boolean
     */
    checkPath (e) {
      const path = e.path || this.composedPath(e)
      const delta = e.deltaY || -e.wheelDelta

      if (e.type === 'keydown' && path[0] === document.body) {
        const dialog = this.$refs.dialog
        const selected = window.getSelection().anchorNode
        if (this.hasScrollbar(dialog) && this.isInside(selected, dialog)) {
          return this.shouldScroll(dialog, delta)
        }
        return true
      }

      for (let index = 0; index < path.length; index++) {
        const el = path[index]

        if (el === document) return true
        if (el === document.documentElement) return true
        if (el === this.$refs.content) return true

        if (this.hasScrollbar(el)) return this.shouldScroll(el, delta)
      }

      return true
    },
    /**
     * Polyfill for Event.prototype.composedPath
     * @param {Event} e
     * @returns Element[]
     */
    composedPath (e) {
      if (e.composedPath) return e.composedPath()

      const path = []
      let el = e.target

      while (el) {
        path.push(el)

        if (el.tagName === 'HTML') {
          path.push(document)
          path.push(window)

          return path
        }

        el = el.parentElement
      }
    },
    hideScroll () {
      if (this.$vuetify.breakpoint.smAndDown) {
        document.documentElement.classList.add('overflow-y-hidden')
      } else {
        window.addEventListener('wheel', this.scrollListener)
        window.addEventListener('keydown', this.scrollListener)
      }
    },
    showScroll () {
      document.documentElement.classList.remove('overflow-y-hidden')
      window.removeEventListener('wheel', this.scrollListener)
      window.removeEventListener('keydown', this.scrollListener)
    }
  }
}


padding: $list-top-padding 0 $list-bottom-padding

transition: height .3s $transition.fast-out-slow-in

keep keep keep keep keep replace keep keep keep keep keep

import { Vue } from 'vue/types/vue'
import { VNode, VNodeDirective, FunctionalComponentOptions } from 'vue/types'

export function createSimpleFunctional (
  c: string,
  el = 'div',
  name?: string
): FunctionalComponentOptions {
  return {
    name: name || c.replace(/__/g, '-'),

    functional: true,

    render (h, { data, children }): VNode {
      data.staticClass = (`${c} ${data.staticClass || ''}`).trim()

      return h(el, data, children)
    }
  }
}

export function createSimpleTransition (
  name: string,
  origin = 'top center 0',
  mode?: string
): FunctionalComponentOptions {
  return {
    name,

    functional: true,

    props: {
      group: {
        type: Boolean,
        default: false
      },
      mode: {
        type: String,
        default: mode
      },
      origin: {
        type: String,
        default: origin
      }
    },

    render (h, context): VNode {
      const tag = `transition${context.props.group ? '-group' : ''}`
      context.data = context.data || {}
      context.data.props = {
        name,
        mode: context.props.mode
      }
      context.data.on = context.data.on || {}
      if (!Object.isExtensible(context.data.on)) {
        context.data.on = { ...context.data.on }
      }

      context.data.on.beforeEnter = (el: HTMLElement) => {
        el.style.transformOrigin = context.props.origin
        el.style.webkitTransformOrigin = context.props.origin
      }

      if (!context.props.mode) {
        const group = context.props.group

        context.data.on.beforeLeave = (el: HTMLElement) => {
          // When using group, this helps Vue FLIP
          // look correct when transitioning
          // https://vuejs.org/v2/guide/transitions.html#List-Move-Transitions
          if (group) el.style.display = 'none'
          else el.style.position = 'absolute'
        }
      }

      return h(tag, context.data, context.children)
    }
  }
}

export function createJavaScriptTransition (
  name: string,
  functions: Record<string, () => any>,
  mode = 'in-out'
): FunctionalComponentOptions {
  return {
    name,

    functional: true,

    props: {
      mode: {
        type: String,
        default: mode
      }
    },

    render (h, context): VNode {
      const data = {
        props: {
          ...context.props,
          name
        },
        on: functions
      }

      return h('transition', data, context.children)
    }
  }
}

export type BindingConfig = Pick<VNodeDirective, 'arg' | 'modifiers' | 'value'>
export function directiveConfig (binding: BindingConfig, defaults = {}): VNodeDirective {
  return {
    ...defaults,
    ...binding.modifiers,
    value: binding.arg,
    ...(binding.value || {})
  }
}

export function addOnceEventListener (el: EventTarget, event: string, cb: () => void): void {
  var once = () => {
    cb()
    el.removeEventListener(event, once, false)
  }

  el.addEventListener(event, once, false)
}

export function getNestedValue (obj: any, path: (string | number)[], fallback?: any): any {
  const last = path.length - 1

  if (last < 0) return obj === undefined ? fallback : obj

  for (let i = 0; i < last; i++) {
    if (obj == null) {
      return fallback
    }
    obj = obj[path[i]]
  }

  if (obj == null) return fallback

  return obj[path[last]] === undefined ? fallback : obj[path[last]]
}

export function deepEqual (a: any, b: any): boolean {
  if (a === b) return true

  if (a instanceof Date && b instanceof Date) {
    // If the values are Date, they were convert to timestamp with getTime and compare it
    if (a.getTime() !== b.getTime()) return false
  }

  if (a !== Object(a) || b !== Object(b)) {
    // If the values aren't objects, they were already checked for equality
    return false
  }

  const props = Object.keys(a)

  if (props.length !== Object.keys(b).length) {
    // Different number of props, don't bother to check
    return false
  }

  return props.every(p => deepEqual(a[p], b[p]))
}

export function getObjectValueByPath (obj: object, path: string, fallback?: any): any {
  // credit: http://stackoverflow.com/questions/6491463/accessing-nested-javascript-objects-with-string-key#comment55278413_6491621
  if (!path || path.constructor !== String) return fallback
  path = path.replace(/\[(\w+)\]/g, '.$1') // convert indexes to properties
  path = path.replace(/^\./, '') // strip a leading dot
  return getNestedValue(obj, path.split('.'), fallback)
}

export function getPropertyFromItem (
  item: object,
  property: string | (string | number)[] | ((item: object, fallback?: any) => any),
  fallback?: any
): any {
  if (property == null) return item === undefined ? fallback : item

  if (item !== Object(item)) return fallback === undefined ? item : fallback

  if (typeof property === 'string') return getObjectValueByPath(item, property, fallback)

  if (Array.isArray(property)) return getNestedValue(item, property, fallback)

  if (typeof property !== 'function') return fallback

  const value = property(item, fallback)

  return typeof value === 'undefined' ? fallback : value
}

export function createRange (length: number): number[] {
  return Array.from({ length }, (v, k) => k)
}

export function getZIndex (el?: Element | null): number {
  if (!el || el.nodeType !== Node.ELEMENT_NODE) return 0

  const index = +window.getComputedStyle(el).getPropertyValue('z-index')

  if (isNaN(index)) return getZIndex(el.parentNode as Element)
  return index
}

const tagsToReplace = {
  '&': '&',
  '<': '<',
  '>': '>'
} as any

export function escapeHTML (str: string): string {
  return str.replace(/[&<>]/g, tag => tagsToReplace[tag] || tag)
}

export function filterObjectOnKeys<T, K extends keyof T> (obj: T, keys: K[]): { [N in K]: T[N] } {
  const filtered = {} as { [N in K]: T[N] }

  for (let i = 0; i < keys.length; i++) {
    const key = keys[i]
    if (typeof obj[key] !== 'undefined') {
      filtered[key] = obj[key]
    }
  }

  return filtered
}

export function filterChildren (array: VNode[] = [], tag: string): VNode[] {
  return array.filter(child => {
    return child.componentOptions &&
      child.componentOptions.Ctor.options.name === tag
  })
}

export function convertToUnit (str: string | number | null | undefined, unit = 'px'): string | undefined {
  if (str == null || str === '') {
    return undefined
  } else if (isNaN(+str!)) {
    return String(str)
  } else {
    return `${Number(str)}${unit}`
  }
}

export function kebabCase (str: string): string {
  return (str || '').replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()
}

export function isObject (obj: any): obj is object {
  return obj !== null && typeof obj === 'object'
}

// KeyboardEvent.keyCode aliases
export const keyCodes = Object.freeze({
  enter: 13,
  tab: 9,
  delete: 46,
  esc: 27,
  space: 32,
  up: 38,
  down: 40,
  left: 37,
  right: 39,
  end: 35,
  home: 36,
  del: 46,
  backspace: 8,
  insert: 45,
  pageup: 33,
  pagedown: 34
})

const ICONS_PREFIX = '$vuetify.icons.'

// This remaps internal names like '$vuetify.icons.cancel' to the current name
// for that icon.
export function remapInternalIcon (vm: Vue, iconName: string): string {
  if (!iconName.startsWith(ICONS_PREFIX)) {
    return iconName
  }

  // Now look up icon indirection name, e.g. '$vuetify.icons.cancel'
  return getObjectValueByPath(vm, iconName, iconName)
}

export function keys<O> (o: O) {
  return Object.keys(o) as (keyof O)[]
}

/**
 * Camelize a hyphen-delimited string.
 */
const camelizeRE = /-(\w)/g
export const camelize = (str: string): string => {
  return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : '')
}


// Directives
import Ripple from '../directives/ripple'

// Types
import Vue, { VNode, VNodeData, VNodeDirective } from 'vue'

export default Vue.extend({
  name: 'rippleable',

  directives: { Ripple },

  props: {
    ripple: {
      type: [Boolean, Object],
      default: true
    }
  },

  methods: {
    genRipple (data: VNodeData = {}): VNode | null {
      if (!this.ripple) return null

      data.staticClass = 'v-input--selection-controls__ripple'

      data.directives = data.directives || []
      data.directives.push({
        name: 'ripple',
        value: { center: true }
      } as VNodeDirective)
      data.on = Object.assign({
        click: this.onChange
      }, this.$listeners)

      return this.$createElement('div', data)
    },
    onChange () {}
  }
})


import { test } from '@/test'
import VTab from '@/components/VTabs/VTab'
import Vue from 'vue'

const tabClick = 'Injection "tabClick" not found'
const tabsWarning = '[Vuetify] The v-tab component must be used inside a v-tabs'
const stub = {
  name: 'router-link',

  props: {
    to: [String, Object]
  },

  render (h) {
    return h('a', {
      domProps: { href: this.to }
    })
  }
}

test('VTab', ({ mount }) => {
  it('should render a div when disabled', async () => {
    const wrapper = mount(VTab, {
      propsData: {
        href: '#foo'
      }
    })

    expect(wrapper.find('.v-tabs__item')[0].vNode.elm.tagName).toBe('A')
    wrapper.setProps({ disabled: true })
    expect(wrapper.find('.v-tabs__item')[0].vNode.elm.tagName).toBe('DIV')

    expect(tabClick).toHaveBeenWarned()
    expect(tabsWarning).toHaveBeenTipped()
  })

  it('should register and unregister', async () => {
    const register = jest.fn()
    const unregister = jest.fn()
    const wrapper = mount({
      provide: {
        tabs: {
          register,
          unregister
        }
      },
      render (h) {
        return h('div', this.$slots.default)
      }
    }, {
      slots: {
        default: [VTab]
      }
    })

    const item = wrapper.find(VTab)[0]
    item.destroy()

    expect(register).toHaveBeenCalled()
    expect(unregister).toHaveBeenCalled()
    expect(tabClick).toHaveBeenWarned()
  })

  it('should emit click event and prevent default', async () => {
    const click = jest.fn()
    const wrapper = mount({
      provide: {
        tabClick: click
      },
      render (h) { return h('div', this.$slots.default) }
    }, {
      slots: {
        default: [{
          render: h => h(VTab, {
            props: { href: '#foo' }
          })
        }]
      }
    })

    const tab = wrapper.find(VTab)[0]
    tab.vm.$on('click', click)
    const event = new Event('click')
    tab.vm.click(event)
    await wrapper.vm.$nextTick()
    // Cannot figure out how to ensure this actually happens
    // expect(event.defaultPrevented).toBe(false)
    expect(click).toHaveBeenCalled()
    expect(tabsWarning).toHaveBeenTipped()
  })

  it('should toggle isActive', () => {
    const wrapper = mount(VTab, {
      propsData: { href: '#foo' }
    })

    expect(wrapper.vm.isActive).toBe(false)
    wrapper.vm.toggle('foo')
    expect(wrapper.vm.isActive).toBe(true)
    expect(tabClick).toHaveBeenWarned()
    expect(tabsWarning).toHaveBeenTipped()
  })

  it('should not call tabClick', async () => {
    const instance = Vue.extend()
    instance.component('router-link', stub)
    const wrapper = mount(VTab, {
      instance
    })
    const mockClick = jest.fn()
    const click = jest.fn()

    wrapper.vm.$on('click', click)
    wrapper.setMethods({ tabClick: mockClick })
    wrapper.vm.onRouteChange()
    await wrapper.vm.$nextTick()
    expect(mockClick).not.toHaveBeenCalled()

    wrapper.vm.click(new Event('click'))
    expect(mockClick.mock.calls.length).toBe(1)

    wrapper.setProps({ href: '/foo' })
    wrapper.vm.click(new Event('click'))
    await wrapper.vm.$nextTick()
    expect(mockClick.mock.calls.length).toBe(2)

    wrapper.setProps({ href: null, to: '/foo' })
    wrapper.vm.click(new Event('click'))
    await wrapper.vm.$nextTick()
    expect(mockClick.mock.calls.length).toBe(2)

    expect(tabClick).toHaveBeenWarned()
    expect(tabsWarning).toHaveBeenTipped()
  })

  // This can no longer be accurately tested due
  // to limitations with mocking tests for vue-router
  // and the changes for https://github.com/vuetifyjs/vuetify/issues/3010
  //
  // Current conversation on vue-router tests
  // https://github.com/vuejs/vue-router/issues/1768
  it('should call tabClick', async () => {
    const instance = Vue.extend()
    instance.component('router-link', stub)
    const wrapper = mount(VTab, {
      propsData: {
        to: '/foo'
      },
      globals: {
        $route: { path: '/foo' }
      },
      instance
    })

    const mockClick = jest.fn()
    wrapper.setMethods({ tabClick: mockClick })

    wrapper.vm.onRouteChange()
    await wrapper.vm.$nextTick()
    expect(mockClick).not.toHaveBeenCalled()

    // Mock the actions that would normally
    // happen with a route-link
    wrapper.vm.isActive = true
    wrapper.vm.$el.firstChild.classList.add('v-tabs__item--active')
    await wrapper.vm.$nextTick()

    // Mock on route change
    wrapper.vm.onRouteChange()
    await wrapper.vm.$nextTick()

    // expect(mockClick).toHaveBeenCalled()
    expect(tabClick).toHaveBeenWarned()
    expect(tabsWarning).toHaveBeenTipped()
  })

  it('should have the correct actions', () => {
    const instance = Vue.extend()
    instance.component('router-link', stub)
    const wrapper = mount(VTab, {
      propsData: {
        href: '#foo'
      },
      instance,
      globals: {
        $route: { path: '/' },
        $router: {
          resolve: (to, route, append) => {
            let href
            if (to.path) href = to.path

            return { href }
          }
        }
      }
    })

    expect(wrapper.vm.action).toBe('foo')
    wrapper.setProps({ href: null, to: '/foo' })
    expect(wrapper.vm.action).toBe('/foo')
    wrapper.setProps({ to: null })
    expect(wrapper.vm.action).toBe(wrapper.vm)
    wrapper.setProps({ to: { path: 'bar' }})
    expect(wrapper.vm.action).toBe('bar')

    expect(tabClick).toHaveBeenWarned()
    expect(tabsWarning).toHaveBeenTipped()
  })
})


export default function Grid (name) {
  /* @vue/component */
  return {
    name: `v-${name}`,

    functional: true,

    props: {
      id: String,
      tag: {
        type: String,
        default: 'div'
      }
    },

    render: (h, { props, data, children }) => {
      data.staticClass = (`${name} ${data.staticClass || ''}`).trim()

      if (data.attrs) {
        const classes = Object.keys(data.attrs).filter(key => {
          // TODO: Remove once resolved
          // https://github.com/vuejs/vue/issues/7841
          if (key === 'slot') return false

          const value = data.attrs[key]
          return value || typeof value === 'string'
        })

        if (classes.length) data.staticClass += ` ${classes.join(' ')}`
        delete data.attrs
      }

      if (props.id) {
        data.domProps = data.domProps || {}
        data.domProps.id = props.id
      }

      return h(props.tag, data, children)
    }
  }
}


.v-input__slot
margin: 0
&__tile
align-items: center
color: inherit
display: flex
font-size: $list-tile-font-size


import { test } from '@/test'
import VCombobox from '@/components/VCombobox'

test('VCombobox - multiple', ({ shallow, compileToFunctions }) => {
  const app = document.createElement('div')
  app.setAttribute('data-app', true)
  document.body.appendChild(app)
  const backspace = new Event('keydown')
  backspace.keyCode = 8

  function createMultipleCombobox (propsData) {
    const change = jest.fn()
    const wrapper = shallow(VCombobox, {
      attachToDocument: true,
      propsData: Object.assign({
        multiple: true,
        value: []
      }, propsData)
    })

    wrapper.vm.$on('input', change)
    return { wrapper, change }
  }

  it('should create new values when tagging', async () => {
    const { wrapper, change } = createMultipleCombobox()

    const input = wrapper.first('input')

    input.trigger('focus')
    input.element.value = 'foo'
    input.trigger('input')
    input.trigger('keydown.enter')

    expect(change).toHaveBeenCalledWith(['foo'])
  })

  it('should change selectedIndex with keyboard', async () => {
    const { wrapper } = createMultipleCombobox({
      value: ['foo', 'bar']
    })

    const input = wrapper.first('input')

    input.trigger('focus')
    await wrapper.vm.$nextTick()

    for (const index of [1, 0, -1]) {
      input.trigger('keydown.left')
      await wrapper.vm.$nextTick()
      expect(wrapper.vm.selectedIndex).toBe(index)
    }

  })

  it('should delete a tagged item when selected and backspace/delete is pressed', async () => {
    const { wrapper, change } = createMultipleCombobox({
      value: ['foo', 'bar']
    })

    const input = wrapper.first('input')

    input.trigger('focus')
    input.trigger('keydown.left')
    expect(wrapper.vm.selectedIndex).toBe(1)

    input.trigger('keydown.delete')
    await wrapper.vm.$nextTick()
    expect(change).toHaveBeenCalledWith(['foo'])
    expect(wrapper.vm.selectedIndex).toBe(0)

    input.element.dispatchEvent(backspace) // Avoriaz doesn't wrap keydown.backspace
    await wrapper.vm.$nextTick()
    expect(change).toHaveBeenCalledWith([])
    expect(wrapper.vm.selectedIndex).toBe(-1)

  })

  it('should add a tag on tab using the first suggestion', async () => {
    const { wrapper, change } = createMultipleCombobox({
      items: ['bar']
    })

    const input = wrapper.first('input')
    const menu = wrapper.first('.v-menu')

    input.trigger('focus')
    input.element.value = 'b'
    input.trigger('input')
    menu.trigger('keydown.down')

    // Give DOM time to update
    // list tile classes
    await wrapper.vm.$nextTick()

    expect(wrapper.vm.isMenuActive).toBe(true)

    input.trigger('keydown.tab')

    expect(change).toBeCalledWith(['bar'])
    expect(wrapper.vm.getMenuIndex()).toBe(0)
  })

  it('should add a tag on tab using the current searchValue', async () => {
    const { wrapper, change } = createMultipleCombobox({
      items: ['bar']
    })

    const input = wrapper.first('input')

    input.trigger('focus')

    wrapper.setProps({ searchInput: 'ba' })
    input.trigger('keydown.tab')
    await wrapper.vm.$nextTick()
    expect(change).toBeCalledWith(['ba'])

    wrapper.setProps({ searchInput: 'it' })
    input.trigger('keydown.tab')
    await wrapper.vm.$nextTick()
    expect(change).toBeCalledWith(['ba', 'it'])
  })

  it('should add a tag on enter using the current searchValue', async () => {
    const { wrapper, change } = createMultipleCombobox({
      items: ['bar']
    })

    const input = wrapper.first('input')

    input.trigger('focus')
    await wrapper.vm.$nextTick()

    input.element.value = 'ba'
    input.trigger('input')
    await wrapper.vm.$nextTick()
    input.trigger('keydown.enter')
    await wrapper.vm.$nextTick()

    expect(change).toBeCalledWith(['ba'])
  })

  it('should add a tag on left arrow and select the previous tag', async () => {
    const { wrapper, change } = createMultipleCombobox({
      value: ['foo'],
      items: ['foo', 'bar']
    })

    const input = wrapper.first('input')

    input.trigger('focus')
    input.element.value = 'b'
    input.trigger('input')
    input.trigger('keydown.left')

    expect(change).toBeCalledWith(['foo', 'b'])
    expect(wrapper.vm.selectedIndex).toBe(0)
  })

  it('should remove a duplicate tag and add it to the end', async () => {
    const { wrapper, change } = createMultipleCombobox({
      value: ['foo', 'bar']
    })

    const input = wrapper.first('input')

    input.trigger('focus')
    await wrapper.vm.$nextTick()

    input.element.value = 'foo'
    input.trigger('input')
    input.trigger('keydown.tab')
    await wrapper.vm.$nextTick()

    expect(change).toBeCalledWith(['bar', 'foo'])
  })

  it('should add tag with valid search value on blur', async () => {
    const { wrapper, change } = createMultipleCombobox()

    const input = wrapper.first('input')

    input.trigger('focus')
    input.element.value = 'bar'
    input.trigger('input')
    input.trigger('keydown.tab')

    expect(change).toBeCalledWith(['bar'])
  })

  it('should be able to add a tag from user input after deleting a tag with delete', async () => {
    const { wrapper, change } = createMultipleCombobox({
      multiple: true,
      value: ['foo', 'bar']
    })

    let input = wrapper.first('input')

    input.trigger('focus')
    input.trigger('keydown.left')
    expect(wrapper.vm.selectedIndex).toBe(1)
    input.trigger('keydown.delete')
    expect(change).toHaveBeenCalledWith(['foo'])
    expect(wrapper.vm.selectedIndex).toBe(0)

    // Must be reset for input to update
    wrapper.vm.selectedIndex = -1
    await wrapper.vm.$nextTick()

    input.element.value = 'baz'
    input.trigger('input')
    input.trigger('keydown.enter')

    expect(change).toBeCalledWith(['foo', 'baz'])
    expect(wrapper.vm.selectedIndex).toBe(-1)

  })

  it('should be able to add a tag from user input after clicking a deletable chip', async () => {
    const { wrapper, change } = createMultipleCombobox({
      chips: true,
      clearable: true,
      deletableChips: true,
      multiple: true,
      value: ['foo', 'bar']
    })

    const input = wrapper.first('input')
    const chip = wrapper.find('.v-chip')[1]
    const close = chip.first('.v-chip__close')

    input.trigger('focus')
    chip.trigger('click')
    close.trigger('click')
    expect(change).toHaveBeenCalledWith(['foo'])
    expect(wrapper.vm.selectedIndex).toBe(-1)

    input.element.value = 'baz'
    input.trigger('input')
    expect(wrapper.vm.internalSearch).toBe('baz')
    input.trigger('keydown.enter')

    expect(change).toBeCalledWith(['foo', 'baz'])
    expect(wrapper.vm.selectedIndex).toBe(-1)
  })

  // This test is actually almost useless
  it('should not change search when selecting an index', () => {
    const { wrapper } = createMultipleCombobox({
      chips: true,
      multiple: true,
      value: ['foo', 'bar']
    })

    const input = wrapper.first('input')

    input.trigger('focus')
    expect(wrapper.vm.selectedIndex).toBe(-1)

    input.trigger('keydown.left')
    expect(wrapper.vm.selectedIndex).toBe(1)

    expect(wrapper.vm.internalSearch).toBe(undefined)
    input.trigger('keydown.right')
    input.element.value = 'fizz'
    input.trigger('input')

    expect(wrapper.vm.internalSearch).toBe('fizz')
    expect(wrapper.vm.selectedIndex).toBe(-1)
  })

  it('should create new items when a delimiter is entered', async () => {
    const { wrapper, change } = createMultipleCombobox({
      delimiters: [', ', 'baz']
    })

    await wrapper.vm.$nextTick()

    const input = wrapper.first('input')
    input.trigger('focus')

    input.element.value = 'foo,'
    input.trigger('input')

    await wrapper.vm.$nextTick()
    expect(change).toHaveBeenCalledTimes(0)

    input.element.value += ' '
    input.trigger('input')

    await wrapper.vm.$nextTick()
    expect(change).toHaveBeenCalledTimes(1)
    expect(change).toHaveBeenCalledWith(['foo'])
    expect(input.element.value).toBe('')

    input.element.value = 'foo,barba'
    input.trigger('input')

    await wrapper.vm.$nextTick()
    expect(change).toHaveBeenCalledTimes(1)

    input.element.value += 'z'
    input.trigger('input')

    await wrapper.vm.$nextTick()
    expect(change).toHaveBeenCalledTimes(2)
    expect(change).toHaveBeenCalledWith(['foo', 'foo,bar'])
    expect(input.element.value).toBe('')
  })

  it('should allow the editing of an existing value', async () => {
    const { wrapper } = createMultipleCombobox({
      chips: true,
      value: ['foo']
    })

    const change = jest.fn()
    const internal = jest.fn()
    const chip = wrapper.first('.v-chip')
    const input = wrapper.first('input')

    wrapper.vm.$on('change', change)
    wrapper.vm.$watch('internalValue', internal)

    expect(wrapper.vm.editingIndex).toBe(-1)
    expect(wrapper.vm.internalSearch).toBe(undefined)

    chip.trigger('dblclick')

    expect(wrapper.vm.editingIndex).toBe(0)
    expect(wrapper.vm.internalSearch).toBe('foo')

    input.element.value = 'foobar'
    input.trigger('input')
    input.trigger('keydown.enter')

    await wrapper.vm.$nextTick()

    expect(change).toBeCalledWith(['foobar'])
    expect(internal).toBeCalledWith(['foobar'], ['foo'])
  })

  it('should react to tabs', async () => {
    const updateTags = jest.fn()
    const wrapper = shallow(VCombobox, {
      propsData: {
        items: ['fizz', 'buzz'],
        multiple: true
      },
      methods: {
        updateTags
      }
    })

    const input = wrapper.first('input')
    const menu = wrapper.first('.v-menu')
    const tile = wrapper.first('.v-list__tile')

    input.trigger('focus')
    input.element.value = 'foo'
    input.trigger('input')
    input.trigger('keydown.tab')

    expect(wrapper.vm.getMenuIndex()).toBe(-1)
    expect(updateTags).toBeCalled()

    input.trigger('focus')
    input.element.value = 'fizz'
    input.trigger('input')
    menu.trigger('keydown.down')

    // Allow dom to update class for
    // selected tile
    await wrapper.vm.$nextTick()

    expect(wrapper.vm.isMenuActive).toBe(true)
    expect(wrapper.vm.getMenuIndex()).toBe(0)

    input.trigger('keydown.tab')

    // We overwrite update tags so above
    // is does not persist
    expect(wrapper.vm.internalValue).toEqual(['fizz'])
    expect(updateTags).toHaveBeenCalledTimes(2)
  })
})


/**
 * Tabs touch
 *
 * @mixin
 */
/* @vue/component */
export default {
  methods: {
    newOffset (direction) {
      const clientWidth = this.$refs.wrapper.clientWidth

      if (direction === 'prev') {
        return Math.max(this.scrollOffset - clientWidth, 0)
      } else {
        return Math.min(this.scrollOffset + clientWidth, this.$refs.container.clientWidth - clientWidth)
      }
    },
    onTouchStart (e) {
      this.startX = this.scrollOffset + e.touchstartX
      this.$refs.container.style.transition = 'none'
      this.$refs.container.style.willChange = 'transform'
    },
    onTouchMove (e) {
      this.scrollOffset = this.startX - e.touchmoveX
    },
    onTouchEnd () {
      const container = this.$refs.container
      const wrapper = this.$refs.wrapper
      const maxScrollOffset = container.clientWidth - wrapper.clientWidth
      container.style.transition = null
      container.style.willChange = null

      /* istanbul ignore else */
      if (this.scrollOffset < 0 || !this.isOverflowing) {
        this.scrollOffset = 0
      } else if (this.scrollOffset >= maxScrollOffset) {
        this.scrollOffset = maxScrollOffset
      }
    }
  }
}


/**
 * Tabs computed
 *
 * @mixin
 */
/* @vue/component */
export default {
  computed: {
    activeIndex () {
      return this.tabs.findIndex((tab, index) => {
        const id = tab.action === tab ? index : tab.action
        return id === this.lazyValue
      })
    },
    activeTab () {
      if (!this.tabs.length) return undefined

      return this.tabs[this.activeIndex]
    },
    containerStyles () {
      return this.height ? {
        height: `${parseInt(this.height, 10)}px`
      } : null
    },
    hasArrows () {
      return (this.showArrows || !this.isMobile) && this.isOverflowing
    },
    inputValue: {
      get () {
        return this.lazyValue
      },
      set (val) {
        if (this.inputValue === val) return

        this.lazyValue = val
        this.$emit('input', val)
      }
    },
    isMobile () {
      return this.$vuetify.breakpoint.width < this.mobileBreakPoint
    },
    sliderStyles () {
      return {
        left: `${this.sliderLeft}px`,
        transition: this.sliderLeft != null ? null : 'none',
        width: `${this.sliderWidth}px`
      }
    },
    target () {
      return this.activeTab
        ? this.activeTab.action
        : null
    }
  }
}


export function createSimpleTransition (

transitions: undefined | Function | Function[],

if (Array.isArray(transitions)) return transitions.concat(array)


import Vue from 'vue'
import { test, touch } from '@/test'
import VDatePicker from '@/components/VDatePicker/VDatePicker'

test('VDatePicker.js', ({ mount, compileToFunctions }) => {
  it('should display the correct date in title and header', () => {
    const wrapper = mount(VDatePicker, {
      propsData: {
        value: '2005-11-01',
      }
    })

    const title = wrapper.find('.v-date-picker-title__date')[0]
    const header = wrapper.find('.v-date-picker-header__value strong')[0]

    expect(title.text()).toBe('Tue, Nov 1')
    expect(header.text()).toBe('November 2005')
  })

  it('should match snapshot with default settings', () => {
    const wrapper = mount(VDatePicker, {
      propsData: {
        value: '2013-05-07'
      }
    })

    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should render readonly picker', () => {
    const wrapper = mount(VDatePicker, {
      propsData: {
        value: '2013-05-07',
        readonly: true
      }
    })

    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should emit input event on date click', async () => {
    const wrapper = mount(VDatePicker, {
      propsData: {
        value: '2013-05-07'
      }
    })

    const input = jest.fn()
    wrapper.vm.$on('input', input)

    const change = jest.fn()
    wrapper.vm.$on('change', change)

    wrapper.find('.v-date-picker-table--date tbody tr+tr td:first-child button')[0].trigger('click')
    expect(input).toBeCalledWith('2013-05-05')
    expect(change).toBeCalledWith('2013-05-05')
  })

  it('should not emit input event on month click if date is not allowed', async () => {
    const cb = jest.fn()
    const wrapper = mount(VDatePicker, {
      propsData: {
        value: '2013-05-13',
        allowedDates: () => false
      },
      data: {
        activePicker: 'MONTH'
      }
    })

    wrapper.vm.$on('input', cb);
    wrapper.find('.v-date-picker-table--month button')[0].trigger('click')
    expect(cb).not.toBeCalled()
  })

  it('should emit input event on year click (reactive picker)', async () => {
    const wrapper = mount(VDatePicker, {
      propsData: {
        value: '2013-05-13',
        reactive: true
      },
      data: {
        activePicker: 'YEAR'
      }
    })

    const input = jest.fn()
    wrapper.vm.$on('input', input);

    const change = jest.fn()
    wrapper.vm.$on('change', input);

    wrapper.find('.v-date-picker-years li.active + li')[0].trigger('click')
    expect(input).toBeCalledWith('2012-05-13')
    expect(change).not.toBeCalled()
  })

  it('should not emit input event on year click if date is not allowed', async () => {
    const cb = jest.fn()
    const wrapper = mount(VDatePicker, {
      propsData: {
        value: '2013-05-13',
        allowedDates: () => false
      },
      data: {
        activePicker: 'YEAR'
      }
    })

    wrapper.vm.$on('input', cb);
    wrapper.find('.v-date-picker-years li.active + li')[0].trigger('click')
    expect(cb).not.toBeCalled()
  })

  it('should emit input event with selected dates after click', async () => {
    const cb = jest.fn()
    const wrapper = mount(VDatePicker, {
      propsData: {
        multiple: true,
        value: ['2013-05-07', '2013-05-08']
      }
    })

    wrapper.vm.$on('input', cb);
    wrapper.find('.v-date-picker-table--date tbody tr+tr td:first-child button')[0].trigger('click')
    expect(cb.mock.calls[0][0]).toHaveLength(3)
    expect(cb.mock.calls[0][0][2]).toBe('2013-05-05')
    expect(cb.mock.calls[0][0]).toEqual(
      expect.arrayContaining(['2013-05-07', '2013-05-08', '2013-05-05'])
    )
  })

  it('should emit input without unselected dates after click', async () => {
    const cb = jest.fn()
    const wrapper = mount(VDatePicker, {
      propsData: {
        multiple: true,
        value: ['2013-05-07', '2013-05-08', '2013-05-05']
      }
    })

    wrapper.vm.$on('input', cb);
    wrapper.find('.v-date-picker-table--date tbody tr+tr td:first-child button')[0].trigger('click')
    expect(cb.mock.calls[0][0]).toHaveLength(2)
    expect(cb.mock.calls[0][0]).toEqual(expect.arrayContaining(['2013-05-07', '2013-05-08']))
    expect(cb.mock.calls[0][0]).not.toEqual(expect.arrayContaining(['2013-05-05']))
  })

  it('should be scrollable', async () => {
    const wrapper = mount(VDatePicker, {
      propsData: {
        value: '2013-05-07',
        scrollable: true
      }
    })

    wrapper.find('.v-date-picker-table--date')[0].trigger('wheel')
    expect(wrapper.vm.tableDate).toBe('2013-06')
  })

  it('should change tableDate on touch', async () => {
    const wrapper = mount(VDatePicker, {
      propsData: {
        value: '2013-05-07',
        scrollable: true
      }
    })

    const table = wrapper.find('.v-date-picker-table--date')[0]
    touch(table).start(0, 0).end(20, 0)
    expect(wrapper.vm.tableDate).toBe('2013-04')

    touch(table).start(0, 0).end(-20, 0)
    expect(wrapper.vm.tableDate).toBe('2013-06')
  })

  it('should match snapshot with dark theme', () => {
    const wrapper = mount(VDatePicker, {
      propsData: {
        value: '2013-05-07',
        dark: true
      }
    })

    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should match snapshot with no title', () => {
    const wrapper = mount(VDatePicker, {
      propsData: {
        value: '2013-05-07',
        noTitle: true
      }
    })

    expect(wrapper.find('.v-picker__title')).toHaveLength(0)
  })

  it('should pass first day of week to v-date-picker-table component', () => {
    const wrapper = mount(VDatePicker, {
      propsData: {
        value: '2013-05-07',
        firstDayOfWeek: 2
      }
    })

    expect(wrapper.vm.$refs.table.firstDayOfWeek).toBe(2)
  })

  // TODO: This fails in different ways for multiple people
  // Avoriaz/Jsdom (?) doesn't fully support date formatting using locale
  // This should be tested in browser env
  it.skip('should match snapshot with locale', () => {
    const wrapper = mount(VDatePicker, {
      propsData: {
        value: '2013-05-07',
        locale: 'fa-AF'
      }
    })

    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should match snapshot with title/header formatting functions', () => {
    const dateFormat = date => `(${date})`
    const wrapper = mount(VDatePicker, {
      propsData: {
        value: '2005-11-01',
        headerDateFormat: dateFormat,
        titleDateFormat: dateFormat
      }
    })

    expect(wrapper.find('.v-date-picker-title__date')[0].text()).toBe('(2005-11-01)')
    expect(wrapper.find('.v-date-picker-header__value')[0].text()).toBe('(2005-11)')
  })

  it('should match snapshot with colored picker', () => {
    const wrapper = mount(VDatePicker, {
      propsData: {
        value: '2005-11-01',
        color: 'primary',
        headerColor: 'orange darken-1'
      }
    })

    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should match snapshot with colored picker', () => {
    const wrapper = mount(VDatePicker, {
      propsData: {
        value: '2005-11-01',
        color: 'orange darken-1'
      }
    })

    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should match snapshot with year icon', () => {
    const wrapper = mount(VDatePicker, {
      propsData: {
        value: '2005-11-01',
        yearIcon: 'year'
      }
    })

    expect(wrapper.find('.v-picker__title')[0].html()).toMatchSnapshot()
  })

  it('should match change month when clicked on header arrow buttons', () => {
    const wrapper = mount(VDatePicker, {
      propsData: {
        value: '2005-11-01'
      }
    })

    const [leftButton, rightButton] = wrapper.find('.v-date-picker-header button')

    leftButton.trigger('click')
    expect(wrapper.vm.tableDate).toBe('2005-10')

    rightButton.trigger('click')
    expect(wrapper.vm.tableDate).toBe('2005-12')
  })

  it('should match change active picker when clicked on month button', () => {
    const wrapper = mount(VDatePicker, {
      propsData: {
        value: '2005-11-01'
      }
    })

    const button = wrapper.find('.v-date-picker-header strong')[0]

    button.trigger('click')
    expect(wrapper.vm.activePicker).toBe('MONTH')
  })

  it('should match snapshot with slot', async () => {
    const vm = new Vue()
    const slot = props => vm.$createElement('div', { class: 'scoped-slot' })
    const component = Vue.component('test', {
      render (h) {
        return h(VDatePicker, {
          props: {
            type: 'date',
            value: '2005-11-01'
          },
          scopedSlots: {
            default: slot
          }
        })
      }
    })

    const wrapper = mount(component)
    expect(wrapper.find('.v-picker__actions .scoped-slot')).toHaveLength(1)
  })

  it('should match years snapshot', async () => {
    const wrapper = mount(VDatePicker, {
      data: {
        activePicker: 'YEAR'
      },
      propsData: {
        type: 'date',
        value: '2005-11-01'
      }
    })

    expect(wrapper.vm.activePicker).toBe('YEAR')

    wrapper.find('.v-date-picker-title__date')[0].trigger('click')
    await wrapper.vm.$nextTick()
    expect(wrapper.vm.activePicker).toBe('DATE')

    wrapper.find('.v-date-picker-title__year')[0].trigger('click')
    await wrapper.vm.$nextTick()
    expect(wrapper.vm.activePicker).toBe('YEAR')
  })

  it('should select year', async () => {
    const wrapper = mount(VDatePicker, {
      data: {
        activePicker: 'YEAR'
      },
      propsData: {
        type: 'date',
        value: '2005-11-01'
      }
    })

    wrapper.find('.v-date-picker-years li.active + li')[0].trigger('click')
    expect(wrapper.vm.activePicker).toBe('MONTH')
    expect(wrapper.vm.tableDate).toBe('2004-11')
  })

  it('should set the table date when value has changed', () => {
    const wrapper = mount(VDatePicker, {
      propsData: {
        value: null
      }
    })

    wrapper.setProps({ value: '2005-11-11' })
    expect(wrapper.vm.tableDate).toBe('2005-11')
  })

  it('should update the active picker if type has changed', () => {
    const wrapper = mount(VDatePicker, {
      propsData: {
        value: '1999-12-13',
        type: 'date'
      }
    })

    wrapper.vm.$on('input', value => wrapper.setProps({ value }))

    wrapper.setProps({ type: 'month' })
    expect(wrapper.vm.activePicker).toBe('MONTH')
    expect(wrapper.vm.value).toBe('1999-12')
    // TODO: uncomment when type: 'year' is implemented
    // wrapper.setProps({ type: 'year' })
    // expect(wrapper.vm.activePicker).toBe('YEAR')
    // expect(wrapper.vm.inputDate).toBe('1999')
    // wrapper.setProps({ type: 'month' })
    // expect(wrapper.vm.activePicker).toBe('MONTH')
    // expect(wrapper.vm.inputDate).toBe('1999-01')
    wrapper.setProps({ type: 'date' })
    expect(wrapper.vm.activePicker).toBe('DATE')
    expect(wrapper.vm.value).toBe('1999-12-01')
  })

  it('should format title date', () => {
    const wrapper = mount(VDatePicker, {
      propsData: {
        value: '2013-05-07',
      }
    })

    expect(wrapper.vm.defaultTitleDateFormatter('2013-03-05')).toBe('Tue, Mar 5')

    wrapper.setProps({ landscape: true })
    expect(wrapper.vm.defaultTitleDateFormatter('2013-03-05')).toBe('Tue,<br>Mar 5')
  })

  it('should use prev and next icons', () => {
    const wrapper = mount(VDatePicker, {
      propsData: {
        prevIcon: 'block',
        nextIcon: 'check'
      }
    })

    const icons = wrapper.find('.v-date-picker-header .v-icon')
    expect(icons[0].element.textContent).toBe('block')
    expect(icons[1].element.textContent).toBe('check')
  })

  it('should emit update:pickerDate event when tableDate changes', async () => {
    const wrapper = mount(VDatePicker, {
      propsData: {
        value: '2017-09'
      }
    })

    const pickerDate = jest.fn()
    wrapper.vm.$on('update:pickerDate', pickerDate)
    wrapper.vm.tableDate = '2013-11'
    await wrapper.vm.$nextTick()
    expect(pickerDate).toBeCalledWith('2013-11')
  })

  it('should set tableDate to pickerDate if provided', async () => {
    const wrapper = mount(VDatePicker, {
      propsData: {
        value: '2017-09',
        pickerDate: '2013-11'
      }
    })

    expect(wrapper.vm.tableDate).toBe('2013-11')
  })

  it('should update pickerDate to the selected month after setting it to null', async () => {
    const wrapper = mount(VDatePicker, {
      propsData: {
        value: '2017-09-13',
        pickerDate: '2013-11'
      }
    })

    const update = jest.fn()
    wrapper.vm.$on('update:pickerDate', update)
    await wrapper.vm.$nextTick()

    wrapper.setProps({
      pickerDate: null
    })
    await wrapper.vm.$nextTick()
    expect(update).toBeCalledWith('2017-09')
  })

  it('should render component with min/max props', async () => {
    const wrapper = mount(VDatePicker, {
      propsData: {
        value: '2013-01-07',
        min: '2013-01-03',
        max: '2013-01-17'
      }
    })

    expect(wrapper.html()).toMatchSnapshot()
    wrapper.vm.activePicker = 'MONTH'
    await wrapper.vm.$nextTick()
    expect(wrapper.html()).toMatchSnapshot()
    wrapper.vm.activePicker = 'YEAR'
    await wrapper.vm.$nextTick()
    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should emit @input and not emit @change when month is clicked (not reative picker)', async () => {
    const wrapper = mount(VDatePicker, {
      propsData: {
        value: '2013-02-07',
        reactive: true
      },
      data: {
        activePicker: 'MONTH'
      }
    })

    const input = jest.fn()
    wrapper.vm.$on('input', input)

    const change = jest.fn()
    wrapper.vm.$on('change', change)

    wrapper.find('tbody tr td button')[0].trigger('click')
    wrapper.vm.$nextTick()
    expect(change).not.toBeCalled()
    expect(input).toBeCalledWith('2013-01-07')
  })

  it('should not emit @input and not emit @change when month is clicked (lazy picker)', async () => {
    const wrapper = mount(VDatePicker, {
      propsData: {
        value: '2013-02-07'
      },
      data: {
        activePicker: 'MONTH'
      }
    })

    const input = jest.fn()
    wrapper.vm.$on('input', input)

    const change = jest.fn()
    wrapper.vm.$on('change', change)

    wrapper.find('tbody tr td button')[0].trigger('click')
    wrapper.vm.$nextTick()
    expect(change).not.toBeCalled()
    expect(input).not.toBeCalled()
  })
})


// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`VListTile.vue should render <div> with <a> when using href prop 1`] = `

`;

exports[`VListTile.vue should render <div> with <button> when using to prop 1`] = `

  <button class="v-list__tile v-list__tile--link">
  </button>

`;

exports[`VListTile.vue should render with a div when href and to are not used 1`] = `

`;


import { createSimpleFunctional } from '../../util/helpers'

import VToolbar from './VToolbar'
import VToolbarSideIcon from './VToolbarSideIcon'

const VToolbarTitle = createSimpleFunctional('v-toolbar__title')
const VToolbarItems = createSimpleFunctional('v-toolbar__items')

export { VToolbar, VToolbarSideIcon, VToolbarTitle, VToolbarItems }

export default {
  $_vuetify_subcomponents: {
    VToolbar,
    VToolbarItems,
    VToolbarTitle,
    VToolbarSideIcon
  }
}


props: {
group: {
type: Boolean,
default: false
},
mode: {
type: String,
default: mode
},

keep keep keep keep add keep keep keep keep

hideOnLeave: {
type: Boolean,
default: false
},
leaveAbsolute: {
type: Boolean,
default: false
},


import { test } from '@/test'
import VDatePickerTitle from '@/components/VDatePicker/VDatePickerTitle'

test('VDatePickerTitle.js', ({ mount }) => {
  it('should render component and match snapshot', () => {
    const wrapper = mount(VDatePickerTitle, {
      propsData: {
        year: '1234',
        date: '2005-11-01'
      }
    })

    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should render component when selecting year and match snapshot', () => {
    const wrapper = mount(VDatePickerTitle, {
      propsData: {
        year: '1234',
        date: '2005-11-01',
        selectingYear: true
      }
    })

    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should render year icon', () => {
    const wrapper = mount(VDatePickerTitle, {
      propsData: {
        year: '1234',
        yearIcon: 'year',
        date: '2005-11-01'
      }
    })

    expect(wrapper.find('.v-date-picker-title__year')[0].html()).toMatchSnapshot()
  })

  it('should emit input event on year/date click', () => {
    const wrapper = mount(VDatePickerTitle, {
      propsData: {
        year: '1234',
        yearIcon: 'year',
        date: '2005-11-01'
      }
    })

    const input = jest.fn(value => wrapper.setProps({ selectingYear: value }))
    wrapper.vm.$on('update:selectingYear', input)

    wrapper.find('.v-date-picker-title__date')[0].trigger('click')
    expect(input).not.toBeCalled()
    wrapper.find('.v-date-picker-title__year')[0].trigger('click')
    expect(input).toBeCalledWith(true)
    wrapper.find('.v-date-picker-title__date')[0].trigger('click')
    expect(input).toBeCalledWith(false)
    wrapper.find('.v-date-picker-title__year')[0].trigger('click')
    wrapper.find('.v-date-picker-title__year')[0].trigger('click')
    expect(input).toBeCalledWith(false)
  })

  it('should have the correct transition', () => {
    const wrapper = mount(VDatePickerTitle, {
      propsData: {
        year: '2018',
        date: 'Tue, Mar 3',
        value: '2018-03-03'
      }
    })

    expect(wrapper.vm.isReversing).toBe(false)

    wrapper.setProps({
      date: 'Wed, Mar 4',
      value: '2018-03-04'
    })

    expect(wrapper.vm.isReversing).toBe(false)

    wrapper.setProps({
      date: 'Wed, Mar 3',
      value: '2018-03-03'
    })

    expect(wrapper.vm.isReversing).toBe(true)
  })
})


# Backers

You can join them in supporting Vuetify.js by [pledging on Patreon](https://www.patreon.com/vuetify)

### $500+
- [LMAX Exchange](https://www.lmax.com/)
- [Intygrate](http://intygrate.com/)
- [Resale AI](http://resaleai.com/)

### $250+
- [Eikos Partners](http://www.eikospartners.com/)
- [rateGenius](https://application.rategenius.com/)
- [Clearblue Technologies](http://www.clearbluetechnologies.com/)
- [DigitalMaas](https://www.digitalmaas.com/)

### $100+
- [Anthony Gherghetta](https://gorilladash.com/)

### $50+
- [Xavier Escoté](http://www.deister.net/)
- [Steve Gehrman](https://cocoatech.com/)
- [Cycloid](https://www.cycloid.io/)
- [Nate Controls](http://www.natecontrols.com/) <!-- Brad Stewart -->
- [Zweidenker](http://zweidenker.de) <!-- Christian Denker -->
- [Software IDM](https://softwareidm.com/) <!-- Peter Sidebotham -->
- [Hohenstein Laboratories](https://www.hohenstein.de/en/home/home.xhtml)
- [P2L Technologies](https://p2l.tech/) <!-- Blaise Laflamme -->
- Terence Tsang

### $20+
- Michael Marti
- [Aeroden](https://www.aeroden.com) <!-- Adrian Belovic -->
- Sam Mitchell
- David Hess
- Jay Blanchard
- Christo Crampton
- Team Of Monkeys
- Robin Paolini
- [Integra CCS](https://www.integraccs.com/)
- Joel Hatch
- David Briscese
- Juha Vehnia
- [White Rabbit Express](https://www.whiterabbitexpress.com/) <!-- Max Hodges -->
- Kent Johnson
- Working Group Two

### $10+
- Kevin Pilard
- Espen Bratberg
- Ivan Vedernikov
- Chava Sobreyra
- Costa Huang
- Paul
- Jordan Wood
- Alexey Toroshchin
- Phillippe Genois
- Sam Bosell
- Cliff Hess
- Omar Spira
- Jiří Žižka
- Dejan Kovac
- Anythony Estebe
- Luis F Rocha
- Eeico Wentrup
- Chris Britton
- Matthew O'Leary
- Mike Margerum
- Reto Aebersold
- Mark Freedman
- Fernando Rosentalski Neto
- Eric Nguyen
- Alvin Wang
- Jadranko Dragoje
- Charles McKeever
- Stanislovas Janonis
- Brandon Deo
- Idea Junction Ltd.
- Michael Hipp
- Keyur Desai
- Casey Dwyer
- Bohdan Kokotko
- Blaine Landowski
- Paul Whittemore
- Alexander Weiher
- Helmi
- Hexodus
- Pierre Vanhulst
- Brett Lindenberg
- Randall Adams
- Andrew Harvey
- Cảnh Kiều
- Dave Willenberg
- James Chang
- Maxence Rose
- Omar Abu Samra
- MEHMET SAGLAM
- bizzy.tv
- Philipp Naderer-Puiu


firefox()
  @-moz-document url-prefix()
    {block}


context.data.on = { ...context.data.on }

context.data.on.beforeEnter = (el: HTMLElement) => {

keep keep keep keep keep replace keep keep

const absolute = (el: HTMLElement) => (el.style.position = 'absolute')

ourBeforeEnter.push((el: HTMLElement) => {

@import '../bootstrap'
@import '../theme'

/* Themes */
v-list($material)
  background: $material.cards
  color: $material.text.primary

  .v-list--disabled
    color: $material.text.disabled

  .v-list__tile__sub-title
    color: $material.text.secondary

  .v-list__tile__mask
    color: $material.text.disabled

    background: $material.expansion-panels.focus

  .v-list__tile--link:hover,
  .v-list__tile--highlighted,
  .v-list__group__header:hover
    background: $material.list-tile.hover

  .v-list__group--active:before,
  .v-list__group--active:after
    background: $material.dividers

  .v-list__group--disabled
    .v-list__tile
      color: $material.text.disabled !important

    .v-list__group__header__prepend-icon .v-icon
      color: $material.icons.inactive !important

theme(v-list, "v-list")

.v-list
  list-style-type: none
  padding: $list-top-padding 0 $list-bottom-padding
  transition: height .3s $transition.fast-out-slow-in

  .v-input
    .v-messages
      display: none

  .v-input,
  .v-input__slot
    margin: 0

  &__tile
    align-items: center
    color: inherit
    display: flex
    font-size: $list-tile-font-size
    font-weight: $list-tile-font-weight
    height: $list-item-single-height
    margin: 0
    padding: 0 16px
    position: relative
    text-decoration: none
    transition: $primary-transition

    &--link
      cursor: pointer
      user-select: none

    &__content,
    &__action
      height: 100%

    &__title,
    &__sub-title
      white-space: nowrap
      overflow: hidden
      text-overflow: ellipsis
      transition: $primary-transition
      width: 100%

    &__title
      height: 24px
      line-height: 24px
      position: relative
      text-align: left

    &__sub-title
      font-size: $list-sub-title-font-size

    &__avatar
      display: flex
      justify-content: flex-start
      min-width: 56px

    &__action
      display: flex
      justify-content: flex-start
      min-width: 56px
      align-items: center

      .v-btn // fix button padding if used
        padding 0
        margin 0

        &--icon
          margin -6px

      .v-radio.v-radio
        margin: 0

      // QoL when using selection controls in actions
      .v-input--selection-controls
        padding: 0

      &-text
        color: $list-action-text-font-color
        font-size: $list-action-text-font-size

      &--stack
        align-items: flex-end
        justify-content: space-between
        padding-top: $list-top-padding
        padding-bottom: $list-bottom-padding
        white-space: nowrap
        flex-direction: column

    &__content
      text-align: left
      flex: 1 1 auto
      overflow: hidden
      display: flex
      align-items: flex-start
      justify-content: center
      flex-direction: column

      ~ .v-list__tile__avatar
        justify-content: flex-end

      ~ .v-list__tile__action:not(.v-list__tile__action--stack)
        justify-content: flex-end

    &--active
      .v-list__tile__action:first-of-type
        .v-icon
          color: inherit

    &--avatar
      height: $list-item-avatar-single-height

  &--dense
    padding-top: $list-item-dense-top-padding
    padding-bottom: $list-item-dense-top-padding

    .v-subheader
      font-size: $subheader-dense-font-size
      height: $list-dense-subheader-height

    .v-list__group
      .v-subheader
        height $list-dense-subheader-group-height

    .v-list__tile
      font-size: $list-item-dense-font-size

      &--avatar
        height: $list-item-dense-avatar-single-height

      &:not(.v-list__tile--avatar)
        height: $list-item-dense-single-height

      .v-icon
        font-size: $list-item-dense-icon-font-size

    .v-list__tile__sub-title
      font-size: $list-item-dense-font-size

  &--disabled
    pointer-events: none

  &--two-line
    .v-list__tile
      height: $list-item-double-height

    &.v-list--dense
      .v-list__tile
        height: $list-item-dense-double-height

  &--three-line
    .v-list__tile
      height: $list-item-triple-height

    .v-list__tile__avatar
      margin-top: $list-item-avatar-triple-margin-top

    .v-list__tile__sub-title
      white-space: initial
      -webkit-line-clamp: 2
      -webkit-box-orient: vertical
      display: -webkit-box

    &.v-list--dense
      .v-list__tile
        height: $list-item-dense-triple-height

  > .v-list__group
    &:before
      top: 0

      .v-list__tile__avatar
        margin-top: $list-item-dense-avatar-triple-margin-top

  &__group
    padding: 0
    position: relative
    transition: inherit

    &:before,
    &:after
      content: ''
      height: 1px
      left: 0
      position: absolute
      transition: $primary-transition
      width: 100%

    &--active ~ .v-list__group
      &:before
        display: none

    &__header
      align-items: center
      cursor: pointer
      display: flex
      list-style-type: none

      > div:not(.v-list__group__header__prepend-icon):not(.v-list__group__header__append-icon)
        flex: 1 0 auto

      .v-list__group__header__append-icon,
      .v-list__group__header__prepend-icon
        padding: 0 16px
        user-select: none

      &--sub-group
        align-items: center
        display: flex

        div .v-list__tile
          padding-left: 0

        .v-list__group__header__prepend-icon
          padding: 0 0 0 40px
          margin-right: 8px

      .v-list__group__header__prepend-icon
        display: flex
        justify-content: flex-start
        min-width: 56px

      &--active
        .v-list__group__header__append-icon
          .v-icon
            transform: rotate(-180deg)

        .v-list__group__header__prepend-icon
          .v-icon
            color: inherit

        &.v-list__group__header--sub-group
          .v-list__group__header__prepend-icon
            .v-icon
              transform: rotate(-180deg)

    &__items
      position: relative
      padding: 0
      transition: inherit

      > div
        display: block

      &--no-action
        .v-list__tile
          padding-left: $list-item-left-padding

    &--disabled
      pointer-events: none

  &--subheader
    padding-top: 0


@import '../bootstrap'
@import '../theme'

v-text-field($material)
  .v-input__slot:before
    border-color: $material.input-bottom-line

  &:not(.v-input--has-state)
    .v-input__slot:hover:before
      border-color: $material.text.primary

  &.v-input--is-disabled
    .v-input__slot:before
      border-image: repeating-linear-gradient(
        to right,
        $material.text.disabled 0px,
        $material.text.disabled 2px,
        transparent 2px,
        transparent 4px
      ) 1 repeat

  &__prefix,
  &__suffix
    color: $material.text.secondary

  &--solo
    .v-input__slot
      border-radius: 2px
      background: $material.cards

  &--solo-inverted.v-text-field--solo
    .v-input__slot
      background: rgba($material.inputs.solo-inverted)

    &.v-input--is-focused
      .v-input__slot
        background: rgba($material.inputs.solo-inverted-focused)

        .v-label,
        input
          color: $material.inputs.solo-inverted-focused-text

  &--box
    .v-input__slot
      background: $material.text-fields.box

    &:not(.v-input--is-focused)
      .v-input__slot:hover
        background: $material.text-fields.box-hover

  &--outline
    .v-input__slot
      border: 2px solid $material.text.secondary

    &:not(.v-input--is-focused)
      .v-input__slot:hover
        border: 2px solid $material.text.primary

v-text-field-rtl()
  .v-label
    transform-origin: top right

  .v-counter
    margin-left: 0
    margin-right: 8px

  &--enclosed
    .v-input__append-outer
      margin-left: 0
      margin-right: 16px

    .v-input__prepend-outer
      margin-left: 16px
      margin-right: 0

  &--reverse
    input
      text-align: left

    .v-label
      transform-origin: top left

  &__prefix
    text-align: left
    padding-right: 0
    padding-left: 4px

  &__suffix
    padding-left: 0
    padding-right: 4px

  &--reverse
    .v-text-field__prefix
      text-align: right
      padding-left: 0
      padding-right: 4px

    .v-text-field__suffix
      padding-left: 0
      padding-right: 4px

theme(v-text-field, "v-text-field")
rtl(v-text-field-rtl, "v-text-field")

.v-text-field
  padding-top: $text-field-active-label-height
  margin-top: $input-top-spacing - $text-field-active-label-height

  input
    flex: 1 1 auto
    line-height: 20px
    padding: 8px 0 8px
    max-width: 100%
    min-width: 0px
    width: 100%

  .v-input__prepend-inner,
  .v-input__append-inner
    align-self: flex-start
    display: inline-flex
    margin-top: 4px
    line-height: 1
    user-select: none

  .v-input__prepend-inner
    margin-right: auto
    padding-right: 4px

  .v-input__append-inner
    margin-left: auto
    padding-left: 4px

  .v-counter
    margin-left: 8px
    white-space: nowrap

  .v-label
    max-width: 90%
    overflow: hidden
    text-overflow: ellipsis
    top: 6px // equal to the margin top of icons
    transform-origin: top left
    white-space: nowrap
    pointer-events: none

    &--active
      max-width: 100%
      transform: translateY(-18px) scale(.75)

  & > .v-input__control > .v-input__slot
    cursor: text
    transition: background $primary-transition

    &:before,
    &:after
      bottom: -1px // Needed for browser autocomplete styles
      content: ''
      left: 0
      position: absolute
      transition: $primary-transition
      width: 100%

    &:before
      border-style: solid
      border-width: thin 0 0 0

    &:after
      border-color: currentColor
      border-style: solid
      border-width: thin 0 thin 0
      transform: scaleX(0)

  &__details
    display: flex
    flex: 1 0 auto
    max-width: 100%
    overflow: hidden

  &__prefix,
  &__suffix
    align-self: center
    cursor: default

  &__prefix
    width: 16px
    text-align: right
    padding-right: 4px

  &__suffix
    padding-left: 4px
    white-space: nowrap

  &--reverse
    .v-text-field__prefix
      text-align: left
      padding-right: 0
      padding-left: 4px

    .v-text-field__suffix
      padding-left: 0
      padding-right: 4px

  &__slot
    display: flex
    flex: 1 1 auto
    height: 100%
    position: relative

  &--box,
  &--outline
    position: relative

    .v-input__slot
      align-items: stretch

    input
      margin-top: 22px

    &.v-text-field--single-line
      input
        margin-top: 12px

    .v-label
      top: 18px

      &--active
        transform: translateY(-6px) scale(.75)

    .v-input__slot
      min-height: 56px

  &--box
    .v-input__slot
      border-top-left-radius: 4px
      border-top-right-radius: 4px

      &:before
        border-style: solid
        border-width: thin 0 thin 0

  &.v-text-field--enclosed
    margin: 0
    padding: 0

    &:not(.v-text-field--box)
      .v-progress-linear__background
        display: none

    .v-input__prepend-outer,
    .v-input__prepend-inner,
    .v-input__append-inner,
    .v-input__append-outer
      margin-top: 16px

    .v-text-field__details,
    .v-input__slot
      padding: 0 12px

    .v-text-field__details
      margin-bottom: 8px

  &.v-text-field--full-width
    &.v-input
      margin-bottom: 0
      margin-top: 0

    .v-label
      top: calc(50% - 10px)

    .v-input__control
      padding: 12px 0

    .v-input__prepend-outer,
    .v-input__append-outer
      margin-top: 4px

    .v-input__append-inner
      align-self: center
      margin-top: 0

  &--reverse
    input
      text-align: right

    .v-label
      transform-origin: top right

    .v-input__slot,
    .v-text-field__slot
      flex-direction: row-reverse

  &--solo,
  &--outline,
  &--full-width
    .v-input__slot
      &:before,
      &:after
        display: none

  &--outline
    margin-bottom: 16px
    transition: border $primary-transition

    .v-input__slot
      background: transparent !important
      border-radius: 4px

    .v-input__prepend-outer,
    .v-input__append-outer
      margin-top: 18px // 2px for border

    &.v-input--is-focused
      .v-input__slot
        border: 2px solid currentColor
        transition: border $primary-transition

  &.v-text-field--solo
    .v-label
      top: calc(50% - 10px)

    .v-input__control
      min-height: 48px
      padding: 0

    &:not(.v-text-field--solo-flat)
      .v-input__slot
        elevation(2)

    .v-text-field__slot
      align-items: center

    .v-input__append-inner,
    .v-input__prepend-inner
      align-self: center
      margin-top: 0

    .v-input__prepend-outer,
    .v-input__append-outer
      margin-top: 12px

  &.v-input--is-focused
    .v-input__slot:after
      transform: scaleX(1)

  &.v-input--has-state
    .v-input__slot:before
      border-color: currentColor


import Vue from 'vue'
import { test } from '@/test'
import VSelectList from '@/components/VSelect/VSelectList'
import {
  VListTile,
  VListTileTitle,
  VListTileContent
} from '@/components/VList'

test('VSelectList', ({ mount }) => {
  const app = document.createElement('div')
  app.setAttribute('data-app', true)
  document.body.appendChild(app)

  it('should generate a divider', () => {
    const wrapper = mount(VSelectList)

    const divider = wrapper.vm.genDivider({
      inset: true
    })

    expect(divider.data.props.inset).toBe(true)
  })

  // TODO: wat
  it.skip('should generate a header', () => {
    const wrapper = mount(VSelectList)

    const divider = wrapper.vm.genHeader({
      light: true,
      header: 'foobar'
    })

    expect(divider.data.props.light).toBe(true)

    // Check that header exists
    expect(divider.children.length).toBe(1)
    expect(divider.children[0].text).toBe('foobar')
  })

  it('should use no-data slot', () => {
    const wrapper = mount(VSelectList, {
      slots: {
        'no-data': [{
          render: h => h('div', 'foo')
        }]
      }
    })
    expect(wrapper.vm.$slots['no-data'].length).toBe(1)
  })

  it('should display no-data-text when item slot is provided', async () => {
    const vm = new Vue()
    const itemSlot = () => vm.$createElement('div', ['this is not ok'])
    const component = Vue.component('test', {
      render (h) {
        return h(VSelectList, {
          props: {
            items: [],
            noDataText: 'this is ok'
          },
          scopedSlots: {
            item: itemSlot,
          }
        })
      }
    })

    const wrapper = mount(component)
    await wrapper.vm.$nextTick()
    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should generate children', () => {
    const wrapper = mount(VSelectList, {
      propsData: {
        items: [
          { header: true },
          { divider: true },
          'foo'
        ]
      }
    })

    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should return defined item value', async () => {
    const wrapper = mount(VSelectList, {
      propsData: {
        itemValue: 'foo'
      }
    })

    const getValue = wrapper.vm.getValue
    const getText = wrapper.vm.getText

    expect(getValue({ fizz: 'buzz' })).toEqual(getText({ fizz: 'buzz' }))

    wrapper.setProps({ itemValue: 'fizz' })

    expect(getValue({ fizz: 'buzz' })).toEqual('buzz')
  })

  it('should hide selected items', async () => {
    const wrapper = mount(VSelectList, {
      propsData: {
        selectedItems: ['foo'],
        hideSelected: true,
        items: ['foo', 'bar', 'fizz']
      }
    })

    expect(wrapper.find('.v-list__tile').length).toBe(2)

    wrapper.setProps({ selectedItems: ['foo', 'bar'] })

    await wrapper.vm.$nextTick()

    expect(wrapper.find('.v-list__tile').length).toBe(1)
  })

  // https://github.com/vuetifyjs/vuetify/issues/4431
  it('should display falsy items', () => {
    const wrapper = mount(VSelectList, {
      propsData: {
        items: [0, null, false, undefined, '']
      }
    })

    expect(wrapper.html()).toMatchSnapshot()
  })
})


import '../../stylus/components/_subheaders.styl'

import Themeable from '../../mixins/themeable'

/* @vue/component */
export default {
  name: 'v-subheader',

  mixins: [Themeable],

  props: {
    inset: Boolean
  },

  render (h) {
    return h('div', {
      staticClass: 'v-subheader',
      class: {
        'v-subheader--inset': this.inset,
        ...this.themeClasses
      },
      attrs: this.$attrs,
      on: this.$listeners
    }, this.$slots.default)
  }
}


ourLeave.push((el: HTMLElement) => (el.style.display = 'none'))


import Vue from 'vue'
import { test } from '@/test'
import VDataIterator from '@/components/VDataIterator'
import VBtn from '@/components/VBtn'

test('VDataIterator.js', ({ mount, compileToFunctions }) => {
  function dataIteratorTestData () {
    return {
      propsData: {
        pagination: {
          descending: false,
          sortBy: 'col1',
          rowsPerPage: 5,
          page: 1
        },
        items: [
          { other: 1, col1: 'foo', col2: 'a', col3: 1 },
          { other: 2, col1: null, col2: 'b', col3: 2 },
          { other: 3, col1: undefined, col2: 'c', col3: 3 }
        ]
      }
    }
  }

  it('should match a snapshot - no matching records', () => {
    const data = dataIteratorTestData()
    data.propsData.search = "asdf"
    const wrapper = mount(VDataIterator, data)

    expect(wrapper.html()).toMatchSnapshot()

    const content = wrapper.find('.v-data-iterator div div')[0]
    expect(content.element.textContent).toBe('No matching records found')

    expect('Unable to locate target [data-app]').toHaveBeenTipped()
  })

  it('should match a snapshot - hideActions and no footer slot', () => {
    const data = dataIteratorTestData()
    data.propsData.hideActions = true
    const wrapper = mount(VDataIterator, data)

    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should match a snapshot - footer slot', () => {
    const data = dataIteratorTestData()
    data.slots = {
      footer: [compileToFunctions('footer')],
    }
    const wrapper = mount(VDataIterator, data)

    expect(wrapper.html()).toMatchSnapshot()
    expect('Unable to locate target [data-app]').toHaveBeenTipped()
  })

  it('should match a snapshot - no data', () => {
    const data = dataIteratorTestData()
    data.propsData.items = []
    const wrapper = mount(VDataIterator, data)

    expect(wrapper.html()).toMatchSnapshot()

    const content = wrapper.find('.v-data-iterator div div')[0]
    expect(content.element.textContent).toBe('No data available')

    expect('Unable to locate target [data-app]').toHaveBeenTipped()
  })

  it('should match a snapshot - with data', async () => {
    const data = dataIteratorTestData()

    const vm = new Vue()
    const item = props => vm.$createElement('div', [props.item.col2])
    const component = Vue.component('test', {
      render (h) {
        return h(VDataIterator, {
          props: {
            'content-tag': 'span',
            ...data.propsData
          },
          scopedSlots: {
            item
          }
        })
      }
    })

    const wrapper = mount(component)

    expect(wrapper.html()).toMatchSnapshot()

    wrapper.vm.$vuetify.rtl = true
    await wrapper.vm.$nextTick()
    expect(wrapper.find('.v-data-iterator__actions__range-controls')[0].html()).toMatchSnapshot()
    wrapper.vm.$vuetify.rtl = undefined

    expect('Unable to locate target [data-app]').toHaveBeenTipped()
  })

  it('should pass attrs, class and props to content', () => {
    const data = dataIteratorTestData()

    const vm = new Vue()
    const item = props => vm.$createElement('div', [props.item.col2])
    const component = Vue.component('test', {
      render (h) {
        return h(VDataIterator, {
          props: {
            'content-tag': 'button', // TODO: use v-btn so we can test content-props
            ...data.propsData,
            // 'content-props': { block: true }, // TODO: enable when migrating to vue-test-utils
            'content-class': 'test__class'
          },
          attrs: {
            id: "testButtonId"
          },
          scopedSlots: {
            item
          }
        })
      }
    })

    const wrapper = mount(component)

    const mainDiv = wrapper.find('.v-data-iterator')[0]
    expect(mainDiv.hasAttribute('id')).toBe(false)

    var button = mainDiv.find('button')[0]
    expect(button.getAttribute('id')).toBe('testButtonId')
    // expect(button.hasClass('v-btn--block')).toBe(true) // TODO: enable when migrating to vue-test-utils
    expect(button.hasClass('test__class')).toBe(true)

    expect('Unable to locate target [data-app]').toHaveBeenTipped()
  })

  it('should not filter items if search is empty', async () => {
    const data = dataIteratorTestData()
    data.propsData.search = '    '
    const wrapper = mount(VDataIterator, data)

    expect(wrapper.instance().filteredItems).toHaveLength(data.propsData.items.length)

    expect('Unable to locate target [data-app]').toHaveBeenTipped()
  })

  it('should render header slot', async () => {
    const data = dataIteratorTestData()
    data.slots = {
      footer: [compileToFunctions('header')],
    }

    const wrapper = mount(VDataIterator, data)

    expect(wrapper.find('span.header').length).toBe(1)
    expect('Unable to locate target [data-app]').toHaveBeenTipped()
  })
})


import Vue from 'vue'
import { test } from '@/test'
import VDataTable from '@/components/VDataTable/VDataTable'

test('VDataTable.vue', ({ mount, compileToFunctions }) => {
  function dataTableTestData () {
    return {
      propsData: {
        pagination: {
          descending: false,
          sortBy: 'col1',
          rowsPerPage: 1,
          page: 1
        },
        headers: [
          { text: 'First Column', value: 'col1', class: 'a-string' },
          { text: 'Second Column', value: 'col2', sortable: false },
          { text: 'Third Column', value: 'col3', class: ['an', 'array'] }
        ],
        items: [
          { other: 1, col1: 'foo', col2: 'a', col3: 1 },
          { other: 2, col1: null, col2: 'b', col3: 2 },
          { other: 3, col1: undefined, col2: 'c', col3: 3 }
        ]
      }
    }
  }

  function dataTableNestedTestData () {
    return {
      propsData: {
        value: [{ nested: { value: { id: 1, name: 'bar' } } }],
        itemKey: 'nested.value.id',
        headers: [
          { text: 'ID', value: 'nested.value.id' },
          { text: 'Name', value: 'nested.value.name' },
        ],
        items: [
          { nested: { value: { id: 0, name: 'foo' } } },
          { nested: { value: { id: 1, name: 'bar' } } },
          { nested: { value: { id: 2, name: 'baz' } } }
        ]
      }
    }
  }

  function dataTableTestDataFilter () {
    return {
      propsData: {
        headers: [
          { text: 'First Column', value: 'first' },
          { text: 'Second Column', value: 'second.first' },
          { text: 'Third Column', value: 'third.first.second' }
        ],
        items: [
          { other: 1, first: 'foo', second: { first: 'bar' }, third: { first: { second: 'baz', third: 'outside' } }, fourth: 'outside' }
        ]
      }
    }
  }

  // TODO: This doesn't actually test anything
  it.skip('should be able to filter null and undefined values', async () => {
    const data = dataTableTestData()
    const pagination = data.propsData.pagination
    const wrapper = mount(VDataTable, data)

    pagination.descending = true

    expect(wrapper.vm.$props.pagination.descending).toBe(true)
    expect('Unable to locate target [data-app]').toHaveBeenTipped()
  })

  it('should match a snapshot - no matching results', () => {
    const data = dataTableTestData()
    data.propsData.search = "asdf"
    const wrapper = mount(VDataTable, data)

    expect(wrapper.html()).toMatchSnapshot()

    const content = wrapper.find('table.v-datatable tbody > tr > td')[0]
    expect(content.element.textContent).toBe('No matching records found')

    expect('Unable to locate target [data-app]').toHaveBeenTipped()
  })

  it('should match a snapshot - no data', () => {
    const data = dataTableTestData()
    data.propsData.items = []
    const wrapper = mount(VDataTable, data)

    expect(wrapper.html()).toMatchSnapshot()

    const content = wrapper.find('table.v-datatable tbody > tr > td')[0]
    expect(content.element.textContent).toBe('No data available')

    expect('Unable to locate target [data-app]').toHaveBeenTipped()
  })

  it('should match a snapshot - with data', () => {
    const data = dataTableTestData()
    data.propsData.pagination.rowsPerPage = 3

    const vm = new Vue()
    const items = props => vm.$createElement('td', [props.item.col2])
    const component = Vue.component('test', {
      render (h) {
        return h(VDataTable, {
          props: {
            ...data.propsData
          },
          scopedSlots: {
            items
          }
        })
      }
    })

    const wrapper = mount(component)

    expect(wrapper.html()).toMatchSnapshot()
    expect('Unable to locate target [data-app]').toHaveBeenTipped()
  })

  it('should match a snapshot with single rows-per-page-items', () => {
    const data = dataTableTestData()
    data.propsData.rowsPerPageItems = [1]
    const wrapper = mount(VDataTable, data)

    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should match display no-data-text when no data', () => {
    const data = dataTableTestData()
    data.propsData.items = []
    data.propsData.noDataText = 'foo'
    const wrapper = mount(VDataTable, data)

    expect(wrapper.find('tbody td')[0].html()).toMatchSnapshot()
    expect('Unable to locate target [data-app]').toHaveBeenTipped()
  })

  it('should match display no-results-text when no results', () => {
    const data = dataTableTestData()
    data.propsData.noResultsText = 'bar'
    data.propsData.search = "no such item"
    const wrapper = mount(VDataTable, data)

    expect(wrapper.find('tbody tr td')[0].html()).toMatchSnapshot()
    expect('Unable to locate target [data-app]').toHaveBeenTipped()
  })

  it('should render aria-sort attribute on column headers', async () => {
    const data = dataTableTestData()
    const wrapper = mount(VDataTable, data)

    const headers = wrapper.find('thead:first-of-type > tr:first-of-type > th')

    expect(
      headers.map(h => h.getAttribute('aria-sort'))
    ).toEqual(['ascending', 'none', 'none'])

    wrapper.setProps({
      pagination: {
        sortBy: 'col3',
        descending: false
      }
    })

    expect(
      headers.map(h => h.getAttribute('aria-sort'))
    ).toEqual(['none', 'none', 'ascending'])

    expect('Unable to locate target [data-app]').toHaveBeenTipped()
  })

  it('should match not allow a null sort', async () => {
    const data = {
      propsData: {
        mustSort: true,
        headers: [
          { text: 'First Column', value: 'col1' },
          { text: 'Second Column', value: 'col2', sortable: false },
          { text: 'Third Column', value: 'col3' }
        ],
        items: [
          { other: 1, col1: 'foo', col2: 'a', col3: 1 },
          { other: 2, col1: null, col2: 'b', col3: 2 },
          { other: 3, col1: undefined, col2: 'c', col3: 3 }
        ]
      }
    }

    const wrapper = mount(VDataTable, data)

    expect(wrapper.vm.defaultPagination.descending).toBe(false)
    wrapper.vm.sort(0)
    await wrapper.vm.$nextTick()
    expect(wrapper.vm.defaultPagination.descending).toBe(false)
    wrapper.vm.sort(0)
    await wrapper.vm.$nextTick()
    expect(wrapper.vm.defaultPagination.descending).toBe(true)
    wrapper.vm.sort(0)
    await wrapper.vm.$nextTick()
    expect(wrapper.vm.defaultPagination.descending).toBe(false)

    expect('Unable to locate target [data-app]').toHaveBeenTipped()
  })

  it('should render a progress with headers slot', () => {
    const vm = new Vue()
    const wrapper = mount(Vue.component('test', {
      render (h) {
        return h(VDataTable, {
          props: {
            items: []
          },
          scopedSlots: {
            headers: props => vm.$createElement('tr')
          }
        })
      }
    }))

    expect(wrapper.find('.v-datatable__progress')).toHaveLength(1)
    expect('Unable to locate target [data-app]').toHaveBeenTipped()
  })

  it('should only filter on data specified in headers', async () => {
    const wrapper = mount(VDataTable, dataTableTestDataFilter())

    expect(wrapper.instance().filteredItems).toHaveLength(1)
    wrapper.setProps({
      search: 'outside'
    })
    expect(wrapper.instance().filteredItems).toHaveLength(0)
    wrapper.setProps({
      search: 'baz'
    })
    expect(wrapper.instance().filteredItems).toHaveLength(1)

    expect('Unable to locate target [data-app]').toHaveBeenTipped()
  })

  it('should not filter items if search is empty', async () => {
    const data = dataTableTestDataFilter()
    data.propsData.search = '    '
    const wrapper = mount(VDataTable, data)

    expect(wrapper.instance().filteredItems).toHaveLength(data.propsData.items.length)

    expect('Unable to locate target [data-app]').toHaveBeenTipped()
  })

  it('should allow custom tr when using no-data slot', async () => {
    const wrapper = mount(Vue.component('test', {
      render (h) {
        return h(VDataTable, {
          props: {
            items: []
          },
        }, [h('tr', { slot: 'no-data', class: 'custom-class' })])
      }
    }))

    expect(wrapper.find('table tbody tr.custom-class').length).toBe(1)
    expect('Unable to locate target [data-app]').toHaveBeenTipped()
  })

  it('should allow custom td when using no-results slot', async () => {
    const wrapper = mount(Vue.component('test', {
      render (h) {
        return h(VDataTable, {
          props: {
            items: [{}],
            search: 'foo'
          },
        }, [h('td', { slot: 'no-results', class: 'custom-class' })])
      }
    }))

    expect(wrapper.find('table tbody tr td.custom-class').length).toBe(1)
    expect('Unable to locate target [data-app]').toHaveBeenTipped()
  })

  it('should render tr and td when using no-results slot', async () => {
    const wrapper = mount(Vue.component('test', {
      render (h) {
        return h(VDataTable, {
          props: {
            items: [{}],
            search: 'foo'
          },
        }, [h('div', { slot: 'no-results', class: 'custom-class' })])
      }
    }))

    expect(wrapper.find('table tbody tr td div.custom-class').length).toBe(1)
    expect('Unable to locate target [data-app]').toHaveBeenTipped()
  })

  it('should initialize everyItem state', async () => {
    const data = dataTableTestData()
    data.propsData.value = data.propsData.items
    const wrapper = mount(VDataTable, data)

    expect(wrapper.vm.everyItem).toBe(true);
    expect('Unable to locate target [data-app]').toHaveBeenTipped()
  })

  it('should update everyItem state', async () => {
    const data = dataTableTestData()
    data.propsData.itemKey = 'other';
    const wrapper = mount(VDataTable, data)

    expect(wrapper.vm.everyItem).toBe(false);
    wrapper.vm.value.push(wrapper.vm.items[0]);
    expect(wrapper.vm.everyItem).toBe(false);

    wrapper.vm.value.push(wrapper.vm.items[1]);
    wrapper.vm.value.push(wrapper.vm.items[2]);
    expect(wrapper.vm.everyItem).toBe(true);
    expect('Unable to locate target [data-app]').toHaveBeenTipped()
  })

  it('should render correct colspan when using headers-length prop', async () => {
    const data = dataTableTestData()
    data.propsData.headersLength = 11
    const wrapper = mount(VDataTable, data)

    expect(wrapper.find('tr.v-datatable__progress th')[0].getAttribute('colspan')).toBe('11')
    expect('Unable to locate target [data-app]').toHaveBeenTipped()
  })

  it('should not emit pagination event after load (#3585)', async () => {
    const data = dataTableTestDataFilter()
    data.propsData.totalItems = 0
    data.propsData.search = ''
    data.propsData.pagination = {}

    const wrapper = mount(VDataTable, data)
    const pagination = jest.fn()
    wrapper.vm.$on('update:pagination', pagination)

    await wrapper.vm.$nextTick()

    expect(pagination).toHaveBeenCalledTimes(0)

    expect('Unable to locate target [data-app]').toHaveBeenTipped()
  })

  it('should emit correct totalItems property in pagination when searching (#3511)', async () => {
    const data = {
      propsData: {
        pagination: {
          rowsPerPage: 5,
          descending: false,
          page: 1,
          sortBy: 'calories'
        },
        headers: [{ text: 'Calories', value: 'calories' }],
        items: [{ calories: 19 }, { calories: 19 }, { calories: 152 }]
      }
    }

    const wrapper = mount(VDataTable, data)
    const pagination = jest.fn()
    wrapper.vm.$on('update:pagination', pagination)

    await wrapper.vm.$nextTick()

    wrapper.setProps({ search: '9' })

    await wrapper.vm.$nextTick()

    expect(wrapper.instance().filteredItems).toHaveLength(2)
    expect(pagination).toHaveBeenCalledWith(Object.assign({}, data.propsData.pagination, { totalItems: 2 }))

    expect('Unable to locate target [data-app]').toHaveBeenTipped()
  })

  it('should not reset page when total-items prop changes (#3766)', async () => {
    const data = {
      propsData: {
        pagination: {
          rowsPerPage: 1,
          descending: false,
          page: 2,
          sortBy: 'calories'
        },
        totalItems: 3,
        headers: [{ text: 'Calories', value: 'calories' }],
        items: [{ calories: 19 }, { calories: 19 }, { calories: 152 }]
      }
    }

    const wrapper = mount(VDataTable, data)

    await wrapper.vm.$nextTick()

    expect(wrapper.instance().computedPagination.page).toBe(2)

    wrapper.setProps({ totalItems: 10 })

    await wrapper.vm.$nextTick()

    expect(wrapper.instance().computedPagination.page).toBe(2)

    expect('Unable to locate target [data-app]').toHaveBeenTipped()
  })

  it('should allow selection using nested values', async () => {
    const data = dataTableNestedTestData()
    const wrapper = mount(VDataTable, data)

    await wrapper.vm.$nextTick()

    expect(wrapper.vm.selected.hasOwnProperty(1)).toBe(true)
    expect(wrapper.vm.selected[1]).toBe(true)

    wrapper.setProps({ value: [{ nested: { value: { id: 2, name: 'baz' } } }] })
    await wrapper.vm.$nextTick()

    expect(wrapper.vm.selected.hasOwnProperty(1)).toBe(false)
    expect(wrapper.vm.selected.hasOwnProperty(2)).toBe(true)
    expect(wrapper.vm.selected[2]).toBe(true)

    expect('Unable to locate target [data-app]').toHaveBeenTipped()
  })
})


import Vue from 'vue'

/**
 * SSRBootable
 *
 * @mixin
 *
 * Used in layout components (drawer, toolbar, content)
 * to avoid an entry animation when using SSR
 */
export default Vue.extend({
  name: 'ssr-bootable',

  data: () => ({
    isBooted: false
  }),

  mounted () {
    // Use setAttribute instead of dataset
    // because dataset does not work well
    // with unit tests
    window.requestAnimationFrame(() => {
      this.$el.setAttribute('data-booted', 'true')
      this.isBooted = true
    })
  }
})


const { beforeEnter, leave } = context.data.on


// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`VPicker.js should render component with title and match snapshot 1`] = `

      title

      default

`;

exports[`VPicker.js should render component without title and match snapshot 1`] = `

      default

`;

exports[`VPicker.js should render dark component and match snapshot 1`] = `

      title

      default

`;


import '../../stylus/components/_tables.styl'
import '../../stylus/components/_data-table.styl'

import DataIterable from '../../mixins/data-iterable'

import Head from './mixins/head'
import Body from './mixins/body'
import Foot from './mixins/foot'
import Progress from './mixins/progress'

import {
  createSimpleFunctional,
  getObjectValueByPath
} from '../../util/helpers'

// Importing does not work properly
const VTableOverflow = createSimpleFunctional('v-table__overflow')

/* @vue/component */
export default {
  name: 'v-data-table',

  mixins: [DataIterable, Head, Body, Foot, Progress],

  props: {
    headers: {
      type: Array,
      default: () => []
    },
    headersLength: {
      type: Number
    },
    headerText: {
      type: String,
      default: 'text'
    },
    headerKey: {
      type: String,
      default: null
    },
    hideHeaders: Boolean,
    rowsPerPageText: {
      type: String,
      default: '$vuetify.dataTable.rowsPerPageText'
    },
    customFilter: {
      type: Function,
      default: (items, search, filter, headers) => {
        search = search.toString().toLowerCase()
        if (search.trim() === '') return items

        const props = headers.map(h => h.value)

        return items.filter(item => props.some(prop => filter(getObjectValueByPath(item, prop), search)))
      }
    }
  },

  data () {
    return {
      actionsClasses: 'v-datatable__actions',
      actionsRangeControlsClasses: 'v-datatable__actions__range-controls',
      actionsSelectClasses: 'v-datatable__actions__select',
      actionsPaginationClasses: 'v-datatable__actions__pagination'
    }
  },

  computed: {
    classes () {
      return {
        'v-datatable v-table': true,
        'v-datatable--select-all': this.selectAll !== false,
        ...this.themeClasses
      }
    },
    filteredItems () {
      return this.filteredItemsImpl(this.headers)
    },
    headerColumns () {
      return this.headersLength || this.headers.length + (this.selectAll !== false)
    }
  },

  created () {
    const firstSortable = this.headers.find(h => (
      !('sortable' in h) || h.sortable)
    )

    this.defaultPagination.sortBy = !this.disableInitialSort && firstSortable
      ? firstSortable.value
      : null

    this.initPagination()
  },

  methods: {
    hasTag (elements, tag) {
      return Array.isArray(elements) && elements.find(e => e.tag === tag)
    },
    genTR (children, data = {}) {
      return this.$createElement('tr', data, children)
    }
  },

  render (h) {
    const tableOverflow = h(VTableOverflow, {}, [
      h('table', {
        'class': this.classes
      }, [
        this.genTHead(),
        this.genTBody(),
        this.genTFoot()
      ])
    ])

    return h('div', [
      tableOverflow,
      this.genActionsFooter()
    ])
  }
}


.blockquote
  padding: $spacers.three.y 0 $spacers.three.y $spacers.four.x
  font-size: 18px
  font-weight: 300