---
id: caching
title: Caching Examples
---

> Please thoroughly read the [Important Defaults](../guides/important-defaults) before reading this guide

## Basic Example

This caching example illustrates the story and lifecycle of:

- Query Instances with and without cache data
- Background Refetching
- Inactive Queries
- Garbage Collection

Let's assume we are using the default `cacheTime` of **5 minutes** and the default `staleTime` of `0`.

- A new instance of `useQuery({ queryKey: ['todos'], queryFn: fetchTodos })` mounts.
  - Since no other queries have been made with the `['todos']` query key, this query will show a hard loading state and make a network request to fetch the data.
  - When the network request has completed, the returned data will be cached under the `['todos']` key.
  - The hook will mark the data as stale after the configured `staleTime` (defaults to `0`, or immediately).
- A second instance of `useQuery({ queryKey: ['todos'], queryFn: fetchTodos })` mounts elsewhere.
  - Since the cache already has data for the `['todos']` key from the first query, that data is immediately returned from the cache.
  - The new instance triggers a new network request using its query function.
    - Note that regardless of whether both `fetchTodos` query functions are identical or not, both queries' [`status`](../reference/useQuery) are updated (including `isFetching`, `isLoading`, and other related values) because they have the same query key.
  - When the request completes successfully, the cache's data under the `['todos']` key is updated with the new data, and both instances are updated with the new data.
- Both instances of the `useQuery({ queryKey: ['todos'], queryFn: fetchTodos })` query are unmounted and no longer in use.
  - Since there are no more active instances of this query, a cache timeout is set using `cacheTime` to delete and garbage collect the query (defaults to **5 minutes**).
- Before the cache timeout has completed, another instance of `useQuery({ queryKey: ['todos'], queyFn: fetchTodos })` mounts. The query immediately returns the available cached data while the `fetchTodos` function is being run in the background. When it completes successfully, it will populate the cache with fresh data.
- The final instance of `useQuery({ queryKey: ['todos'], queryFn: fetchTodos })` unmounts.
- No more instances of `useQuery({ queyKey: ['todos'], queryFn: fetchTodos })` appear within **5 minutes**.
  - The cached data under the `['todos']` key is deleted and garbage collected.


import { useQuery } from "somewhere-else";

useQuery(queryKey, queryFn, { enabled });

keep keep keep keep add keep keep keep keep

import { useQuery } from "@tanstack/react-query";

const getPosts = async () => Promise.resolve([]);

const postsQuery = { queryKey: ["posts"], queryFn: () => getPosts() };

const usePosts = () => useQuery(postsQuery);

import type {
  DefaultedInfiniteQueryObserverOptions,
  FetchNextPageOptions,
  FetchPreviousPageOptions,
  InfiniteData,
  InfiniteQueryObserverOptions,
  InfiniteQueryObserverResult,
  QueryKey,
} from './types'
import type { QueryClient } from './queryClient'
import type { NotifyOptions, ObserverFetchOptions } from './queryObserver'
import { QueryObserver } from './queryObserver'
import {
  hasNextPage,
  hasPreviousPage,
  infiniteQueryBehavior,
} from './infiniteQueryBehavior'
import type { Query } from './query'

type InfiniteQueryObserverListener<TData, TError> = (
  result: InfiniteQueryObserverResult<TData, TError>,
) => void

export class InfiniteQueryObserver<
  TQueryFnData = unknown,
  TError = unknown,
  TData = TQueryFnData,
  TQueryData = TQueryFnData,
  TQueryKey extends QueryKey = QueryKey,
> extends QueryObserver<
  TQueryFnData,
  TError,
  InfiniteData<TData>,
  InfiniteData<TQueryData>,
  TQueryKey
> {
  // Type override
  subscribe!: (
    listener?: InfiniteQueryObserverListener<TData, TError>,
  ) => () => void

  // Type override
  getCurrentResult!: () => InfiniteQueryObserverResult<TData, TError>

  // Type override
  protected fetch!: (
    fetchOptions: ObserverFetchOptions,
  ) => Promise<InfiniteQueryObserverResult<TData, TError>>

  // eslint-disable-next-line @typescript-eslint/no-useless-constructor
  constructor(
    client: QueryClient,
    options: InfiniteQueryObserverOptions<
      TQueryFnData,
      TError,
      TData,
      TQueryData,
      TQueryKey
    >,
  ) {
    super(client, options)
  }

  protected bindMethods(): void {
    super.bindMethods()
    this.fetchNextPage = this.fetchNextPage.bind(this)
    this.fetchPreviousPage = this.fetchPreviousPage.bind(this)
  }

  setOptions(
    options?: InfiniteQueryObserverOptions<
      TQueryFnData,
      TError,
      TData,
      TQueryData,
      TQueryKey
    >,
    notifyOptions?: NotifyOptions,
  ): void {
    super.setOptions(
      {
        ...options,
        behavior: infiniteQueryBehavior(),
      },
      notifyOptions,
    )
  }

  getOptimisticResult(
    options: DefaultedInfiniteQueryObserverOptions<
      TQueryFnData,
      TError,
      TData,
      TQueryData,
      TQueryKey
    >,
  ): InfiniteQueryObserverResult<TData, TError> {
    options.behavior = infiniteQueryBehavior()
    return super.getOptimisticResult(options) as InfiniteQueryObserverResult<
      TData,
      TError
    >
  }

  fetchNextPage({ pageParam, ...options }: FetchNextPageOptions = {}): Promise<
    InfiniteQueryObserverResult<TData, TError>
  > {
    return this.fetch({
      ...options,
      meta: {
        fetchMore: { direction: 'forward', pageParam },
      },
    })
  }

  fetchPreviousPage({
    pageParam,
    ...options
  }: FetchPreviousPageOptions = {}): Promise<
    InfiniteQueryObserverResult<TData, TError>
  > {
    return this.fetch({
      ...options,
      meta: {
        fetchMore: { direction: 'backward', pageParam },
      },
    })
  }

  protected createResult(
    query: Query<TQueryFnData, TError, InfiniteData<TQueryData>, TQueryKey>,
    options: InfiniteQueryObserverOptions<
      TQueryFnData,
      TError,
      TData,
      TQueryData,
      TQueryKey
    >,
  ): InfiniteQueryObserverResult<TData, TError> {
    const { state } = query
    const result = super.createResult(query, options)
    return {
      ...result,
      fetchNextPage: this.fetchNextPage,
      fetchPreviousPage: this.fetchPreviousPage,
      hasNextPage: hasNextPage(options, state.data?.pages),
      hasPreviousPage: hasPreviousPage(options, state.data?.pages),
      isFetchingNextPage:
        state.fetchStatus === 'fetching' &&
        state.fetchMeta?.fetchMore?.direction === 'forward',
      isFetchingPreviousPage:
        state.fetchStatus === 'fetching' &&
        state.fetchMeta?.fetchMore?.direction === 'backward',
    }
  }
}


---
id: useInfiniteQuery
title: useInfiniteQuery
---

```tsx
const {
  fetchNextPage,
  fetchPreviousPage,
  hasNextPage,
  hasPreviousPage,
  isFetchingNextPage,
  isFetchingPreviousPage,
  ...result
} = useInfiniteQuery({
  queryKey,
  queryFn: ({ pageParam = 1 }) => fetchPage(pageParam),
  ...options,
  getNextPageParam: (lastPage, allPages) => lastPage.nextCursor,
  getPreviousPageParam: (firstPage, allPages) => firstPage.prevCursor,
})
```

**Options**

The options for `useInfiniteQuery` are identical to the [`useQuery` hook](../reference/useQuery) with the addition of the following:

- `queryFn: (context: QueryFunctionContext) => Promise<TData>`
  - **Required, but only if no default query function has been defined** [`defaultQueryFn`](../guides/default-query-function)
  - The function that the query will use to request data.
  - Receives a [QueryFunctionContext](../guides/query-functions#queryfunctioncontext)
  - Must return a promise that will either resolve data or throw an error.
  - Make sure you return the data *and* the `pageParam` if needed for use in the props below.
- `getNextPageParam: (lastPage, allPages) => unknown | undefined`
  - When new data is received for this query, this function receives both the last page of the infinite list of data and the full array of all pages.
  - It should return a **single variable** that will be passed as the last optional parameter to your query function.
  - Return `undefined` to indicate there is no next page available.
- `getPreviousPageParam: (firstPage, allPages) => unknown | undefined`
  - When new data is received for this query, this function receives both the first page of the infinite list of data and the full array of all pages.
  - It should return a **single variable** that will be passed as the last optional parameter to your query function.
  - Return `undefined` to indicate there is no previous page available.

**Returns**

The returned properties for `useInfiniteQuery` are identical to the [`useQuery` hook](../reference/useQuery), with the addition of the following:

- `data.pages: TData[]`
  - Array containing all pages.
- `data.pageParams: unknown[]`
  - Array containing all page params.
- `isFetchingNextPage: boolean`
  - Will be `true` while fetching the next page with `fetchNextPage`.
- `isFetchingPreviousPage: boolean`
  - Will be `true` while fetching the previous page with `fetchPreviousPage`.
- `fetchNextPage: (options?: FetchNextPageOptions) => Promise<UseInfiniteQueryResult>`
  - This function allows you to fetch the next "page" of results.
  - `options.pageParam: unknown` allows you to manually specify a page param instead of using `getNextPageParam`.
  - `options.cancelRefetch: boolean` if set to `true`, calling `fetchNextPage` repeatedly will invoke `fetchPage` every time, whether the previous
  invocation has resolved or not. Also, the result from previous invocations will be ignored. If set to `false`, calling `fetchNextPage`
  repeatedly won't have any effect until the first invocation has resolved. Default is `true`.
- `fetchPreviousPage: (options?: FetchPreviousPageOptions) => Promise<UseInfiniteQueryResult>`
  - This function allows you to fetch the previous "page" of results.
  - `options.pageParam: unknown` allows you to manually specify a page param instead of using `getPreviousPageParam`.
  - `options.cancelRefetch: boolean` same as for `fetchNextPage`.
- `hasNextPage: boolean`
  - This will be `true` if there is a next page to be fetched (known via the `getNextPageParam` option).
- `hasPreviousPage: boolean`
  - This will be `true` if there is a previous page to be fetched (known via the `getPreviousPageParam` option).


keep keep keep keep keep replace keep keep keep keep

import type { TSESLint } from '@typescript-eslint/utils'
import { createRule } from '../../utils/create-rule'
import { ASTUtils } from '../../utils/ast-utils'

const QUERY_CALLS = ['useQuery', 'createQuery']

export const name = 'prefer-query-object-syntax'

export const rule = createRule({
  name,
  meta: {
    type: 'problem',
    docs: {
      description: 'Prefer object syntax for useQuery',
      recommended: 'error',
    },
    messages: {
      preferObjectSyntax: `Objects syntax for useQuery is preferred`,
    },
    fixable: 'code',
    schema: [],
  },
  defaultOptions: [],

  create(context, _, helpers) {
    const sourceCode = context.getSourceCode()
    return {
      CallExpression(node) {
        const isUseQuery =
          node.callee.type === 'Identifier' &&
          QUERY_CALLS.includes(node.callee.name) &&
          helpers.isReactQueryImport(node.callee)
        if (!isUseQuery) {
          return
        }

        const firstArgument = node.arguments[0]
        if (!firstArgument) {
          return
        }

        const hasFirstObjectArgument = firstArgument.type === 'ObjectExpression'
        if (hasFirstObjectArgument) {
          return
        }

        const secondArgument = node.arguments[1]
        const thirdArgument = node.arguments[2]

        const optionsObject =
          secondArgument?.type === 'ObjectExpression'
            ? secondArgument
            : thirdArgument?.type === 'ObjectExpression'
            ? thirdArgument
            : undefined

        if (
          secondArgument &&
          !thirdArgument &&
          secondArgument !== optionsObject &&
          secondArgument.type === 'Identifier'
        ) {
          // Unable to determine if the secondArgument identifier is the options object or query fn.
          // User has to fix the code manually.
          context.report({ node, messageId: 'preferObjectSyntax' })
          return
        }

        context.report({
          node,
          messageId: 'preferObjectSyntax',
          fix(fixer) {
            const ruleFixes: TSESLint.RuleFix[] = []
            const optionsObjectProperties: string[] = []

            // queryKey
            const queryKey = sourceCode.getText(firstArgument)
            const queryKeyProperty =
              queryKey === 'queryKey' ? 'queryKey' : `queryKey: ${queryKey}`
            optionsObjectProperties.push(queryKeyProperty)

            // queryFn
            if (secondArgument && secondArgument !== optionsObject) {
              const queryFn = sourceCode.getText(secondArgument)
              const queryFnProperty =
                queryFn === 'queryFn' ? 'queryFn' : `queryFn: ${queryFn}`
              optionsObjectProperties.push(queryFnProperty)
            }

            // options
            if (optionsObject) {
              const existingObjectProperties = optionsObject.properties.map(
                (objectLiteral) => {
                  return sourceCode.getText(objectLiteral)
                },
              )
              optionsObjectProperties.push(...existingObjectProperties)
            }

            const argumentsRange = ASTUtils.getRangeOfArguments(node)
            if (argumentsRange) {
              ruleFixes.push(fixer.removeRange(argumentsRange))
            }

            ruleFixes.push(
              fixer.insertTextAfterRange(
                [node.range[0], node.range[1] - 1],
                `{ ${optionsObjectProperties.join(', ')} }`,
              ),
            )

            return ruleFixes
          },
        })
      },
    }
  },
})


import { fireEvent, waitFor } from '@testing-library/react'
import * as React from 'react'
import { useIsMutating } from '../useIsMutating'
import { useMutation } from '../useMutation'
import {
  createQueryClient,
  renderWithClient,
  setActTimeout,
  sleep,
} from './utils'
import { ErrorBoundary } from 'react-error-boundary'
import { QueryClient } from '@tanstack/query-core'
import * as MutationCacheModule from '../../../query-core/src/mutationCache'

describe('useIsMutating', () => {
  it('should return the number of fetching mutations', async () => {
    const isMutatings: number[] = []
    const queryClient = createQueryClient()

    function IsMutating() {
      const isMutating = useIsMutating()
      isMutatings.push(isMutating)
      return null
    }

    function Mutations() {
      const { mutate: mutate1 } = useMutation(['mutation1'], async () => {
        await sleep(150)
        return 'data'
      })
      const { mutate: mutate2 } = useMutation(['mutation2'], async () => {
        await sleep(50)
        return 'data'
      })

      React.useEffect(() => {
        mutate1()
        setActTimeout(() => {
          mutate2()
        }, 50)
      }, [mutate1, mutate2])

      return null
    }

    function Page() {
      return (

          <IsMutating />
          <Mutations />

      )
    }

    renderWithClient(queryClient, <Page />)
    await waitFor(() => expect(isMutatings).toEqual([0, 1, 2, 1, 0]))
  })

  it('should filter correctly by mutationKey', async () => {
    const isMutatings: number[] = []
    const queryClient = createQueryClient()

    function IsMutating() {
      const isMutating = useIsMutating(['mutation1'])
      isMutatings.push(isMutating)
      return null
    }

    function Page() {
      const { mutate: mutate1 } = useMutation(['mutation1'], async () => {
        await sleep(100)
        return 'data'
      })
      const { mutate: mutate2 } = useMutation(['mutation2'], async () => {
        await sleep(100)
        return 'data'
      })

      React.useEffect(() => {
        mutate1()
        mutate2()
      }, [mutate1, mutate2])

      return <IsMutating />
    }

    renderWithClient(queryClient, <Page />)
    await waitFor(() => expect(isMutatings).toEqual([0, 1, 1, 0]))
  })

  it('should filter correctly by predicate', async () => {
    const isMutatings: number[] = []
    const queryClient = createQueryClient()

    function IsMutating() {
      const isMutating = useIsMutating({
        predicate: (mutation) =>
          mutation.options.mutationKey?.[0] === 'mutation1',
      })
      isMutatings.push(isMutating)
      return null
    }

    function Page() {
      const { mutate: mutate1 } = useMutation(['mutation1'], async () => {
        await sleep(100)
        return 'data'
      })
      const { mutate: mutate2 } = useMutation(['mutation2'], async () => {
        await sleep(100)
        return 'data'
      })

      React.useEffect(() => {
        mutate1()
        mutate2()
      }, [mutate1, mutate2])

      return <IsMutating />
    }

    renderWithClient(queryClient, <Page />)
    await waitFor(() => expect(isMutatings).toEqual([0, 1, 1, 0]))
  })

  it('should not change state if unmounted', async () => {
    // We have to mock the MutationCache to not unsubscribe
    // the listener when the component is unmounted
    class MutationCacheMock extends MutationCacheModule.MutationCache {
      subscribe(listener: any) {
        super.subscribe(listener)
        return () => void 0
      }
    }

    const MutationCacheSpy = jest
      .spyOn(MutationCacheModule, 'MutationCache')
      .mockImplementation((fn) => {
        return new MutationCacheMock(fn)
      })

    const queryClient = createQueryClient()

    function IsMutating() {
      useIsMutating()
      return null
    }

    function Page() {
      const [mounted, setMounted] = React.useState(true)
      const { mutate: mutate1 } = useMutation(['mutation1'], async () => {
        await sleep(10)
        return 'data'
      })

      React.useEffect(() => {
        mutate1()
      }, [mutate1])

      return (

          <button onClick={() => setMounted(false)}>unmount</button>
          {mounted && <IsMutating />}

      )
    }

    const { getByText } = renderWithClient(queryClient, <Page />)
    fireEvent.click(getByText('unmount'))

    // Should not display the console error
    // "Warning: Can't perform a React state update on an unmounted component"

    await sleep(20)
    MutationCacheSpy.mockRestore()
  })

  describe('with custom context', () => {
    it('should return the number of fetching mutations', async () => {
      const context = React.createContext<QueryClient | undefined>(undefined)

      const isMutatings: number[] = []
      const queryClient = new QueryClient()

      function IsMutating() {
        const isMutating = useIsMutating(undefined, { context })
        isMutatings.push(isMutating)
        return null
      }

      function Page() {
        const { mutate: mutate1 } = useMutation(
          ['mutation1'],
          async () => {
            await sleep(150)
            return 'data'
          },
          { context },
        )
        const { mutate: mutate2 } = useMutation(
          ['mutation2'],
          async () => {
            await sleep(50)
            return 'data'
          },
          { context },
        )

        React.useEffect(() => {
          mutate1()
          setActTimeout(() => {
            mutate2()
          }, 50)
        }, [mutate1, mutate2])

        return <IsMutating />
      }

      renderWithClient(queryClient, <Page />, { context })
      await waitFor(() => expect(isMutatings).toEqual([0, 1, 1, 2, 2, 1, 0]))
    })

    it('should throw if the context is not passed to useIsMutating', async () => {
      const context = React.createContext<QueryClient | undefined>(undefined)

      const isMutatings: number[] = []
      const queryClient = new QueryClient()

      function IsMutating() {
        const isMutating = useIsMutating(undefined)
        isMutatings.push(isMutating)
        return null
      }

      function Page() {
        const { mutate } = useMutation(['mutation'], async () => 'data', {
          useErrorBoundary: true,
          context,
        })

        React.useEffect(() => {
          mutate()
        }, [mutate])

        return <IsMutating />
      }

      const rendered = renderWithClient(
        queryClient,
        <ErrorBoundary fallbackRender={() =>
error boundary
}>
          <Page />
        </ErrorBoundary>,
        { context },
      )

      await waitFor(() => rendered.getByText('error boundary'))
    })
  })
})


import { fireEvent, render, screen, waitFor } from 'solid-testing-library'

import {
  createRenderEffect,
  createSignal,
  ErrorBoundary,
  on,
  Show,
  Suspense,
} from 'solid-js'
import type { CreateInfiniteQueryResult, CreateQueryResult } from '..'
import {
  createInfiniteQuery,
  createQuery,
  QueryCache,
  QueryClientProvider,
} from '..'
import { createQueryClient, queryKey, sleep } from './utils'

describe("useQuery's in Suspense mode", () => {
  const queryCache = new QueryCache()
  const queryClient = createQueryClient({ queryCache })

  it('should render the correct amount of times in Suspense mode', async () => {
    const key = queryKey()
    const states: CreateQueryResult<number>[] = []

    let count = 0
    let renders = 0

    function Page() {
      const [stateKey, setStateKey] = createSignal(key())

      const state = createQuery(
        stateKey,
        async () => {
          count++
          await sleep(10)
          return count
        },
        { suspense: true },
      )

      createRenderEffect(() => {
        states.push({ ...state })
      })

      createRenderEffect(
        on([() => ({ ...state }), key], () => {
          renders++
        }),
      )

      return (

          <button aria-label="toggle" onClick={() => setStateKey(queryKey())} />
          data: {String(state.data)}

      )
    }

    render(() => (
      <QueryClientProvider client={queryClient}>
        <Suspense fallback="loading">
          <Page />
        </Suspense>
      </QueryClientProvider>
    ))

    await waitFor(() => screen.getByText('data: 1'))
    fireEvent.click(screen.getByLabelText('toggle'))

    await waitFor(() => screen.getByText('data: 2'))

    expect(renders).toBe(4)
    // TODO(lukemurray): verify that this expectation is valid. this is 2 in
    // react, but 4 in solid, because in solid suspense is triggered on read and
    // the component needs to render in order to trigger suspense.
    expect(states.length).toBe(4)
    expect(states[1]).toMatchObject({ data: 1, status: 'success' })
    expect(states[3]).toMatchObject({ data: 2, status: 'success' })
  })

  it('should return the correct states for a successful infinite query', async () => {
    const key = queryKey()
    const states: CreateInfiniteQueryResult<number>[] = []

    function Page() {
      const [multiplier, setMultiplier] = createSignal(1)
      const state = createInfiniteQuery(
        () => [`${key()}_${multiplier()}`],
        async ({ pageParam = 1 }) => {
          await sleep(10)
          return Number(pageParam * multiplier())
        },
        {
          suspense: true,
          getNextPageParam: (lastPage) => lastPage + 1,
        },
      )

      createRenderEffect(() => {
        states.push({ ...state })
      })

      return (

          <button onClick={() => setMultiplier(2)}>next</button>
          data: {state.data?.pages.join(',')}

      )
    }

    render(() => (
      <QueryClientProvider client={queryClient}>
        <Suspense fallback="loading">
          <Page />
        </Suspense>
      </QueryClientProvider>
    ))

    await waitFor(() => screen.getByText('data: 1'))

    // TODO(lukemurray): in react this is 1 in solid this is 2 because suspense
    // occurs on read.
    expect(states.length).toBe(2)
    expect(states[1]).toMatchObject({
      data: { pages: [1], pageParams: [undefined] },
      status: 'success',
    })

    fireEvent.click(screen.getByText('next'))
    await waitFor(() => screen.getByText('data: 2'))

    // TODO(lukemurray): in react this is 2 and in solid it is 4
    expect(states.length).toBe(4)
    expect(states[3]).toMatchObject({
      data: { pages: [2], pageParams: [undefined] },
      status: 'success',
    })
  })

  it('should not call the queryFn twice when used in Suspense mode', async () => {
    const key = queryKey()

    const queryFn = jest.fn<string, unknown[]>()
    queryFn.mockImplementation(() => {
      sleep(10)
      return 'data'
    })

    function Page() {
      createQuery(() => [key()], queryFn, { suspense: true })

      return <>rendered</>
    }

    render(() => (
      <QueryClientProvider client={queryClient}>
        <Suspense fallback="loading">
          <Page />
        </Suspense>
      </QueryClientProvider>
    ))

    await waitFor(() => screen.getByText('rendered'))

    expect(queryFn).toHaveBeenCalledTimes(1)
  })

  it('should remove query instance when component unmounted', async () => {
    const key = queryKey()

    function Page() {
      createQuery(
        key,
        () => {
          sleep(10)
          return 'data'
        },
        { suspense: true },
      )

      return <>rendered</>
    }

    function App() {
      const [show, setShow] = createSignal(false)

      return (
        <>
          <Suspense fallback="loading">{show() && <Page />}</Suspense>
          <button
            aria-label="toggle"
            onClick={() => setShow((prev) => !prev)}
          />
        </>
      )
    }

    render(() => (
      <QueryClientProvider client={queryClient}>
        <App />
      </QueryClientProvider>
    ))

    expect(screen.queryByText('rendered')).toBeNull()
    expect(queryCache.find(key())).toBeFalsy()

    fireEvent.click(screen.getByLabelText('toggle'))
    await waitFor(() => screen.getByText('rendered'))

    expect(queryCache.find(key())?.getObserversCount()).toBe(1)

    fireEvent.click(screen.getByLabelText('toggle'))

    expect(screen.queryByText('rendered')).toBeNull()
    expect(queryCache.find(key())?.getObserversCount()).toBe(0)
  })

  it('should call onSuccess on the first successful call', async () => {
    const key = queryKey()

    const successFn = jest.fn()

    function Page() {
      createQuery(
        () => [key()],
        async () => {
          await sleep(10)
          return key()
        },
        {
          suspense: true,
          select: () => 'selected',
          onSuccess: successFn,
        },
      )

      return <>rendered</>
    }

    render(() => (
      <QueryClientProvider client={queryClient}>
        <Suspense fallback="loading">
          <Page />
        </Suspense>
      </QueryClientProvider>
    ))

    await waitFor(() => screen.getByText('rendered'))

    await waitFor(() => expect(successFn).toHaveBeenCalledTimes(1))
    await waitFor(() => expect(successFn).toHaveBeenCalledWith('selected'))
  })

  it('should call every onSuccess handler within a suspense boundary', async () => {
    const key = queryKey()

    const successFn1 = jest.fn()
    const successFn2 = jest.fn()

    function FirstComponent() {
      createQuery(
        key,
        () => {
          sleep(10)
          return 'data'
        },
        {
          suspense: true,
          onSuccess: successFn1,
        },
      )

      return first
    }

    function SecondComponent() {
      createQuery(
        key,
        () => {
          sleep(10)
          return 'data'
        },
        {
          suspense: true,
          onSuccess: successFn2,
        },
      )

      return second
    }

    render(() => (
      <QueryClientProvider client={queryClient}>
        <Suspense fallback="loading">
          <FirstComponent />
          <SecondComponent />
        </Suspense>
        ,
      </QueryClientProvider>
    ))

    await waitFor(() => screen.getByText('second'))

    await waitFor(() => expect(successFn1).toHaveBeenCalledTimes(1))
    await waitFor(() => expect(successFn2).toHaveBeenCalledTimes(1))
  })

  // https://github.com/tannerlinsley/react-query/issues/468
  it('should reset error state if new component instances are mounted', async () => {
    const key = queryKey()

    let succeed = false

    function Page() {
      const state = createQuery(
        key,
        async () => {
          await sleep(10)

          if (!succeed) {
            throw new Error('Suspense Error Bingo')
          } else {
            return 'data'
          }
        },
        {
          retryDelay: 10,
          suspense: true,
        },
      )

      // Suspense only triggers if used in JSX
      return (
        <Show when={state.data}>

rendered

        </Show>
      )
    }

    render(() => (
      <QueryClientProvider client={queryClient}>
        <ErrorBoundary
          fallback={(_err, resetSolid) => (

error boundary

              <button
                onClick={() => {
                  succeed = true
                  resetSolid()
                }}
              >
                retry
              </button>

          )}
        >
          <Suspense fallback={'Loading...'}>
            <Page />
          </Suspense>
        </ErrorBoundary>
      </QueryClientProvider>
    ))

    await waitFor(() => screen.getByText('Loading...'))

    await waitFor(() => screen.getByText('error boundary'))

    await waitFor(() => screen.getByText('retry'))

    fireEvent.click(screen.getByText('retry'))

    await waitFor(() => screen.getByText('rendered'))
  })

  it('should retry fetch if the reset error boundary has been reset', async () => {
    const key = queryKey()

    let succeed = false

    function Page() {
      const state = createQuery(
        key,
        async () => {
          await sleep(10)
          if (!succeed) {
            throw new Error('Suspense Error Bingo')
          } else {
            return 'data'
          }
        },
        {
          retry: false,
          suspense: true,
        },
      )

      // Suspense only triggers if used in JSX
      return (
        <Show when={state.data}>

rendered

        </Show>
      )
    }

    render(() => (
      <QueryClientProvider client={queryClient}>
        <ErrorBoundary
          fallback={(_err, resetSolid) => (

error boundary

              <button
                onClick={() => {
                  resetSolid()
                }}
              >
                retry
              </button>

          )}
        >
          <Suspense fallback="Loading...">
            <Page />
          </Suspense>
        </ErrorBoundary>
      </QueryClientProvider>
    ))

    await waitFor(() => screen.getByText('Loading...'))
    await waitFor(() => screen.getByText('error boundary'))
    await waitFor(() => screen.getByText('retry'))
    fireEvent.click(screen.getByText('retry'))
    await waitFor(() => screen.getByText('error boundary'))
    await waitFor(() => screen.getByText('retry'))
    succeed = true
    fireEvent.click(screen.getByText('retry'))
    await waitFor(() => screen.getByText('rendered'))
  })

  it('should refetch when re-mounting', async () => {
    const key = queryKey()
    let count = 0

    function Component() {
      const result = createQuery(
        key,
        async () => {
          await sleep(100)
          count++
          return count
        },
        {
          retry: false,
          suspense: true,
          staleTime: 0,
        },
      )
      return (

          data: {result.data}
          fetching: {result.isFetching ? 'true' : 'false'}

      )
    }

    function Page() {
      const [show, setShow] = createSignal(true)
      return (

          <button
            onClick={() => {
              setShow(!show())
            }}
          >
            {show() ? 'hide' : 'show'}
          </button>
          <Suspense fallback="Loading...">{show() && <Component />}</Suspense>

      )
    }

    render(() => (
      <QueryClientProvider client={queryClient}>
        <Page />
      </QueryClientProvider>
    ))

    await waitFor(() => screen.getByText('Loading...'))
    await waitFor(() => screen.getByText('data: 1'))
    await waitFor(() => screen.getByText('fetching: false'))
    await waitFor(() => screen.getByText('hide'))
    fireEvent.click(screen.getByText('hide'))
    await waitFor(() => screen.getByText('show'))
    fireEvent.click(screen.getByText('show'))
    await waitFor(() => screen.getByText('fetching: true'))
    await waitFor(() => screen.getByText('data: 2'))
    await waitFor(() => screen.getByText('fetching: false'))
  })

  it('should suspend when switching to a new query', async () => {
    const key1 = queryKey()
    const key2 = queryKey()

    function Component(props: { queryKey: Array<string> }) {
      const result = createQuery(
        () => props.queryKey,
        async () => {
          await sleep(100)
          return props.queryKey
        },
        {
          retry: false,
          suspense: true,
        },
      )
      return
data: {result.data}

    }

    function Page() {
      const [key, setKey] = createSignal(key1())
      return (

          <button
            onClick={() => {
              setKey(key2())
            }}
          >
            switch
          </button>
          <Suspense fallback="Loading...">
            <Component queryKey={key()} />
          </Suspense>

      )
    }

    render(() => (
      <QueryClientProvider client={queryClient}>
        <Page />
      </QueryClientProvider>
    ))

    await waitFor(() => screen.getByText('Loading...'))
    await waitFor(() => screen.getByText(`data: ${key1()}`))
    fireEvent.click(screen.getByText('switch'))
    await waitFor(() => screen.getByText('Loading...'))
    await waitFor(() => screen.getByText(`data: ${key2()}`))
    expect(
      // @ts-expect-error
      queryClient.getQueryCache().find(key2())!.observers[0].listeners.length,
    ).toBe(1)
  })

  it('should throw errors to the error boundary by default', async () => {
    const key = queryKey()

    function Page() {
      const state = createQuery(
        key,
        async (): Promise<unknown> => {
          await sleep(10)
          throw new Error('Suspense Error a1x')
        },
        {
          retry: false,
          suspense: true,
        },
      )

      // read state.data to trigger suspense.
      createRenderEffect(() => {
        // eslint-disable-next-line @typescript-eslint/no-unused-expressions -- trigger suspense
        state.data
      })

      return
rendered

    }

    function App() {
      return (
        <ErrorBoundary
          fallback={() => (

error boundary

          )}
        >
          <Suspense fallback="Loading...">
            <Page />
          </Suspense>
        </ErrorBoundary>
      )
    }

    render(() => (
      <QueryClientProvider client={queryClient}>
        <App />
      </QueryClientProvider>
    ))

    await waitFor(() => screen.getByText('Loading...'))
    await waitFor(() => screen.getByText('error boundary'))
  })

  it('should not throw errors to the error boundary when useErrorBoundary: false', async () => {
    const key = queryKey()

    function Page() {
      const state = createQuery(
        key,
        async (): Promise<unknown> => {
          await sleep(10)
          throw new Error('Suspense Error a2x')
        },
        {
          retry: false,
          suspense: true,
          useErrorBoundary: false,
        },
      )

      // read state.data to trigger suspense.
      createRenderEffect(() => {
        // eslint-disable-next-line @typescript-eslint/no-unused-expressions -- trigger suspense
        state.data
      })

      return
rendered

    }

    function App() {
      return (
        <ErrorBoundary
          fallback={() => (

error boundary

          )}
        >
          <Suspense fallback="Loading...">
            <Page />
          </Suspense>
        </ErrorBoundary>
      )
    }

    render(() => (
      <QueryClientProvider client={queryClient}>
        <App />
      </QueryClientProvider>
    ))

    await waitFor(() => screen.getByText('Loading...'))
    await waitFor(() => screen.getByText('rendered'))
  })

  it('should not throw errors to the error boundary when a useErrorBoundary function returns true', async () => {
    const key = queryKey()

    function Page() {
      const state = createQuery(
        key,
        async (): Promise<unknown> => {
          await sleep(10)
          return Promise.reject('Remote Error')
        },
        {
          retry: false,
          suspense: true,
          useErrorBoundary: (err) => err !== 'Local Error',
        },
      )

      // read state.data to trigger suspense.
      createRenderEffect(() => {
        // eslint-disable-next-line @typescript-eslint/no-unused-expressions -- trigger suspense
        state.data
      })

      return
rendered

    }

    function App() {
      return (
        <ErrorBoundary
          fallback={() => (

error boundary

          )}
        >
          <Suspense fallback="Loading...">
            <Page />
          </Suspense>
        </ErrorBoundary>
      )
    }

    render(() => (
      <QueryClientProvider client={queryClient}>
        <App />
      </QueryClientProvider>
    ))

    await waitFor(() => screen.getByText('Loading...'))
    await waitFor(() => screen.getByText('error boundary'))
  })

  it('should not throw errors to the error boundary when a useErrorBoundary function returns false', async () => {
    const key = queryKey()

    function Page() {
      const state = createQuery(
        key,
        async (): Promise<unknown> => {
          await sleep(10)
          return Promise.reject('Local Error')
        },
        {
          retry: false,
          suspense: true,
          useErrorBoundary: (err) => err !== 'Local Error',
        },
      )

      // read state.data to trigger suspense.
      createRenderEffect(() => {
        // eslint-disable-next-line @typescript-eslint/no-unused-expressions -- trigger suspense
        state.data
      })

      return
rendered

    }

    function App() {
      return (
        <ErrorBoundary
          fallback={() => (

error boundary

          )}
        >
          <Suspense fallback="Loading...">
            <Page />
          </Suspense>
        </ErrorBoundary>
      )
    }

    render(() => (
      <QueryClientProvider client={queryClient}>
        <App />
      </QueryClientProvider>
    ))

    await waitFor(() => screen.getByText('Loading...'))
    await waitFor(() => screen.getByText('rendered'))
  })

  it('should not call the queryFn when not enabled', async () => {
    const key = queryKey()

    const queryFn = jest.fn<Promise<string>, unknown[]>()
    queryFn.mockImplementation(async () => {
      await sleep(10)
      return '23'
    })

    function Page() {
      const [enabled, setEnabled] = createSignal(false)
      const result = createQuery(() => [key()], queryFn, {
        suspense: true,
        get enabled() {
          return enabled()
        },
      })

      return (

          <button onClick={() => setEnabled(true)}>fire</button>

{result.data}

      )
    }

    render(() => (
      <QueryClientProvider client={queryClient}>
        <Suspense fallback="loading">
          <Page />
        </Suspense>
      </QueryClientProvider>
    ))

    expect(queryFn).toHaveBeenCalledTimes(0)
    await sleep(5)
    fireEvent.click(screen.getByRole('button', { name: /fire/i }))

    await waitFor(() => {
      expect(screen.getByRole('heading').textContent).toBe('23')
    })

    expect(queryFn).toHaveBeenCalledTimes(1)
  })

  it('should error catched in error boundary without infinite loop', async () => {
    const key = queryKey()

    let succeed = true

    function Page() {
      const [nonce] = createSignal(0)
      const queryKeys = () => [`${key()}-${succeed}`]
      const result = createQuery(
        queryKeys,
        async () => {
          await sleep(10)
          if (!succeed) {
            throw new Error('Suspense Error Bingo')
          } else {
            return nonce()
          }
        },
        {
          retry: false,
          suspense: true,
        },
      )
      return (

          rendered {result.data}
          <button
            aria-label="fail"
            onClick={async () => {
              await queryClient.resetQueries()
            }}
          >
            fail
          </button>

      )
    }

    function App() {
      return (
        <ErrorBoundary fallback={() =>
error boundary
}>
          <Suspense fallback="Loading...">
            <Page />
          </Suspense>
        </ErrorBoundary>
      )
    }

    render(() => (
      <QueryClientProvider client={queryClient}>
        <App />
      </QueryClientProvider>
    ))

    // render suspense fallback (Loading...)
    await waitFor(() => screen.getByText('Loading...'))
    // resolve promise -> render Page (rendered)
    await waitFor(() => screen.getByText('rendered'))

    // change query key
    succeed = false
    // reset query -> and throw error
    fireEvent.click(screen.getByLabelText('fail'))
    // render error boundary fallback (error boundary)
    await waitFor(() => screen.getByText('error boundary'))
  })

  it('should error catched in error boundary without infinite loop when query keys changed', async () => {
    let succeed = true

    function Page() {
      const [key, setKey] = createSignal(0)

      const result = createQuery(
        () => [`${key()}-${succeed}`],
        async () => {
          await sleep(10)
          if (!succeed) {
            throw new Error('Suspense Error Bingo')
          } else {
            return 'data'
          }
        },
        {
          retry: false,
          suspense: true,
        },
      )
      return (

          rendered {result.data}
          <button aria-label="fail" onClick={() => setKey((k) => k + 1)}>
            fail
          </button>

      )
    }

    function App() {
      return (
        <ErrorBoundary fallback={() =>
error boundary
}>
          <Suspense fallback="Loading...">
            <Page />
          </Suspense>
        </ErrorBoundary>
      )
    }

    render(() => (
      <QueryClientProvider client={queryClient}>
        <App />
      </QueryClientProvider>
    ))

    // render suspense fallback (Loading...)
    await waitFor(() => screen.getByText('Loading...'))
    // resolve promise -> render Page (rendered)
    await waitFor(() => screen.getByText('rendered'))

    // change promise result to error
    succeed = false
    // change query key
    fireEvent.click(screen.getByLabelText('fail'))
    // render error boundary fallback (error boundary)
    await waitFor(() => screen.getByText('error boundary'))
  })

  it('should error catched in error boundary without infinite loop when enabled changed', async () => {
    function Page() {
      const queryKeys = '1'
      const [enabled, setEnabled] = createSignal(false)

      const result = createQuery<string>(
        () => [queryKeys],
        async () => {
          await sleep(10)
          throw new Error('Suspense Error Bingo')
        },
        {
          retry: false,
          suspense: true,
          get enabled() {
            return enabled()
          },
        },
      )
      return (

          rendered {result.data}
          <button
            aria-label="fail"
            onClick={() => {
              setEnabled(true)
            }}
          >
            fail
          </button>

      )
    }

    function App() {
      return (
        <ErrorBoundary fallback={() =>
error boundary
}>
          <Suspense fallback="Loading...">
            <Page />
          </Suspense>
        </ErrorBoundary>
      )
    }

    render(() => (
      <QueryClientProvider client={queryClient}>
        <App />
      </QueryClientProvider>
    ))

    // render empty data with 'rendered' when enabled is false
    await waitFor(() => screen.getByText('rendered'))

    // change enabled to true
    fireEvent.click(screen.getByLabelText('fail'))

    // render pending fallback
    await waitFor(() => screen.getByText('Loading...'))

    // render error boundary fallback (error boundary)
    await waitFor(() => screen.getByText('error boundary'))
  })

  it('should render the correct amount of times in Suspense mode when cacheTime is set to 0', async () => {
    const key = queryKey()
    let state: CreateQueryResult<number> | null = null

    let count = 0
    let renders = 0

    function Page() {
      state = createQuery(
        key,
        async () => {
          count++
          await sleep(10)
          return count
        },
        { suspense: true, cacheTime: 0 },
      )

      createRenderEffect(
        on([() => ({ ...state })], () => {
          renders++
        }),
      )

      return (

          rendered

      )
    }

    render(() => (
      <QueryClientProvider client={queryClient}>
        <Suspense fallback="loading">
          <Page />
        </Suspense>
      </QueryClientProvider>
    ))

    await waitFor(() =>
      expect(state).toMatchObject({
        data: 1,
        status: 'success',
      }),
    )

    expect(renders).toBe(2)
    expect(screen.queryByText('rendered')).not.toBeNull()
  })
})


# Example

To run this example:

- `npm install`
- `npm run start`


const hasFirstObjectArgument = firstArgument.type === 'ObjectExpression'

keep keep keep add keep keep keep keep keep

.references.find((ref) => ref.identifier === firstArgument)

reference?.resolved?.defs[0]?.node.type === 'VariableDeclarator' &&


# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# production
/build

pnpm-lock.yaml
yarn.lock
package-lock.json

# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local

npm-debug.log*
yarn-debug.log*
yarn-error.log*


import type {
  PersistedClient,
  Persister,
  PersistRetryer,
} from '@tanstack/query-persist-client-core'

interface Storage {
  getItem: (key: string) => string | null
  setItem: (key: string, value: string) => void
  removeItem: (key: string) => void
}

interface CreateSyncStoragePersisterOptions {
  /** The storage client used for setting and retrieving items from cache.
   * For SSR pass in `undefined`.
   */
  storage: Storage | undefined
  /** The key to use when storing the cache */
  key?: string
  /** To avoid spamming,
   * pass a time in ms to throttle saving the cache to disk */
  throttleTime?: number
  /**
   * How to serialize the data to storage.
   * @default `JSON.stringify`
   */
  serialize?: (client: PersistedClient) => string
  /**
   * How to deserialize the data from storage.
   * @default `JSON.parse`
   */
  deserialize?: (cachedString: string) => PersistedClient

  retry?: PersistRetryer
}

export function createSyncStoragePersister({
  storage,
  key = `REACT_QUERY_OFFLINE_CACHE`,
  throttleTime = 1000,
  serialize = JSON.stringify,
  deserialize = JSON.parse,
  retry,
}: CreateSyncStoragePersisterOptions): Persister {
  if (typeof storage !== 'undefined') {
    const trySave = (persistedClient: PersistedClient): Error | undefined => {
      try {
        storage.setItem(key, serialize(persistedClient))
      } catch (error) {
        return error as Error
      }
    }
    return {
      persistClient: throttle((persistedClient) => {
        let client: PersistedClient | undefined = persistedClient
        let error = trySave(client)
        let errorCount = 0
        while (error && client) {
          errorCount++
          client = retry?.({
            persistedClient: client,
            error,
            errorCount,
          })

          if (client) {
            error = trySave(client)
          }
        }
      }, throttleTime),
      restoreClient: () => {
        const cacheString = storage.getItem(key)

        if (!cacheString) {
          return
        }

        return deserialize(cacheString) as PersistedClient
      },
      removeClient: () => {
        storage.removeItem(key)
      },
    }
  }

  return {
    persistClient: noop,
    restoreClient: () => undefined,
    removeClient: noop,
  }
}

function throttle<TArgs extends any[]>(
  func: (...args: TArgs) => any,
  wait = 100,
) {
  let timer: ReturnType<typeof setTimeout> | null = null
  let params: TArgs
  return function (...args: TArgs) {
    params = args
    if (timer === null) {
      timer = setTimeout(() => {
        func(...params)
        timer = null
      }, wait)
    }
  }
}

// eslint-disable-next-line @typescript-eslint/no-empty-function
function noop() {}


import movies from '../data/movies.json';

export type Movie = {
  title: string;
  year: number;
};

export type MovieDetails = Movie & {
  info: {
    plot: string;
    actors: string[];
  };
};

function delay(t: number) {
  return new Promise(function (resolve) {
    setTimeout(resolve, t);
  });
}

export async function fetchMovies() {
  console.log('fetchMovies');

  await delay(200 + Math.floor(Math.random() * 2000));

  return movies
    .slice(0, 100)
    .map((movie) => ({ title: movie.title, year: movie.year })) as Promise<
    Movie[]
  >;
}

export async function fetchMovie(title: string) {
  console.log('fetchMovie', title);

  await delay(200 + Math.floor(Math.random() * 2000));

  const result = movies.filter((item) => item.title === title);

  if (result.length == 0) {
    throw new Error('Movie not found');
  }
  return result[0] as Promise<MovieDetails>;
}


'list__tile--link': this.isLink && !this.inactive,

'list__tile--active': !this.to && this.isActive,

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

exports[`VSelect should be clearable with prop, dirty and multi select 1`] = `

        1

      <input disabled="disabled"
             tabindex="-1"
             class="input-group--select__autocomplete"
             style="display: none;"
      >

                        check_box_outline_blank

                        check_box

                    1

                        check_box_outline_blank

                    2

      clear

`;

exports[`VSelect should be clearable with prop, dirty and multi select 2`] = `

      <input disabled="disabled"
             tabindex="-1"
             class="input-group--select__autocomplete"
             style="display: none;"
      >

                        check_box

                        check_box_outline_blank

                    1

                        check_box_outline_blank

                    2

      arrow_drop_down

`;

exports[`VSelect should be clearable with prop, dirty and single select 1`] = `

        1

      <input disabled="disabled"
             tabindex="-1"
             class="input-group--select__autocomplete"
             style="display: none;"
      >

                    1

                    2

      clear

`;

exports[`VSelect should be clearable with prop, dirty and single select 2`] = `

      <input disabled="disabled"
             tabindex="-1"
             class="input-group--select__autocomplete"
             style="display: none;"
      >

                    1

                    2

      arrow_drop_down

`;


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

exports[`VDialog.js should render a disabled component and match snapshot 1`] = `

`;

exports[`VDialog.js should render a fullscreen component and match snapshot 1`] = `

`;

exports[`VDialog.js should render a lazy component and match snapshot 1`] = `

`;

exports[`VDialog.js should render a persistent component and match snapshot 1`] = `

`;

exports[`VDialog.js should render a scrollable component and match snapshot 1`] = `

`;

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

`;

exports[`VDialog.js should render component with custom origin and match snapshot 1`] = `

`;

exports[`VDialog.js should render component with custom transition and match snapshot 1`] = `

`;

exports[`VDialog.js should render component with custom width (max-width) and match snapshot 1`] = `

`;


import { test } from '~util/testing'
import { maskText, unmaskText } from './mask'

test('mask.js', ({ mount }) => {
  // Mask
  it('should add delimiter', () => {
    expect(maskText('5', '(#')).toBe('(5')
  })

  it('should add delimiter if explicity typed', () => {
    expect(maskText('(', '(#')).toBe('(')
  })

  it('should add delimiters and masks', () => {
    expect(maskText('4567', '(###) #')).toBe('(456) 7')
    expect(maskText('444444444', '#### - #### - #### - ####')).toBe('4444 - 4444 - 4')
    expect(maskText('A314444', 'A## - ####')).toBe('A31 - 4444')
  })

  it('should fill mask blanks', () => {
    expect(maskText('55', '## - ##')).toBe('55 - ')
  })

  it('should not fill mask blanks if told not to', () => {
    expect(maskText('55', '## - ##', true)).toBe('55')
  })

  it('should not fill if no value is provided', () => {
    expect(maskText('', '## - ##')).toBe('')
  })

  it('should only allow exact input if dontFillMaskBlanks is true', () => {
    expect(maskText('4567', '(###) #', true)).toBe('')
    expect(maskText('(456)', '(###) #', true)).toBe('(456)')
  })

  it('should convert alphanumeric to the proper case', () => {
    expect(maskText('aa', 'Aa')).toBe('Aa')
    expect(maskText('AA', 'aa')).toBe('aa')
    expect(maskText('A1', 'Aa')).toBe('A')
    expect(maskText('12abAB', 'NnNnNn')).toBe('12AbAb')
  })

  it('should not fill if wrong value is provided even if it is correct later in mask', () => {
    expect(maskText('a', '#a')).toBe('')
  })

  /* Not sure how to implement this
  it('should not fill in delimiter value if it does not match', () => {
    expect(maskText('a', '(')).toBe('')
  })
  */

  it('should fill last characters if they are all delimiters', () => {
    expect(maskText('1', '#)')).toBe('1)')
    expect(maskText('123', '(###)!!')).toBe('(123)!!')
  })

  // Unmasks
  it('should remove delimiter', () => {
    expect(unmaskText('(5')).toBe('5')
  })

  it('should return proper text length', () => {
    expect(unmaskText('1111')).toBe('1111')
  })

  it('should remove delimiters and masks', () => {
    expect(unmaskText('(123)4')).toBe('1234')
    expect(unmaskText('(456) 7)')).toBe('4567')
    expect(unmaskText('4444 - 4444 - 4')).toBe('444444444')
    expect(unmaskText('A31 - 4444')).toBe('A314444')
  })

  it('should include all possible matching values', () => {
    expect(unmaskText('555')).toBe('555')
  })

  it('should mask numbers', () => {
    expect(maskText(1234, '##.##')).toBe('12.34')
  })
})


import VBottomNav from './VBottomNav'

VBottomNav.install = function install (Vue) {
  Vue.component(VBottomNav.name, VBottomNav)
}

export default VBottomNav


class="list__tile list__tile--link list__tile--active primary--text"

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

exports[`VSelect should render buttons correctly when using items array with segmented prop 1`] = `

      <button type="button"
              class="btn btn--flat"
              data-ripple="true"
      >

          Hello

      </button>
      <input disabled="disabled"
             tabindex="-1"
             class="input-group--select__autocomplete"
             style="display: none;"
      >

                    Hello

      arrow_drop_down

`;

exports[`VSelect should render buttons correctly when using slot with segmented prop 1`] = `

        Hello

      <input disabled="disabled"
             tabindex="-1"
             class="input-group--select__autocomplete"
             style="display: none;"
      >

                    Hello

      arrow_drop_down

`;


import { test } from '~util/testing'
import Vue from 'vue/dist/vue.common'
import VTextField from '~components/VTextField'
import VProgressLinear from '~components/VProgressLinear'

test('VTextField.js', ({ mount }) => {
  it('should render component and match snapshot', () => {
    const wrapper = mount(VTextField)

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

  it('should pass events to internal input field', () => {
    const keyup = jest.fn()
    const component = {
      render (h) {
        return h(VTextField, { on: { keyUp: keyup }, props: { download: '' }, attrs: {} })
      }
    }
    const wrapper = mount(component)

    const input = wrapper.find('input')[0]
    input.trigger('keyUp', { keyCode: 65 })

    expect(keyup).toBeCalled()
  })

  it('should render aria-label attribute on text field element with label value and no id', () => {
    const wrapper = mount(VTextField, {
      propsData: {
        label: 'Test'
      },
      attrs: {}
    })

    const inputGroup = wrapper.find('input')[0]
    expect(inputGroup.getAttribute('aria-label')).toBe('Test')
  })

  it('should not render aria-label attribute on text field element with no label value or id', () => {
    const wrapper = mount(VTextField, {
      propsData: {
        label: null
      },
      attrs: {}
    })

    const inputGroup = wrapper.find('input')[0]
    expect(inputGroup.element.getAttribute('aria-label')).toBeFalsy()
  })

  it('should not render aria-label attribute on text field element with id', () => {
    const wrapper = mount(VTextField, {
      propsData: {
        label: 'Test'
      },
      attrs: {
        id: 'Test'
      }
    })

    const inputGroup = wrapper.find('input')[0]
    expect(inputGroup.element.getAttribute('aria-label')).toBeFalsy()
  })

  it('should start out as invalid', () => {
    const wrapper = mount(VTextField, {
      propsData: {
        rules: [(v) => !!v || 'Required']
      }
    })

    expect(wrapper.data().valid).toEqual(false)
  })

  it('should start validating on input', async () => {
    const wrapper = mount(VTextField, {})

    expect(wrapper.data().shouldValidate).toEqual(false)
    wrapper.setProps({ value: 'asd' })
    await wrapper.vm.$nextTick()
    expect(wrapper.data().shouldValidate).toEqual(true)
  })

  it('should not start validating on input if validate-on-blur prop is set', async () => {
    const wrapper = mount(VTextField, {
      propsData: {
        validateOnBlur: true
      }
    })

    expect(wrapper.data().shouldValidate).toEqual(false)
    wrapper.setProps({ value: 'asd' })
    await wrapper.vm.$nextTick()
    expect(wrapper.data().shouldValidate).toEqual(false)
  })

  it('should not display counter when set to false', async () => {
    const wrapper = mount(VTextField, {
      propsData: {
        counter: true,
        max: 50
      }
    })

    expect(wrapper.find('.input-group__counter')[0]).not.toBe(undefined)
    expect(wrapper.html()).toMatchSnapshot()

    wrapper.setProps({ counter: false })
    await wrapper.vm.$nextTick()

    expect(wrapper.html()).toMatchSnapshot()
    expect(wrapper.find('.input-group__counter')[0]).toBe(undefined)
  })

  it('should have readonly attribute', () => {
    const wrapper = mount(VTextField, {
      propsData: {
        readonly: true
      }
    })

    const input = wrapper.find('input')[0]

    expect(input.getAttribute('readonly')).toBe('readonly')
  })

  it('should clear input value', async () => {
    const wrapper = mount(VTextField, {
      propsData: {
        clearable: true,
        value: 'foo'
      }
    })

    const clear = wrapper.find('.input-group__append-icon')[0]
    const input = jest.fn()
    wrapper.vm.$on('input', input)

    expect(wrapper.vm.inputValue).toBe('foo')

    clear.trigger('click')

    await wrapper.vm.$nextTick()

    expect(input).toHaveBeenCalledWith(null)
  })

  it('should not clear input if not clearable and has appended icon (with callback)', async () => {
    const appendIconCb = jest.fn()
    const wrapper = mount(VTextField, {
      propsData: {
        value: 'foo',
        appendIcon: 'block',
        appendIconCb
      }
    })

    const icon = wrapper.find('.input-group__append-icon')[0]
    icon.trigger('click')
    await wrapper.vm.$nextTick()
    expect(wrapper.vm.inputValue).toBe('foo')
    expect(appendIconCb.mock.calls).toHaveLength(1)
  })

  it('should not clear input if not clearable and has appended icon (without callback)', async () => {
    const wrapper = mount(VTextField, {
      propsData: {
        value: 'foo',
        appendIcon: 'block',
      }
    })

    const icon = wrapper.find('.input-group__append-icon')[0]
    icon.trigger('click')
    await wrapper.vm.$nextTick()
    expect(wrapper.vm.inputValue).toBe('foo')
  })

  it('should start validating on blur', async () => {
    const wrapper = mount(VTextField, {})

    const input = wrapper.find('input')[0]
    expect(wrapper.data().shouldValidate).toEqual(false)
    input.trigger('focus')
    await wrapper.vm.$nextTick()
    input.trigger('blur')
    await wrapper.vm.$nextTick()
    expect(wrapper.data().shouldValidate).toEqual(true)
  })

  it('should keep its value on blur', async () => {
    const wrapper = mount(VTextField, {
      propsData: {
        value: 'asd'
      }
    })

    const input = wrapper.find('input')[0]

    input.element.value = 'fgh'
    input.trigger('input')
    input.trigger('blur')

    expect(input.element.value).toBe('fgh')
  })

  it('should update if value is changed externally', async () => {
    const wrapper = mount(VTextField, {})

    const input = wrapper.find('input')[0]

    wrapper.setProps({ value: 'fgh' })
    expect(input.element.value).toBe('fgh')

    input.trigger('focus')
    wrapper.setProps({ value: 'jkl' })
    expect(input.element.value).toBe('jkl')
  })

  it('should fire a single change event on blur', async () => {
    let value = 'asd'
    const change = jest.fn()

    const component = {
      render (h) {
        return h(VTextField, {
          on: {
            input: (i) => value = i,
            change
          },
          props: { value }
        })
      }
    }
    const wrapper = mount(component)

    const input = wrapper.find('input')[0]

    input.trigger('focus')
    await wrapper.vm.$nextTick()
    input.element.value = 'fgh'
    input.trigger('input')

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

    expect(change).toBeCalledWith('fgh')
    expect(change.mock.calls).toHaveLength(1)
  })

  it('should not make prepend icon clearable', () => {
    const wrapper = mount(VTextField, {
      propsData: {
        prependIcon: 'check',
        appendIcon: 'check',
        value: 'test',
        clearable: true
      }
    })

    const prepend = wrapper.find('.input-group__prepend-icon')[0]
    expect(prepend.text()).toBe('check')
    expect(prepend.element.classList).not.toContain('input-group__icon-cb')
  })

  it('should not emit change event if value has not changed', async () => {
    const change = jest.fn()
    let value = 'test'
    const component = {
      render (h) {
        return h(VTextField, {
          on: {
            input: i => value = i,
            change
          },
          props: { value }
        })
      }
    }
    const wrapper = mount(component)

    const input = wrapper.find('input')[0]

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

    expect(change.mock.calls.length).toBe(0)
  })

  it('should render component with async loading and match snapshot', () => {
    const wrapper = mount(VTextField, {
      components: {
        VProgressLinear
      },
      propsData: {
        loading: true
      }
    })

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

  it('should render component with async loading and custom progress and match snapshot', () => {
    const progress = Vue.component('test', {
      components: {
        VProgressLinear
      },
      render (h) {
        return h('v-progress-linear', {
          props: {
            indeterminate: true,
            height: 7,
            color: 'orange'
          }
        })
      }
    })

    const wrapper = mount(VTextField, {
      propsData: {
        loading: true
      },
      slots: {
        progress: [progress]
      }
    })

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

  it('should display the number 0', async () => {
    const wrapper = mount(VTextField, {
      propsData: { value: 0 }
    })

    expect(wrapper.vm.$refs.input.value).toBe('0')
  })
})


/* ! ress.css • v1.1.1 - MIT License - github.com/filipelinhares/ress */

/* # =================================================================
   # Global selectors
   # ================================================================= */

html {
  box-sizing: border-box;
  overflow-y: scroll; /* All browsers without overlaying scrollbars */
  -webkit-text-size-adjust: 100%; /* iOS 8+ */
}

*,
::before,
::after {
  box-sizing: inherit;
}

::before,
::after {
  text-decoration: inherit; /* Inherit text-decoration and vertical align to ::before and ::after pseudo elements */
  vertical-align: inherit;
}

/* Remove margin, padding of all elements and set background-no-repeat as default */
* {
  background-repeat: no-repeat; /* Set `background-repeat: no-repeat` to all elements */
  padding: 0; /* Reset `padding` and `margin` of all elements */
  margin: 0;
}

/* # =================================================================
   # General elements
   # ================================================================= */

/* Add the correct display in iOS 4-7.*/
audio:not([controls]) {
  display: none;
  height: 0;
}

hr {
  overflow: visible; /* Show the overflow in Edge and IE */
}

/*
* Correct `block` display not defined for any HTML5 element in IE 8/9
* Correct `block` display not defined for `details` or `summary` in IE 10/11
* and Firefox
* Correct `block` display not defined for `main` in IE 11
*/
article,
aside,
details,
figcaption,
figure,
footer,
header,
main,
menu,
nav,
section,
summary {
  display: block;
}

summary {
  display: list-item; /* Add the correct display in all browsers */
}

small {
  font-size: 80%; /* Set font-size to 80% in `small` elements */
}

[hidden],
template {
  display: none; /* Add the correct display in IE */
}

abbr[title] {
  border-bottom: 1px dotted; /* Add a bordered underline effect in all browsers */
  text-decoration: none; /* Remove text decoration in Firefox 40+ */
}

a {
  background-color: transparent; /* Remove the gray background on active links in IE 10 */
  -webkit-text-decoration-skip: objects; /* Remove gaps in links underline in iOS 8+ and Safari 8+ */
}

a:active,
a:hover {
  outline-width: 0; /* Remove the outline when hovering in all browsers */
}

code,
kbd,
pre,
samp {
  font-family: monospace, monospace; /* Specify the font family of code elements */
}

b,
strong {
  font-weight: bolder; /* Correct style set to `bold` in Edge 12+, Safari 6.2+, and Chrome 18+ */
}

dfn {
  font-style: italic; /* Address styling not present in Safari and Chrome */
}

/* Address styling not present in IE 8/9 */
mark {
  background-color: #ff0;
  color: #000;
}

/* https://gist.github.com/unruthless/413930 */
sub,
sup {
  font-size: 75%;
  line-height: 0;
  position: relative;
  vertical-align: baseline;
}

sub {
  bottom: -0.25em;
}

sup {
  top: -0.5em;
}

/* # =================================================================
   # Forms
   # ================================================================= */

input {
  border-radius: 0;
}

/* Apply cursor pointer to button elements */
button,
[type="button"],
[type="reset"],
[type="submit"]
[role="button"] {
  cursor: pointer;
}

/* Replace pointer cursor in disabled elements */
[disabled] {
  cursor: default;
}

[type="number"] {
  width: auto; /* Firefox 36+ */
}

[type="search"] {
  -webkit-appearance: textfield; /* Safari 8+ */
}

[type="search"]::-webkit-search-cancel-button,
[type="search"]::-webkit-search-decoration {
  -webkit-appearance: none; /* Safari 8 */
}

textarea {
  overflow: auto; /* Internet Explorer 11+ */
  resize: vertical; /* Specify textarea resizability */
}

button,
input,
optgroup,
select,
textarea {
  font: inherit; /* Specify font inheritance of form elements */
}

optgroup {
  font-weight: bold; /* Restore the font weight unset by the previous rule. */
}

button {
  overflow: visible; /* Address `overflow` set to `hidden` in IE 8/9/10/11 */
}

/* Remove inner padding and border in Firefox 4+ */
button::-moz-focus-inner,
[type="button"]::-moz-focus-inner,
[type="reset"]::-moz-focus-inner,
[type="submit"]::-moz-focus-inner {
  border-style: 0;
  padding: 0;
}

/* Replace focus style removed in the border reset above */
button:-moz-focusring,
[type="button"]::-moz-focus-inner,
[type="reset"]::-moz-focus-inner,
[type="submit"]::-moz-focus-inner {
  outline: 0;
  border: 0;
}

button,
html [type="button"], /* Prevent a WebKit bug where (2) destroys native `audio` and `video`controls in Android 4 */
[type="reset"],
[type="submit"] {
  -webkit-appearance: button; /* Correct the inability to style clickable types in iOS */
}

button,
select {
  text-transform: none; /* Firefox 40+, Internet Explorer 11- */
}

/* Remove the default button styling in all browsers */
button,
input,
select,
textarea {
  background-color: transparent;
  border-style: none;
  color: inherit;
}

/* Style select like a standard input */
select {
  -moz-appearance: none; /* Firefox 36+ */
  -webkit-appearance: none; /* Chrome 41+ */
}

select::-ms-expand {
  display: none; /* Internet Explorer 11+ */
}

select::-ms-value {
  color: currentColor; /* Internet Explorer 11+ */
}

legend {
  border: 0; /* Correct `color` not being inherited in IE 8/9/10/11 */
  color: inherit; /* Correct the color inheritance from `fieldset` elements in IE */
  display: table; /* Correct the text wrapping in Edge and IE */
  max-width: 100%; /* Correct the text wrapping in Edge and IE */
  white-space: normal; /* Correct the text wrapping in Edge and IE */
}

::-webkit-file-upload-button {
  -webkit-appearance: button; /* Correct the inability to style clickable types in iOS and Safari */
  font: inherit; /* Change font properties to `inherit` in Chrome and Safari */
}

[type="search"] {
  -webkit-appearance: textfield; /* Correct the odd appearance in Chrome and Safari */
  outline-offset: -2px; /* Correct the outline style in Safari */
}

/* # =================================================================
   # Specify media element style
   # ================================================================= */

img {
  border-style: none; /* Remove border when inside `a` element in IE 8/9/10 */
}

/* Add the correct vertical alignment in Chrome, Firefox, and Opera */
progress {
  vertical-align: baseline;
}

svg:not(:root) {
  overflow: hidden; /* Internet Explorer 11- */
}

audio,
canvas,
progress,
video {
  display: inline-block; /* Internet Explorer 11+, Windows Phone 8.1+ */
}

/* # =================================================================
   # Acessibility
   # ================================================================= */

/* Hide content from screens but not screenreaders */
@media screen {
  [hidden~="screen"] {
    display: inherit;
  }
  [hidden~="screen"]:not(:active):not(:focus):not(:target) {
    position: absolute !important;
    clip: rect(0 0 0 0) !important;
  }
}

/* Specify the progress cursor of updating elements */
[aria-busy="true"] {
  cursor: progress;
}

/* Specify the pointer cursor of trigger elements */
[aria-controls] {
  cursor: pointer;
}

/* Specify the unstyled cursor of disabled, not-editable, or otherwise inoperable elements */
[aria-disabled] {
  cursor: default;
}

/* # =================================================================
   # Selection
   # ================================================================= */

/* Specify text selection background color and omit drop shadow */

::-moz-selection {
  background-color: #b3d4fc; /* Required when declaring ::selection */
  color: #000;
  text-shadow: none;
}

::selection {
  background-color: #b3d4fc; /* Required when declaring ::selection */
  color: #000;
  text-shadow: none;
}


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

exports[`VAlert.vue should be closed by default 1`] = `

`;

exports[`VAlert.vue should be dismissible 1`] = `

      cancel

`;

exports[`VAlert.vue should have a close icon 1`] = `

      cancel

`;


keep keep keep keep keep replace keep keep keep

import Routable from '../../mixins/routable'
import Toggleable from '../../mixins/toggleable'
import Ripple from '../../directives/ripple'

export default {
  name: 'v-list-tile',

  mixins: [Routable, Toggleable],

  directives: {
    Ripple
  },

  inheritAttrs: false,

  data: () => ({
    proxyClass: 'list__tile--active'
  }),

  props: {
    activeClass: {
      type: String,
      default: 'primary--text'
    },
    avatar: Boolean,
    inactive: Boolean,
    tag: String
  },

  computed: {
    classes () {
      return {
        'list__tile': true,
        'list__tile--link': this.isLink && !this.inactive,
        'list__tile--avatar': this.avatar,
        'list__tile--disabled': this.disabled,
        [this.activeClass]: this.isActive
      }
    },
    isLink () {
      return this.href || this.to ||
        (this.$listeners && (this.$listeners.click || this.$listeners['!click']))
    }
  },

  render (h) {
    const isRouteLink = !this.inactive && this.isLink
    const { tag, data } = isRouteLink ? this.generateRouteLink() : {
      tag: this.tag || 'div',
      data: {
        class: this.classes
      }
    }

    data.attrs = Object.assign({}, data.attrs, this.$attrs)

    return h('li', {
      attrs: {
        disabled: this.disabled
      },
      on: {
        ...this.$listeners
      }
    }, [h(tag, data, this.$slots.default)])
  }
}


import VTimePicker from './VTimePicker'

VTimePicker.install = function install (Vue) {
  Vue.component(VTimePicker.name, VTimePicker)
}

export default VTimePicker


@import '../bootstrap'

.progress-circular
  position: relative
  display: inline-flex

  &--indeterminate
    svg
      animation: $progress-circular-rotate-animation
      transform-origin: center center
      width: 100%
      height: 100%
      margin: auto
      position: absolute
      top: 0
      bottom: 0
      left: 0
      right: 0
      transition: $process-circular-intermediate-svg-transition
      z-index: 0

    .progress-circular__overlay
      animation: $progress-circular-rotate-dash
      stroke-linecap: round
      stroke-dasharray: 80,200
      stroke-dashoffset: 0px

  &__underlay
    stroke: $progress-circular-underlay-stroke
    z-index: 1

  &__overlay
    stroke: currentColor
    z-index: 2
    transition: $progress-circular-overlay-transition

  &__info
    position: absolute
    top: 50%
    left: 50%
    transform: translate3d(-50%, -50%, 0)

  @keyframes progress-circular-dash
    0%
      stroke-dasharray: 1,200
      stroke-dashoffset: 0px

    50%
      stroke-dasharray: 100,200
      stroke-dashoffset: -15px

    100%
      stroke-dasharray: 100,200
      stroke-dashoffset: -125px

  @keyframes progress-circular-rotate
    100%
      transform: rotate(360deg)


keep keep keep replace keep keep keep keep


import ClickOutside from './click-outside'
import Resize from './resize'
import Ripple from './ripple'
import Scroll from './scroll'
import Touch from './touch'

export {
  ClickOutside,
  Ripple,
  Resize,
  Scroll,
  Touch
}

export default function install (Vue) {
  Vue.directive('click-outside', ClickOutside)
  Vue.directive('ripple', Ripple)
  Vue.directive('resize', Resize)
  Vue.directive('scroll', Scroll)
  Vue.directive('touch', Touch)
}


require('../../stylus/components/_steppers.styl')

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

export default {
  name: 'v-stepper',

  mixins: [Themeable],

  provide () {
    return {
      stepClick: this.stepClick
    }
  },

  data () {
    return {
      inputValue: null,
      isBooted: false,
      steps: [],
      content: [],
      isReverse: false
    }
  },

  props: {
    nonLinear: Boolean,
    altLabels: Boolean,
    vertical: Boolean,
    value: [Number, String]
  },

  computed: {
    classes () {
      return {
        'stepper': true,
        'stepper--is-booted': this.isBooted,
        'stepper--vertical': this.vertical,
        'stepper--alt-labels': this.altLabels,
        'stepper--non-linear': this.nonLinear,
        'theme--dark': this.dark,
        'theme--light': this.light
      }
    }
  },

  watch: {
    inputValue (val, prev) {
      this.isReverse = Number(val) < Number(prev)
      this.steps.forEach(i => i.toggle(this.inputValue))
      this.content.forEach(i => i.toggle(this.inputValue, this.isReverse))

      this.$emit('input', this.inputValue)
      prev && (this.isBooted = true)
    },
    value () {
      this.getSteps()
      this.$nextTick(() => (this.inputValue = this.value))
    }
  },

  mounted () {
    this.$vuetify.load(this.init)
  },

  methods: {
    init () {
      this.getSteps()

      this.inputValue = this.value || this.steps[0].step || 1
    },
    getSteps () {
      this.steps = []
      this.content = []
      this.$children.forEach(i => {
        if (i.$options._componentTag === 'v-stepper-step') {
          this.steps.push(i)
        } else if (i.$options._componentTag === 'v-stepper-content') {
          i.isVertical = this.vertical
          this.content.push(i)
        }
      })
    },
    stepClick (step) {
      this.getSteps()
      this.$nextTick(() => (this.inputValue = step))
    }
  },

  render (h) {
    return h('div', {
      'class': this.classes
    }, this.$slots.default)
  }
}


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

    path.node.value = path.node.value.replace(matches[0], `${matches[0]}../src/`)
  }
}


keep keep keep keep replace keep keep keep keep keep


require('../../stylus/components/_breadcrumbs.styl')

export default {
  name: 'v-breadcrumbs',

  props: {
    divider: {
      type: String,
      default: '/'
    },
    large: Boolean,
    justifyCenter: Boolean,
    justifyEnd: Boolean
  },

  computed: {
    classes () {
      return {
        'breadcrumbs--large': this.large
      }
    },
    computedDivider () {
      return this.$slots.divider
        ? this.$slots.divider
        : this.divider
    },
    styles () {
      const justify = this.justifyCenter
        ? 'center'
        : this.justifyEnd
          ? 'flex-end'
          : 'flex-start'

      return {
        'justify-content': justify
      }
    }
  },

  methods: {
    /**
     * Add dividers between
     * v-breadcrumbs-item
     *
     * @return {array}
     */
    genChildren () {
      if (!this.$slots.default) return null

      const children = []
      const dividerData = { staticClass: 'breadcrumbs__divider' }
      const length = this.$slots.default.length

      this.$slots.default.forEach((elm, i) => {
        children.push(elm)

        if (!elm.componentOptions ||
          elm.componentOptions.tag !== 'v-breadcrumbs-item' ||
          i === length - 1
        ) return

        children.push(this.$createElement('li', dividerData, this.computedDivider))
      })

      return children
    }
  },

  render (h) {
    return h('ul', {
      staticClass: 'breadcrumbs',
      'class': this.classes,
      style: this.styles
    }, this.genChildren())
  }
}


import VApp from '~components/VApp'
import { test } from '~util/testing'

test('breakpoint.js', ({ mount }) => {
  const scenarios = [
    {
      description: 'Huawei Smartwatch',
      width: 400,
      height: 400,
      name: 'xs',
      mustBeTrue: [
        'xs',
        'xsOnly',
        'smAndDown',
        'mdAndDown',
        'lgAndDown'
      ]
    },
    {
      description: 'Galaxy S5 (portrait)',
      width: 360,
      height: 640,
      name: 'xs',
      mustBeTrue: [
        'xs',
        'xsOnly',
        'smAndDown',
        'mdAndDown',
        'lgAndDown'
      ]
    },
    {
      description: 'Galaxy S5 (landscape)',
      width: 640,
      height: 360,
      name: 'sm',
      mustBeTrue: [
        'sm',
        'smOnly',
        'smAndDown',
        'smAndUp',
        'mdAndDown',
        'lgAndDown'
      ]
    },
    {
      description: 'iPhone 6 (portrait)',
      width: 375,
      height: 667,
      name: 'xs',
      mustBeTrue: [
        'xs',
        'xsOnly',
        'smAndDown',
        'mdAndDown',
        'lgAndDown'
      ]
    },
    {
      description: 'iPhone 6 (landscape)',
      width: 667,
      height: 375,
      name: 'sm',
      mustBeTrue: [
        'sm',
        'smOnly',
        'smAndDown',
        'smAndUp',
        'mdAndDown',
        'lgAndDown'
      ]
    },
    {
      description: 'iPad (portrait)',
      width: 768,
      height: 1024,
      name: 'sm',
      mustBeTrue: [
        'sm',
        'smOnly',
        'smAndDown',
        'smAndUp',
        'mdAndDown',
        'lgAndDown'
      ]
    },
    {
      description: 'iPad (landscape)',
      width: 1024,
      height: 768,
      name: 'md',
      mustBeTrue: [
        'md',
        'mdOnly',
        'smAndUp',
        'mdAndDown',
        'mdAndUp',
        'lgAndDown'
      ]
    },
    {
      description: 'iPad Pro (portrait)',
      width: 1024,
      height: 1366,
      name: 'md',
      mustBeTrue: [
        'md',
        'mdOnly',
        'smAndUp',
        'mdAndDown',
        'mdAndUp',
        'lgAndDown'
      ]
    },
    {
      description: 'iPad Pro (landscape)',
      width: 1366,
      height: 1024,
      name: 'lg',
      mustBeTrue: [
        'lg',
        'lgOnly',
        'smAndUp',
        'mdAndUp',
        'lgAndDown',
        'lgAndUp'
      ]
    },
    {
      description: 'WSXGA+ (portrait)',
      width: 1050,
      height: 1680,
      name: 'md',
      mustBeTrue: [
        'md',
        'mdOnly',
        'smAndUp',
        'mdAndDown',
        'mdAndUp',
        'lgAndDown'
      ]
    },
    {
      description: 'WSXGA+ (landscape)',
      width: 1680,
      height: 1050,
      name: 'lg',
      mustBeTrue: [
        'lg',
        'lgOnly',
        'smAndUp',
        'mdAndUp',
        'lgAndDown',
        'lgAndUp'
      ]
    },
    {
      description: 'FHD (portrait)',
      width: 1080,
      height: 1920,
      name: 'md',
      mustBeTrue: [
        'md',
        'mdOnly',
        'smAndUp',
        'mdAndDown',
        'mdAndUp',
        'lgAndDown'
      ]
    },
    {
      description: 'FHD (landscape)',
      width: 1920,
      height: 1080,
      name: 'xl',
      mustBeTrue: [
        'xl',
        'xlOnly',
        'smAndUp',
        'mdAndUp',
        'lgAndUp'
      ]
    },
    {
      description: 'WQHD (portrait)',
      width: 1440,
      height: 2560,
      name: 'lg',
      mustBeTrue: [
        'lg',
        'lgOnly',
        'smAndUp',
        'mdAndUp',
        'lgAndDown',
        'lgAndUp'
      ]
    },
    {
      description: 'WQHD (landscape)',
      width: 2560,
      height: 1440,
      name: 'xl',
      mustBeTrue: [
        'xl',
        'xlOnly',
        'smAndUp',
        'mdAndUp',
        'lgAndUp'
      ]
    }
  ]

  const allFlags = [
    'xs',
    'sm',
    'md',
    'lg',
    'xl',
    'xsOnly',
    'smOnly',
    'smAndDown',
    'smAndUp',
    'mdOnly',
    'mdAndDown',
    'mdAndUp',
    'lgOnly',
    'lgAndDown',
    'lgAndUp',
    'xlOnly'
  ]

  scenarios.forEach(scenario => {
    it('should calculate breakpoint for ' + scenario.description, () => {
      const wrapper = mount(VApp)
      wrapper.setData({
        clientWidth: scenario.width,
        clientHeight: scenario.height
      })
      const breakpoint = wrapper.vm.breakpoint

      expect(breakpoint.width).toBe(scenario.width)
      expect(breakpoint.height).toBe(scenario.height)
      expect(breakpoint.name).toBe(scenario.name)
      allFlags.forEach(flag => {
        const expectedValue = scenario.mustBeTrue.indexOf(flag) !== -1
        expect(breakpoint[flag]).toBe(expectedValue)
      })
    })
  })
})


import { test } from '~util/testing'
import { mount } from 'avoriaz'
import VSystemBar from '~components/VSystemBar'

test('VSystemBar.vue', () => {
  it('should render a colored system bar', () => {
    const wrapper = mount(VSystemBar, {
      propsData: {
        color: 'blue lighten-1'
      }
    })

    expect(wrapper.element.classList).toContain('blue')
    expect(wrapper.element.classList).toContain('lighten-1')
  })

  it('should render system bar with fixed prop', () => {
    const wrapper = mount(VSystemBar, {
      propsData: {
        fixed: true
      }
    })

    expect(wrapper.element.classList).toContain('system-bar--fixed')
  })

  it('should render system bar with absolute prop', () => {
    const wrapper = mount(VSystemBar, {
      propsData: {
        absolute: true
      }
    })

    expect(wrapper.element.classList).toContain('system-bar--absolute')
  })
})