import { extractSqliteSources } from '../generation/extractSqliteSources'
import { serializeDatasources } from '../generation/serializeDatasources'

test('ignore comments', () => {
  const datamodel = `datasource db {
    provider = "sqlite"
    // url = "file:another/wrong/folder/dev.db"
    url      = "file:my/folder/dev.db"
    default  = true
  }

  generator photon {
    provider  = "photonjs"
    output    = "@generated/photon"
    transpile = false
  }

  model User {
    id    String  @id @default(uuid())
    email String  @unique
    name  String?
    posts Post[]
  }

  model Post {
    id         String   @id @default(uuid())
    createdAt  DateTime @default(now())
    updatedAt  DateTime @updatedAt
    randomDate DateTime
    published  Boolean
    title      String
    content    String?
    author     User?
  }

  /// Role num comment
  enum Role {
    USER
    ADMIN
  }`

  const result = extractSqliteSources(datamodel, '/cwd', '/outputdir')

  expect(result).toMatchInlineSnapshot(`
    Array [
      Object {
        "name": "db",
        "url": "'file:' + path.resolve(__dirname, '../cwd/my/folder/dev.db')",
      },
    ]
  `)

  expect(serializeDatasources(result)).toMatchInlineSnapshot(`
    "[
      {
        \\"name\\": \\"db\\",
        \\"url\\": 'file:' + path.resolve(__dirname, '../cwd/my/folder/dev.db')
      }
    ]"
  `)
})

test('basic happy path', () => {
  const datamodel = `datasource db {
    provider = "sqlite"
    url      = "file:my/folder/dev.db"
    default  = true
  }

  generator photon {
    provider  = "photonjs"
    output    = "@generated/photon"
    transpile = false
  }

  model User {
    id    String  @id @default(uuid())
    email String  @unique
    name  String?
    posts Post[]
  }

  model Post {
    id         String   @id @default(uuid())
    createdAt  DateTime @default(now())
    updatedAt  DateTime @updatedAt
    randomDate DateTime
    published  Boolean
    title      String
    content    String?
    author     User?
  }

  /// Role num comment
  enum Role {
    USER
    ADMIN
  }`

  const result = extractSqliteSources(datamodel, '/cwd', '/outputdir')

  expect(result).toMatchInlineSnapshot(`
                Array [
                  Object {
                    "name": "db",
                    "url": "'file:' + path.resolve(__dirname, '../cwd/my/folder/dev.db')",
                  },
                ]
        `)

  expect(serializeDatasources(result)).toMatchInlineSnapshot(`
            "[
              {
                \\"name\\": \\"db\\",
                \\"url\\": 'file:' + path.resolve(__dirname, '../cwd/my/folder/dev.db')
              }
            ]"
      `)
})


this._collectTimestamps = new CollectTimestamps("PhotonClient")

if (process.env.NODE_ENV !== 'production' && this._errorFormat !== 'minimal') {

keep keep replace replace keep keep keep keep


export function getFieldTypeName(field: DMMF.SchemaField) {

if (typeof field.outputType.type === 'string') {

keep keep keep keep replace keep keep keep keep keep

import { GeneratorConfig } from '@prisma/generator-helper'
import 'flat-map-polyfill' // unfortunately needed as it's not properly polyfilled in TypeScript
import indent from 'indent-string'
import { DMMFClass } from '../runtime/dmmf'
import { BaseField, DMMF } from '../runtime/dmmf-types'
import { capitalize, GraphQLScalarToJSTypeTable } from '../runtime/utils/common'
import { InternalDatasource } from '../runtime/utils/printDatasources'
import { DatasourceOverwrite } from './extractSqliteSources'
import { serializeDatasources } from './serializeDatasources'
import {
  flatMap,
  getDefaultName,
  getFieldArgName,
  getFieldTypeName,
  getIncludeName,
  getModelArgName,
  getPayloadName,
  getRelativePathResolveStatement,
  getScalarsName,
  getSelectName,
  getSelectReturnType,
  getType,
  // getExtractName,
  indentAllButFirstLine,
  isQueryAction,
  Projection,
  renderInitialClientArgs,
} from './utils'

const tab = 2

const commonCode = (runtimePath: string, version?: string) => `import {
  DMMF,
  DMMFClass,
  deepGet,
  deepSet,
  makeDocument,
  Engine,
  debugLib,
  transformDocument,
  chalk,
  printStack,
  mergeBy,
  unpack,
  stripAnsi
} from '${runtimePath}'

/**
 * Query Engine version: ${version || 'latest'}
 */

import path = require('path')
import fs = require('fs')

const debug = debugLib('photon')

/**
 * Utility Types
 */

export type Enumerable<T> = T | Array<T>

export type MergeTruthyValues<R extends object, S extends object> = {
  [key in keyof S | keyof R]: key extends false
    ? never
    : key extends keyof S
    ? S[key] extends false
      ? never
      : S[key]
    : key extends keyof R
    ? R[key]
    : never
}

export type CleanupNever<T> = { [key in keyof T]: T[key] extends never ? never : key }[keyof T]

/**
 * Subset
 * @desc From \`T\` pick properties that exist in \`U\`. Simple version of Intersection
 */
export type Subset<T, U> = { [key in keyof T]: key extends keyof U ? T[key] : never }

/**
 * A PhotonRequestError is an error that is thrown in conjunction to a concrete query that has been performed with Photon.js.
 */
export class PhotonRequestError extends Error {
  constructor(public message: string, public code?: string, public meta?: any) {
    super(message)
    this.code = code
    this.meta = meta
  }
}

class PhotonFetcher {
  constructor(
    private readonly photon: Photon,
    private readonly engine: Engine,
    private readonly debug = false,
    private readonly hooks?: Hooks,
    private readonly errorFormat?: ErrorFormat
  ) {}
  async request<T>(document: any, dataPath: string[] = [], rootField?: string, typeName?: string, isList?: boolean, callsite?: string, collectTimestamps?: any): Promise<T> {
    const query = String(document)
    debug('Request:')
    debug(query)
    if (this.hooks && this.hooks.beforeRequest) {
      this.hooks.beforeRequest({ query, path: dataPath, rootField, typeName, document })
    }
    try {
      collectTimestamps && collectTimestamps.record("Pre-connect")
      await this.photon.connect()
      collectTimestamps && collectTimestamps.record("Post-connect")
      collectTimestamps && collectTimestamps.record("Pre-engine")
      const result = await this.engine.request(query, typeName)
      collectTimestamps && collectTimestamps.record("Post-engine")
      debug('Response:')
      debug(result)
      collectTimestamps && collectTimestamps.record("Pre-unpack")
      const unpackResult = this.unpack(document, result, dataPath, rootField, isList)
      collectTimestamps && collectTimestamps.record("Post-unpack")
      return unpackResult
    } catch (e) {
      if (callsite) {
        const { stack } = printStack({
          callsite,
          originalMethod: dataPath.join('.'),
          onUs: e.isPanic
        })
        const message = stack + '\\n\\n' + e.message
        if (e.code) {
          throw new PhotonRequestError(this.sanitizeMessage(message), e.code, e.meta)
        }
        throw new Error(this.sanitizeMessage(message))
      } else {
        if (e.code) {
          throw new PhotonRequestError(this.sanitizeMessage(e.message), e.code, e.meta)
        }
        if (e.isPanic) {
          throw e
        } else {
          throw new Error(this.sanitizeMessage(e.message))
        }
      }
    }
  }
  sanitizeMessage(message: string): string {
    if (this.errorFormat && this.errorFormat !== 'pretty') {
      return stripAnsi(message)
    }

    return message
  }
  protected unpack(document: any, data: any, path: string[], rootField?: string, isList?: boolean) {
    const getPath: string[] = []
    if (rootField) {
      getPath.push(rootField)
    }
    getPath.push(...path.filter(p => p !== 'select' && p !== 'include'))
    return unpack({ document, data, path: getPath })
  }
}
class CollectTimestamps {
  public readonly records: Array<{ name: string, value: [number, number]}> = []
  public start: { name: string, value: [number, number]} | undefined = undefined
  constructor(startName: string) {
    this.start = { name: startName, value: process.hrtime() }
  }
  public record(name: string) {
    this.records.push({ name, value: process.hrtime() })
  }
  public elapsed(start: [number, number], end: [number, number]) {
    const diff = [ end[0] - start[0], end[1] - start[1] ];
    const nanoseconds = (diff[0] * 1e9) + diff[1];
    const milliseconds = nanoseconds / 1e6;
    return milliseconds;
  }
  public getResults() {
    const results = this.records.reduce((acc, record) => {
      const name = record.name.split('-')[1]
      if (acc[name]) {
        acc[name] = this.elapsed(acc[name], record.value)
      } else {
        acc[name] = record.value
      }
      return acc
    }, {})
    results.total = this.elapsed(this.start.value, this.records[this.records.length - 1].value)
    return results
  }
}
`

interface TSClientOptions {
  version?: string
  document: DMMF.Document
  runtimePath: string
  browser?: boolean
  datasources: InternalDatasource[]
  generator?: GeneratorConfig
  platforms?: string[]
  sqliteDatasourceOverrides?: DatasourceOverwrite[]
  schemaDir?: string
  outputDir: string
}

export class TSClient {
  protected readonly dmmf: DMMFClass
  protected readonly document: DMMF.Document
  protected readonly runtimePath: string
  protected readonly browser: boolean
  protected readonly outputDir: string
  protected readonly internalDatasources: InternalDatasource[]
  protected readonly generator?: GeneratorConfig
  protected readonly platforms?: string[]
  protected readonly sqliteDatasourceOverrides?: DatasourceOverwrite[]
  protected readonly version?: string
  protected readonly schemaDir?: string
  constructor({
    document,
    runtimePath,
    browser = false,
    datasources,
    generator,
    platforms,
    sqliteDatasourceOverrides,
    schemaDir,
    outputDir,
  }: TSClientOptions) {
    this.document = document
    this.runtimePath = runtimePath
    this.browser = browser
    this.internalDatasources = datasources
    this.generator = generator
    this.platforms = platforms
    this.sqliteDatasourceOverrides = sqliteDatasourceOverrides
    // We make a deep clone here as otherwise we would serialize circular references
    // which we're building up in the DMMFClass
    this.dmmf = new DMMFClass(JSON.parse(JSON.stringify(document)))
    this.schemaDir = schemaDir
    this.outputDir = outputDir
  }
  public toString() {
    return `${commonCode(this.runtimePath, this.version)}

/**
 * Build tool annotations
 * In order to make \`ncc\` and \`node-file-trace\` happy.
**/

${
  this.platforms
    ? this.platforms
        .map(p => `path.join(__dirname, 'runtime/query-engine-${p}');`)
        .join('\n')
    : ''
}

/**
 * Client
**/

${new PhotonClientClass(
  this.dmmf,
  this.internalDatasources,
  this.outputDir,
  this.browser,
  this.generator,
  this.sqliteDatasourceOverrides,
  this.schemaDir,
)}

${/*new Query(this.dmmf, 'query')*/ ''}

/**
 * Enums
 */

// Based on
// https://github.com/microsoft/TypeScript/issues/3192#issuecomment-261720275

function makeEnum<T extends {[index: string]: U}, U extends string>(x: T) { return x }

${this.dmmf.schema.enums.map(type => new Enum(type)).join('\n\n')}

${Object.values(this.dmmf.modelMap)
  .map(model => new Model(model, this.dmmf))
  .join('\n')}

/**
 * Deep Input Types
 */

${this.dmmf.inputTypes.map(inputType => new InputType(inputType)).join('\n')}

/**
 * Batch Payload for updateMany & deleteMany
 */

export type BatchPayload = {
  count: number
}

/**
 * DMMF
 */

export const dmmf: DMMF.Document = ${JSON.stringify(this.document)}
    `
  }
}

class Datasources {
  constructor(protected readonly internalDatasources: InternalDatasource[]) {}
  public toString() {
    const sources = this.internalDatasources
    return `export type Datasources = {
${indent(sources.map(s => `${s.name}?: string`).join('\n'), 2)}
}`
  }
}

class PhotonClientClass {
  constructor(
    protected readonly dmmf: DMMFClass,
    protected readonly internalDatasources: InternalDatasource[],
    protected readonly outputDir: string,
    protected readonly browser?: boolean,
    protected readonly generator?: GeneratorConfig,
    protected readonly sqliteDatasourceOverrides?: DatasourceOverwrite[],
    protected readonly cwd?: string,
  ) {}
  public toString() {
    const { dmmf } = this

    return `
${new Datasources(this.internalDatasources)}

export type LogLevel = 'INFO' | 'WARN' | 'QUERY'

export type LogOption = LogLevel | {
  level: LogLevel
  /**
   * @default 'stdout'
   */
  emit?: 'event' | 'stdout'
}

export type ErrorFormat = 'pretty' | 'colorless' | 'minimal'

export interface PhotonOptions {
  datasources?: Datasources

  /**
   * @default "pretty"
   */
  errorFormat?: ErrorFormat

  /**
   * @default false
   */
  log?: boolean | LogOption[]

  debug?: any

  /**
   * You probably don't want to use this. \`__internal\` is used by internal tooling.
   */
  __internal?: {
    debug?: boolean
    hooks?: Hooks
    engine?: {
      cwd?: string
      binaryPath?: string
    }
  }
}

export type Hooks = {
  beforeRequest?: (options: {query: string, path: string[], rootField?: string, typeName?: string, document: any}) => any
}

export class Photon {
  private fetcher: PhotonFetcher
  private readonly dmmf: DMMFClass
  private readonly engine: Engine
  private connectionPromise?: Promise<any>
  private errorFormat: ErrorFormat
  constructor(options: PhotonOptions = {}) {
    const useDebug = options.debug === true ? true : typeof options.debug === 'object' ? Boolean(options.debug.library) : false
    if (useDebug) {
      debugLib.enable('photon')
    }
    const debugEngine = options.debug === true ? true : typeof options.debug === 'object' ? Boolean(options.debug.engine) : false

    // datamodel = datamodel without datasources + printed datasources

    const predefinedDatasources = ${
      this.sqliteDatasourceOverrides
        ? indentAllButFirstLine(
            serializeDatasources(this.sqliteDatasourceOverrides),
            4,
          )
        : '[]'
    }
    const inputDatasources = Object.entries(options.datasources || {}).map(([name, url]) => ({ name, url: url! }))
    const datasources = mergeBy(predefinedDatasources, inputDatasources, (source: any) => source.name)

    const internal = options.__internal || {}
    const engineConfig = internal.engine || {}

    if (options.errorFormat) {
      this.errorFormat = options.errorFormat
    } else if (process.env.NODE_ENV === 'production') {
      this.errorFormat = 'minimal'
    } else if (process.env.NO_COLOR) {
      this.errorFormat = 'colorless'
    } else {
      this.errorFormat = 'pretty'
    }

    this.engine = new Engine({
      cwd: engineConfig.cwd || ${getRelativePathResolveStatement(
        this.outputDir,
        this.cwd,
      )},
      debug: debugEngine,
      datamodelPath: path.resolve(__dirname, 'schema.prisma'),
      prismaPath: engineConfig.binaryPath || undefined,
      datasources,
      generator: ${
        this.generator ? JSON.stringify(this.generator) : 'undefined'
      },
      showColors: this.errorFormat === 'pretty'
    })

    this.dmmf = new DMMFClass(dmmf)

    this.fetcher = new PhotonFetcher(this, this.engine, false, internal.hooks, this.errorFormat)
  }
  private async connectEngine(publicCall?: boolean) {
    return this.engine.start()
  }
  connect(): Promise<void> {
    if (this.connectionPromise) {
      return this.connectionPromise
    }
    this.connectionPromise = this.connectEngine(true)
    return this.connectionPromise!
  }
  async disconnect() {
    await this.engine.stop()
  }
  // won't be generated for now
  // private _query?: QueryDelegate
  // get query(): QueryDelegate {
  //   return this._query ? this._query: (this._query = QueryDelegate(this.dmmf, this.fetcher))
  // }
${indent(
  dmmf.mappings
    .filter(m => m.findMany)
    .map(
      m => `
get ${m.plural}(): ${m.model}Delegate {
  return ${m.model}Delegate(this.dmmf, this.fetcher, this.errorFormat, this.collectTimestamps)
}`,
    )
    .join('\n'),
  2,
)}
}
`
  }
}

class QueryPayloadType {
  constructor(protected readonly type: OutputType) {}
  public toString() {
    const { type } = this
    const { name } = type

    const relationFields = type.fields.filter(
      f => f.outputType.kind === 'object' && f.name !== 'node',
    )
    const relationFieldConditions =
      relationFields.length === 0
        ? ''
        : `\n${relationFields
            .map(f =>
              indent(
                `: P extends '${f.name}'\n? ${this.wrapArray(
                  f,
                  `${getPayloadName(
                    (f.outputType.type as DMMF.OutputType).name,
                    Projection.select,
                  )}<Extract${getModelArgName(
                    (f.outputType.type as DMMF.OutputType).name,
                    Projection.select,
                    f.outputType.isList
                      ? DMMF.ModelAction.findMany
                      : DMMF.ModelAction.findOne,
                  )}<S[P]>>`,
                )}`,
                8,
              ),
            )
            .join('\n')}`

    return `\
type ${getPayloadName(
      name,
      Projection.select,
    )}<S extends ${name}Args> = S extends ${name}Args
  ? {
      [P in keyof S] ${relationFieldConditions}
        : never
    } : never
  `
  }
  protected wrapArray(field: DMMF.SchemaField, str: string) {
    if (field.outputType.isList) {
      return `Array<${str}>`
    }
    return str
  }
}

/**
 * Generates the generic type to calculate a payload based on a include statement
 */
class PayloadType {
  constructor(
    protected readonly type: OutputType,
    protected readonly projection: Projection,
  ) {}
  public toString() {
    const { type, projection } = this
    const { name } = type

    const relationFields = type.fields.filter(
      f => f.outputType.kind === 'object',
    )
    const relationFieldConditions =
      relationFields.length === 0
        ? ''
        : `\n${relationFields
            .map(f =>
              indent(
                `: P extends '${f.name}'\n? ${this.wrapArray(
                  f,
                  `${getPayloadName(
                    (f.outputType.type as DMMF.OutputType).name,
                    projection,
                  )}<Extract${getFieldArgName(f, projection)}<S[P]>>${
                    !f.outputType.isRequired && !f.outputType.isList
                      ? ' | null'
                      : ''
                  }`,
                )}`,
                8,
              ),
            )
            .join('\n')}`

    const hasScalarFields =
      type.fields.filter(f => f.outputType.kind !== 'object').length > 0
    const projectionName =
      projection === Projection.select
        ? getSelectName(name)
        : getIncludeName(name)
    return `\
export type ${getPayloadName(
      name,
      projection,
    )}<S extends boolean | ${projectionName}> = S extends true
  ? ${name}
  : S extends ${projectionName}
  ? {
      [P in CleanupNever<MergeTruthyValues<${
        projection === Projection.select ? '{}' : getDefaultName(name)
      }, S>>]${
      hasScalarFields
        ? `: P extends ${getScalarsName(name)}
        ? ${name}[P]`
        : ''
    }${relationFieldConditions}
        : never
    }
   : never`
  }
  protected wrapArray(field: DMMF.SchemaField, str: string) {
    if (field.outputType.isList) {
      return `Array<${str}>`
    }
    return str
  }
}

/**
 * Generates the default selection of a model
 */
class ModelDefault {
  constructor(
    protected readonly model: DMMF.Model,
    protected readonly dmmf: DMMFClass,
  ) {}
  public toString() {
    const { model } = this
    return `\
type ${getDefaultName(model.name)} = {
${indent(
  model.fields
    .filter(f => this.isDefault(f))
    .map(f => `${f.name}: true`)
    .join('\n'),
  tab,
)}
}
`
  }
  protected isDefault(field: DMMF.Field) {
    if (field.kind !== 'object') {
      return true
    }

    const model = this.dmmf.datamodel.models.find(m => field.type === m.name)
    return model!.isEmbedded
  }
}

export class Model {
  protected outputType?: OutputType
  protected mapping: DMMF.Mapping
  constructor(
    protected readonly model: DMMF.Model,
    protected readonly dmmf: DMMFClass,
  ) {
    const outputType = dmmf.outputTypeMap[model.name]
    this.outputType = new OutputType(outputType)
    this.mapping = dmmf.mappings.find(m => m.model === model.name)!
  }
  protected get argsTypes() {
    const { mapping, model } = this
    const argsTypes: Stringifiable[] = []
    for (const action in DMMF.ModelAction) {
      const fieldName = mapping[action]
      if (!fieldName) {
        continue
      }
      const field = this.dmmf.getField(fieldName)
      if (!field) {
        throw new Error(
          `Oops this must not happen. Could not find field ${fieldName} on either Query or Mutation`,
        )
      }
      if (action === 'updateMany' || action === 'deleteMany') {
        argsTypes.push(
          new MinimalArgsType(field.args, model, action as DMMF.ModelAction),
        )
      } else {
        argsTypes.push(
          new ArgsType(field.args, model, action as DMMF.ModelAction),
        )
      }
    }

    argsTypes.push(new ArgsType([], model))

    return argsTypes
  }
  public toString() {
    const { model, outputType } = this

    if (!outputType) {
      return ''
    }

    const scalarFields = model.fields.filter(f => f.kind !== 'object')

    return `
/**
 * Model ${model.name}
 */

export type ${model.name} = {
${indent(
  model.fields
    .filter(f => f.kind !== 'object')
    .map(field => new OutputField(field).toString())
    .join('\n'),
  tab,
)}
}

${
  scalarFields.length > 0
    ? `export type ${getScalarsName(model.name)} = ${
        scalarFields.length > 0
          ? scalarFields.map(f => `'${f.name}'`).join(' | ')
          : ``
      }
  `
    : ''
}

export type ${getSelectName(model.name)} = {
${indent(
  outputType.fields
    .map(
      f =>
        `${f.name}?: boolean` +
        (f.outputType.kind === 'object'
          ? ` | ${getFieldArgName(f, Projection.select)}Optional`
          : ''),
    )
    .join('\n'),
  tab,
)}
}

export type ${getIncludeName(model.name)} = {
${indent(
  outputType.fields
    .filter(f => f.outputType.kind === 'object')
    .map(
      f =>
        `${f.name}?: boolean` +
        (f.outputType.kind === 'object'
          ? ` | ${getFieldArgName(f, Projection.include)}Optional`
          : ''),
    )
    .join('\n'),
  tab,
)}
}

${new ModelDefault(model, this.dmmf)}

${new PayloadType(this.outputType!, Projection.select)}

${new PayloadType(this.outputType!, Projection.include)}

${new ModelDelegate(this.outputType!, this.dmmf)}

// Custom InputTypes
${this.argsTypes.map(String).join('\n')}
`
  }
}

export class Query {
  constructor(
    protected readonly dmmf: DMMFClass,
    protected readonly operation: 'query' | 'mutation',
  ) {}
  public toString() {
    const { dmmf, operation } = this
    const queryName = capitalize(operation)
    const mappings = dmmf.mappings.map(mapping => ({
      name: mapping.model,
      mapping: Object.entries(mapping).filter(([key]) =>
        isQueryAction(key as DMMF.ModelAction, operation),
      ),
    }))
    const queryType = operation === 'query' ? dmmf.queryType : dmmf.mutationType
    const outputType = new OutputType(queryType)
    return `\
/**
 * ${queryName}
 */

export type ${queryName}Args = {
${indent(
  flatMap(mappings, ({ name, mapping }) =>
    mapping
      .filter(([action, field]) => field)
      .map(
        ([action, field]) =>
          `${field}?: ${getModelArgName(
            name,
            Projection.select,
            action as DMMF.ModelAction,
          )}`,
      ),
  ).join('\n'),
  tab,
)}
}

${new QueryPayloadType(outputType)}

${new QueryDelegate(outputType)}
`
  }
}
export class ModelDelegate {
  constructor(
    protected readonly outputType: OutputType,
    protected readonly dmmf: DMMFClass,
  ) {}
  public toString() {
    const { fields, name } = this.outputType
    const mapping = this.dmmf.mappings.find(m => m.model === name)!
    const actions = Object.entries(mapping).filter(
      ([key, value]) =>
        key !== 'model' && key !== 'plural' && key !== 'aggregate' && value,
    )

    const listConstraint = getModelArgName(
      name,
      /*projection*/ undefined,
      DMMF.ModelAction.findMany,
    )
    // TODO: The following code needs to be split up and is a mess
    return `\
export interface ${name}Delegate {
  <T extends ${listConstraint}>(args?: Subset<T, ${getModelArgName(
      name,
      undefined,
      DMMF.ModelAction.findMany,
    )}>): ${getSelectReturnType({
      name,
      actionName: DMMF.ModelAction.findMany,
      hideCondition: true,
      isField: false,
      renderPromise: true,
      projection: Projection.select,
    })}
${indent(
  actions
    .map(
      ([actionName]: [any, any]) =>
        `${actionName}<T extends ${getModelArgName(
          name,
          /*projection*/ undefined,
          actionName,
        )}>(
  args${
    actionName === DMMF.ModelAction.findMany ? '?' : ''
  }: Subset<T, ${getModelArgName(name, undefined, actionName)}>
): ${getSelectReturnType({ name, actionName, projection: Projection.select })}`,
    )
    .join('\n'),
  tab,
)}
  count(): Promise<number>
}
function ${name}Delegate(dmmf: DMMFClass, fetcher: PhotonFetcher, errorFormat: ErrorFormat, collectTimestamps?: CollectTimestamps): ${name}Delegate {
  const ${name} = <T extends ${listConstraint}>(args: Subset<T, ${getModelArgName(
      name,
      undefined,
      DMMF.ModelAction.findMany,
    )}>) => new ${name}Client<${getSelectReturnType({
      name,
      actionName: DMMF.ModelAction.findMany,
      projection: Projection.select,
    })}>(dmmf, fetcher, 'query', '${mapping.findMany}', '${
      mapping.plural
    }', args, [], errorFormat)
${indent(
  actions
    .map(([actionName, fieldName]: [any, any]) =>
      actionName === 'deleteMany' || actionName === 'updateMany'
        ? `${name}.${actionName} = (args: ${getModelArgName(
            name,
            undefined,
            actionName,
          )}) => new ${name}Client<Promise<BatchPayload>>(${renderInitialClientArgs(
            actionName,
            fieldName,
            mapping,
          )})`
        : `${name}.${actionName} = <T extends ${getModelArgName(
            name,
            /*projection*/ undefined,
            actionName as DMMF.ModelAction,
          )}>(args: Subset<T, ${getModelArgName(
            name,
            Projection.select,
            actionName as DMMF.ModelAction,
          )}>) => ${
            actionName !== 'findMany' ? `args.select ? ` : ''
          }new ${name}Client<${getSelectReturnType({
            name,
            actionName,
            hideCondition: false,
            isField: true,
            projection: Projection.select,
          })}>(${renderInitialClientArgs(actionName, fieldName, mapping)})${
            actionName !== 'findMany'
              ? ` : new ${name}Client<${(getType(
                  name,
                  actionName === 'findMany',
                ),
                actionName === 'findOne')}>(${renderInitialClientArgs(
                  actionName,
                  fieldName,
                  mapping,
                )})`
              : ''
          }`,
    )
    .join('\n'),
  tab,
)}
  ${name}.count = () => new ${name}Client<number>(dmmf, fetcher, 'query', '${mapping.aggregate!}', '${
      mapping.plural
    }.count', {}, ['count'], errorFormat)
  return ${name} as any // any needed because of https://github.com/microsoft/TypeScript/issues/31335
}

export class ${name}Client<T> implements Promise<T> {
  private _callsite: any
  private _requestPromise?: Promise<any>
  constructor(
    private readonly _dmmf: DMMFClass,
    private readonly _fetcher: PhotonFetcher,
    private readonly _queryType: 'query' | 'mutation',
    private readonly _rootField: string,
    private readonly _clientMethod: string,
    private readonly _args: any,
    private readonly _dataPath: string[],
    private readonly _errorFormat: ErrorFormat,
    private readonly _collectTimestamps: CollectTimestamps,
    private _isList = false
  ) {
    // Timestamps for performance checks
    this._collectTimestamps = new CollectTimestamps("PhotonClient")
    // @ts-ignore
    if (process.env.NODE_ENV !== 'production' && this._errorFormat !== 'minimal') {
      const error = new Error()
      if (error && error.stack) {
        const stack = error.stack
        this._callsite = stack
      }
    }
  }
  readonly [Symbol.toStringTag]: 'PhotonPromise'

${indent(
  fields
    .filter(f => f.outputType.kind === 'object')
    .map(f => {
      const fieldTypeName = (f.outputType.type as DMMF.OutputType).name
      return `
${f.name}<T extends ${getFieldArgName(
        f,
      )} = {}>(args?: Subset<T, ${getFieldArgName(f)}>): ${getSelectReturnType({
        name: fieldTypeName,
        actionName: f.outputType.isList
          ? DMMF.ModelAction.findMany
          : DMMF.ModelAction.findOne,
        hideCondition: false,
        isField: true,
        renderPromise: true,
        fieldName: f.name,
        projection: Projection.select,
      })} {
  const prefix = this._dataPath.includes('select') ? 'select' : this._dataPath.includes('include') ? 'include' : 'select'
  const dataPath = [...this._dataPath, prefix, '${f.name}']
  const newArgs = deepSet(this._args, dataPath, args || true)
  this._isList = ${f.outputType.isList}
  return new ${getFieldTypeName(f)}Client<${getSelectReturnType({
        name: fieldTypeName,
        actionName: f.outputType.isList
          ? DMMF.ModelAction.findMany
          : DMMF.ModelAction.findOne,
        hideCondition: false,
        isField: true,
        renderPromise: true,
        projection: Projection.select,
      })}>(this._dmmf, this._fetcher, this._queryType, this._rootField, this._clientMethod, newArgs, dataPath, this._errorFormat, this._collectTimestamps, this._isList) as any
}`
    })
    .join('\n'),
  2,
)}

  private get _document() {
    const { _rootField: rootField } = this
    this._collectTimestamps && this._collectTimestamps.record("Pre-makeDocument")
    const document = makeDocument({
      dmmf: this._dmmf,
      rootField,
      rootTypeName: this._queryType,
      select: this._args
    })
    this._collectTimestamps && this._collectTimestamps.record("Post-makeDocument")
    try {
      this._collectTimestamps && this._collectTimestamps.record("Pre-document.validate")
      document.validate(this._args, false, this._clientMethod, this._errorFormat)
      this._collectTimestamps && this._collectTimestamps.record("Post-document.validate")
    } catch (e) {
      const x: any = e
      if (this._errorFormat !== 'minimal' && x.render) {
        if (this._callsite) {
          e.message = x.render(this._callsite)
        }
      }
      throw e
    }
    this._collectTimestamps && this._collectTimestamps.record("Pre-transformDocument")
    const transformedDocument = transformDocument(document)
    this._collectTimestamps && this._collectTimestamps.record("Post-transformDocument")
    return transformedDocument
  }

  /**
   * Attaches callbacks for the resolution and/or rejection of the Promise.
   * @param onfulfilled The callback to execute when the Promise is resolved.
   * @param onrejected The callback to execute when the Promise is rejected.
   * @returns A Promise for the completion of which ever callback is executed.
   */
  then<TResult1 = T, TResult2 = never>(
    onfulfilled?: ((value: T) => TResult1 | Promise<TResult1>) | undefined | null,
    onrejected?: ((reason: any) => TResult2 | Promise<TResult2>) | undefined | null,
  ): Promise<TResult1 | TResult2> {
    if (!this._requestPromise){
      this._requestPromise = this._fetcher.request<T>(this._document, this._dataPath, this._rootField, '${name}', this._isList, this._callsite, this._collectTimestamps)
    }
    return this._requestPromise!.then(onfulfilled, onrejected)
  }

  /**
   * Attaches a callback for only the rejection of the Promise.
   * @param onrejected The callback to execute when the Promise is rejected.
   * @returns A Promise for the completion of the callback.
   */
  catch<TResult = never>(
    onrejected?: ((reason: any) => TResult | Promise<TResult>) | undefined | null,
  ): Promise<T | TResult> {
    if (!this._requestPromise) {
      this._requestPromise = this._fetcher.request<T>(this._document, this._dataPath, this._rootField, '${name}', this._isList, this._callsite, this._collectTimestamps)
    }
    return this._requestPromise!.catch(onrejected)
  }

  /**
   * Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The
   * resolved value cannot be modified from the callback.
   * @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected).
   * @returns A Promise for the completion of the callback.
   */
  finally(onfinally?: (() => void) | undefined | null): Promise<T> {
    if (!this._requestPromise) {
      this._requestPromise = this._fetcher.request<T>(this._document, this._dataPath, this._rootField, '${name}', this._isList, this._callsite, this._collectTimestamps)
    }
    return this._requestPromise!.finally(onfinally)
  }
}
    `
  }
}

export class QueryDelegate {
  constructor(protected readonly outputType: OutputType) {}
  public toString() {
    const name = this.outputType.name
    return `\
interface ${name}Delegate {
  <T extends ${name}Args>(args: Subset<T,${name}Args>): Promise<${getPayloadName(
      name,
      Projection.select,
    )}<T>>
}
function ${name}Delegate(dmmf: DMMFClass, fetcher: PhotonFetcher): ${name}Delegate {
  const ${name} = <T extends ${name}Args>(args: ${name}Args) => new ${name}Client<T>(dmmf, fetcher, args, [])
  return ${name}
}

class ${name}Client<T extends ${name}Args, U = ${getPayloadName(
      name,
      Projection.select,
    )}<T>> implements Promise<U> {
  constructor(private readonly dmmf: DMMFClass, private readonly fetcher: PhotonFetcher, private readonly args: ${name}Args, private readonly _dataPath: []) {}

  readonly [Symbol.toStringTag]: 'Promise'

  protected get document() {
    const rootField = Object.keys(this.args)[0]
    const document = makeDocument({
      dmmf: this.dmmf,
      rootField,
      rootTypeName: 'query',
      // @ts-ignore
      select: this.args[rootField]
    })
    // @ts-ignore
    document.validate(this.args[rootField], true)
    return document
  }

  /**
   * Attaches callbacks for the resolution and/or rejection of the Promise.
   * @param onfulfilled The callback to execute when the Promise is resolved.
   * @param onrejected The callback to execute when the Promise is rejected.
   * @returns A Promise for the completion of which ever callback is executed.
   */
  then<TResult1 = U, TResult2 = never>(
    onfulfilled?: ((value: U) => TResult1 | Promise<TResult1>) | undefined | null,
    onrejected?: ((reason: any) => TResult2 | Promise<TResult2>) | undefined | null,
  ): Promise<TResult1 | TResult2> {
    return this.fetcher.request<U>(this.document, this._dataPath, undefined, '${name}').then(onfulfilled, onrejected)
  }

  /**
   * Attaches a callback for only the rejection of the Promise.
   * @param onrejected The callback to execute when the Promise is rejected.
   * @returns A Promise for the completion of the callback.
   */
  catch<TResult = never>(
    onrejected?: ((reason: any) => TResult | Promise<TResult>) | undefined | null,
  ): Promise<U | TResult> {
    return this.fetcher.request<U>(this.document, this._dataPath, undefined, '${name}').catch(onrejected)
  }
}
    `
  }
}

export class InputField {
  constructor(
    protected readonly field: DMMF.SchemaArg,
    protected readonly prefixFilter = false,
  ) {}
  public toString() {
    const { field } = this
    let fieldType
    if (Array.isArray(field.inputType)) {
      fieldType = flatMap(field.inputType, t =>
        typeof t.type === 'string'
          ? GraphQLScalarToJSTypeTable[t.type] || t.type
          : this.prefixFilter
          ? `Base${t.type.name}`
          : t.type.name,
      ).join(' | ')
    }
    const fieldInputType = field.inputType[0]
    const optionalStr = fieldInputType.isRequired ? '' : '?'
    if (fieldInputType.isList) {
      fieldType = `Enumerable<${fieldType}>`
    }
    const nullableStr = !fieldInputType.isRequired ? ' | null' : ''
    return `${field.name}${optionalStr}: ${fieldType}${nullableStr}`
  }
}

export class OutputField {
  constructor(protected readonly field: BaseField) {}
  public toString() {
    const { field } = this
    // ENUMTODO
    let fieldType =
      typeof field.type === 'string'
        ? GraphQLScalarToJSTypeTable[field.type] || field.type
        : field.type[0].name
    if (Array.isArray(fieldType)) {
      fieldType = fieldType[0]
    }
    const arrayStr = field.isList ? `[]` : ''
    const nullableStr = !field.isRequired && !field.isList ? ' | null' : ''
    return `${field.name}: ${fieldType}${arrayStr}${nullableStr}`
  }
}

export class OutputType {
  public name: string
  public fields: DMMF.SchemaField[]
  constructor(protected readonly type: DMMF.OutputType) {
    this.name = type.name
    this.fields = type.fields
  }
  public toString() {
    const { type } = this
    return `
export type ${type.name} = {
${indent(
  type.fields
    .map(field => new OutputField({ ...field, ...field.outputType }).toString())
    .join('\n'),
  tab,
)}
}`
  }
}

interface Stringifiable {
  toString(): string
}

export class MinimalArgsType {
  constructor(
    protected readonly args: DMMF.SchemaArg[],
    protected readonly model: DMMF.Model,
    protected readonly action?: DMMF.ModelAction,
  ) {}
  public toString() {
    const { action, args } = this
    const { name } = this.model

    return `
/**
 * ${name} ${action ? action : 'without action'}
 */
export type ${getModelArgName(name, undefined, action)} = {
${indent(args.map(arg => new InputField(arg).toString()).join('\n'), tab)}
}
`
  }
}

export class ArgsType {
  constructor(
    protected readonly args: DMMF.SchemaArg[],
    protected readonly model: DMMF.Model,
    protected readonly action?: DMMF.ModelAction,
  ) {}
  public toString() {
    const { action, args } = this
    const { name } = this.model

    const bothArgsOptional: DMMF.SchemaArg[] = [
      {
        name: 'select',
        inputType: [
          {
            type: getSelectName(name),
            kind: 'object',
            isList: false,
            isRequired: false,
          },
        ],
      },
      {
        name: 'include',
        inputType: [
          {
            type: getIncludeName(name),
            kind: 'object',
            isList: false,
            isRequired: false,
          },
        ],
      },
      ...args,
    ]

    const bothArgsRequired: DMMF.SchemaArg[] = [
      {
        name: 'select',
        inputType: [
          {
            type: getSelectName(name),
            kind: 'object',
            isList: false,
            isRequired: true,
          },
        ],
      },
      {
        name: 'include',
        inputType: [
          {
            type: getIncludeName(name),
            kind: 'object',
            isList: false,
            isRequired: true,
          },
        ],
      },
      ...args,
    ]

    const selectArgsRequired: DMMF.SchemaArg[] = [
      {
        name: 'select',
        inputType: [
          {
            type: getSelectName(name),
            kind: 'object',
            isList: false,
            isRequired: true,
          },
        ],
      },
      ...args,
    ]

    const selectArgsOptional: DMMF.SchemaArg[] = [
      {
        name: 'select',
        inputType: [
          {
            type: getSelectName(name),
            kind: 'object',
            isList: false,
            isRequired: false,
          },
        ],
      },
      ...args,
    ]

    const includeArgsRequired: DMMF.SchemaArg[] = [
      {
        name: 'include',
        inputType: [
          {
            type: getIncludeName(name),
            kind: 'object',
            isList: false,
            isRequired: true,
          },
        ],
      },
      ...args,
    ]

    const includeArgsOptional: DMMF.SchemaArg[] = [
      {
        name: 'include',
        inputType: [
          {
            type: getIncludeName(name),
            kind: 'object',
            isList: false,
            isRequired: false,
          },
        ],
      },
      ...args,
    ]

    return `
/**
 * ${name} ${action ? action : 'without action'}
 */
export type ${getModelArgName(name, undefined, action)} = {
${indent(
  bothArgsOptional.map(arg => new InputField(arg).toString()).join('\n'),
  tab,
)}
}

export type ${getModelArgName(name, undefined, action)}Required = {
${indent(
  bothArgsRequired.map(arg => new InputField(arg).toString()).join('\n'),
  tab,
)}
}

export type ${getModelArgName(name, Projection.select, action)} = {
${indent(
  selectArgsRequired.map(arg => new InputField(arg).toString()).join('\n'),
  tab,
)}
}

export type ${getModelArgName(name, Projection.select, action)}Optional = {
${indent(
  selectArgsOptional.map(arg => new InputField(arg).toString()).join('\n'),
  tab,
)}
}

export type ${getModelArgName(name, Projection.include, action)} = {
${indent(
  includeArgsRequired.map(arg => new InputField(arg).toString()).join('\n'),
  tab,
)}
}

export type ${getModelArgName(name, Projection.include, action)}Optional = {
${indent(
  includeArgsOptional.map(arg => new InputField(arg).toString()).join('\n'),
  tab,
)}
}

export type Extract${getModelArgName(
      name,
      Projection.select,
      action,
    )}<S extends undefined | boolean | ${getModelArgName(
      name,
      Projection.select,
      action,
    )}Optional> = S extends undefined
  ? false
  : S extends boolean
  ? S
  : S extends ${getModelArgName(name, Projection.select, action)}
  ? S['select']
  : true

export type Extract${getModelArgName(
      name,
      Projection.include,
      action,
    )}<S extends undefined | boolean | ${getModelArgName(
      name,
      Projection.include,
      action,
    )}Optional> = S extends undefined
  ? false
  : S extends boolean
  ? S
  : S extends ${getModelArgName(name, Projection.include, action)}
  ? S['include']
  : true

`
  }
}

export class InputType {
  constructor(protected readonly type: DMMF.InputType) {}
  public toString() {
    const { type } = this
    // TO DISCUSS: Should we rely on TypeScript's error messages?
    const body = `{
${indent(
  type.fields
    .map(arg => new InputField(arg /*, type.atLeastOne && !type.atMostOne*/))
    .join('\n'),
  tab,
)}
}`
    //     if (type.atLeastOne && !type.atMostOne) {
    //       return `export type Base${type.name} = ${body}
    // export type ${type.name} = AtLeastOne<Base${type.name}>
    //       `
    //     } else if (type.atLeastOne && type.atMostOne) {
    //       return `export type Base${type.name} = ${body}
    // export type ${type.name} = OnlyOne<Base${type.name}>
    //       `
    //     }
    return `
export type ${type.name} = ${body}`
  }
}

export class Enum {
  constructor(protected readonly type: DMMF.Enum) {}
  public toString() {
    const { type } = this

    return `export const ${type.name} = makeEnum({
${indent(type.values.map(v => `${v}: '${v}'`).join(',\n'), tab)}
})

export type ${type.name} = (typeof ${type.name})[keyof typeof ${type.name}]\n`
  }
}


const childProcess = require('child_process')
const { promisify } = require('util')
const exec = promisify(childProcess.exec)
const c = require('./colors')

async function main() {
  const localPath = getLocalPackagePath()
  try {
    if (localPath) {
      await run('node', [localPath, 'generate'])
      return
    }

    const installedGlobally = await isInstalledGlobally()
    if (installedGlobally) {
      await run('prisma2', ['generate'])
      return
    }
  } catch (e) {
    console.error(e)
  }
  throw new Error(
    `In order to use "@prisma/photon", please install prisma2. You can install it with "npm add -D prisma2".`,
  )
}

function getLocalPackagePath() {
  let packagePath
  try {
    packagePath = require.resolve('prisma2/package.json')
  } catch (e) {
    return null
  }

  if (packagePath) {
    const pkg = require('prisma2/package.json')
    const photonVersion = require('../package.json').version
    if (pkg.version !== photonVersion) {
      console.error(
        `${c.red('Error')} ${c.bold(
          '@prisma/photon',
        )} and the locally installed ${c.bold(
          'prisma2',
        )} must have the same version:
  ${c.bold(`@prisma/photon@${photonVersion}`)} doesn't match ${c.bold(
          `prisma2@${pkg.version}`,
        )}`,
      )
    }
    return require.resolve('prisma2')
  }

  return null
}

async function isInstalledGlobally() {
  try {
    await exec('prisma2 -v')
    return true
  } catch (e) {
    return false
  }
}

if (!process.env.SKIP_GENERATE) {
  main().catch(e => {
    if (e.stderr) {
      if (e.stderr.includes(`Can't find schema.prisma`)) {
        console.error(
          `${c.yellow('warning')} @prisma/photon needs a ${c.bold(
            'schema.prisma',
          )} to function, but couldn't find it.
        Please either create one manually or use ${c.bold('prisma2 init')}.
        Once you created it, run ${c.bold('prisma2 generate')}.
        To keep Prisma related things separate, we recommend creating it in a subfolder called ${c.underline(
          './prisma',
        )} like so: ${c.underline('./prisma/schema.prisma')}\n`,
        )
      } else {
        console.error(e.stderr)
      }
    } else {
      console.error(e)
    }
    process.exit(0)
  })
}

function run(cmd, params) {
  const child = childProcess.spawn(cmd, params, {
    stdio: ['pipe', 'inherit', 'inherit'],
  })

  return new Promise((resolve, reject) => {
    child.on('close', () => {
      resolve()
    })
    child.on('exit', code => {
      if (code === 0) {
        resolve()
      } else {
        reject(code)
      }
    })
    child.on('error', () => {
      reject()
    })
  })
}


import Routable from '../../mixins/routable'

keep keep keep keep add keep keep keep keep

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

import { test } from '@util/testing'
import VTab from './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('.tabs__item')[0].vNode.elm.tagName).toBe('A')
    wrapper.setProps({ disabled: true })
    expect(wrapper.find('.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()
  })

  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('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
    })

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

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


export default {
  name: 'returnable',

  data: () => ({
    originalValue: null
  }),

  props: {
    returnValue: null
  },

  watch: {
    isActive (val) {
      if (val) {
        this.originalValue = this.returnValue
      } else {
        this.$emit('update:returnValue', this.originalValue)
      }
    }
  },

  methods: {
    save (value) {
      this.originalValue = value
      this.$emit('update:returnValue', value)
      this.isActive = false
    }
  }
}


.mx-auto
  margin-left: auto !important
  margin-right: auto !important

$i = 0
for level, spacer in $spacers
  .mt-{$i}
    margin-top: spacer.y !important
  .mr-{$i}
    margin-right: spacer.x !important
  .mb-{$i}
    margin-bottom: spacer.y !important
  .ml-{$i}
    margin-left: spacer.x !important
  .mx-{$i}
    margin-left: spacer.x !important
    margin-right: spacer.x !important
  .my-{$i}
    margin-top: spacer.y !important
    margin-bottom: spacer.y !important
  .ma-{$i}
    margin: spacer.y spacer.x !important

  .pt-{$i}
    padding-top: spacer.y !important
  .pr-{$i}
    padding-right: spacer.x !important
  .pb-{$i}
    padding-bottom: spacer.y !important
  .pl-{$i}
    padding-left: spacer.x !important
  .px-{$i}
    padding-left: spacer.x !important
    padding-right: spacer.x !important
  .py-{$i}
    padding-top: spacer.y !important
    padding-bottom: spacer.y !important
  .pa-{$i}
    padding: spacer.y spacer.x !important

  $i = $i + 1


var types = require('babel-types');
var pathLib = require('path');
var wrapListener = require('babel-plugin-detective/wrap-listener');

module.exports = wrapListener(listener, 'transform-stylus-paths');

function listener(path, file, opts) {
  const regex = /((?:\.\.?\/)+)/gi
  if (path.isLiteral() && path.node.value.endsWith('.styl')) {
    const matches = regex.exec(path.node.value)
    if (!matches) return
    const m = matches[0].startsWith('./') ? matches[0].substr(2) : matches[0]
    path.node.value = path.node.value.replace(matches[0], `${m}../src/`)
  }
}


keep keep keep keep keep replace replace replace replace keep keep keep keep

// Mixins
import Routable from '../../mixins/routable'
import {
  inject as RegistrableInject
} from '../../mixins/registrable'

export default {
  name: 'v-tab',

  mixins: [
    RegistrableInject('tabs', 'v-tab', 'v-tabs'),
    Routable
  ],

  inject: ['tabClick'],

  data () {
    return {
      isActive: false
    }
  },

  props: {
    activeClass: {
      type: String,
      default: 'tabs__item--active'
    },
    ripple: {
      type: [Boolean, Object],
      default: true
    }
  },

  computed: {
    classes () {
      return {
        'tabs__item': true,
        'tabs__item--disabled': this.disabled,
        [this.activeClass]: !this.to && this.isActive
      }
    },
    action () {
      const to = this.to || this.href

      if (typeof to === 'string') return to.replace('#', '')
      if (to === Object(to) &&
        to.hasOwnProperty('path')
      ) return to.path

      return this
    }
  },

  watch: {
    $route: {
      immediate: true,
      handler: 'onRouteChange'
    }
  },

  mounted () {
    this.tabs.register(this)
  },

  beforeDestroy () {
    this.tabs.unregister(this)
  },

  methods: {
    click (e) {
      // If user provides an
      // actual link, do not
      // prevent default
      if (this.href &&
        this.href.indexOf('#') > -1
      ) e.preventDefault()

      this.$emit('click', e)

      this.to || this.tabClick(this)
    },
    onRouteChange () {
      if (!this.to) return

      this.$nextTick(() => {
        if (this.$el.firstChild.className.indexOf(this.activeClass) > -1) {
          this.tabClick(this)
        }
      })
    },
    toggle (action) {
      this.isActive = (action === this) || (action === this.action)
    }
  },

  render (h) {
    const link = this.generateRouteLink()
    const { data } = link

    // If disabled, use div as anchor tags do not support
    // being disabled
    const tag = this.disabled ? 'div' : link.tag

    return h('div', {
      staticClass: 'tabs__div'
    }, [h(tag, data, this.$slots.default)])
  }
}


import { compileToFunctions } from 'vue-template-compiler'
import VDatePickerMonthTable from './VDatePickerMonthTable'
import { test } from '@util/testing'

test('VDatePickerMonthTable.js', ({ mount }) => {
  it('should render component and match snapshot', () => {
    const wrapper = mount(VDatePickerMonthTable, {
      propsData: {
        tableDate: '2005',
        current: '2005-05',
        value: '2005-11'
      }
    })

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

  it('should watch tableDate value and run transition', async () => {
    const wrapper = mount(VDatePickerMonthTable, {
      propsData: {
        tableDate: '2005',
        current: '2005-05',
        value: '2005-11'
      }
    })

    wrapper.setProps({
      tableDate: '2006'
    })
    await wrapper.vm.$nextTick()
    expect(wrapper.find('table')[0].element.className).toBe('tab-transition-enter tab-transition-enter-active')
  })

  it('should watch tableDate value and run reverse transition', async () => {
    const wrapper = mount(VDatePickerMonthTable, {
      propsData: {
        tableDate: '2005',
        current: '2005-05',
        value: '2005-11'
      }
    })

    wrapper.setProps({
      tableDate: '2004'
    })
    await wrapper.vm.$nextTick()
    expect(wrapper.find('table')[0].element.className).toBe('tab-reverse-transition-enter tab-reverse-transition-enter-active')
  })

  it('should emit event when month button is clicked', () => {
    const wrapper = mount(VDatePickerMonthTable, {
      propsData: {
        tableDate: '2005',
        current: '2005-05',
        value: '2005-11'
      }
    })

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

    wrapper.find('tbody button')[0].trigger('click')
    expect(input).toBeCalledWith('2005-01')
  })

  it('should not emit event when disabled month button is clicked', () => {
    const wrapper = mount(VDatePickerMonthTable, {
      propsData: {
        tableDate: '2005',
        current: '2005-05',
        value: '2005-11',
        allowedDates: () => false
      }
    })

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

    wrapper.find('tbody button')[0].trigger('click')
    expect(input).not.toBeCalled()
  })

  it('should emit tableDate event when scrolled and scrollable', () => {
    const wrapper = mount(VDatePickerMonthTable, {
      propsData: {
        tableDate: '2005',
        scrollable: true
      }
    })

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

    wrapper.trigger('wheel')
    expect(tableDate).toBeCalledWith('2006')
  })

  it('should not emit tableDate event when scrolled and not scrollable', () => {
    const wrapper = mount(VDatePickerMonthTable, {
      propsData: {
        tableDate: '2005'
      }
    })

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

    wrapper.trigger('wheel')
    expect(tableDate).not.toBeCalled()
  })

  // TODO
  it.skip('should emit tableDate event when swiped', () => {
    const wrapper = mount(VDatePickerMonthTable, {
      propsData: {
        tableDate: '2005'
      }
    })

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

    wrapper.trigger('touchstart')
    wrapper.trigger('touchend')
    expect(tableDate).toBeCalledWith(2006)
  })

  it('should change tableDate when touch is called', () => {
    const wrapper = mount(VDatePickerMonthTable, {
      propsData: {
        tableDate: '2005'
      }
    })

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

    wrapper.vm.touch(1)
    expect(tableDate).toBeCalledWith('2006')
    wrapper.vm.touch(-1)
    expect(tableDate).toBeCalledWith('2004')
  })
})


import { test } from '@util/testing'
import VParallax from '@components/VParallax'

test('VParallax.js', ({ mount }) => {
  it('should render', async () => {
    const wrapper = mount(VParallax, {
      attachToDocument: true
    })

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

  it('should use alt tag when supplied', async () => {
    const wrapper = mount(VParallax, {
      attachToDocument: true,
      propsData: {
        alt: 'name'
      }
    })

    const img = wrapper.find('img')[0]
    expect(img.getAttribute('alt')).toBe('name')
    expect(wrapper.html()).toMatchSnapshot()
  })
})


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

selects($material)
  &.input-group
    &--editable,
    &--overflow,
    &--segmented
      .input-group__input:hover
        background: $material.cards

      &.input-group--focused .input-group__input
        background: $material.cards

theme(selects, "input-group--select")

.input-group--select
  // TODO: revisit this
  .input-group--select__autocomplete
    display: block
    height: 0px

    &--index
      background-color: transparent !important

  .input-group__append-icon
    transition: .3s $transition.linear-out-slow-in

    &.input-group__icon-clearable
      transition: none

  &:not(.input-group--dirty),
  &.input-group--focused
    .input-group--select__autocomplete
      padding-bottom: 1px
      height: 30px

  &.input-group--focused
    .input-group--select__autocomplete
      display: inline-block
      opacity: 1

    &.input-group--select--selecting-index
      .input-group--select__autocomplete
        opacity: 0

    &.input-group--open
      .input-group__append-icon:not(.input-group__icon-clearable)
        transform: rotate(-180deg)

  .input-group__input
    cursor: pointer

  &.input-group--disabled
    cursor: default
    pointer-events: none

.input-group--select
  .input-group__selections
    align-items: center
    display: flex
    flex-wrap: wrap
    position: relative
    width: 100%

    &__comma
      display: inline-flex
      font-size: $input-font-size
      padding: 3px 4px 3px 0

      &--active
        color: inherit

  .menu
    display: inline

  .fade-transition-leave-active
    position: absolute
    left: 0

  &.input-group--autocomplete.input-group--search-focused
    .input-group__selections__comma
      display: none

.input-group--autocomplete
  .input-group__selections
    cursor: text

.input-group
  &.input-group--chips
    .input-group__input
      padding-top: 0
      padding-bottom: 0

  &.input-group--overflow,
  &.input-group--editable,
  &.input-group--segmented
    padding: 0

    .input-group__append-icon
      padding: 0

    .input-group__selections,
    input
      height: 48px

    // Handle weird transition issue
    .input-group__selections__comma, input
      top: 0
      left: 0
      padding-left: 16px

    .input-group__selections
      width: calc(100% - 55px)

      .btn
        border-radius: 0
        margin: 0
        height: 48px
        width: 100%

        .btn__content
          justify-content: start

    .input-group__input
      transition: .3s $transition.swing

    &.input-group--focused
      .input-group__input
        elevation(8)

    label
      left: 16px !important
      top: 9px !important

    .input-group__details
      &:after
        display: none

    .input-group__input
      padding: 0

      &:before
        content: ''
        position: absolute
        left: 0
        width: 100%
        height: 1px
        top: 0
        transition: .3s $transition.swing

    .input-group__append-icon
      width: 55px

  &--segmented .input-group__input,
  &--editable .input-group__input:hover,
  &.input-group--focused.input-group--editable .input-group__input
    &:after
      background-color: rgba(0,0,0,0.12)
      content: ''
      position: absolute
      right: 55px
      height: 48px
      top: 0
      width: 1px

.menu__content--select
  // // IE11 Fix for overflow
  .input-group--selection-controls__ripple
    display: none

.menu__content--autocomplete
    border-radius: 0

    > .card
      border-radius: 0


},
mounted () {
this.tabs.register(this)
},
beforeDestroy () {


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

  return {
    name: `v-${name}`,
    functional: true,

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

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

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

    functional: true,

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

    render (h, context) {
      context.data = context.data || {}
      context.data.props = { name }
      context.data.on = context.data.on || {}
      if (!Object.isExtensible(context.data.on)) {
        context.data.on = { ...context.data.on }
      }

      if (mode) context.data.props.mode = mode

      context.data.on.beforeEnter = el => {
        el.style.transformOrigin = context.props.origin
        el.style.webkitTransformOrigin = context.props.origin
      }

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

export function createJavaScriptTransition (name, functions, css = true, mode = 'in-out') {
  return {
    name,

    functional: true,

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

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

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

export function directiveConfig (binding, defaults = {}) {
  return Object.assign({},
    defaults,
    binding.modifiers,
    { value: binding.arg },
    binding.value || {}
  )
}

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

  el.addEventListener(event, once, false)
}

export function getObjectValueByPath (obj, path) {
  // credit: http://stackoverflow.com/questions/6491463/accessing-nested-javascript-objects-with-string-key#comment55278413_6491621
  if (!path || path.constructor !== String) return
  path = path.replace(/\[(\w+)\]/g, '.$1') // convert indexes to properties
  path = path.replace(/^\./, '') // strip a leading dot
  const a = path.split('.')
  for (var i = 0, n = a.length; i < n; ++i) {
    var k = a[i]
    if (obj instanceof Object && k in obj) {
      obj = obj[k]
    } else {
      return
    }
  }
  return obj
}

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

export function getZIndex (el) {
  if (!el || el.nodeType !== Node.ELEMENT_NODE) return 0
  var zi = document.defaultView.getComputedStyle(el).getPropertyValue('z-index')
  if (isNaN(zi)) return getZIndex(el.parentNode)

  return zi
}

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

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

export function filterObjectOnKeys (obj, keys) {
  const filtered = {}

  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 = [], tag) {
  return array.filter(child => {
    return child.componentOptions &&
      child.componentOptions.Ctor.options.name === tag
  })
}


import Ripple from '../directives/ripple'

export default {
  name: 'routable',

  directives: {
    Ripple
  },

  props: {
    activeClass: String,
    append: Boolean,
    disabled: Boolean,
    exact: {
      type: Boolean,
      default: undefined
    },
    exactActiveClass: String,
    href: [String, Object],
    to: [String, Object],
    nuxt: Boolean,
    replace: Boolean,
    ripple: [Boolean, Object],
    tag: String,
    target: String
  },

  methods: {
    click () {},
    generateRouteLink () {
      let exact = this.exact
      let tag

      const data = {
        attrs: { disabled: this.disabled },
        class: this.classes,
        props: {},
        directives: [{
          name: 'ripple',
          value: (this.ripple && !this.disabled) ? this.ripple : false
        }],
        [this.to ? 'nativeOn' : 'on']: {
          ...this.$listeners,
          click: this.click
        }
      }

      if (typeof this.exact === 'undefined') {
        exact = this.to === '/' ||
          (this.to === Object(this.to) && this.to.path === '/')
      }

      if (this.to) {
        // Add a special activeClass hook
        // for component level styles
        let activeClass = this.activeClass
        let exactActiveClass = this.exactActiveClass || activeClass

        if (this.proxyClass) {
          activeClass += ' ' + this.proxyClass
          exactActiveClass += ' ' + this.proxyClass
        }

        tag = this.nuxt ? 'nuxt-link' : 'router-link'
        Object.assign(data.props, {
          to: this.to,
          exact,
          activeClass,
          exactActiveClass,
          append: this.append,
          replace: this.replace
        })
      } else {
        tag = (this.href && 'a') || this.tag || 'a'

        if (tag === 'a') {
          if (this.href) data.attrs.href = this.href
          if (this.target) data.attrs.target = this.target
        }
      }

      return { tag, data }
    }
  }
}


import VAlert from './VAlert'

/* istanbul ignore next */
VAlert.install = function install (Vue) {
  Vue.component(VAlert.name, VAlert)
}

export default VAlert


this.to || this.tabClick(this)
},
onRouteChange () {
if (!this.to) return

if (!this.to || !this.$refs.link) return

const path = `_vnode.data.class.${this.activeClass}`


// Components
import VIcon from '../../components/VIcon'

// Mixins
import Bootable from '../../mixins/bootable'
import Toggleable from '../../mixins/toggleable'
import {
  inject as RegistrableInject
} from '../../mixins/registrable'

// Transitions
import { VExpandTransition } from '../transitions'

/**
 * List group
 *
 * @component
 */
export default {
  name: 'v-list-group',

  mixins: [
    Bootable,
    RegistrableInject('list', 'v-list-group', 'v-list'),
    Toggleable
  ],

  inject: ['listClick'],

  data: () => ({
    groups: []
  }),

  props: {
    activeClass: {
      type: String,
      default: 'primary--text'
    },
    appendIcon: {
      type: String,
      default: 'keyboard_arrow_down'
    },
    disabled: Boolean,
    group: String,
    noAction: Boolean,
    prependIcon: String,
    subGroup: Boolean
  },

  computed: {
    groupClasses () {
      return {
        'list__group--active': this.isActive,
        'list__group--disabled': this.disabled
      }
    },
    headerClasses () {
      return {
        'list__group__header--active': this.isActive,
        'list__group__header--sub-group': this.subGroup
      }
    },
    itemsClasses () {
      return {
        'list__group__items--no-action': this.noAction
      }
    }
  },

  watch: {
    isActive (val) {
      if (!this.subGroup && val) {
        this.listClick(this._uid)
      }
    },
    $route (to) {
      const isActive = this.matchRoute(to.path)

      if (this.group) {
        if (isActive && this.isActive !== isActive) {
          this.listClick(this._uid)
        }

        this.isActive = isActive
      }
    }
  },

  mounted () {
    this.list.register(this._uid, this.toggle)

    if (this.group &&
      this.$route &&
      this.value == null
    ) {
      this.isActive = this.matchRoute(this.$route.path)
    }
  },

  beforeDestroy () {
    this.list.unregister(this._uid)
  },

  methods: {
    click () {
      if (this.disabled) return

      this.isActive = !this.isActive
    },
    genIcon (icon) {
      return this.$createElement(VIcon, icon)
    },
    genAppendIcon () {
      const icon = !this.subGroup ? this.appendIcon : ''

      return this.$createElement('li', {
        staticClass: 'list__group__header__append-icon'
      }, [
        this.$slots.appendIcon || this.genIcon(icon)
      ])
    },
    genGroup () {
      return this.$createElement('ul', {
        staticClass: 'list__group__header',
        'class': this.headerClasses,
        on: Object.assign({}, {
          click: this.click
        }, this.$listeners),
        ref: 'item'
      }, [
        this.genPrependIcon(),
        this.$slots.activator,
        this.genAppendIcon()
      ])
    },
    genItems () {
      return this.$createElement('ul', {
        staticClass: 'list__group__items',
        'class': this.itemsClasses,
        directives: [{
          name: 'show',
          value: this.isActive
        }],
        ref: 'group'
      }, this.showLazyContent(this.$slots.default))
    },
    genPrependIcon () {
      const icon = this.prependIcon
        ? this.prependIcon
        : this.subGroup
          ? 'arrow_drop_down'
          : ''

      return this.$createElement('li', {
        staticClass: 'list__group__header__prepend-icon',
        'class': {
          [this.activeClass]: this.isActive
        }
      }, [
        this.$slots.prependIcon || this.genIcon(icon)
      ])
    },
    toggle (uid) {
      this.isActive = this._uid === uid
    },
    matchRoute (to) {
      if (!this.group) return false
      return to.match(this.group) !== null
    }
  },

  render (h) {
    return h('li', {
      staticClass: 'list__group',
      'class': this.groupClasses
    }, [
      this.genGroup(),
      h(VExpandTransition, [this.genItems()])
    ])
  }
}


export default {
  name: 'soloable',

  props: {
    flat: Boolean,
    soloInverted: Boolean,
    solo: Boolean
  },

  computed: {
    isSolo () {
      return this.solo || this.soloInverted
    }
  },

  methods: {
    genSoloClasses () {
      return {
        'input-group--solo': this.isSolo,
        'input-group--solo-inverted': this.soloInverted,
        'elevation-0': this.flat
      }
    }
  }
}


import { test, touch } from '@util/testing'
import VSwitch from '@components/VSwitch'

test('VSwitch.js', ({ mount }) => {
  it('should set ripple data attribute based on ripple prop state', () => {
    const wrapper = mount(VSwitch, {
      propsData: {
        inputValue: false,
        ripple: false
      }
    })

    const ripple = wrapper.find('.input-group--selection-controls__ripple')[0]

    expect(ripple.element._ripple.enabled).toBe(false)

    wrapper.setProps({ ripple: true })

    expect(ripple.element._ripple.enabled).toBe(true)
    expect(ripple.element._ripple.centered).toBe(true)
  })

  it('should emit change event on swipe', async () => {
    const wrapper = mount(VSwitch, {
      props: {
        inputValue: false
      }
    })

    const change = jest.fn()
    wrapper.vm.$on('change', change)
    touch(wrapper.find('.input-group--selection-controls__ripple')[0]).start(0, 0).end(20, 0)
    expect(change).toBeCalledWith(true)

    wrapper.setProps({ inputValue: true })
    touch(wrapper.find('.input-group--selection-controls__ripple')[0]).start(0, 0).end(-20, 0)
    expect(change).toBeCalledWith(false)
  })

  it('shouldn not emit change event on swipe when not active', async () => {
    const wrapper = mount(VSwitch, {
      props: {
        inputValue: false
      }
    })

    const change = jest.fn()
    wrapper.vm.$on('change', change)
    touch(wrapper.find('.input-group--selection-controls__ripple')[0]).start(0, 0).end(-20, 0)
    expect(change).not.toBeCalled()

    wrapper.setProps({ inputValue: true })
    touch(wrapper.find('.input-group--selection-controls__ripple')[0]).start(0, 0).end(20, 0)
    expect(change).not.toBeCalled()
  })

  it('should render component with error', async () => {
    const wrapper = mount(VSwitch, {
      props: {
        errorMessages: ['error']
      }
    })

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


if (this.$el.firstChild.className.indexOf(this.activeClass) > -1) {

this.isActive = (action === this) || (action === this.action)

keep replace replace replace replace replace keep keep keep

if (getObjectValueByPath(this.$refs.link, path)) {


import '../../stylus/components/_app.styl'

// Component level mixins
import AppTheme from './mixins/app-theme'
import AppBreakpoint from './mixins/app-breakpoint'

// Directives
import Resize from '../../directives/resize'

export default {
  name: 'v-app',

  mixins: [
    AppBreakpoint,
    AppTheme
  ],

  directives: {
    Resize
  },

  props: {
    id: {
      type: String,
      default: 'app'
    },
    dark: Boolean
  },

  computed: {
    classes () {
      return {
        [`theme--${this.dark ? 'dark' : 'light'}`]: true
      }
    }
  },

  mounted () {
    this.$vuetify.dark = this.dark
  },

  watch: {
    dark () {
      this.$vuetify.dark = this.dark
    }
  },

  render (h) {
    const data = {
      staticClass: 'application',
      'class': this.classes,
      attrs: { 'data-app': true },
      domProps: { id: this.id },
      directives: [{
        name: 'resize',
        value: this.onResize
      }]
    }

    const wrapper = h('div', { staticClass: 'application--wrap' }, this.$slots.default)

    return h('div', data, [wrapper])
  }
}


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

exports[`VCard.vue should render a card with custom height 1`] = `

`;

exports[`VCard.vue should render a flat card 1`] = `

`;

exports[`VCard.vue should render a raised card 1`] = `

`;

exports[`VCard.vue should render a tile card 1`] = `

`;

exports[`VCard.vue should render a tile card 2`] = `

`;

exports[`VCard.vue should render card with img 1`] = `

`;

exports[`VCard.vue should render component and match snapshot 1`] = `

`;


import '../../stylus/components/_time-picker-clock.styl'

// Mixins
import Colorable from '../../mixins/colorable'
import Themeable from '../../mixins/themeable'

export default {
  name: 'v-time-picker-clock',

  mixins: [
    Colorable,
    Themeable
  ],

  data () {
    return {
      defaultColor: 'accent',
      inputValue: this.value,
      isDragging: false
    }
  },

  props: {
    allowedValues: Function,
    double: Boolean,
    format: {
      type: Function,
      default: val => val
    },
    max: {
      type: Number,
      required: true
    },
    min: {
      type: Number,
      required: true
    },
    scrollable: Boolean,
    rotate: {
      type: Number,
      default: 0
    },
    size: {
      type: [Number, String],
      default: 270
    },
    step: {
      type: Number,
      default: 1
    },
    value: Number
  },

  computed: {
    count () {
      return this.max - this.min + 1
    },
    innerRadius () {
      return this.radius - Math.max(this.radius * 0.4, 48)
    },
    outerRadius () {
      return this.radius - 4
    },
    roundCount () {
      return this.double ? (this.count / 2) : this.count
    },
    degreesPerUnit () {
      return 360 / this.roundCount
    },
    degrees () {
      return this.degreesPerUnit * Math.PI / 180
    },
    radius () {
      return this.size / 2
    },
    displayedValue () {
      return this.value == null ? this.min : this.value
    }
  },

  watch: {
    value (value) {
      this.inputValue = value
    }
  },

  methods: {
    wheel (e) {
      e.preventDefault()
      const value = this.displayedValue + Math.sign(e.wheelDelta || 1)
      this.update((value - this.min + this.count) % this.count + this.min)
    },
    handScale (value) {
      return this.double && (value - this.min >= this.roundCount) ? (this.innerRadius / this.radius) : (this.outerRadius / this.radius)
    },
    isAllowed (value) {
      return !this.allowedValues || this.allowedValues(value)
    },
    genValues () {
      const children = []

      for (let value = this.min; value <= this.max; value = value + this.step) {
        const classes = {
          active: value === this.displayedValue,
          disabled: !this.isAllowed(value)
        }

        children.push(this.$createElement('span', {
          'class': this.addBackgroundColorClassChecks(classes, value === this.value ? this.computedColor : null),
          style: this.getTransform(value),
          domProps: { innerHTML: `<span>${this.format(value)}</span>` }
        }))
      }

      return children
    },
    genHand () {
      const scale = `scaleY(${this.handScale(this.displayedValue)})`
      const angle = this.rotate + this.degreesPerUnit * (this.displayedValue - this.min)

      return this.$createElement('div', {
        staticClass: 'time-picker-clock__hand',
        'class': this.value == null ? {} : this.addBackgroundColorClassChecks(),
        style: {
          transform: `rotate(${angle}deg) ${scale}`
        }
      })
    },
    getTransform (i) {
      const { x, y } = this.getPosition(i)
      return { transform: `translate(${x}px, ${y}px)` }
    },
    getPosition (value) {
      const radius = (this.radius - 24) * this.handScale(value)
      const rotateRadians = this.rotate * Math.PI / 180
      return {
        x: Math.round(Math.sin((value - this.min) * this.degrees + rotateRadians) * radius),
        y: Math.round(-Math.cos((value - this.min) * this.degrees + rotateRadians) * radius)
      }
    },
    onMouseDown (e) {
      e.preventDefault()

      this.isDragging = true
      this.onDragMove(e)
    },
    onMouseUp () {
      this.isDragging = false
      this.isAllowed(this.inputValue) && this.$emit('change', this.inputValue)
    },
    onDragMove (e) {
      e.preventDefault()
      if (!this.isDragging && e.type !== 'click') return

      const { width, top, left } = this.$refs.clock.getBoundingClientRect()
      const { clientX, clientY } = 'touches' in e ? e.touches[0] : e
      const center = { x: width / 2, y: -width / 2 }
      const coords = { x: clientX - left, y: top - clientY }
      const handAngle = Math.round(this.angle(center, coords) - this.rotate + 360) % 360
      const insideClick = this.double && this.euclidean(center, coords) < (this.outerRadius + this.innerRadius) / 2 - 16
      const value = Math.round(handAngle / this.degreesPerUnit) +
        this.min + (insideClick ? this.roundCount : 0)

      // Necessary to fix edge case when selecting left part of max value
      if (handAngle >= (360 - this.degreesPerUnit / 2)) {
        this.update(insideClick ? this.max : this.min)
      } else {
        this.update(value)
      }
    },
    update (value) {
      if (this.inputValue !== value && this.isAllowed(value)) {
        this.inputValue = value
        this.$emit('input', value)
      }
    },
    euclidean (p0, p1) {
      const dx = p1.x - p0.x
      const dy = p1.y - p0.y

      return Math.sqrt(dx * dx + dy * dy)
    },
    angle (center, p1) {
      const value = 2 * Math.atan2(p1.y - center.y - this.euclidean(center, p1), p1.x - center.x)
      return Math.abs(value * 180 / Math.PI)
    }
  },

  render (h) {
    const data = {
      staticClass: 'time-picker-clock',
      class: {
        'time-picker-clock--indeterminate': this.value == null
      },
      on: {
        mousedown: this.onMouseDown,
        mouseup: this.onMouseUp,
        mouseleave: () => (this.isDragging && this.onMouseUp()),
        touchstart: this.onMouseDown,
        touchend: this.onMouseUp,
        mousemove: this.onDragMove,
        touchmove: this.onDragMove
      },
      style: {
        height: `${this.size}px`,
        width: `${this.size}px`
      },
      ref: 'clock'
    }

    this.scrollable && (data.on.wheel = this.wheel)

    return this.$createElement('div', data, [
      this.genHand(),
      this.genValues()
    ])
  }
}


const tag = this.disabled ? 'div' : link.tag


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

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

`;

exports[`VListTileAction.js should render component with many children and match snapshot 1`] = `

`;

exports[`VListTileAction.js should render component with static class and match snapshot 1`] = `

`;


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

export default function (expandedParentClass = '') {
  return {
    enter (el, done) {
      el._parent = el.parentNode

      addOnceEventListener(el, 'transitionend', done)

      // Get height that is to be scrolled
      el.style.overflow = 'hidden'
      el.style.height = 0
      el.style.display = 'block'
      expandedParentClass && el._parent.classList.add(expandedParentClass)

      setTimeout(() => (el.style.height = `${el.scrollHeight}px`), 100)
    },

    afterEnter (el) {
      el.style.overflow = null
      el.style.height = null
    },

    leave (el, done) {
      // Remove initial transition
      addOnceEventListener(el, 'transitionend', done)

      // Set height before we transition to 0
      el.style.overflow = 'hidden'
      el.style.height = `${el.offsetHeight}px`

      setTimeout(() => (el.style.height = 0), 100)
    },

    afterLeave (el) {
      expandedParentClass && el._parent.classList.remove(expandedParentClass)
    }
  }
}


import { test } from '@util/testing'
import VIcon from '@components/VIcon'
import { VToolbarSideIcon } from '@components/VToolbar'

test('VToolbarSideIcon.js', ({ mount, functionalContext }) => {
  it('should create default icon when no slot used', () => {
    const context = functionalContext()
    const wrapper = mount(VToolbarSideIcon, context)

    expect(wrapper.find('i')[0].hasClass('material-icons')).toBe(true)
    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should create slot icon when present', () => {
    const iconWrapper = mount(VIcon, functionalContext({}, 'fa-add'))

    const context = functionalContext({}, iconWrapper.vNode)
    const wrapper = mount(VToolbarSideIcon, context)

    expect(wrapper.find('i')[0].hasClass('fa-add')).toBe(true)
    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should pass through events properly', () => {
    const click = jest.fn()
    const context = functionalContext({
      on: { click }
    })
    const wrapper = mount(VToolbarSideIcon, context)

    wrapper.trigger('click')

    expect(click).toBeCalled()
  })

  it('should pass through props to button component', () => {
    const context = functionalContext({
      props: {
        dark: true
      }
    })
    const wrapper = mount(VToolbarSideIcon, context)

    expect(wrapper.hasClass('theme--dark')).toBe(true)
  })

  it('should pass through css classes to button component', () => {
    const context = functionalContext({
      staticClass: 'hidden-sm-and-up'
    })
    const wrapper = mount(VToolbarSideIcon, context)

    expect(wrapper.hasClass('hidden-sm-and-up')).toBe(true)
  })

  it('should pass through directives to button component', () => {
    const context = functionalContext({
      directives: [
        {
          name: 'show',
          value: false
        }
      ]
    })
    const wrapper = mount(VToolbarSideIcon, context)

    expect(wrapper.hasStyle('display', 'none')).toBe(true)
  })
})


it('should call tabClick', async () => {

keep keep keep keep replace keep keep keep

// 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

it.skip('should call tabClick', async () => {


import VBottomNav from './VBottomNav'

/* istanbul ignore next */
VBottomNav.install = function install (Vue) {
  Vue.component(VBottomNav.name, VBottomNav)
}

export default VBottomNav


import VSpeedDial from './VSpeedDial'

/* istanbul ignore next */
VSpeedDial.install = function install (Vue) {
  Vue.component(VSpeedDial.name, VSpeedDial)
}

export default VSpeedDial


import VApp from '@components/VApp'
import VNavigationDrawer from '@components/VNavigationDrawer'
import { test, resizeWindow } from '@util/testing'

beforeEach(() => resizeWindow(1920, 1080))

test('VNavigationDrawer', ({ mount }) => {
  // v-app is needed to initialise $vuetify.application
  const app = mount(VApp)

  it('should become temporary when the window resizes', async () => {
    const wrapper = mount(VNavigationDrawer)

    expect(wrapper.vm.isActive).toBe(true)
    await resizeWindow(1200)
    expect(wrapper.vm.isActive).toBe(false)
    expect(wrapper.vm.overlay).toBeFalsy()
  })

  it('should not resize the content when temporary', async () => {
    const wrapper = mount(VNavigationDrawer, { propsData: {
      app: true,
      temporary: true,
      value: true
    }})

    await wrapper.vm.$nextTick()
    expect(wrapper.vm.$vuetify.application.left).toBe(0)
    expect(wrapper.vm.overlay).toBeTruthy()
  })

  it('should not resize the content when permanent and stateless', async () => {
    const wrapper = mount(VNavigationDrawer, { propsData: {
      app: true,
      permanent: true,
      stateless: true
    }})

    await wrapper.vm.$nextTick()
    expect(wrapper.vm.$vuetify.application.left).toBe(300)

    await resizeWindow(1200)
    expect(wrapper.vm.$vuetify.application.left).toBe(300)
    expect(wrapper.vm.overlay).toBeFalsy()
  })

  it('should not resize the content when permanent and resize watcher is disabled', async () => {
    const wrapper = mount(VNavigationDrawer, {
      propsData: {
        app: true,
        permanent: true,
        disableResizeWatcher: true
      }
    })

    await wrapper.vm.$nextTick()
    expect(wrapper.vm.$vuetify.application.left).toBe(300)

    await resizeWindow(1200)
    expect(wrapper.vm.$vuetify.application.left).toBe(300)
    expect(wrapper.vm.overlay).toBeFalsy()
  })

  it('should stay active when resizing a temporary drawer', async () => {
    const wrapper = mount(VNavigationDrawer, {
      propsData: {
        app: true,
        temporary: true,
        value: true
      }
    })

    await wrapper.vm.$nextTick()

    expect(wrapper.vm.isActive).toBe(true)
    expect(wrapper.vm.overlay).toBeTruthy()

    await resizeWindow(1200)

    expect(wrapper.vm.isActive).toBe(true)
    expect(wrapper.vm.overlay).toBeTruthy()
  })

  it('should open when changed to permanent', async () => {
    const wrapper = mount(VNavigationDrawer, {
      propsData: {
        value: null
      }
    })

    wrapper.setProps({ permanent: true })

    await wrapper.vm.$nextTick()

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

  it('should not close when value changes and permanent', async () => {
    const wrapper = mount(VNavigationDrawer, {
      propsData: {
        permanent: true,
        value: true
      }
    })

    wrapper.setProps({ value: false })

    await wrapper.vm.$nextTick()

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

  it('should update content padding when temporary state is changed', async () => {
    const wrapper = mount(VNavigationDrawer, { propsData: {
      app: true
    }})

    expect(wrapper.vm.$vuetify.application.left).toBe(300)

    wrapper.setProps({ temporary: true })
    expect(wrapper.vm.$vuetify.application.left).toBe(0)

    wrapper.setProps({ temporary: false })
    expect(wrapper.vm.$vuetify.application.left).toBe(300)
  })

  it('should update content padding when permanent state is changed', async () => {
    await resizeWindow(800)
    const wrapper = mount(VNavigationDrawer, { propsData: {
      app: true
    }})

    expect(wrapper.vm.$vuetify.application.left).toBe(0)

    wrapper.setProps({ permanent: true })
    expect(wrapper.vm.$vuetify.application.left).toBe(300)

    wrapper.setProps({ permanent: false })
    expect(wrapper.vm.$vuetify.application.left).toBe(0)
  })

  it('should update content padding when miniVariant is changed', async () => {
    const wrapper = mount(VNavigationDrawer, { propsData: {
      app: true
    }})

    expect(wrapper.vm.$vuetify.application.left).toBe(300)

    wrapper.setProps({ miniVariant: true })
    expect(wrapper.vm.$vuetify.application.left).toBe(80)

    wrapper.setProps({ miniVariant: false })
    expect(wrapper.vm.$vuetify.application.left).toBe(300)
  })

  it('should not remain mobile when temporary is toggled', async () => {
    await resizeWindow(800)
    const wrapper = mount(VNavigationDrawer, { propsData: {
      temporary: true
    }})

    await resizeWindow(1920)
    expect(wrapper.vm.isMobile).toBe(false)
  })

  it('should stay closed when mobile and temporary is enabled', async () => {
    await resizeWindow(800)
    const wrapper = mount(VNavigationDrawer)
    const input = jest.fn(value => wrapper.setProps({ value }))
    wrapper.vm.$on('input', input)

    wrapper.setProps({ temporary: true })
    await wrapper.vm.$nextTick()

    expect(wrapper.vm.isActive).toBe(false)
    expect(input.mock.calls).toHaveLength(0)
  })

  it('should update content padding when mobile is toggled', async () => {
    const input = jest.fn()
    const wrapper = mount(VNavigationDrawer, { propsData: {
      app: true,
      fixed: true,
      value: true
    }})

    wrapper.vm.$on('input', input)
    expect(wrapper.vm.$vuetify.application.left).toBe(300)
    await resizeWindow(800)
    expect(wrapper.vm.$vuetify.application.left).toBe(0)
    expect(wrapper.vm.isActive).toBe(false)
    expect(input).toBeCalledWith(false)
    wrapper.setProps({ value: false })
    await wrapper.vm.$nextTick()
    wrapper.setProps({ value: true })
    await wrapper.vm.$nextTick()
    expect(wrapper.vm.isActive).toBe(true)
    expect(wrapper.vm.$vuetify.application.left).toBe(0)
    await resizeWindow(1920)
    expect(wrapper.vm.isActive).toBe(true)
    expect(wrapper.vm.isMobile).toBe(false)
    expect(wrapper.vm.$vuetify.application.left).toBe(300)
  })
})


keep keep keep replace keep keep keep keep

import { test } from '@util/testing'
import VTab from './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('.tabs__item')[0].vNode.elm.tagName).toBe('A')
    wrapper.setProps({ disabled: true })
    expect(wrapper.find('.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()
  })

  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('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
    })

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

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


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

subheader($material)
  color: $material.text.secondary

theme(subheader, "subheader")

.subheader
  height: $list-item-single-height
  display: flex
  align-items: center
  font-size: $subheader-height-font-size
  font-weight: $subheader-font-weight
  padding: 0 $list-right-padding 0 $list-left-padding

  &--inset
    margin-left: $subheader-inset-margin