({
  baseUrl,
  isFetching
}) => {
  const formatMessage = useFormatMessage();
  const { push } = useEnhancedHistory();
  const orderDetailData = useSelector<CatalogRootState, OrderDetail>(
    ({ orderReducer: { orderDetail } }) => orderDetail || {}
  );
  const { pathname, search } = useLocation();
  const { order } = orderDetailData;
  const navItems = useNavItems(order, formatMessage);
  let activeKey = navItems.findIndex(
    ({ link }) => pathname.split('/').pop() === link.replace('/', '')
  );
  activeKey = activeKey > 0 ? activeKey : 0;
  const handleTabClick = (tabIndex: number) =>
    push({ pathname: `${baseUrl}${navItems[tabIndex].link}`, search });

  return (

        {navItems.map(({ link, title, isDisabled }, index) => (


({ orderReducer: { orderDetail } }) => orderDetail || {}


({ link }) => pathname.split('/').pop() === link.replace('/', '')


(tabIndex: number) =>
    push({ pathname: `${baseUrl}${navItems[tabIndex].link}`, search })


({ link, title, isDisabled }, index) => (
          <StyledTabItem
            className={`pf-c-tabs__item${
              index === activeKey ? ' pf-m-current' : ''
            }`


export interface OrderDetailMenuProps {
  baseUrl: string;
  isFetching?: boolean;
}


(message: string) => {
  console.log(message); // eslint-disable-line
}


() => {
  shell.mkdir(WORKING_DIR);
  shell.cd(WORKING_DIR);
}


() => {
  log('Cleaning Up');

  shell.cd('../');
  shell.rm('-rf', WORKING_DIR);
}


({default_branch}: GitRepo): boolean => {
  return default_branch === 'master';
}


(repoDir: string): boolean => {
  return shell.ls(repoDir).length === 0;
}


async (octokit: Octokit, page: number): Promise<Array<GitRepo>> => {
  const {data} = await octokit.request(`/user/repos?affiliation=owner&page=${page}&per_page=100`);

  return data;
}


async (octokit: Octokit): Promise<Array<GitRepo>> => {

  // this is hard coded because this is a utility and i know how many repos I have
  const page1 = await requestReposPage(octokit, 1);
  const page2 = await requestReposPage(octokit, 2);

  return [...page1, ...page2];
}


async (octokit: Octokit, repos: Array<GitRepo>) => {
  makeWorkingDirectory();
  await forEachSynchronous(repos, renameDefaultBranch(octokit));
  removeWorkingDirectory();
}


(octokit: Octokit) => async (repo: GitRepo): Promise<void> => {
  if (!isMasterTheDefaultBranch(repo)) {
    log(`${repo.full_name} already has a default branch of ${repo.default_branch}`);
    return;
  }

  const REPO_DIR = `./${repo.name}`;

  // clone
  await shellExec(`git clone ${repo.ssh_url}`);

  // check if the repo is empty
  if (isRepoEmpty(REPO_DIR)) {
    log(`${repo.full_name} is an empty repository`);
    return;
  }

  // move branch
  const git: SimpleGit = simpleGit(REPO_DIR);
  await git.branch(['-m', 'master', 'main']);
  await git.push('origin', 'main', ['u']);

  // change default
  await octokit.request(`/repos/${repo.owner.login}/${repo.name}`, {
    method: 'PATCH',
    data: {'default_branch': 'main'}
  });
}


async (repo: GitRepo): Promise<void> => {
  if (!isMasterTheDefaultBranch(repo)) {
    log(`${repo.full_name} already has a default branch of ${repo.default_branch}`);
    return;
  }

  const REPO_DIR = `./${repo.name}`;

  // clone
  await shellExec(`git clone ${repo.ssh_url}`);

  // check if the repo is empty
  if (isRepoEmpty(REPO_DIR)) {
    log(`${repo.full_name} is an empty repository`);
    return;
  }

  // move branch
  const git: SimpleGit = simpleGit(REPO_DIR);
  await git.branch(['-m', 'master', 'main']);
  await git.push('origin', 'main', ['u']);

  // change default
  await octokit.request(`/repos/${repo.owner.login}/${repo.name}`, {
    method: 'PATCH',
    data: {'default_branch': 'main'}
  });
}


async () => {
  const octokit = new Octokit({
    auth: settings.ACCESS_TOKEN,
  });

  log('Getting repos');
  const repos = await requestRepos(octokit);

  log('Cloning Repos');
  await cloneRepos(octokit, repos);

  log('Done');
}


/**
 * 计算元素偏移量
 * @param ele
 * @param {Window} host
 * @returns {{left: number, top: number}}
 */
export function getOffset(ele: HTMLElement, host: Window = window): Offset {
  const offset: Offset = { left: 0, top: 0 };
  let cur = ele;
  while (cur) {
    offset.left += cur.offsetLeft - cur.scrollLeft;
    offset.top += cur.offsetTop - cur.scrollTop;
    cur = cur.offsetParent as HTMLElement;
  }
  return {
    top: offset.top + (host.scrollY || host.pageYOffset),
    left: offset.left + (host.scrollX || host.pageXOffset),
  };
}


/**
 * 获取边界
 * @param {Element} ele
 * @param {Window} host
 * @returns {IBounds}
 */
export function getBounds(ele: Element, host: Window = window): IBounds {
  const svgEle = ele as SVGAElement;
  if (svgEle.ownerSVGElement && svgEle.getBBox) {
    const box = svgEle.getBBox();
    return {
      left: box.x,
      top: box.y,
      width: box.width,
      height: box.height,
    };
  }
  const htmlEle = ele as HTMLElement;
  const { top, left } = getOffset(htmlEle);
  const style = getComputedStyle(htmlEle);
  const width = parseInt(style.width || '-1');
  const height = parseInt(style.height || '-1');
  return {
    top: top - (host.scrollY || host.pageYOffset),
    left: left - (host.scrollX || host.pageXOffset),
    width,
    height,
  }
    ;
}


/**
 * 获取剩余边界
 * @param ele
 */
export function getRestBounds(ele: HTMLElement) {
  const { top, left, width, height } = getBounds(ele);
  const { innerHeight, innerWidth } = window;
  return {
    top,
    left,
    bottom: innerHeight - top - height,
    right: innerWidth - left - width,
  };
}


interface IBounds {
  width: number;
  height: number;
  left: number;
  top: number;
}


type Offset = {
  left: number;
  top: number;
}


export function patternKeys(props: PairNode<JsonStringNode, JsonNode>[]) {
	return [...new Set((props
		.find(p => p.key?.value === 'pattern' && p.value?.type === 'json:array')?.value?.children ?? [])
		.map(n => (n as ItemNode<JsonNode>).value)
		.filter(n => n && n.type === 'json:string')
		.flatMap(n => [...(n as JsonStringNode).value])
		.filter(v => v !== ' '))]
}


p => p.key?.value === 'pattern' && p.value?.type === 'json:array'


n => (n as ItemNode<JsonNode>).value


n => n && n.type === 'json:string'


n => [...(n as JsonStringNode).value]


v => v !== ' '


@NgModule({
    imports: [
        CommonModule,
        NgbModule
    ],
    declarations: [
        AlertComponent
    ],
    exports: [
        AlertComponent
    ]
})
export class AlertModule { }


({
  server,
  graphQLSchema,
  createContext,
  sessionImplementation,
}: {
  server: express.Express;
  graphQLSchema: GraphQLSchema;
  createContext: CreateContext;
  sessionImplementation?: SessionImplementation;
}) => {
  const apolloServer = new ApolloServer({
    uploads: false,
    schema: graphQLSchema,
    // FIXME: allow the dev to control where/when they get a playground
    playground: { settings: { 'request.credentials': 'same-origin' } },
    formatError, // TODO: this needs to be discussed
    context: async ({ req, res }: { req: IncomingMessage; res: ServerResponse }) =>
      createContext({
        sessionContext: await sessionImplementation?.createSessionContext(req, res, createContext),
        req,
      }),
    // FIXME: support for apollo studio tracing
    // ...(process.env.ENGINE_API_KEY || process.env.APOLLO_KEY
    //   ? { tracing: true }
    //   : {
    //       engine: false,
    //       // Only enable tracing in dev mode so we can get local debug info, but
    //       // don't bother returning that info on prod when the `engine` is
    //       // disabled.
    //       tracing: dev,
    //     }),
    // FIXME: Support for generic custom apollo configuration
    // ...apolloConfig,
  });
  // FIXME: Support custom API path via config.graphql.path.
  // Note: Core keystone uses '/admin/api' as the default.
  // FIXME: Support for file handling configuration
  // maxFileSize: 200 * 1024 * 1024,
  // maxFiles: 5,
  server.use(graphqlUploadExpress());
  apolloServer.applyMiddleware({ app: server, path: '/api/graphql', cors: false });
}


async ({ req, res }: { req: IncomingMessage; res: ServerResponse }) =>
      createContext({
        sessionContext: await sessionImplementation?.createSessionContext(req, res, createContext),
        req,
      })


async (
  config: KeystoneConfig,
  graphQLSchema: GraphQLSchema,
  createContext: CreateContext,
  dev: boolean,
  projectAdminPath: string
) => {
  const server = express();

  if (config.server?.cors) {
    // Setting config.server.cors = true will provide backwards compatible defaults
    // Otherwise, the user can provide their own config object to use
    const corsConfig: CorsOptions =
      typeof config.server.cors === 'boolean'
        ? { origin: true, credentials: true }
        : config.server.cors;
    server.use(cors(corsConfig));
  }

  const sessionImplementation = config.session ? implementSession(config.session()) : undefined;

  console.log('✨ Preparing GraphQL Server');
  addApolloServer({ server, graphQLSchema, createContext, sessionImplementation });

  console.log('✨ Preparing Next.js app');
  server.use(
    await createAdminUIServer(
      config.ui,
      createContext,
      dev,
      projectAdminPath,
      sessionImplementation
    )
  );

  return server;
}


class ClientTypeDAO {
  public save(clientType: ClientType) {
    const clientTypeRepository = getRepository(ClientType)
    return clientTypeRepository.save(clientType)
  }

  public saveMany(clientTypes: Array<ClientType>) {
    const clientTypeRepository = getRepository(ClientType)
    return clientTypeRepository.save(clientTypes)
  }

  public delete(query: any) {
    const clientTypeRepository = getRepository(ClientType)
    return clientTypeRepository.delete(query)
  }

  public remove(clientTypes: Array<ClientType>) {
    const clientTypeRepository = getRepository(ClientType)
    return clientTypeRepository.remove(clientTypes)
  }

  public findByData(data: any) {
    const clientTypeRepository = getRepository(ClientType);
    return clientTypeRepository.find(data)
  }
}


public save(clientType: ClientType) {
    const clientTypeRepository = getRepository(ClientType)
    return clientTypeRepository.save(clientType)
  }


public saveMany(clientTypes: Array<ClientType>) {
    const clientTypeRepository = getRepository(ClientType)
    return clientTypeRepository.save(clientTypes)
  }


public delete(query: any) {
    const clientTypeRepository = getRepository(ClientType)
    return clientTypeRepository.delete(query)
  }


public remove(clientTypes: Array<ClientType>) {
    const clientTypeRepository = getRepository(ClientType)
    return clientTypeRepository.remove(clientTypes)
  }


public findByData(data: any) {
    const clientTypeRepository = getRepository(ClientType);
    return clientTypeRepository.find(data)
  }


() => {
    const { signIn, loading } = useAuth();

    const handleSignIn = useCallback(async () => {
        try {
            await signIn();
        } catch(error) {
            Alert.alert(error);
        }
    }, []);

    return (
        <Background>
            <View style={styles.container}>
                <Image
                    source={IllustrationIMG}
                    style={styles.image}
                    resizeMode="stretch"
                />

                <View style={styles.content}>
                    <Text style={styles.title}>
                        Conecte-se e{`\n`}
                        organize suas{`\n`}
                        jogatinas
                    </Text>

                    <Text style={styles.subtitle}>
                        Crie grupos para jogar seus games {'\n'}
                        favoritos com seus amigos
                    </Text>

                    {
                        loading
                        ? <ActivityIndicator color={theme.colors.primary} />


async () => {
        try {
            await signIn();
        } catch(error) {
            Alert.alert(error);
        }
    }


/**
 * Emits the most recently emitted value from the source Observable within
 * periodic time intervals.
 *
 * Samples the source Observable at periodic time
 * intervals, emitting what it samples.
 *
 * ![](sampleTime.png)
 *
 * `sampleTime` periodically looks at the source Observable and emits whichever
 * value it has most recently emitted since the previous sampling, unless the
 * source has not emitted anything since the previous sampling. The sampling
 * happens periodically in time every `period` milliseconds (or the time unit
 * defined by the optional `scheduler` argument). The sampling starts as soon as
 * the output Observable is subscribed.
 *
 * ## Example
 *
 * Every second, emit the most recent click at most once
 *
 * ```ts
 * import { fromEvent, sampleTime } from 'rxjs';
 *
 * const clicks = fromEvent(document, 'click');
 * const result = clicks.pipe(sampleTime(1000));
 *
 * result.subscribe(x => console.log(x));
 * ```
 *
 * @see {@link auditTime}
 * @see {@link debounceTime}
 * @see {@link delay}
 * @see {@link sample}
 * @see {@link throttleTime}
 *
 * @param {number} period The sampling period expressed in milliseconds or the
 * time unit determined internally by the optional `scheduler`.
 * @param {SchedulerLike} [scheduler=async] The {@link SchedulerLike} to use for
 * managing the timers that handle the sampling.
 * @return A function that returns an Observable that emits the results of
 * sampling the values emitted by the source Observable at the specified time
 * interval.
 */
export declare function sampleTime<T>(period: number, scheduler?: SchedulerLike): MonoTypeOperatorFunction<T>;


export class GetFaceSearchCommand extends $Command<
  GetFaceSearchCommandInput,
  GetFaceSearchCommandOutput,
  RekognitionClientResolvedConfig
> {
  // Start section: command_properties
  // End section: command_properties

  constructor(readonly input: GetFaceSearchCommandInput) {
    // Start section: command_constructor
    super();
    // End section: command_constructor
  }

  resolveMiddleware(
    clientStack: MiddlewareStack<ServiceInputTypes, ServiceOutputTypes>,
    configuration: RekognitionClientResolvedConfig,
    options?: __HttpHandlerOptions
  ): Handler<GetFaceSearchCommandInput, GetFaceSearchCommandOutput> {
    this.middlewareStack.use(getSerdePlugin(configuration, this.serialize, this.deserialize));

    const stack = clientStack.concat(this.middlewareStack);

    const handlerExecutionContext: HandlerExecutionContext = {
      logger: {} as any,
    };
    const { requestHandler } = configuration;
    return stack.resolve(
      (request: FinalizeHandlerArguments<any>) =>
        requestHandler.handle(request.request as __HttpRequest, options || {}),
      handlerExecutionContext
    );
  }

  private serialize(input: GetFaceSearchCommandInput, context: __SerdeContext): Promise<__HttpRequest> {
    return serializeAws_json1_1GetFaceSearchCommand(input, context);
  }

  private deserialize(output: __HttpResponse, context: __SerdeContext): Promise<GetFaceSearchCommandOutput> {
    return deserializeAws_json1_1GetFaceSearchCommand(output, context);
  }

  // Start section: command_body_extra
  // End section: command_body_extra
}


export type GetFaceSearchCommandInput = GetFaceSearchRequest;


export type GetFaceSearchCommandOutput = GetFaceSearchResponse & __MetadataBearer;


resolveMiddleware(
    clientStack: MiddlewareStack<ServiceInputTypes, ServiceOutputTypes>,
    configuration: RekognitionClientResolvedConfig,
    options?: __HttpHandlerOptions
  ): Handler<GetFaceSearchCommandInput, GetFaceSearchCommandOutput> {
    this.middlewareStack.use(getSerdePlugin(configuration, this.serialize, this.deserialize));

    const stack = clientStack.concat(this.middlewareStack);

    const handlerExecutionContext: HandlerExecutionContext = {
      logger: {} as any,
    };
    const { requestHandler } = configuration;
    return stack.resolve(
      (request: FinalizeHandlerArguments<any>) =>
        requestHandler.handle(request.request as __HttpRequest, options || {}),
      handlerExecutionContext
    );
  }


private serialize(input: GetFaceSearchCommandInput, context: __SerdeContext): Promise<__HttpRequest> {
    return serializeAws_json1_1GetFaceSearchCommand(input, context);
  }


private deserialize(output: __HttpResponse, context: __SerdeContext): Promise<GetFaceSearchCommandOutput> {
    return deserializeAws_json1_1GetFaceSearchCommand(output, context);
  }


type GameState = {
    board: number[][],
    plays: boolean,
    availableColumns: number[],
    isOver: boolean,
    wasWon: boolean,
    winner: string | undefined | null,
    wasTied: boolean
}


() => !wasm && unsupported("WASM module unavailable")


(op: "leb128_encode_s_js" | "leb128_encode_u_js") => (
    x: number
) => {
    ensureWASM();
    return U8.slice(0, wasm[op](x));
}


(
    x: number
) => {
    ensureWASM();
    return U8.slice(0, wasm[op](x));
}


(op: "leb128_decode_s_js" | "leb128_decode_u_js") => (
    src: Uint8Array,
    idx = 0
) => {
    ensureWASM();
    U8.set(src.subarray(idx, Math.min(idx + 10, src.length)), 0);
    return [wasm[op](0, 0), U8[0]];
}


(
    src: Uint8Array,
    idx = 0
) => {
    ensureWASM();
    U8.set(src.subarray(idx, Math.min(idx + 10, src.length)), 0);
    return [wasm[op](0, 0), U8[0]];
}


interface LEB128 {
    memory: WebAssembly.Memory;
    buf: number;
    leb128_encode_u_js(x: number): number;
    leb128_decode_u_js(buf: number, num: number): number;
    leb128_encode_s_js(x: number): number;
    leb128_decode_s_js(buf: number, num: number): number;
}


stub => (...args: Array<{}>) => {
          if (this._terminated) {
            return Promise.reject('The client has already been closed.');
          }
          const func = stub[methodName];
          return func.apply(stub, args);
        }


(...args: Array<{}>) => {
          if (this._terminated) {
            return Promise.reject('The client has already been closed.');
          }
          const func = stub[methodName];
          return func.apply(stub, args);
        }


(err: Error|null|undefined) => () => {
          throw err;
        }


() => {
          throw err;
        }


stub => {
        this._terminated = true;
        stub.close();
      }


/**
   * Initialize the client.
   * Performs asynchronous operations (such as authentication) and prepares the client.
   * This function will be called automatically when any class method is called for the
   * first time, but if you need to initialize it before calling an actual method,
   * feel free to call initialize() directly.
   *
   * You can await on this method if you want to make sure the client is initialized.
   *
   * @returns {Promise} A promise that resolves to an authenticated service stub.
   */
initialize() {
    // If the client stub promise is already initialized, return immediately.
    if (this.predictionServiceStub) {
      return this.predictionServiceStub;
    }

    // Put together the "service stub" for
    // google.cloud.recommendationengine.v1beta1.PredictionService.
    this.predictionServiceStub = this._gaxGrpc.createStub(
        this._opts.fallback ?
          (this._protos as protobuf.Root).lookupService('google.cloud.recommendationengine.v1beta1.PredictionService') :
          // eslint-disable-next-line @typescript-eslint/no-explicit-any
          (this._protos as any).google.cloud.recommendationengine.v1beta1.PredictionService,
        this._opts, this._providedCustomServicePath) as Promise<{[method: string]: Function}>;

    // Iterate over each of the methods that the service provides
    // and create an API call method for each.
    const predictionServiceStubMethods =
        ['predict'];
    for (const methodName of predictionServiceStubMethods) {
      const callPromise = this.predictionServiceStub.then(
        stub => (...args: Array<{}>) => {
          if (this._terminated) {
            return Promise.reject('The client has already been closed.');
          }
          const func = stub[methodName];
          return func.apply(stub, args);
        },
        (err: Error|null|undefined) => () => {
          throw err;
        });

      const descriptor =
        this.descriptors.page[methodName] ||
        undefined;
      const apiCall = this._gaxModule.createApiCall(
        callPromise,
        this._defaults[methodName],
        descriptor
      );

      this.innerApiCalls[methodName] = apiCall;
    }

    return this.predictionServiceStub;
  }


getProjectId(): Promise<string>;


getProjectId(callback: Callback<string, undefined, undefined>): void;


/**
   * Return the project ID used by this class.
   * @returns {Promise} A promise that resolves to string containing the project ID.
   */
getProjectId(callback?: Callback<string, undefined, undefined>):
      Promise<string>|void {
    if (callback) {
      this.auth.getProjectId(callback);
      return;
    }
    return this.auth.getProjectId();
  }


// -------------------
// -- Service calls --
// -------------------
predict(
      request?: protos.google.cloud.recommendationengine.v1beta1.IPredictRequest,
      options?: CallOptions):
      Promise<[
        protos.google.cloud.recommendationengine.v1beta1.PredictResponse.IPredictionResult[],
        protos.google.cloud.recommendationengine.v1beta1.IPredictRequest|null,
        protos.google.cloud.recommendationengine.v1beta1.IPredictResponse
      ]>;


predict(
      request: protos.google.cloud.recommendationengine.v1beta1.IPredictRequest,
      options: CallOptions,
      callback: PaginationCallback<
          protos.google.cloud.recommendationengine.v1beta1.IPredictRequest,
          protos.google.cloud.recommendationengine.v1beta1.IPredictResponse|null|undefined,
          protos.google.cloud.recommendationengine.v1beta1.PredictResponse.IPredictionResult>): void;


predict(
      request: protos.google.cloud.recommendationengine.v1beta1.IPredictRequest,
      callback: PaginationCallback<
          protos.google.cloud.recommendationengine.v1beta1.IPredictRequest,
          protos.google.cloud.recommendationengine.v1beta1.IPredictResponse|null|undefined,
          protos.google.cloud.recommendationengine.v1beta1.PredictResponse.IPredictionResult>): void;


/**
 * Equivalent to `method.name.toCamelCase()`, but returns a NodeJS Stream object.
 * @param {Object} request
 *   The request object that will be sent.
 * @param {string} request.name
 *   Required. Full resource name of the format:
 *   `{name=projects/* /locations/global/catalogs/default_catalog/eventStores/default_event_store/placements/*}`
 *   The id of the recommendation engine placement. This id is used to identify
 *   the set of models that will be used to make the prediction.
 *
 *   We currently support three placements with the following IDs by default:
 *
 *   * `shopping_cart`: Predicts items frequently bought together with one or
 *     more catalog items in the same shopping session. Commonly displayed after
 *     `add-to-cart` events, on product detail pages, or on the shopping cart
 *     page.
 *
 *   * `home_page`: Predicts the next product that a user will most likely
 *     engage with or purchase based on the shopping or viewing history of the
 *     specified `userId` or `visitorId`. For example - Recommendations for you.
 *
 *   * `product_detail`: Predicts the next product that a user will most likely
 *     engage with or purchase. The prediction is based on the shopping or
 *     viewing history of the specified `userId` or `visitorId` and its
 *     relevance to a specified `CatalogItem`. Typically used on product detail
 *     pages. For example - More items like this.
 *
 *   * `recently_viewed_default`: Returns up to 75 items recently viewed by the
 *     specified `userId` or `visitorId`, most recent ones first. Returns
 *     nothing if neither of them has viewed any items yet. For example -
 *     Recently viewed.
 *
 *   The full list of available placements can be seen at
 *   https://console.cloud.google.com/recommendation/datafeeds/default_catalog/dashboard
 * @param {google.cloud.recommendationengine.v1beta1.UserEvent} request.userEvent
 *   Required. Context about the user, what they are looking at and what action
 *   they took to trigger the predict request. Note that this user event detail
 *   won't be ingested to userEvent logs. Thus, a separate userEvent write
 *   request is required for event logging.
 * @param {number} [request.pageSize]
 *   Optional. Maximum number of results to return per page. Set this property
 *   to the number of prediction results required. If zero, the service will
 *   choose a reasonable default.
 * @param {string} [request.pageToken]
 *   Optional. The previous PredictResponse.next_page_token.
 * @param {string} [request.filter]
 *   Optional. Filter for restricting prediction results. Accepts values for
 *   tags and the `filterOutOfStockItems` flag.
 *
 *    * Tag expressions. Restricts predictions to items that match all of the
 *      specified tags. Boolean operators `OR` and `NOT` are supported if the
 *      expression is enclosed in parentheses, and must be separated from the
 *      tag values by a space. `-"tagA"` is also supported and is equivalent to
 *      `NOT "tagA"`. Tag values must be double quoted UTF-8 encoded strings
 *      with a size limit of 1 KiB.
 *
 *    * filterOutOfStockItems. Restricts predictions to items that do not have a
 *      stockState value of OUT_OF_STOCK.
 *
 *   Examples:
 *
 *    * tag=("Red" OR "Blue") tag="New-Arrival" tag=(NOT "promotional")
 *    * filterOutOfStockItems  tag=(-"promotional")
 *    * filterOutOfStockItems
 * @param {boolean} [request.dryRun]
 *   Optional. Use dryRun mode for this prediction query. If set to true, a
 *   dummy model will be used that returns arbitrary catalog items.
 *   Note that the dryRun mode should only be used for testing the API, or if
 *   the model is not ready.
 * @param {number[]} [request.params]
 *   Optional. Additional domain specific parameters for the predictions.
 *
 *   Allowed values:
 *
 *   * `returnCatalogItem`: Boolean. If set to true, the associated catalogItem
 *      object will be returned in the
 *     `PredictResponse.PredictionResult.itemMetadata` object in the method
 *      response.
 *   * `returnItemScore`: Boolean. If set to true, the prediction 'score'
 *      corresponding to each returned item will be set in the `metadata`
 *      field in the prediction response. The given 'score' indicates the
 *      probability of an item being clicked/purchased given the user's context
 *      and history.
 * @param {number[]} [request.labels]
 *   Optional. The labels for the predict request.
 *
 *    * Label keys can contain lowercase letters, digits and hyphens, must start
 *      with a letter, and must end with a letter or digit.
 *    * Non-zero label values can contain lowercase letters, digits and hyphens,
 *      must start with a letter, and must end with a letter or digit.
 *    * No more than 64 labels can be associated with a given request.
 *
 *   See https://goo.gl/xmQnxf for more information on and examples of labels.
 * @param {object} [options]
 *   Call options. See {@link https://googleapis.dev/nodejs/google-gax/latest/interfaces/CallOptions.html|CallOptions} for more details.
 * @returns {Stream}
 *   An object stream which emits an object representing [PredictionResult]{@link google.cloud.recommendationengine.v1beta1.PredictResponse.PredictionResult} on 'data' event.
 *   The client library will perform auto-pagination by default: it will call the API as many
 *   times as needed. Note that it can affect your quota.
 *   We recommend using `predictAsync()`
 *   method described below for async iteration which you can stop as needed.
 *   Please see the
 *   [documentation](https://github.com/googleapis/gax-nodejs/blob/master/client-libraries.md#auto-pagination)
 *   for more details and examples.
 */
predictStream(
      request?: protos.google.cloud.recommendationengine.v1beta1.IPredictRequest,
      options?: CallOptions):
    Transform{
    request = request || {};
    options = options || {};
    options.otherArgs = options.otherArgs || {};
    options.otherArgs.headers = options.otherArgs.headers || {};
    options.otherArgs.headers[
      'x-goog-request-params'
    ] = gax.routingHeader.fromParams({
      'name': request.name || '',
    });
    const defaultCallSettings = this._defaults['predict'];
    const callSettings = defaultCallSettings.merge(options);
    this.initialize();
    return this.descriptors.page.predict.createStream(
      this.innerApiCalls.predict as gax.GaxCall,
      request,
      callSettings
    );
  }


/**
 * Equivalent to `predict`, but returns an iterable object.
 *
 * `for`-`await`-`of` syntax is used with the iterable to get response elements on-demand.
 * @param {Object} request
 *   The request object that will be sent.
 * @param {string} request.name
 *   Required. Full resource name of the format:
 *   `{name=projects/* /locations/global/catalogs/default_catalog/eventStores/default_event_store/placements/*}`
 *   The id of the recommendation engine placement. This id is used to identify
 *   the set of models that will be used to make the prediction.
 *
 *   We currently support three placements with the following IDs by default:
 *
 *   * `shopping_cart`: Predicts items frequently bought together with one or
 *     more catalog items in the same shopping session. Commonly displayed after
 *     `add-to-cart` events, on product detail pages, or on the shopping cart
 *     page.
 *
 *   * `home_page`: Predicts the next product that a user will most likely
 *     engage with or purchase based on the shopping or viewing history of the
 *     specified `userId` or `visitorId`. For example - Recommendations for you.
 *
 *   * `product_detail`: Predicts the next product that a user will most likely
 *     engage with or purchase. The prediction is based on the shopping or
 *     viewing history of the specified `userId` or `visitorId` and its
 *     relevance to a specified `CatalogItem`. Typically used on product detail
 *     pages. For example - More items like this.
 *
 *   * `recently_viewed_default`: Returns up to 75 items recently viewed by the
 *     specified `userId` or `visitorId`, most recent ones first. Returns
 *     nothing if neither of them has viewed any items yet. For example -
 *     Recently viewed.
 *
 *   The full list of available placements can be seen at
 *   https://console.cloud.google.com/recommendation/datafeeds/default_catalog/dashboard
 * @param {google.cloud.recommendationengine.v1beta1.UserEvent} request.userEvent
 *   Required. Context about the user, what they are looking at and what action
 *   they took to trigger the predict request. Note that this user event detail
 *   won't be ingested to userEvent logs. Thus, a separate userEvent write
 *   request is required for event logging.
 * @param {number} [request.pageSize]
 *   Optional. Maximum number of results to return per page. Set this property
 *   to the number of prediction results required. If zero, the service will
 *   choose a reasonable default.
 * @param {string} [request.pageToken]
 *   Optional. The previous PredictResponse.next_page_token.
 * @param {string} [request.filter]
 *   Optional. Filter for restricting prediction results. Accepts values for
 *   tags and the `filterOutOfStockItems` flag.
 *
 *    * Tag expressions. Restricts predictions to items that match all of the
 *      specified tags. Boolean operators `OR` and `NOT` are supported if the
 *      expression is enclosed in parentheses, and must be separated from the
 *      tag values by a space. `-"tagA"` is also supported and is equivalent to
 *      `NOT "tagA"`. Tag values must be double quoted UTF-8 encoded strings
 *      with a size limit of 1 KiB.
 *
 *    * filterOutOfStockItems. Restricts predictions to items that do not have a
 *      stockState value of OUT_OF_STOCK.
 *
 *   Examples:
 *
 *    * tag=("Red" OR "Blue") tag="New-Arrival" tag=(NOT "promotional")
 *    * filterOutOfStockItems  tag=(-"promotional")
 *    * filterOutOfStockItems
 * @param {boolean} [request.dryRun]
 *   Optional. Use dryRun mode for this prediction query. If set to true, a
 *   dummy model will be used that returns arbitrary catalog items.
 *   Note that the dryRun mode should only be used for testing the API, or if
 *   the model is not ready.
 * @param {number[]} [request.params]
 *   Optional. Additional domain specific parameters for the predictions.
 *
 *   Allowed values:
 *
 *   * `returnCatalogItem`: Boolean. If set to true, the associated catalogItem
 *      object will be returned in the
 *     `PredictResponse.PredictionResult.itemMetadata` object in the method
 *      response.
 *   * `returnItemScore`: Boolean. If set to true, the prediction 'score'
 *      corresponding to each returned item will be set in the `metadata`
 *      field in the prediction response. The given 'score' indicates the
 *      probability of an item being clicked/purchased given the user's context
 *      and history.
 * @param {number[]} [request.labels]
 *   Optional. The labels for the predict request.
 *
 *    * Label keys can contain lowercase letters, digits and hyphens, must start
 *      with a letter, and must end with a letter or digit.
 *    * Non-zero label values can contain lowercase letters, digits and hyphens,
 *      must start with a letter, and must end with a letter or digit.
 *    * No more than 64 labels can be associated with a given request.
 *
 *   See https://goo.gl/xmQnxf for more information on and examples of labels.
 * @param {object} [options]
 *   Call options. See {@link https://googleapis.dev/nodejs/google-gax/latest/interfaces/CallOptions.html|CallOptions} for more details.
 * @returns {Object}
 *   An iterable Object that allows [async iteration](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols).
 *   When you iterate the returned iterable, each element will be an object representing
 *   [PredictionResult]{@link google.cloud.recommendationengine.v1beta1.PredictResponse.PredictionResult}. The API will be called under the hood as needed, once per the page,
 *   so you can stop the iteration when you don't need more results.
 *   Please see the
 *   [documentation](https://github.com/googleapis/gax-nodejs/blob/master/client-libraries.md#auto-pagination)
 *   for more details and examples.
 * @example
 * const iterable = client.predictAsync(request);
 * for await (const response of iterable) {
 *   // process response
 * }
 */
predictAsync(
      request?: protos.google.cloud.recommendationengine.v1beta1.IPredictRequest,
      options?: CallOptions):
    AsyncIterable<protos.google.cloud.recommendationengine.v1beta1.PredictResponse.IPredictionResult>{
    request = request || {};
    options = options || {};
    options.otherArgs = options.otherArgs || {};
    options.otherArgs.headers = options.otherArgs.headers || {};
    options.otherArgs.headers[
      'x-goog-request-params'
    ] = gax.routingHeader.fromParams({
      'name': request.name || '',
    });
    options = options || {};
    const defaultCallSettings = this._defaults['predict'];
    const callSettings = defaultCallSettings.merge(options);
    this.initialize();
    return this.descriptors.page.predict.asyncIterate(
      this.innerApiCalls['predict'] as GaxCall,
      request as unknown as RequestType,
      callSettings
    ) as AsyncIterable<protos.google.cloud.recommendationengine.v1beta1.PredictResponse.IPredictionResult>;
  }


// --------------------
// -- Path templates --
// --------------------
/**
   * Return a fully-qualified placement resource name string.
   *
   * @param {string} project
   * @param {string} location
   * @param {string} catalog
   * @param {string} event_store
   * @param {string} placement
   * @returns {string} Resource name string.
   */
placementPath(project:string,location:string,catalog:string,eventStore:string,placement:string) {
    return this.pathTemplates.placementPathTemplate.render({
      project: project,
      location: location,
      catalog: catalog,
      event_store: eventStore,
      placement: placement,
    });
  }


/**
   * Parse the project from Placement resource.
   *
   * @param {string} placementName
   *   A fully-qualified path representing Placement resource.
   * @returns {string} A string representing the project.
   */
matchProjectFromPlacementName(placementName: string) {
    return this.pathTemplates.placementPathTemplate.match(placementName).project;
  }


/**
   * Parse the location from Placement resource.
   *
   * @param {string} placementName
   *   A fully-qualified path representing Placement resource.
   * @returns {string} A string representing the location.
   */
matchLocationFromPlacementName(placementName: string) {
    return this.pathTemplates.placementPathTemplate.match(placementName).location;
  }


/**
   * Parse the catalog from Placement resource.
   *
   * @param {string} placementName
   *   A fully-qualified path representing Placement resource.
   * @returns {string} A string representing the catalog.
   */
matchCatalogFromPlacementName(placementName: string) {
    return this.pathTemplates.placementPathTemplate.match(placementName).catalog;
  }


/**
   * Parse the event_store from Placement resource.
   *
   * @param {string} placementName
   *   A fully-qualified path representing Placement resource.
   * @returns {string} A string representing the event_store.
   */
matchEventStoreFromPlacementName(placementName: string) {
    return this.pathTemplates.placementPathTemplate.match(placementName).event_store;
  }


/**
   * Parse the placement from Placement resource.
   *
   * @param {string} placementName
   *   A fully-qualified path representing Placement resource.
   * @returns {string} A string representing the placement.
   */
matchPlacementFromPlacementName(placementName: string) {
    return this.pathTemplates.placementPathTemplate.match(placementName).placement;
  }


/**
   * Terminate the gRPC channel and close the client.
   *
   * The client will no longer be usable and all future behavior is undefined.
   * @returns {Promise} A promise that resolves when the client is closed.
   */
close(): Promise<void> {
    this.initialize();
    if (!this._terminated) {
      return this.predictionServiceStub!.then(stub => {
        this._terminated = true;
        stub.close();
      });
    }
    return Promise.resolve();
  }


export class SmartProperty {
    key: string;
    title: string;
    smartHtml?: (item, key) => string;
    inlineSearch ?= false;
    searchable ?= true;
    visible ?= true;
    width?: string;
    type?: SmartDataTypes = SmartDataTypes.Text;
}


export function gate() {
	return (target: any, key: string, descriptor: PropertyDescriptor) => {
		if (!(typeof descriptor.value === "function")) throw new Error("not supported");

		const gateKey = `$gate$${key}`;
		const fn = descriptor.value;

		descriptor.value = function(this: any, ...args: any[]) {
			if (!this.hasOwnProperty(gateKey)) {
				Object.defineProperty(this, gateKey, {
					configurable: false,
					enumerable: false,
					writable: true,
					value: undefined
				});
			}

			let promise = this[gateKey];
			if (promise === undefined) {
				let result;
				try {
					result = fn!.apply(this, args);
					if (result == null || !Functions.isPromise(result)) {
						return result;
					}
					this[gateKey] = promise = result
						.then((r: any) => {
							this[gateKey] = undefined;
							return r;
						})
						.catch((ex: any) => {
							this[gateKey] = undefined;
							throw ex;
						});
				} catch (ex) {
					this[gateKey] = undefined;
					throw ex;
				}
			}

			return promise;
		};
	};
}


(target: any, key: string, descriptor: PropertyDescriptor) => {
		if (!(typeof descriptor.value === "function")) throw new Error("not supported");

		const gateKey = `$gate$${key}`;
		const fn = descriptor.value;

		descriptor.value = function(this: any, ...args: any[]) {
			if (!this.hasOwnProperty(gateKey)) {
				Object.defineProperty(this, gateKey, {
					configurable: false,
					enumerable: false,
					writable: true,
					value: undefined
				});
			}

			let promise = this[gateKey];
			if (promise === undefined) {
				let result;
				try {
					result = fn!.apply(this, args);
					if (result == null || !Functions.isPromise(result)) {
						return result;
					}
					this[gateKey] = promise = result
						.then((r: any) => {
							this[gateKey] = undefined;
							return r;
						})
						.catch((ex: any) => {
							this[gateKey] = undefined;
							throw ex;
						});
				} catch (ex) {
					this[gateKey] = undefined;
					throw ex;
				}
			}

			return promise;
		};
	}


(r: any) => {
							this[gateKey] = undefined;
							return r;
						}


(ex: any) => {
							this[gateKey] = undefined;
							throw ex;
						}


export class Secret extends pulumi.CustomResource {
    /**
     * Get an existing Secret resource's state with the given name, ID, and optional extra
     * properties used to qualify the lookup.
     *
     * @param name The _unique_ name of the resulting resource.
     * @param id The _unique_ provider ID of the resource to lookup.
     * @param state Any extra arguments used during the lookup.
     * @param opts Optional settings to control the behavior of the CustomResource.
     */
    public static get(name: string, id: pulumi.Input<pulumi.ID>, state?: SecretState, opts?: pulumi.CustomResourceOptions): Secret {
        return new Secret(name, <any>state, { ...opts, id: id });
    }

    /** @internal */
    public static readonly __pulumiType = 'doppler:index/secret:Secret';

    /**
     * Returns true if the given object is an instance of Secret.  This is designed to work even
     * when multiple copies of the Pulumi SDK have been loaded into the same process.
     */
    public static isInstance(obj: any): obj is Secret {
        if (obj === undefined || obj === null) {
            return false;
        }
        return obj['__pulumiType'] === Secret.__pulumiType;
    }

    /**
     * The computed secret value, after resolving secret references
     */
    public /*out*/ readonly computed!: pulumi.Output<string>;
    /**
     * The name of the Doppler config
     */
    public readonly config!: pulumi.Output<string>;
    /**
     * The name of the Doppler secret
     */
    public readonly name!: pulumi.Output<string>;
    /**
     * The name of the Doppler project
     */
    public readonly project!: pulumi.Output<string>;
    /**
     * The raw secret value
     */
    public readonly value!: pulumi.Output<string>;

    /**
     * Create a Secret resource with the given unique name, arguments, and options.
     *
     * @param name The _unique_ name of the resource.
     * @param args The arguments to use to populate this resource's properties.
     * @param opts A bag of options that control this resource's behavior.
     */
    constructor(name: string, args: SecretArgs, opts?: pulumi.CustomResourceOptions)
    constructor(name: string, argsOrState?: SecretArgs | SecretState, opts?: pulumi.CustomResourceOptions) {
        let resourceInputs: pulumi.Inputs = {};
        opts = opts || {};
        if (opts.id) {
            const state = argsOrState as SecretState | undefined;
            resourceInputs["computed"] = state ? state.computed : undefined;
            resourceInputs["config"] = state ? state.config : undefined;
            resourceInputs["name"] = state ? state.name : undefined;
            resourceInputs["project"] = state ? state.project : undefined;
            resourceInputs["value"] = state ? state.value : undefined;
        } else {
            const args = argsOrState as SecretArgs | undefined;
            if ((!args || args.config === undefined) && !opts.urn) {
                throw new Error("Missing required property 'config'");
            }
            if ((!args || args.project === undefined) && !opts.urn) {
                throw new Error("Missing required property 'project'");
            }
            if ((!args || args.value === undefined) && !opts.urn) {
                throw new Error("Missing required property 'value'");
            }
            resourceInputs["config"] = args ? args.config : undefined;
            resourceInputs["name"] = args ? args.name : undefined;
            resourceInputs["project"] = args ? args.project : undefined;
            resourceInputs["value"] = args ? args.value : undefined;
            resourceInputs["computed"] = undefined /*out*/;
        }
        if (!opts.version) {
            opts = pulumi.mergeOptions(opts, { version: utilities.getVersion()});
        }
        super(Secret.__pulumiType, name, resourceInputs, opts);
    }
}


/**
 * Input properties used for looking up and filtering Secret resources.
 */
export interface SecretState {
    /**
     * The computed secret value, after resolving secret references
     */
    computed?: pulumi.Input<string>;
    /**
     * The name of the Doppler config
     */
    config?: pulumi.Input<string>;
    /**
     * The name of the Doppler secret
     */
    name?: pulumi.Input<string>;
    /**
     * The name of the Doppler project
     */
    project?: pulumi.Input<string>;
    /**
     * The raw secret value
     */
    value?: pulumi.Input<string>;
}


/**
 * The set of arguments for constructing a Secret resource.
 */
export interface SecretArgs {
    /**
     * The name of the Doppler config
     */
    config: pulumi.Input<string>;
    /**
     * The name of the Doppler secret
     */
    name?: pulumi.Input<string>;
    /**
     * The name of the Doppler project
     */
    project: pulumi.Input<string>;
    /**
     * The raw secret value
     */
    value: pulumi.Input<string>;
}


/**
     * Get an existing Secret resource's state with the given name, ID, and optional extra
     * properties used to qualify the lookup.
     *
     * @param name The _unique_ name of the resulting resource.
     * @param id The _unique_ provider ID of the resource to lookup.
     * @param state Any extra arguments used during the lookup.
     * @param opts Optional settings to control the behavior of the CustomResource.
     */
public static get(name: string, id: pulumi.Input<pulumi.ID>, state?: SecretState, opts?: pulumi.CustomResourceOptions): Secret {
        return new Secret(name, <any>state, { ...opts, id: id });
    }


/**
     * Returns true if the given object is an instance of Secret.  This is designed to work even
     * when multiple copies of the Pulumi SDK have been loaded into the same process.
     */
public static isInstance(obj: any): obj is Secret {
        if (obj === undefined || obj === null) {
            return false;
        }
        return obj['__pulumiType'] === Secret.__pulumiType;
    }


/**
 * Query format migration.
 * This module can detect query format version and make migration.
 */
export function isGrafana2target(target) {
  if (!target.mode || target.mode === 0 || target.mode === 2) {
    if ((target.hostFilter || target.itemFilter || target.downsampleFunction ||
        (target.host && target.host.host)) &&
        (target.item.filter === undefined && target.host.filter === undefined)) {
      return true;
    } else {
      return false;
    }
  } else {
    return false;
  }
}


export function migrateFrom2To3version(target) {
  target.group.filter = target.group.name === "*" ? "/.*/" : target.group.name;
  target.host.filter = target.host.name === "*" ? convertToRegex(target.hostFilter) : target.host.name;
  target.application.filter = target.application.name === "*" ? "" : target.application.name;
  target.item.filter = target.item.name === "All" ? convertToRegex(target.itemFilter) : target.item.name;
  return target;
}


export function migrate(target) {
  target.resultFormat = target.resultFormat || 'time_series';
  target = fixTargetGroup(target);
  if (isGrafana2target(target)) {
    return migrateFrom2To3version(target);
  }
  return target;
}


function fixTargetGroup(target) {
  if (target.group && Array.isArray(target.group)) {
    target.group = { 'filter': "" };
  }
  return target;
}


function convertToRegex(str) {
  if (str) {
    return '/' + str + '/';
  } else {
    return '/.*/';
  }
}


export function migrateDSConfig(jsonData) {
  if (!jsonData) {
    jsonData = {};
  }
  const oldVersion = jsonData.schema || 1;
  jsonData.schema = DS_CONFIG_SCHEMA;

  if (oldVersion === DS_CONFIG_SCHEMA) {
    return jsonData;
  }

  if (oldVersion < 2) {
    const dbConnectionOptions = jsonData.dbConnection || {};
    jsonData.dbConnectionEnable = dbConnectionOptions.enable || false;
    jsonData.dbConnectionDatasourceId = dbConnectionOptions.datasourceId || null;
    delete jsonData.dbConnection;
  }

  return jsonData;
}


() => {
    let svc: SVCPlugin;
    let client: ReturnType<typeof createAnalyticsClientMock>;

    const someUUIDGenerator = jest.fn(() => someUUID);
    const someUUID = '13ccebdb-0138-45e8-bf70-884817ead190';
    const defaultResult = {
        pageViewId: someUUID,
        encoding: document.characterSet,
        location: 'http://localhost/',
        referrer: 'http://somewhere.over/thereferrer',
        title: 'MAH PAGE',
        screenColor: '24-bit',
        screenResolution: '0x0',
        time: expect.any(String),
        userAgent: navigator.userAgent,
        language: 'en-US',
        hitType: SVCPluginEventTypes.event,
        eventId: someUUID,
    };

    beforeEach(() => {
        jest.clearAllMocks();
        client = createAnalyticsClientMock();
        svc = new SVCPlugin({client, uuidGenerator: someUUIDGenerator});
    });

    it('should register a hook in the client', () => {
        expect(client.registerBeforeSendEventHook).toHaveBeenCalledTimes(1);
    });

    it('should register a mapping for each SVC type', () => {
        expect(client.addEventTypeMapping).toHaveBeenCalledTimes(Object.keys(SVCPluginEventTypes).length);
    });

    describe('tickets', () => {
        it('should set the ticket with the specific format and convert known ticket keys into the measurement protocol format when the hook is called', () => {
            svc.setTicket({
                id: 'Some ID',
                subject: 'Some Subject',
                description: 'Some Description',
                category: 'Some Category',
                productId: 'Some Product ID',
                custom: {someCustomProp: 'some custom prop value'},
            });

            const result = executeRegisteredHook(SVCPluginEventTypes.event, {});

            expect(result).toEqual({
                ...defaultResult,
                svc_ticket_id: 'Some ID',
                svc_ticket_subject: 'Some Subject',
                svc_ticket_description: 'Some Description',
                svc_ticket_category: 'Some Category',
                svc_ticket_product_id: 'Some Product ID',
                svc_ticket_custom: {someCustomProp: 'some custom prop value'},
            });
        });

        it('should append the ticket with the pageview event', () => {
            svc.setTicket({subject: 'Some Subject'});

            const result = executeRegisteredHook(SVCPluginEventTypes.pageview, {});

            expect(result).toEqual({
                ...defaultResult,
                hitType: SVCPluginEventTypes.pageview,
                svc_ticket_subject: 'Some Subject',
            });
        });

        it('should not append the product with a random event type', () => {
            svc.setTicket({subject: 'Some Subject'});

            const result = executeRegisteredHook('🎲', {});

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

        it('should keep the ticket until a valid event type is used', () => {
            svc.setTicket({subject: 'Some Subject'});

            executeRegisteredHook('🎲', {});
            executeRegisteredHook('🐟', {});
            executeRegisteredHook('💀', {});
            const result = executeRegisteredHook(SVCPluginEventTypes.event, {});

            expect(result).toEqual({...defaultResult, svc_ticket_subject: 'Some Subject'});
        });

        it('should keep custom metadata in the ticket', () => {
            svc.setTicket({subject: '🧀', custom: {verycustom: 'value'}});

            const result = executeRegisteredHook(SVCPluginEventTypes.event, {});

            expect(result).toEqual({
                ...defaultResult,
                svc_ticket_subject: '🧀',
                svc_ticket_custom: {verycustom: 'value'},
            });
        });

        it('should flush the ticket once they are sent', () => {
            svc.setTicket({subject: '🍟', description: 'description'});

            const firstResult = executeRegisteredHook(SVCPluginEventTypes.event, {});
            const secondResult = executeRegisteredHook(SVCPluginEventTypes.event, {});

            expect(firstResult).not.toEqual({});
            expect(secondResult).toEqual({...defaultResult});
        });
    });

    it('should be able to set an action', () => {
        svc.setAction('ok');

        const result = executeRegisteredHook(SVCPluginEventTypes.event, {});

        expect(result).toEqual({
            ...defaultResult,
            svcAction: 'ok',
        });
    });

    it('should flush the action once it is sent', () => {
        svc.setAction('ok');

        const firstResult = executeRegisteredHook(SVCPluginEventTypes.event, {});
        const secondResult = executeRegisteredHook(SVCPluginEventTypes.event, {});

        expect(firstResult).not.toEqual({});
        expect(secondResult).toEqual({...defaultResult});
    });

    it('should be able to clear all the data', () => {
        svc.setTicket({subject: '🍨', description: 'Some desc'});
        svc.clearData();

        const result = executeRegisteredHook(SVCPluginEventTypes.event, {});

        expect(result).toEqual({...defaultResult});
    });

    it('should call the uuidv4 method', async () => {
        await executeRegisteredHook(SVCPluginEventTypes.event, {});
        await executeRegisteredHook(SVCPluginEventTypes.event, {});
        await executeRegisteredHook(SVCPluginEventTypes.event, {});

        // One for generating pageViewId, one for each individual event.
        expect(someUUIDGenerator).toHaveBeenCalledTimes(1 + 3);
    });

    it('should update the location when sending a pageview with the page parameter', async () => {
        const payload = {
            page: '/somepage',
        };

        const pageview = await executeRegisteredHook(SVCPluginEventTypes.pageview, payload);
        const event = await executeRegisteredHook(SVCPluginEventTypes.event, {});

        expect(pageview).toEqual({
            ...defaultResult,
            hitType: SVCPluginEventTypes.pageview,
            page: payload.page,
            location: `${defaultResult.location}${payload.page.substring(1)}`,
        });
        expect(event).toEqual({
            ...defaultResult,
            hitType: SVCPluginEventTypes.event,
            location: `${defaultResult.location}${payload.page.substring(1)}`,
        });
    });

    const executeRegisteredHook = (eventType: string, payload: any) => {
        const [beforeHook] = client.registerBeforeSendEventHook.mock.calls[0];
        const [afterHook] = client.registerAfterSendEventHook.mock.calls[0];
        const result = beforeHook(eventType, payload);
        afterHook(eventType, result);
        return result;
    };
}


() => someUUID


() => {
        jest.clearAllMocks();
        client = createAnalyticsClientMock();
        svc = new SVCPlugin({client, uuidGenerator: someUUIDGenerator});
    }


() => {
        expect(client.registerBeforeSendEventHook).toHaveBeenCalledTimes(1);
    }


() => {
        expect(client.addEventTypeMapping).toHaveBeenCalledTimes(Object.keys(SVCPluginEventTypes).length);
    }


() => {
        it('should set the ticket with the specific format and convert known ticket keys into the measurement protocol format when the hook is called', () => {
            svc.setTicket({
                id: 'Some ID',
                subject: 'Some Subject',
                description: 'Some Description',
                category: 'Some Category',
                productId: 'Some Product ID',
                custom: {someCustomProp: 'some custom prop value'},
            });

            const result = executeRegisteredHook(SVCPluginEventTypes.event, {});

            expect(result).toEqual({
                ...defaultResult,
                svc_ticket_id: 'Some ID',
                svc_ticket_subject: 'Some Subject',
                svc_ticket_description: 'Some Description',
                svc_ticket_category: 'Some Category',
                svc_ticket_product_id: 'Some Product ID',
                svc_ticket_custom: {someCustomProp: 'some custom prop value'},
            });
        });

        it('should append the ticket with the pageview event', () => {
            svc.setTicket({subject: 'Some Subject'});

            const result = executeRegisteredHook(SVCPluginEventTypes.pageview, {});

            expect(result).toEqual({
                ...defaultResult,
                hitType: SVCPluginEventTypes.pageview,
                svc_ticket_subject: 'Some Subject',
            });
        });

        it('should not append the product with a random event type', () => {
            svc.setTicket({subject: 'Some Subject'});

            const result = executeRegisteredHook('🎲', {});

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

        it('should keep the ticket until a valid event type is used', () => {
            svc.setTicket({subject: 'Some Subject'});

            executeRegisteredHook('🎲', {});
            executeRegisteredHook('🐟', {});
            executeRegisteredHook('💀', {});
            const result = executeRegisteredHook(SVCPluginEventTypes.event, {});

            expect(result).toEqual({...defaultResult, svc_ticket_subject: 'Some Subject'});
        });

        it('should keep custom metadata in the ticket', () => {
            svc.setTicket({subject: '🧀', custom: {verycustom: 'value'}});

            const result = executeRegisteredHook(SVCPluginEventTypes.event, {});

            expect(result).toEqual({
                ...defaultResult,
                svc_ticket_subject: '🧀',
                svc_ticket_custom: {verycustom: 'value'},
            });
        });

        it('should flush the ticket once they are sent', () => {
            svc.setTicket({subject: '🍟', description: 'description'});

            const firstResult = executeRegisteredHook(SVCPluginEventTypes.event, {});
            const secondResult = executeRegisteredHook(SVCPluginEventTypes.event, {});

            expect(firstResult).not.toEqual({});
            expect(secondResult).toEqual({...defaultResult});
        });
    }


() => {
            svc.setTicket({
                id: 'Some ID',
                subject: 'Some Subject',
                description: 'Some Description',
                category: 'Some Category',
                productId: 'Some Product ID',
                custom: {someCustomProp: 'some custom prop value'},
            });

            const result = executeRegisteredHook(SVCPluginEventTypes.event, {});

            expect(result).toEqual({
                ...defaultResult,
                svc_ticket_id: 'Some ID',
                svc_ticket_subject: 'Some Subject',
                svc_ticket_description: 'Some Description',
                svc_ticket_category: 'Some Category',
                svc_ticket_product_id: 'Some Product ID',
                svc_ticket_custom: {someCustomProp: 'some custom prop value'},
            });
        }


() => {
            svc.setTicket({subject: 'Some Subject'});

            const result = executeRegisteredHook(SVCPluginEventTypes.pageview, {});

            expect(result).toEqual({
                ...defaultResult,
                hitType: SVCPluginEventTypes.pageview,
                svc_ticket_subject: 'Some Subject',
            });
        }


() => {
            svc.setTicket({subject: 'Some Subject'});

            const result = executeRegisteredHook('🎲', {});

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


() => {
            svc.setTicket({subject: 'Some Subject'});

            executeRegisteredHook('🎲', {});
            executeRegisteredHook('🐟', {});
            executeRegisteredHook('💀', {});
            const result = executeRegisteredHook(SVCPluginEventTypes.event, {});

            expect(result).toEqual({...defaultResult, svc_ticket_subject: 'Some Subject'});
        }


() => {
            svc.setTicket({subject: '🧀', custom: {verycustom: 'value'}});

            const result = executeRegisteredHook(SVCPluginEventTypes.event, {});

            expect(result).toEqual({
                ...defaultResult,
                svc_ticket_subject: '🧀',
                svc_ticket_custom: {verycustom: 'value'},
            });
        }


() => {
            svc.setTicket({subject: '🍟', description: 'description'});

            const firstResult = executeRegisteredHook(SVCPluginEventTypes.event, {});
            const secondResult = executeRegisteredHook(SVCPluginEventTypes.event, {});

            expect(firstResult).not.toEqual({});
            expect(secondResult).toEqual({...defaultResult});
        }


() => {
        svc.setAction('ok');

        const result = executeRegisteredHook(SVCPluginEventTypes.event, {});

        expect(result).toEqual({
            ...defaultResult,
            svcAction: 'ok',
        });
    }


() => {
        svc.setAction('ok');

        const firstResult = executeRegisteredHook(SVCPluginEventTypes.event, {});
        const secondResult = executeRegisteredHook(SVCPluginEventTypes.event, {});

        expect(firstResult).not.toEqual({});
        expect(secondResult).toEqual({...defaultResult});
    }


() => {
        svc.setTicket({subject: '🍨', description: 'Some desc'});
        svc.clearData();

        const result = executeRegisteredHook(SVCPluginEventTypes.event, {});

        expect(result).toEqual({...defaultResult});
    }


async () => {
        await executeRegisteredHook(SVCPluginEventTypes.event, {});
        await executeRegisteredHook(SVCPluginEventTypes.event, {});
        await executeRegisteredHook(SVCPluginEventTypes.event, {});

        // One for generating pageViewId, one for each individual event.
        expect(someUUIDGenerator).toHaveBeenCalledTimes(1 + 3);
    }


async () => {
        const payload = {
            page: '/somepage',
        };

        const pageview = await executeRegisteredHook(SVCPluginEventTypes.pageview, payload);
        const event = await executeRegisteredHook(SVCPluginEventTypes.event, {});

        expect(pageview).toEqual({
            ...defaultResult,
            hitType: SVCPluginEventTypes.pageview,
            page: payload.page,
            location: `${defaultResult.location}${payload.page.substring(1)}`,
        });
        expect(event).toEqual({
            ...defaultResult,
            hitType: SVCPluginEventTypes.event,
            location: `${defaultResult.location}${payload.page.substring(1)}`,
        });
    }


(eventType: string, payload: any) => {
        const [beforeHook] = client.registerBeforeSendEventHook.mock.calls[0];
        const [afterHook] = client.registerAfterSendEventHook.mock.calls[0];
        const result = beforeHook(eventType, payload);
        afterHook(eventType, result);
        return result;
    }


function renderTemplate(input: string | Partial<Parameters<typeof defineComponent>[0]>) {
  let defaultComponents = { Listbox, ListboxLabel, ListboxButton, ListboxOptions, ListboxOption }

  if (typeof input === 'string') {
    return render(defineComponent({ template: input, components: defaultComponents }))
  }

  return render(
    defineComponent(
      Object.assign({}, input, {
        components: { ...defaultComponents, ...input.components },
      }) as Parameters<typeof defineComponent>[0]
    )
  )
}


() => {
  jest.spyOn(window, 'requestAnimationFrame').mockImplementation(setImmediate as any)
  jest.spyOn(window, 'cancelAnimationFrame').mockImplementation(clearImmediate as any)
}


() => jest.restoreAllMocks()


() => {
  it.each([
    ['ListboxButton', ListboxButton],
    ['ListboxLabel', ListboxLabel],
    ['ListboxOptions', ListboxOptions],
    ['ListboxOption', ListboxOption],
  ])(
    'should error when we are using a <%s /> without a parent <Listbox />',
    suppressConsoleLogs((name, Component) => {
      expect(() => render(Component)).toThrowError(
        `<${name} /> is missing a parent <Listbox /> component.`
      )
    })
  )

  it(
    'should be possible to render a Listbox without crashing',
    suppressConsoleLogs(async () => {
      renderTemplate({
        template: html`
          <Listbox v-model="value">
            <ListboxButton>Trigger</ListboxButton>
            <ListboxOptions>
              <ListboxOption value="a">Option A</ListboxOption>
              <ListboxOption value="b">Option B</ListboxOption>
              <ListboxOption value="c">Option C</ListboxOption>
            </ListboxOptions>
          </Listbox>
        `,
        setup: () => ({ value: ref(null) }),
      })

      assertListboxButton({
        state: ListboxState.InvisibleUnmounted,
        attributes: { id: 'headlessui-listbox-button-1' },
      })
      assertListbox({ state: ListboxState.InvisibleUnmounted })
    })
  )
}


(name, Component) => {
      expect(() => render(Component)).toThrowError(
        `<${name} /> is missing a parent <Listbox /> component.`
      )
    }


() => render(Component)


async () => {
      renderTemplate({
        template: html`
          <Listbox v-model="value">
            <ListboxButton>Trigger</ListboxButton>
            <ListboxOptions>
              <ListboxOption value="a">Option A</ListboxOption>
              <ListboxOption value="b">Option B</ListboxOption>
              <ListboxOption value="c">Option C</ListboxOption>
            </ListboxOptions>
          </Listbox>
        `,
        setup: () => ({ value: ref(null) }),
      })

      assertListboxButton({
        state: ListboxState.InvisibleUnmounted,
        attributes: { id: 'headlessui-listbox-button-1' },
      })
      assertListbox({ state: ListboxState.InvisibleUnmounted })
    }


() => ({ value: ref(null) })


() => {
    it(
      'should be possible to render a Listbox using a render prop',
      suppressConsoleLogs(async () => {
        renderTemplate({
          template: html`
            <Listbox v-model="value" v-slot="{ open }">
              <ListboxButton>Trigger</ListboxButton>
              <ListboxOptions v-show="open">
                <ListboxOption value="a">Option A</ListboxOption>
                <ListboxOption value="b">Option B</ListboxOption>
                <ListboxOption value="c">Option C</ListboxOption>
              </ListboxOptions>
            </Listbox>
          `,
          setup: () => ({ value: ref(null) }),
        })

        assertListboxButton({
          state: ListboxState.InvisibleUnmounted,
          attributes: { id: 'headlessui-listbox-button-1' },
        })
        assertListbox({ state: ListboxState.InvisibleUnmounted })

        await click(getListboxButton())

        assertListboxButton({
          state: ListboxState.Visible,
          attributes: { id: 'headlessui-listbox-button-1' },
        })
        assertListbox({ state: ListboxState.Visible })
      })
    )

    it(
      'should be possible to disable a Listbox',
      suppressConsoleLogs(async () => {
        renderTemplate({
          template: html`
            <Listbox v-model="value" disabled>
              <ListboxButton>Trigger</ListboxButton>
              <ListboxOptions>
                <ListboxOption value="a">Option A</ListboxOption>
                <ListboxOption value="b">Option B</ListboxOption>
                <ListboxOption value="c">Option C</ListboxOption>
              </ListboxOptions>
            </Listbox>
          `,
          setup: () => ({ value: ref(null) }),
        })

        assertListboxButton({
          state: ListboxState.InvisibleUnmounted,
          attributes: { id: 'headlessui-listbox-button-1' },
        })
        assertListbox({ state: ListboxState.InvisibleUnmounted })

        await click(getListboxButton())

        assertListboxButton({
          state: ListboxState.InvisibleUnmounted,
          attributes: { id: 'headlessui-listbox-button-1' },
        })
        assertListbox({ state: ListboxState.InvisibleUnmounted })

        await press(Keys.Enter, getListboxButton())

        assertListboxButton({
          state: ListboxState.InvisibleUnmounted,
          attributes: { id: 'headlessui-listbox-button-1' },
        })
        assertListbox({ state: ListboxState.InvisibleUnmounted })
      })
    )
  }


async () => {
        renderTemplate({
          template: html`
            <Listbox v-model="value" v-slot="{ open }">
              <ListboxButton>Trigger</ListboxButton>
              <ListboxOptions v-show="open">
                <ListboxOption value="a">Option A</ListboxOption>
                <ListboxOption value="b">Option B</ListboxOption>
                <ListboxOption value="c">Option C</ListboxOption>
              </ListboxOptions>
            </Listbox>
          `,
          setup: () => ({ value: ref(null) }),
        })

        assertListboxButton({
          state: ListboxState.InvisibleUnmounted,
          attributes: { id: 'headlessui-listbox-button-1' },
        })
        assertListbox({ state: ListboxState.InvisibleUnmounted })

        await click(getListboxButton())

        assertListboxButton({
          state: ListboxState.Visible,
          attributes: { id: 'headlessui-listbox-button-1' },
        })
        assertListbox({ state: ListboxState.Visible })
      }


async () => {
        renderTemplate({
          template: html`
            <Listbox v-model="value" disabled>
              <ListboxButton>Trigger</ListboxButton>
              <ListboxOptions>
                <ListboxOption value="a">Option A</ListboxOption>
                <ListboxOption value="b">Option B</ListboxOption>
                <ListboxOption value="c">Option C</ListboxOption>
              </ListboxOptions>
            </Listbox>
          `,
          setup: () => ({ value: ref(null) }),
        })

        assertListboxButton({
          state: ListboxState.InvisibleUnmounted,
          attributes: { id: 'headlessui-listbox-button-1' },
        })
        assertListbox({ state: ListboxState.InvisibleUnmounted })

        await click(getListboxButton())

        assertListboxButton({
          state: ListboxState.InvisibleUnmounted,
          attributes: { id: 'headlessui-listbox-button-1' },
        })
        assertListbox({ state: ListboxState.InvisibleUnmounted })

        await press(Keys.Enter, getListboxButton())

        assertListboxButton({
          state: ListboxState.InvisibleUnmounted,
          attributes: { id: 'headlessui-listbox-button-1' },
        })
        assertListbox({ state: ListboxState.InvisibleUnmounted })
      }


() => {
    it(
      'should be possible to render a ListboxLabel using a render prop',
      suppressConsoleLogs(async () => {
        renderTemplate({
          template: html`
            <Listbox v-model="value">
              <ListboxLabel v-slot="data">{{JSON.stringify(data)}}</ListboxLabel>
              <ListboxButton>Trigger</ListboxButton>
              <ListboxOptions>
                <ListboxOption value="a">Option A</ListboxOption>
                <ListboxOption value="b">Option B</ListboxOption>
                <ListboxOption value="c">Option C</ListboxOption>
              </ListboxOptions>
            </Listbox>
          `,
          setup: () => ({ value: ref(null) }),
        })

        assertListboxButton({
          state: ListboxState.InvisibleUnmounted,
          attributes: { id: 'headlessui-listbox-button-2' },
        })
        assertListboxLabel({
          attributes: { id: 'headlessui-listbox-label-1' },
          textContent: JSON.stringify({ open: false, disabled: false }),
        })
        assertListbox({ state: ListboxState.InvisibleUnmounted })

        await click(getListboxButton())

        assertListboxLabel({
          attributes: { id: 'headlessui-listbox-label-1' },
          textContent: JSON.stringify({ open: true, disabled: false }),
        })
        assertListbox({ state: ListboxState.Visible })
        assertListboxLabelLinkedWithListbox()
        assertListboxButtonLinkedWithListboxLabel()
      })
    )

    it(
      'should be possible to render a ListboxLabel using a render prop and an `as` prop',
      suppressConsoleLogs(async () => {
        renderTemplate({
          template: html`
            <Listbox v-model="value">
              <ListboxLabel as="p" v-slot="data">{{JSON.stringify(data)}}</ListboxLabel>
              <ListboxButton>Trigger</ListboxButton>
              <ListboxOptions>
                <ListboxOption value="a">Option A</ListboxOption>
                <ListboxOption value="b">Option B</ListboxOption>
                <ListboxOption value="c">Option C</ListboxOption>
              </ListboxOptions>
            </Listbox>
          `,
          setup: () => ({ value: ref(null) }),
        })

        assertListboxLabel({
          attributes: { id: 'headlessui-listbox-label-1' },
          textContent: JSON.stringify({ open: false, disabled: false }),
          tag: 'p',
        })
        assertListbox({ state: ListboxState.InvisibleUnmounted })

        await click(getListboxButton())
        assertListboxLabel({
          attributes: { id: 'headlessui-listbox-label-1' },
          textContent: JSON.stringify({ open: true, disabled: false }),
          tag: 'p',
        })
        assertListbox({ state: ListboxState.Visible })
      })
    )
  }


async () => {
        renderTemplate({
          template: html`
            <Listbox v-model="value">
              <ListboxLabel v-slot="data">{{JSON.stringify(data)}}</ListboxLabel>
              <ListboxButton>Trigger</ListboxButton>
              <ListboxOptions>
                <ListboxOption value="a">Option A</ListboxOption>
                <ListboxOption value="b">Option B</ListboxOption>
                <ListboxOption value="c">Option C</ListboxOption>
              </ListboxOptions>
            </Listbox>
          `,
          setup: () => ({ value: ref(null) }),
        })

        assertListboxButton({
          state: ListboxState.InvisibleUnmounted,
          attributes: { id: 'headlessui-listbox-button-2' },
        })
        assertListboxLabel({
          attributes: { id: 'headlessui-listbox-label-1' },
          textContent: JSON.stringify({ open: false, disabled: false }),
        })
        assertListbox({ state: ListboxState.InvisibleUnmounted })

        await click(getListboxButton())

        assertListboxLabel({
          attributes: { id: 'headlessui-listbox-label-1' },
          textContent: JSON.stringify({ open: true, disabled: false }),
        })
        assertListbox({ state: ListboxState.Visible })
        assertListboxLabelLinkedWithListbox()
        assertListboxButtonLinkedWithListboxLabel()
      }


async () => {
        renderTemplate({
          template: html`
            <Listbox v-model="value">
              <ListboxLabel as="p" v-slot="data">{{JSON.stringify(data)}}</ListboxLabel>
              <ListboxButton>Trigger</ListboxButton>
              <ListboxOptions>
                <ListboxOption value="a">Option A</ListboxOption>
                <ListboxOption value="b">Option B</ListboxOption>
                <ListboxOption value="c">Option C</ListboxOption>
              </ListboxOptions>
            </Listbox>
          `,
          setup: () => ({ value: ref(null) }),
        })

        assertListboxLabel({
          attributes: { id: 'headlessui-listbox-label-1' },
          textContent: JSON.stringify({ open: false, disabled: false }),
          tag: 'p',
        })
        assertListbox({ state: ListboxState.InvisibleUnmounted })

        await click(getListboxButton())
        assertListboxLabel({
          attributes: { id: 'headlessui-listbox-label-1' },
          textContent: JSON.stringify({ open: true, disabled: false }),
          tag: 'p',
        })
        assertListbox({ state: ListboxState.Visible })
      }


() => {
    it(
      'should be possible to render a ListboxButton using a render prop',
      suppressConsoleLogs(async () => {
        renderTemplate({
          template: html`
            <Listbox v-model="value">
              <ListboxButton v-slot="data">{{JSON.stringify(data)}}</ListboxButton>
              <ListboxOptions>
                <ListboxOption value="a">Option A</ListboxOption>
                <ListboxOption value="b">Option B</ListboxOption>
                <ListboxOption value="c">Option C</ListboxOption>
              </ListboxOptions>
            </Listbox>
          `,
          setup: () => ({ value: ref(null) }),
        })

        assertListboxButton({
          state: ListboxState.InvisibleUnmounted,
          attributes: { id: 'headlessui-listbox-button-1' },
          textContent: JSON.stringify({ open: false, disabled: false }),
        })
        assertListbox({ state: ListboxState.InvisibleUnmounted })

        await click(getListboxButton())

        assertListboxButton({
          state: ListboxState.Visible,
          attributes: { id: 'headlessui-listbox-button-1' },
          textContent: JSON.stringify({ open: true, disabled: false }),
        })
        assertListbox({ state: ListboxState.Visible })
      })
    )

    it(
      'should be possible to render a ListboxButton using a render prop and an `as` prop',
      suppressConsoleLogs(async () => {
        renderTemplate({
          template: html`
            <Listbox v-model="value">
              <ListboxButton as="div" role="button" v-slot="data"
                >{{JSON.stringify(data)}}</ListboxButton
              >
              <ListboxOptions>
                <ListboxOption value="a">Option A</ListboxOption>
                <ListboxOption value="b">Option B</ListboxOption>
                <ListboxOption value="c">Option C</ListboxOption>
              </ListboxOptions>
            </Listbox>
          `,
          setup: () => ({ value: ref(null) }),
        })

        assertListboxButton({
          state: ListboxState.InvisibleUnmounted,
          attributes: { id: 'headlessui-listbox-button-1' },
          textContent: JSON.stringify({ open: false, disabled: false }),
        })
        assertListbox({ state: ListboxState.InvisibleUnmounted })

        await click(getListboxButton())

        assertListboxButton({
          state: ListboxState.Visible,
          attributes: { id: 'headlessui-listbox-button-1' },
          textContent: JSON.stringify({ open: true, disabled: false }),
        })
        assertListbox({ state: ListboxState.Visible })
      })
    )

    it(
      'should be possible to render a ListboxButton and a ListboxLabel and see them linked together',
      suppressConsoleLogs(async () => {
        renderTemplate({
          template: html`
            <Listbox v-model="value">
              <ListboxLabel>Label</ListboxLabel>
              <ListboxButton>Trigger</ListboxButton>
              <ListboxOptions>
                <ListboxOption value="a">Option A</ListboxOption>
                <ListboxOption value="b">Option B</ListboxOption>
                <ListboxOption value="c">Option C</ListboxOption>
              </ListboxOptions>
            </Listbox>
          `,
          setup: () => ({ value: ref(null) }),
        })

        await new Promise(requestAnimationFrame)

        assertListboxButton({
          state: ListboxState.InvisibleUnmounted,
          attributes: { id: 'headlessui-listbox-button-2' },
        })
        assertListbox({ state: ListboxState.InvisibleUnmounted })
        assertListboxButtonLinkedWithListboxLabel()
      })
    )
  }


async () => {
        renderTemplate({
          template: html`
            <Listbox v-model="value">
              <ListboxButton v-slot="data">{{JSON.stringify(data)}}</ListboxButton>
              <ListboxOptions>
                <ListboxOption value="a">Option A</ListboxOption>
                <ListboxOption value="b">Option B</ListboxOption>
                <ListboxOption value="c">Option C</ListboxOption>
              </ListboxOptions>
            </Listbox>
          `,
          setup: () => ({ value: ref(null) }),
        })

        assertListboxButton({
          state: ListboxState.InvisibleUnmounted,
          attributes: { id: 'headlessui-listbox-button-1' },
          textContent: JSON.stringify({ open: false, disabled: false }),
        })
        assertListbox({ state: ListboxState.InvisibleUnmounted })

        await click(getListboxButton())

        assertListboxButton({
          state: ListboxState.Visible,
          attributes: { id: 'headlessui-listbox-button-1' },
          textContent: JSON.stringify({ open: true, disabled: false }),
        })
        assertListbox({ state: ListboxState.Visible })
      }


async () => {
        renderTemplate({
          template: html`
            <Listbox v-model="value">
              <ListboxButton as="div" role="button" v-slot="data"
                >{{JSON.stringify(data)}}</ListboxButton
              >
              <ListboxOptions>
                <ListboxOption value="a">Option A</ListboxOption>
                <ListboxOption value="b">Option B</ListboxOption>
                <ListboxOption value="c">Option C</ListboxOption>
              </ListboxOptions>
            </Listbox>
          `,
          setup: () => ({ value: ref(null) }),
        })

        assertListboxButton({
          state: ListboxState.InvisibleUnmounted,
          attributes: { id: 'headlessui-listbox-button-1' },
          textContent: JSON.stringify({ open: false, disabled: false }),
        })
        assertListbox({ state: ListboxState.InvisibleUnmounted })

        await click(getListboxButton())

        assertListboxButton({
          state: ListboxState.Visible,
          attributes: { id: 'headlessui-listbox-button-1' },
          textContent: JSON.stringify({ open: true, disabled: false }),
        })
        assertListbox({ state: ListboxState.Visible })
      }


async () => {
        renderTemplate({
          template: html`
            <Listbox v-model="value">
              <ListboxLabel>Label</ListboxLabel>
              <ListboxButton>Trigger</ListboxButton>
              <ListboxOptions>
                <ListboxOption value="a">Option A</ListboxOption>
                <ListboxOption value="b">Option B</ListboxOption>
                <ListboxOption value="c">Option C</ListboxOption>
              </ListboxOptions>
            </Listbox>
          `,
          setup: () => ({ value: ref(null) }),
        })

        await new Promise(requestAnimationFrame)

        assertListboxButton({
          state: ListboxState.InvisibleUnmounted,
          attributes: { id: 'headlessui-listbox-button-2' },
        })
        assertListbox({ state: ListboxState.InvisibleUnmounted })
        assertListboxButtonLinkedWithListboxLabel()
      }


() => {
    it(
      'should be possible to render ListboxOptions using a render prop',
      suppressConsoleLogs(async () => {
        renderTemplate({
          template: html`
            <Listbox v-model="value">
              <ListboxButton>Trigger</ListboxButton>
              <ListboxOptions v-slot="data">
                <ListboxOption value="a">{{JSON.stringify(data)}}</ListboxOption>
              </ListboxOptions>
            </Listbox>
          `,
          setup: () => ({ value: ref(null) }),
        })

        assertListboxButton({
          state: ListboxState.InvisibleUnmounted,
          attributes: { id: 'headlessui-listbox-button-1' },
        })
        assertListbox({ state: ListboxState.InvisibleUnmounted })

        await click(getListboxButton())

        assertListboxButton({
          state: ListboxState.Visible,
          attributes: { id: 'headlessui-listbox-button-1' },
        })
        assertListbox({
          state: ListboxState.Visible,
          textContent: JSON.stringify({ open: true }),
        })
        assertActiveElement(getListbox())
      })
    )

    it('should be possible to always render the ListboxOptions if we provide it a `static` prop', () => {
      renderTemplate({
        template: html`
          <Listbox v-model="value">
            <ListboxButton>Trigger</ListboxButton>
            <ListboxOptions static>
              <ListboxOption value="a">Option A</ListboxOption>
              <ListboxOption value="b">Option B</ListboxOption>
              <ListboxOption value="c">Option C</ListboxOption>
            </ListboxOptions>
          </Listbox>
        `,
        setup: () => ({ value: ref(null) }),
      })

      // Let's verify that the Listbox is already there
      expect(getListbox()).not.toBe(null)
    })

    it('should be possible to use a different render strategy for the ListboxOptions', async () => {
      renderTemplate({
        template: html`
          <Listbox v-model="value">
            <ListboxButton>Trigger</ListboxButton>
            <ListboxOptions :unmount="false">
              <ListboxOption value="a">Option A</ListboxOption>
              <ListboxOption value="b">Option B</ListboxOption>
              <ListboxOption value="c">Option C</ListboxOption>
            </ListboxOptions>
          </Listbox>
        `,
        setup: () => ({ value: ref(null) }),
      })

      await new Promise<void>(nextTick)

      assertListbox({ state: ListboxState.InvisibleHidden })

      // Let's open the Listbox, to see if it is not hidden anymore
      await click(getListboxButton())

      assertListbox({ state: ListboxState.Visible })
    })
  }


async () => {
        renderTemplate({
          template: html`
            <Listbox v-model="value">
              <ListboxButton>Trigger</ListboxButton>
              <ListboxOptions v-slot="data">
                <ListboxOption value="a">{{JSON.stringify(data)}}</ListboxOption>
              </ListboxOptions>
            </Listbox>
          `,
          setup: () => ({ value: ref(null) }),
        })

        assertListboxButton({
          state: ListboxState.InvisibleUnmounted,
          attributes: { id: 'headlessui-listbox-button-1' },
        })
        assertListbox({ state: ListboxState.InvisibleUnmounted })

        await click(getListboxButton())

        assertListboxButton({
          state: ListboxState.Visible,
          attributes: { id: 'headlessui-listbox-button-1' },
        })
        assertListbox({
          state: ListboxState.Visible,
          textContent: JSON.stringify({ open: true }),
        })
        assertActiveElement(getListbox())
      }


() => {
      renderTemplate({
        template: html`
          <Listbox v-model="value">
            <ListboxButton>Trigger</ListboxButton>
            <ListboxOptions static>
              <ListboxOption value="a">Option A</ListboxOption>
              <ListboxOption value="b">Option B</ListboxOption>
              <ListboxOption value="c">Option C</ListboxOption>
            </ListboxOptions>
          </Listbox>
        `,
        setup: () => ({ value: ref(null) }),
      })

      // Let's verify that the Listbox is already there
      expect(getListbox()).not.toBe(null)
    }


async () => {
      renderTemplate({
        template: html`
          <Listbox v-model="value">
            <ListboxButton>Trigger</ListboxButton>
            <ListboxOptions :unmount="false">
              <ListboxOption value="a">Option A</ListboxOption>
              <ListboxOption value="b">Option B</ListboxOption>
              <ListboxOption value="c">Option C</ListboxOption>
            </ListboxOptions>
          </Listbox>
        `,
        setup: () => ({ value: ref(null) }),
      })

      await new Promise<void>(nextTick)

      assertListbox({ state: ListboxState.InvisibleHidden })

      // Let's open the Listbox, to see if it is not hidden anymore
      await click(getListboxButton())

      assertListbox({ state: ListboxState.Visible })
    }


() => {
    it(
      'should be possible to render a ListboxOption using a render prop',
      suppressConsoleLogs(async () => {
        renderTemplate({
          template: html`
            <Listbox v-model="value">
              <ListboxButton>Trigger</ListboxButton>
              <ListboxOptions>
                <ListboxOption value="a" v-slot="data">{{JSON.stringify(data)}}</ListboxOption>
              </ListboxOptions>
            </Listbox>
          `,
          setup: () => ({ value: ref(null) }),
        })

        assertListboxButton({
          state: ListboxState.InvisibleUnmounted,
          attributes: { id: 'headlessui-listbox-button-1' },
        })
        assertListbox({ state: ListboxState.InvisibleUnmounted })

        await click(getListboxButton())

        assertListboxButton({
          state: ListboxState.Visible,
          attributes: { id: 'headlessui-listbox-button-1' },
        })
        assertListbox({
          state: ListboxState.Visible,
          textContent: JSON.stringify({ active: false, selected: false, disabled: false }),
        })
      })
    )
  }


async () => {
        renderTemplate({
          template: html`
            <Listbox v-model="value">
              <ListboxButton>Trigger</ListboxButton>
              <ListboxOptions>
                <ListboxOption value="a" v-slot="data">{{JSON.stringify(data)}}</ListboxOption>
              </ListboxOptions>
            </Listbox>
          `,
          setup: () => ({ value: ref(null) }),
        })

        assertListboxButton({
          state: ListboxState.InvisibleUnmounted,
          attributes: { id: 'headlessui-listbox-button-1' },
        })
        assertListbox({ state: ListboxState.InvisibleUnmounted })

        await click(getListboxButton())

        assertListboxButton({
          state: ListboxState.Visible,
          attributes: { id: 'headlessui-listbox-button-1' },
        })
        assertListbox({
          state: ListboxState.Visible,
          textContent: JSON.stringify({ active: false, selected: false, disabled: false }),
        })
      }


() => {
  it(
    'should be possible to conditionally render classNames (aka className can be a function?!)',
    suppressConsoleLogs(async () => {
      renderTemplate({
        template: html`
          <Listbox v-model="value">
            <ListboxButton>Trigger</ListboxButton>
            <ListboxOptions>
              <ListboxOption value="a" :className="JSON.stringify">
                Option A
              </ListboxOption>
              <ListboxOption value="b" disabled :className="JSON.stringify">
                Option B
              </ListboxOption>
              <ListboxOption value="c" className="no-special-treatment">
                Option C
              </ListboxOption>
            </ListboxOptions>
          </Listbox>
        `,
        setup: () => ({ value: ref(null) }),
      })

      assertListboxButton({
        state: ListboxState.InvisibleUnmounted,
        attributes: { id: 'headlessui-listbox-button-1' },
      })
      assertListbox({ state: ListboxState.InvisibleUnmounted })

      // Open Listbox
      await click(getListboxButton())

      let options = getListboxOptions()

      // Verify correct classNames
      expect('' + options[0].classList).toEqual(
        JSON.stringify({ active: false, selected: false, disabled: false })
      )
      expect('' + options[1].classList).toEqual(
        JSON.stringify({ active: false, selected: false, disabled: true })
      )
      expect('' + options[2].classList).toEqual('no-special-treatment')

      // Double check that nothing is active
      assertNoActiveListboxOption(getListbox())

      // Make the first option active
      await press(Keys.ArrowDown)

      // Verify the classNames
      expect('' + options[0].classList).toEqual(
        JSON.stringify({ active: true, selected: false, disabled: false })
      )
      expect('' + options[1].classList).toEqual(
        JSON.stringify({ active: false, selected: false, disabled: true })
      )
      expect('' + options[2].classList).toEqual('no-special-treatment')

      // Double check that the first option is the active one
      assertActiveListboxOption(options[0])

      // Let's go down, this should go to the third option since the second option is disabled!
      await press(Keys.ArrowDown)

      // Verify the classNames
      expect('' + options[0].classList).toEqual(
        JSON.stringify({ active: false, selected: false, disabled: false })
      )
      expect('' + options[1].classList).toEqual(
        JSON.stringify({ active: false, selected: false, disabled: true })
      )
      expect('' + options[2].classList).toEqual('no-special-treatment')

      // Double check that the last option is the active one
      assertActiveListboxOption(options[2])
    })
  )

  it(
    'should be possible to swap the Listbox option with a button for example',
    suppressConsoleLogs(async () => {
      renderTemplate({
        template: html`
          <Listbox v-model="value">
            <ListboxButton>Trigger</ListboxButton>
            <ListboxOptions>
              <ListboxOption as="button" value="a">
                Option A
              </ListboxOption>
              <ListboxOption as="button" value="b">
                Option B
              </ListboxOption>
              <ListboxOption as="button" value="c">
                Option C
              </ListboxOption>
            </ListboxOptions>
          </Listbox>
        `,
        setup: () => ({ value: ref(null) }),
      })

      assertListboxButton({
        state: ListboxState.InvisibleUnmounted,
        attributes: { id: 'headlessui-listbox-button-1' },
      })
      assertListbox({ state: ListboxState.InvisibleUnmounted })

      // Open Listbox
      await click(getListboxButton())

      // Verify options are buttons now
      getListboxOptions().forEach(option => assertListboxOption(option, { tag: 'button' }))
    })
  )
}


async () => {
      renderTemplate({
        template: html`
          <Listbox v-model="value">
            <ListboxButton>Trigger</ListboxButton>
            <ListboxOptions>
              <ListboxOption value="a" :className="JSON.stringify">
                Option A
              </ListboxOption>
              <ListboxOption value="b" disabled :className="JSON.stringify">
                Option B
              </ListboxOption>
              <ListboxOption value="c" className="no-special-treatment">
                Option C
              </ListboxOption>
            </ListboxOptions>
          </Listbox>
        `,
        setup: () => ({ value: ref(null) }),
      })

      assertListboxButton({
        state: ListboxState.InvisibleUnmounted,
        attributes: { id: 'headlessui-listbox-button-1' },
      })
      assertListbox({ state: ListboxState.InvisibleUnmounted })

      // Open Listbox
      await click(getListboxButton())

      let options = getListboxOptions()

      // Verify correct classNames
      expect('' + options[0].classList).toEqual(
        JSON.stringify({ active: false, selected: false, disabled: false })
      )
      expect('' + options[1].classList).toEqual(
        JSON.stringify({ active: false, selected: false, disabled: true })
      )
      expect('' + options[2].classList).toEqual('no-special-treatment')

      // Double check that nothing is active
      assertNoActiveListboxOption(getListbox())

      // Make the first option active
      await press(Keys.ArrowDown)

      // Verify the classNames
      expect('' + options[0].classList).toEqual(
        JSON.stringify({ active: true, selected: false, disabled: false })
      )
      expect('' + options[1].classList).toEqual(
        JSON.stringify({ active: false, selected: false, disabled: true })
      )
      expect('' + options[2].classList).toEqual('no-special-treatment')

      // Double check that the first option is the active one
      assertActiveListboxOption(options[0])

      // Let's go down, this should go to the third option since the second option is disabled!
      await press(Keys.ArrowDown)

      // Verify the classNames
      expect('' + options[0].classList).toEqual(
        JSON.stringify({ active: false, selected: false, disabled: false })
      )
      expect('' + options[1].classList).toEqual(
        JSON.stringify({ active: false, selected: false, disabled: true })
      )
      expect('' + options[2].classList).toEqual('no-special-treatment')

      // Double check that the last option is the active one
      assertActiveListboxOption(options[2])
    }


async () => {
      renderTemplate({
        template: html`
          <Listbox v-model="value">
            <ListboxButton>Trigger</ListboxButton>
            <ListboxOptions>
              <ListboxOption as="button" value="a">
                Option A
              </ListboxOption>
              <ListboxOption as="button" value="b">
                Option B
              </ListboxOption>
              <ListboxOption as="button" value="c">
                Option C
              </ListboxOption>
            </ListboxOptions>
          </Listbox>
        `,
        setup: () => ({ value: ref(null) }),
      })

      assertListboxButton({
        state: ListboxState.InvisibleUnmounted,
        attributes: { id: 'headlessui-listbox-button-1' },
      })
      assertListbox({ state: ListboxState.InvisibleUnmounted })

      // Open Listbox
      await click(getListboxButton())

      // Verify options are buttons now
      getListboxOptions().forEach(option => assertListboxOption(option, { tag: 'button' }))
    }


option => assertListboxOption(option, { tag: 'button' })