<|start_filename|>packages/blocks/blocksBasic/src/blocks/Html/Html.js<|end_filename|>
/*
  Copyright 2020-2021 Lowdefy, Inc

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

import React from 'react';
import { blockDefaultProps, HtmlComponent } from '@lowdefy/block-tools';

const HtmlBlock = ({ blockId, properties, methods }) => (
  <HtmlComponent
    div={true}
    html={properties.html}
    id={blockId}
    methods={methods}
    style={properties.style}
  />
);

HtmlBlock.defaultProps = blockDefaultProps;

export default HtmlBlock;


<|start_filename|>packages/blocks/blocksAntd/src/blocks/Notification/Notification.js<|end_filename|>
/*
  Copyright 2020-2021 Lowdefy, Inc

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

import React, { useEffect } from 'react';
import { notification } from 'antd';
import { blockDefaultProps, renderHtml } from '@lowdefy/block-tools';
import { type } from '@lowdefy/helpers';
import Button from '../Button/Button';
import Icon from '../Icon/Icon';

const NotificationBlock = ({ blockId, events, properties, methods }) => {
  useEffect(() => {
    methods.registerMethod('open', (args = {}) => {
      notification[args.status || properties.status || 'success']({
        id: `${blockId}_notification`,
        bottom: properties.bottom,
        btn: properties.button && (
          <Button
            blockId={`${blockId}_button`}
            events={events}
            properties={properties.button}
            onClick={() => methods.triggerEvent({ name: 'onClose' })}
          />
        ),
        className: methods.makeCssClass(properties.notificationStyle),
        description: renderHtml({ html: args.description || properties.description, methods }),
        duration: type.isNone(args.duration) ? properties.duration : args.duration,
        icon: properties.icon && (
          <Icon blockId={`${blockId}_icon`} events={events} properties={properties.icon} />
        ),
        closeIcon: properties.closeIcon && (
          <Icon
            blockId={`${blockId}_closeIcon`}
            events={events}
            properties={properties.closeIcon}
          />
        ),
        message: renderHtml({ html: args.message || properties.message || blockId, methods }),
        onClose: () => methods.triggerEvent({ name: 'onClose' }),
        onClick: () => methods.triggerEvent({ name: 'onClick' }),
        placement: properties.placement,
        top: properties.top,
      });
    });
  });
  return <div id={blockId} />;
};

NotificationBlock.defaultProps = blockDefaultProps;

export default NotificationBlock;


<|start_filename|>packages/servers/serverAwsLambda/Dockerfile<|end_filename|>
FROM node:14-buster AS build

WORKDIR /lowdefy

COPY . .

# Install all dependencies needed for production build
# Can't try and cache this for src changes (just copy package.json)
# since we yarn install later
RUN yarn install
RUN yarn build

# Build lowdefy default app
RUN yarn build:lowdefy-default

# clean all depencies
RUN rm -rf node_modules

# install production dependencies only
RUN yarn install --production


FROM amazon/aws-lambda-nodejs:14

ENV NODE_ENV=production

WORKDIR /var/task

# Copy build artifacts
COPY --from=build /lowdefy/node_modules ./node_modules

COPY --from=build /lowdefy/dist ./dist

COPY --from=build /lowdefy/.lowdefy/build ./build

# Copy default public files from shell
# These can be overridden by copying different files to ./public
RUN cp -R ./node_modules/@lowdefy/shell/dist/public ./public

CMD [ "dist/server.handler"]

<|start_filename|>packages/build/src/build/writeConnections.js<|end_filename|>
/*
  Copyright 2020-2021 Lowdefy, Inc

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

import { type } from '@lowdefy/helpers';

async function writeConnections({ components, context }) {
  if (type.isNone(components.connections)) return;
  if (!type.isArray(components.connections)) {
    throw new Error(`Connections is not an array.`);
  }
  const writePromises = components.connections.map(async (connection) => {
    await context.writeBuildArtifact({
      filePath: `connections/${connection.connectionId}.json`,
      content: JSON.stringify(connection, null, 2),
    });
  });
  return Promise.all(writePromises);
}

export default writeConnections;


<|start_filename|>packages/renderer/src/utils/setupLink.js<|end_filename|>
/*
  Copyright 2020-2021 Lowdefy, Inc

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

import { createLink } from '@lowdefy/engine';

function setupLink(lowdefy) {
  const { routeHistory, window } = lowdefy;
  const sameOriginLink = (path, newTab) => {
    if (newTab) {
      return window.open(`${window.location.origin}${lowdefy.basePath}${path}`, '_blank').focus();
    } else {
      return routeHistory.push(`${lowdefy.basePath}${path}`);
    }
  };
  const newOriginLink = (path, newTab) => {
    if (newTab) {
      return window.open(path, '_blank').focus();
    } else {
      return (window.location.href = path);
    }
  };
  const backLink = () => window.history.back();
  return createLink({ backLink, lowdefy, newOriginLink, sameOriginLink });
}

export default setupLink;


<|start_filename|>packages/servers/serverNetlify/src/functions/graphql/index.js<|end_filename|>
/*
  Copyright 2020-2021 Lowdefy, Inc

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

import path from 'path';
import serverless from 'serverless-http';
import getServer from '@lowdefy/server';
import { createGetSecretsFromEnv } from '@lowdefy/node-utils';

const server = getServer({
  // __dirname is important here
  buildDirectory: path.resolve(__dirname, './build'),
  development: false,
  getSecrets: createGetSecretsFromEnv(),
  gqlExpressPath: '/',
  gqlUri: '/.netlify/functions/graphql',
  logger: console,
  serveStaticFiles: false,
});

const handler = serverless(server);

export { handler };


<|start_filename|>packages/cli/src/commands/dev/shell/bootstrap.js<|end_filename|>
/*
Copyright 2020-2021 Lowdefy, Inc

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import React from 'react';
import ReactDOM from 'react-dom';
import { Loading, loadWebpackFederatedModule, useDynamicScript } from '@lowdefy/block-tools';

function Shell({ rendererRemoteEntryUrl }) {
  const { ready, failed } = useDynamicScript({
    src: rendererRemoteEntryUrl,
  });

  if (!ready) {
    return <Loading type="Spinner" properties={{ height: '100vh' }} />;
  }

  if (failed) {
    return <h2>Failed to load dynamic script</h2>;
  }

  const Renderer = React.lazy(loadWebpackFederatedModule('lowdefy_renderer', 'Renderer'));

  return (
    <React.Suspense fallback={<Loading type="Spinner" properties={{ height: '100vh' }} />}>
      <Renderer gqlUri="/api/graphql" />
    </React.Suspense>
  );
}

const getRendererRemoteEntryUrl = async () => {
  return (await fetch(`/api/dev/rendererRemoteEntryUrl`)).json();
};

getRendererRemoteEntryUrl().then((rendererRemoteEntryUrl) => {
  ReactDOM.render(
    <Shell rendererRemoteEntryUrl={rendererRemoteEntryUrl} />,
    document.getElementById('root')
  );
});


<|start_filename|>packages/build/src/build/buildPages/buildBlock.js<|end_filename|>
/*
  Copyright 2020-2021 Lowdefy, Inc

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

import { set, type } from '@lowdefy/helpers';
import buildRequests from './buildRequests';
import setBlockMeta from './setBlockMeta';

async function buildBlock(block, blockContext) {
  if (!type.isObject(block)) {
    throw new Error(
      `Expected block to be an object on ${blockContext.pageId}. Received ${JSON.stringify(block)}`
    );
  }
  if (!type.isString(block.id)) {
    if (type.isUndefined(block.id)) {
      throw new Error(`Block id missing at page "${blockContext.pageId}".`);
    }
    throw new Error(
      `Block id is not a string at page "${blockContext.pageId}". Received ${JSON.stringify(
        block.id
      )}.`
    );
  }
  block.blockId = block.id;
  block.id = `block:${blockContext.pageId}:${block.id}`;
  await setBlockMeta(block, blockContext);

  let newBlockContext = blockContext;
  if (block.meta.category === 'context') {
    newBlockContext = {
      auth: blockContext.auth,
      contextId: block.blockId,
      getMeta: blockContext.getMeta,
      pageId: blockContext.pageId,
      requests: [],
    };
  }
  buildRequests(block, newBlockContext);
  if (block.meta.category === 'context') {
    block.requests = newBlockContext.requests;
  }

  if (block.events) {
    Object.keys(block.events).map((key) => {
      if (type.isArray(block.events[key])) {
        block.events[key] = {
          try: block.events[key],
          catch: [],
        };
      }
      if (!type.isArray(block.events[key].try)) {
        throw new Error(
          `Events must be an array of actions at ${block.blockId} in events ${key} on page ${
            newBlockContext.pageId
          }. Received ${JSON.stringify(block.events[key].try)}`
        );
      }
      if (!type.isArray(block.events[key].catch) && !type.isNone(block.events[key].catch)) {
        throw new Error(
          `Catch events must be an array of actions at ${block.blockId} in events ${key} on page ${
            newBlockContext.pageId
          }. Received ${JSON.stringify(block.events[key].catch)}`
        );
      }
    });
  }

  if (!type.isNone(block.blocks)) {
    if (!type.isArray(block.blocks)) {
      throw new Error(
        `Blocks at ${block.blockId} on page ${
          newBlockContext.pageId
        } is not an array. Received ${JSON.stringify(block.blocks)}`
      );
    }
    set(block, 'areas.content.blocks', block.blocks);
    delete block.blocks;
  }
  if (type.isObject(block.areas)) {
    let promises = [];
    Object.keys(block.areas).forEach((key) => {
      if (type.isNone(block.areas[key].blocks)) {
        block.areas[key].blocks = [];
      }
      if (!type.isArray(block.areas[key].blocks)) {
        throw new Error(
          `Expected blocks to be an array at ${block.blockId} in area ${key} on page ${
            newBlockContext.pageId
          }. Received ${JSON.stringify(block.areas[key].blocks)}`
        );
      }
      const blockPromises = block.areas[key].blocks.map(async (blk) => {
        await buildBlock(blk, newBlockContext);
      });
      promises = promises.concat(blockPromises);
    });
    await Promise.all(promises);
  }
}

export default buildBlock;


<|start_filename|>packages/blocks/blocksAntd/src/blocks/Message/Message.js<|end_filename|>
/*
  Copyright 2020-2021 Lowdefy, Inc

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

import React, { useEffect } from 'react';
import { message } from 'antd';
import { type } from '@lowdefy/helpers';
import { blockDefaultProps, renderHtml } from '@lowdefy/block-tools';

import Icon from '../Icon/Icon';

const MessageBlock = ({ blockId, events, properties, methods }) => {
  useEffect(() => {
    methods.registerMethod('open', (args = {}) => {
      return message[args.status || properties.status || 'success']({
        id: `${blockId}_message`,
        content: renderHtml({ html: args.content || properties.content || blockId, methods }),
        duration: type.isNone(args.duration) ? properties.duration : args.duration,
        onClose: () => methods.triggerEvent({ name: 'onClose' }),
        icon: (args.icon || properties.icon) && (
          <Icon
            blockId={`${blockId}_icon`}
            events={events}
            properties={args.icon || properties.icon}
          />
        ),
        className: methods.makeCssClass(properties.messageStyle),
      });
    });
  });
  return <div id={blockId} />;
};

MessageBlock.defaultProps = blockDefaultProps;

export default MessageBlock;


<|start_filename|>packages/graphql/src/connections/MongoDBCollection/getCollection.js<|end_filename|>
/*
  Copyright 2020-2021 Lowdefy, Inc

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

import { MongoClient } from 'mongodb';

async function getCollection({ connection }) {
  let client;
  const { collection, databaseUri, databaseName, options } = connection;
  client = new MongoClient(databaseUri, {
    ...options,
    useNewUrlParser: true,
    useUnifiedTopology: true,
  });
  await client.connect();
  try {
    const db = client.db(databaseName);
    return {
      client,
      collection: db.collection(collection),
    };
  } catch (error) {
    await client.close();
    throw error;
  }
}

export default getCollection;


<|start_filename|>packages/cli/src/commands/init/lowdefyFile.js<|end_filename|>
/*
  Copyright 2020-2021 Lowdefy, Inc

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

import packageJson from '../../../package.json';
const { version } = packageJson;

export default `
lowdefy: ${version}
name: Lowdefy starter

pages:
  - id: welcome
    type: PageHeaderMenu
    properties:
      title: Welcome
    areas:
      content:
        justify: center
        blocks:
          - id: content_card
            type: Card
            style:
              maxWidth: 800
            blocks:
              - id: content
                type: Result
                properties:
                  title: Welcome to your Lowdefy app
                  subTitle: We are excited to see what you are going to build
                  icon:
                    name: HeartTwoTone
                    color: '#f00'
                areas:
                  extra:
                    blocks:
                      - id: docs_button
                        type: Button
                        properties:
                          size: large
                          title: Let's build something
                          color: '#1890ff'
                        events:
                          onClick:
                            - id: link_to_docs
                              type: Link
                              params:
                                url: https://docs.lowdefy.com
                                newTab: true
      footer:
        blocks:
          - id: footer
            type: Paragraph
            properties:
              type: secondary
              content: |
                Made by a Lowdefy 🤖

`;


<|start_filename|>packages/cli/src/commands/dev/getExpress.js<|end_filename|>
/*
  Copyright 2020-2021 Lowdefy, Inc

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

import path from 'path';
import express from 'express';
import reload from 'reload';
import { get } from '@lowdefy/helpers';
import { readFile } from '@lowdefy/node-utils';
import findOpenPort from '../../utils/findOpenPort';

async function getExpress({ context, gqlServer }) {
  const serveIndex = async (req, res) => {
    let indexHtml = await readFile(path.resolve(__dirname, 'shell/index.html'));
    let appConfig = await readFile(path.resolve(context.outputDirectory, 'app.json'));
    appConfig = JSON.parse(appConfig);
    indexHtml = indexHtml.replace(
      '<!-- __LOWDEFY_APP_HEAD_HTML__ -->',
      get(appConfig, 'html.appendHead', { default: '' })
    );
    indexHtml = indexHtml.replace(
      '<!-- __LOWDEFY_APP_BODY_HTML__ -->',
      get(appConfig, 'html.appendBody', { default: '' })
    );
    res.send(indexHtml);
  };

  const app = express();

  // port is initialized to 3000 in prepare function
  app.set('port', parseInt(context.options.port));

  gqlServer.applyMiddleware({ app, path: '/api/graphql' });

  const reloadPort = await findOpenPort();
  const reloadReturned = await reload(app, { route: '/api/dev/reload.js', port: reloadPort });

  // serve index.html with appended html
  // else static server serves without appended html
  app.get('/', serveIndex);

  // serve public files
  app.use('/public', express.static(path.resolve(process.cwd(), 'public')));

  // serve webpack files
  app.use(express.static(path.resolve(__dirname, 'shell')));

  // Serve rendererRemoteEntryUrl for renderer module federation
  app.use('/api/dev/rendererRemoteEntryUrl', (req, res) => {
    let rendererRemoteEntryUrl;

    if (context.options.blocksServerUrl) {
      rendererRemoteEntryUrl = `${context.options.blocksServerUrl}/renderer/remoteEntry.js`;
    } else {
      rendererRemoteEntryUrl = `https://blocks-cdn.lowdefy.com/v${context.lowdefyVersion}/renderer/remoteEntry.js`;
    }
    res.json(rendererRemoteEntryUrl);
  });

  // Redirect all 404 to index.html with status 200
  // This should always be the last route
  app.use(serveIndex);
  return { expressApp: app, reloadFn: reloadReturned.reload };
}

export default getExpress;


<|start_filename|>packages/graphql/src/connections/Elasticsearch/ElasticsearchIndex/ElasticsearchIndex.json<|end_filename|>
{
    "$schema": "http://json-schema.org/draft-07/schema#",
    "title": "Lowdefy Request Schema - ElasticsearchIndex",
    "type": "object",
    "description": "Adds a JSON document to the specified data stream or index and makes it searchable. If the target is an index and the document already exists, the request updates the document and increments its version.",
    "required": [
        "body"
    ],
    "properties": {
        "id": {
            "anyOf": [
                {
                    "type": "string"
                },
                {
                    "type": "number"
                }
            ],
            "description": "Unique identifier for the document."
        },
        "index": {
            "type": "string",
            "description": "The name of the index."
        },
        "if_seq_no": {
            "type": "integer",
            "description": "Only perform the operation if the document has this sequence number. See Optimistic concurrency control."
        },
        "if_primary_term": {
            "type": "integer",
            "description": "Only perform the operation if the document has this primary term. See Optimistic concurrency control."
        },
        "op_type": {
            "type": "string",
            "description": "Set to create to only index the document if it does not already exist (put if absent). If a document with the specified _id already exists, the indexing operation will fail. Same as using the <index>/_create endpoint. Valid values: index, create. If document id is specified, it defaults to index. Otherwise, it defaults to create. If the request targets a data stream, an op_type of create is required. See Add documents to a data stream.",
            "enum": [
                "index",
                "create"
            ],
            "default": "create"
        },
        "pipeline": {
            "type": "string",
            "description": "ID of the pipeline to use to preprocess incoming documents."
        },
        "refresh": {
            "type": "string",
            "description": "If true, Elasticsearch refreshes the affected shards to make this operation visible to search, if wait_for then wait for a refresh to make this operation visible to search, if false do nothing with refreshes. Valid values: true, false, wait_for. Default: false.",
            "enum": [
                "true",
                "false",
                "wait_for"
            ],
            "default": false
        },
        "routing": {
            "description": "Custom value used to route operations to a specific shard.",
            "type": "string"
        },
        "timeout": {
            "type": "string",
            "description": "Period the request waits for the following operations:\n\nAutomatic index creation\nDynamic mapping updates\nWaiting for active shards\nDefaults to 1m (one minute). This guarantees Elasticsearch waits for at least the timeout before failing. The actual wait time could be longer, particularly when multiple waits occur.",
            "default": "1m"
        },
        "version": {
            "description": "Explicit version number for concurrency control. The specified version must match the current version of the document for the request to succeed.",
            "type": "integer"
        },
        "version_type": {
            "type": "string",
            "description": "Specific version type: external, external_gte.",
            "enum": [
                "internal",
                "external",
                "external_gte"
            ]
        },
        "wait_for_active_shard": {
            "type": "string",
            "description": "The number of shard copies that must be active before proceeding with the operation. Set to all or any positive integer up to the total number of shards in the index (number_of_replicas+1). Default: 1, the primary shard.",
            "default": "1"
        },
        "require_alias": {
            "type": "boolean",
            "description": "If true, the destination must be an index alias. Defaults to false.",
            "default": false
        },
        "body": {
            "type": "object",
            "description": "Request body contains the JSON source for the document data.",
            "additionalProperties": true
        }
    },
    "errorMessage": {
        "type": "ElasticsearchIndex request properties should be an object.",
        "required": {
            "body": "ElasticsearchIndex request should have required property \"body\"."
        }
    }
}


<|start_filename|>packages/graphql/src/connections/Stripe/StripeRequest/StripeRequest.js<|end_filename|>
/*
  Copyright 2020-2021 Lowdefy, Inc

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

import { get } from '@lowdefy/helpers';
import Stripe from 'stripe';
import schema from './StripeRequestSchema.json';

async function stripeRequest({ request, connection }) {
  const stripe = new Stripe(connection.secretKey, {
    apiVersion: connection.apiVersion,
    maxNetworkRetries: connection.maxNetworkRetries,
    timeout: connection.timeout,
    telemetry: connection.telemetry,
  });

  let args = request;
  const path = [];

  do {
    const key = Object.keys(args)[0];

    path.push(key);
    args = args[key];
  } while (args && !Array.isArray(args));

  const resource = get(stripe, path.slice(0, -1).join('.'));
  const method = get(stripe, path.join('.'));

  if (!resource || !method || typeof method !== 'function') {
    throw new Error(`Invalid Stripe method ${path.join('.')}`);
  }

  return method.call(resource, ...(args || []));
}

export default { resolver: stripeRequest, schema, checkRead: false, checkWrite: false };


<|start_filename|>packages/blocks/blocksAntd/src/blocks/Breadcrumb/Breadcrumb.js<|end_filename|>
/*
  Copyright 2020-2021 Lowdefy, Inc

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

import React from 'react';
import { type, get } from '@lowdefy/helpers';
import { Link } from 'react-router-dom';
import { Breadcrumb } from 'antd';
import { blockDefaultProps } from '@lowdefy/block-tools';
import Icon from '../Icon/Icon';

const ItemLink = ({ basePath, link, children, className }) => {
  if (type.isString(link.pageId)) {
    return (
      <Link to={`${basePath}/${link.pageId}`} className={className}>
        {children}
      </Link>
    );
  }
  if (type.isString(link.url)) {
    return (
      <a href={link.url} className={className}>
        {children}
      </a>
    );
  }
  return <span className={className}>{children}</span>;
};

const BreadcrumbBlock = ({ basePath, blockId, events, methods, properties, rename }) => {
  const onClickActionName = get(rename, 'events.onClick', { default: 'onClick' });
  return (
    <Breadcrumb
      id={blockId}
      separator={properties.separator}
      className={methods.makeCssClass(properties.style)}
    >
      {(properties.list || []).map((link, index) => (
        <Breadcrumb.Item
          key={index}
          onClick={
            events[onClickActionName] &&
            (() => methods.triggerEvent({ name: onClickActionName, event: { link, index } }))
          }
        >
          <ItemLink
            basePath={basePath}
            className={methods.makeCssClass([
              {
                cursor: events[onClickActionName] && 'pointer',
              },
              link.style,
            ])}
            link={link}
          >
            {link.icon && (
              <Icon
                blockId={`${blockId}_${index}_icon`}
                events={events}
                properties={{
                  name: type.isString(link.icon) && link.icon,
                  ...(type.isObject(link.icon) ? link.icon : {}),
                  style: { paddingRight: 8, ...(link.icon.style || {}) },
                }}
              />
            )}
            {type.isString(link) ? link : link.label || link.pageId || link.url || `Link ${index}`}
          </ItemLink>
        </Breadcrumb.Item>
      ))}
    </Breadcrumb>
  );
};

BreadcrumbBlock.defaultProps = blockDefaultProps;

export default BreadcrumbBlock;


<|start_filename|>packages/build/src/utils/files/writeBuildArtifact.test.js<|end_filename|>
/*
  Copyright 2020-2021 Lowdefy, Inc
  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at
      http://www.apache.org/licenses/LICENSE-2.0
  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

import fs from 'fs';
import path from 'path';
import createWriteBuildArtifact from './writeBuildArtifact';

const outputDirectory = path.resolve(process.cwd(), 'src/test/fileSetter');

test('writeFile', async () => {
  const filePath = path.resolve(outputDirectory, 'writeFile.txt');
  try {
    fs.unlinkSync(filePath);
  } catch (error) {
    //pass
  }
  expect(fs.existsSync(filePath)).toBe(false);
  const writeBuildArtifact = createWriteBuildArtifact({ outputDirectory });

  await writeBuildArtifact({
    filePath: 'writeFile.txt',
    content: 'Test fileSetter file',
  });
  const res = fs.readFileSync(filePath, 'utf8');
  expect(res).toEqual('Test fileSetter file');
  try {
    fs.unlinkSync(filePath);
  } catch (error) {
    //pass
  }
});


<|start_filename|>packages/renderer/src/page/block/CategorySwitch.js<|end_filename|>
/*
  Copyright 2020-2021 Lowdefy, Inc

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

import React from 'react';
import { BlockLayout } from '@lowdefy/layout';
import { makeContextId } from '@lowdefy/engine';
import { makeCssClass } from '@lowdefy/block-tools';

import Container from './Container';
import Context from './Context';
import List from './List';

const CategorySwitch = ({ block, Blocks, Component, context, isRoot, lowdefy }) => {
  if (isRoot) {
    return (
      <Container
        block={context.RootBlocks.areas.root.blocks[0]}
        Blocks={context.RootBlocks}
        Component={Component}
        context={context}
        lowdefy={lowdefy}
      />
    );
  }
  if (!block.eval) return null; // Renderer updates before eval is executed for the first time on lists. See #520
  if (block.eval.visible === false)
    return <div id={`vs-${block.blockId}`} style={{ display: 'none' }} />;
  switch (block.meta.category) {
    case 'context':
      return (
        <Context
          block={block}
          contextId={makeContextId({
            urlQuery: lowdefy.urlQuery,
            pageId: lowdefy.pageId,
            blockId: block.blockId,
          })}
          lowdefy={lowdefy}
        >
          {(context) => (
            <Container
              block={context.RootBlocks.areas.root.blocks[0]}
              Blocks={context.RootBlocks}
              Component={Component}
              context={context}
              lowdefy={lowdefy}
            />
          )}
        </Context>
      );
    case 'list':
      return (
        <List
          block={block}
          Blocks={Blocks}
          Component={Component}
          context={context}
          lowdefy={lowdefy}
        />
      );
    case 'container':
      return (
        <Container
          block={block}
          Blocks={Blocks}
          Component={Component}
          context={context}
          lowdefy={lowdefy}
        />
      );
    case 'input':
      return (
        <BlockLayout
          id={`bl-${block.blockId}`}
          blockStyle={block.eval.style}
          highlightBorders={lowdefy.lowdefyGlobal.highlightBorders}
          layout={block.eval.layout || {}}
          makeCssClass={makeCssClass}
        >
          <Component
            methods={Object.assign(block.methods, {
              makeCssClass,
              registerEvent: block.registerEvent,
              registerMethod: block.registerMethod,
              setValue: block.setValue,
              triggerEvent: block.triggerEvent,
            })}
            basePath={lowdefy.basePath}
            blockId={block.blockId}
            events={block.eval.events}
            homePageId={lowdefy.homePageId}
            key={block.blockId}
            loading={block.loading}
            menus={lowdefy.menus}
            pageId={lowdefy.pageId}
            properties={block.eval.properties}
            required={block.eval.required}
            user={lowdefy.user}
            validation={block.eval.validation}
            value={block.value}
          />
        </BlockLayout>
      );
    default:
      return (
        <BlockLayout
          id={`bl-${block.blockId}`}
          blockStyle={block.eval.style}
          highlightBorders={lowdefy.lowdefyGlobal.highlightBorders}
          layout={block.eval.layout || {}}
          makeCssClass={makeCssClass}
        >
          <Component
            methods={Object.assign(block.methods, {
              makeCssClass,
              registerEvent: block.registerEvent,
              registerMethod: block.registerMethod,
              triggerEvent: block.triggerEvent,
            })}
            basePath={lowdefy.basePath}
            blockId={block.blockId}
            events={block.eval.events}
            homePageId={lowdefy.homePageId}
            key={block.blockId}
            loading={block.loading}
            menus={lowdefy.menus}
            pageId={lowdefy.pageId}
            properties={block.eval.properties}
            required={block.eval.required}
            user={lowdefy.user}
            validation={block.eval.validation}
          />
        </BlockLayout>
      );
  }
};

export default CategorySwitch;


<|start_filename|>packages/cli/src/index.js<|end_filename|>
/*
  Copyright 2020-2021 Lowdefy, Inc

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

import program from 'commander';
import packageJson from '../package.json';
import build from './commands/build/build.js';
import buildNetlify from './commands/buildNetlify/buildNetlify.js';
import cleanCache from './commands/cleanCache/cleanCache.js';
import dev from './commands/dev/dev.js';
import init from './commands/init/init.js';
import runCommand from './utils/runCommand';

const { description, version } = packageJson;

program.name('lowdefy').description(description).version(version, '-v, --version');

program
  .command('build')
  .description('Build a Lowdefy deployment.')
  .usage(`[options]`)
  .option(
    '--base-directory <base-directory>',
    'Change base directory. Default is the current working directory.'
  )
  .option(
    '--blocks-server-url <blocks-server-url>',
    'The URL from where Lowdefy blocks will be served.'
  )
  .option('--disable-telemetry', 'Disable telemetry.')
  .option(
    '--output-directory <output-directory>',
    'Change the directory to which build artifacts are saved. Default is "<base-directory>/.lowdefy/build".'
  )
  .option(
    '--ref-resolver <ref-resolver-function-path>',
    'Path to a JavaScript file containing a _ref resolver function to be used as the app default _ref resolver.'
  )
  .action(runCommand(build));

program
  .command('build-netlify')
  .description('Build a Lowdefy deployment to deploy in netlify.')
  .usage(`[options]`)
  .option(
    '--base-directory <base-directory>',
    'Change base directory. Default is the current working directory.'
  )
  .option(
    '--blocks-server-url <blocks-server-url>',
    'The URL from where Lowdefy blocks will be served.'
  )
  .option('--disable-telemetry', 'Disable telemetry.')
  .option(
    '--ref-resolver <ref-resolver-function-path>',
    'Path to a JavaScript file containing a _ref resolver function to be used as the app default _ref resolver.'
  )
  .action(runCommand(buildNetlify));

program
  .command('clean-cache')
  .description('Clean cached scripts and block meta descriptions.')
  .usage(`[options]`)
  .option(
    '--base-directory <base-directory>',
    'Change base directory. Default is the current working directory.'
  )
  .option('--disable-telemetry', 'Disable telemetry.')
  .action(runCommand(cleanCache));

program
  .command('dev')
  .description('Start a Lowdefy development server.')
  .usage(`[options]`)
  .option(
    '--base-directory <base-directory>',
    'Change base directory. Default is the current working directory.'
  )
  .option(
    '--blocks-server-url <blocks-server-url>',
    'The URL from where Lowdefy blocks will be served.'
  )
  .option('--disable-telemetry', 'Disable telemetry.')
  .option('--port <port>', 'Change the port the server is hosted at. Default is 3000.')
  .option(
    '--ref-resolver <ref-resolver-function-path>',
    'Path to a JavaScript file containing a _ref resolver function to be used as the app default _ref resolver.'
  )
  .option(
    '--watch <paths...>',
    'A list of paths to files or directories that should be watched for changes.'
  )
  .option(
    '--watch-ignore <paths...>',
    'A list of paths to files or directories that should be ignored by the file watcher. Globs are supported.'
  )
  .action(runCommand(dev));

program
  .command('init')
  .description('Initialize a Lowdefy project.')
  .usage(`[options]`)
  .action(runCommand(init));

program.parse(process.argv);


<|start_filename|>packages/shell/src/location.js<|end_filename|>
/*
Copyright 2020-2021 Lowdefy, Inc

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import path from 'path';

const publicDirectory = path.resolve(__dirname, 'public');
const shellDirectory = path.resolve(__dirname, 'shell');

export { publicDirectory, shellDirectory };


<|start_filename|>packages/server/src/index.js<|end_filename|>
/*
  Copyright 2020-2021 Lowdefy, Inc

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

import path from 'path';
import express from 'express';
import { ApolloServer } from 'apollo-server-express';
import { typeDefs, resolvers, createContext } from '@lowdefy/graphql';
import { get } from '@lowdefy/helpers';
import { readFile } from '@lowdefy/node-utils';

function getServer({
  buildDirectory,
  development = false,
  getSecrets,
  gqlExpressPath,
  gqlUri,
  logger,
  publicDirectory,
  serverBasePath = '',
  serveStaticFiles = true,
  shellDirectory,
}) {
  const context = createContext({
    CONFIGURATION_BASE_PATH: buildDirectory,
    development,
    getSecrets,
    gqlUri,
    logger,
  });
  const gqlServer = new ApolloServer({
    typeDefs,
    resolvers,
    context,
  });

  let indexHtml = null;

  if (serverBasePath !== '') {
    serverBasePath = `/${serverBasePath}`;
  }

  const serveIndex = async (req, res) => {
    // TODO: can do better here?
    if (!indexHtml || development) {
      indexHtml = await readFile(path.resolve(shellDirectory, 'index.html'));
      let appConfig = await readFile(path.resolve(buildDirectory, 'app.json'));
      appConfig = JSON.parse(appConfig);
      indexHtml = indexHtml.replace(
        '<!-- __LOWDEFY_APP_HEAD_HTML__ -->',
        get(appConfig, 'html.appendHead', { default: '' })
      );
      indexHtml = indexHtml.replace(
        '<!-- __LOWDEFY_APP_BODY_HTML__ -->',
        get(appConfig, 'html.appendBody', { default: '' })
      );
      indexHtml = indexHtml.replace(/__LOWDEFY_SERVER_BASE_PATH__/g, serverBasePath);
    }
    res.send(indexHtml);
  };

  const server = express();

  gqlServer.applyMiddleware({
    app: server,
    path: gqlExpressPath || `${serverBasePath}/api/graphql`,
    bodyParserConfig: { limit: '5mb' },
  });

  if (serveStaticFiles) {
    // serve index.html with appended html
    // else static server serves without appended html
    server.get(`${serverBasePath}/`, serveIndex);

    server.use(`${serverBasePath}/shell`, express.static(path.resolve(shellDirectory)));

    // serve public files
    server.use(`${serverBasePath}/public`, express.static(path.resolve(publicDirectory)));

    // Redirect all 404 to index.html with status 200
    // This should always be the last route
    server.use(`${serverBasePath}/*`, serveIndex);
  }
  return server;
}

export default getServer;


<|start_filename|>packages/renderer/webpack.common.js<|end_filename|>
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { ModuleFederationPlugin } = require('webpack').container;
const webpack = require('webpack');
const path = require('path');

const deps = require('./package.json').dependencies;

module.exports = {
  entry: './src/index',
  output: {
    path: path.resolve(__dirname, 'dist'),
  },
  // webpack 5 support polyfills
  resolve: {
    alias: {
      buffer: require.resolve('buffer'),
    },
    fallback: { buffer: false, path: false, fs: false, crypto: false },
  },
  module: {
    rules: [
      // TODO: FIXME: do NOT webpack 5 support with this
      // x-ref: https://github.com/webpack/webpack/issues/11467
      // waiting for babel fix: https://github.com/vercel/next.js/pull/17095#issuecomment-692435147
      {
        test: /\.m?js/,
        resolve: {
          fullySpecified: false,
        },
      },
      {
        test: /\.jsx?$/,
        loader: 'babel-loader',
        exclude: /node_modules/,
        options: {
          presets: ['@babel/preset-react'],
        },
      },
      {
        test: /\.css$/,
        use: [
          {
            loader: 'style-loader',
          },
          {
            loader: 'css-loader', // translates CSS into CommonJS
          },
        ],
      },
    ],
  },
  plugins: [
    new CleanWebpackPlugin(),
    new ModuleFederationPlugin({
      name: 'lowdefy_renderer',
      library: { type: 'var', name: 'lowdefy_renderer' },
      filename: 'remoteEntry.js',
      exposes: {
        './Renderer': './src/Renderer',
      },
      shared: {
        ...deps,
        react: {
          singleton: true, // only a single version of the shared module is allowed
          requiredVersion: '~17.0.0',
          version: deps.react,
        },
        'react-dom': {
          singleton: true, // only a single version of the shared module is allowed
          requiredVersion: '~17.0.0',
          version: deps['react-dom'],
        },
      },
    }),
    new webpack.ProvidePlugin({ Buffer: ['buffer', 'Buffer'] }),
    new HtmlWebpackPlugin({
      template: './public/index.html',
    }),
  ],
};


<|start_filename|>packages/servers/serverDocker/Dockerfile<|end_filename|>
FROM node:14-buster AS build

WORKDIR /lowdefy

COPY . .

# Install all dependencies needed for production build
# Can't try and cache this for src changes (just copy package.json)
# since we yarn install later
RUN yarn install
RUN yarn build

# Build lowdefy default app
RUN yarn build:lowdefy-default

# clean all depencies
RUN rm -rf node_modules

# install production dependencies only
RUN yarn install --production


FROM node:14-alpine

ENV NODE_ENV=production

# Set up working directory and use non-root "node" user
RUN mkdir -p /home/node/lowdefy && chown -R node:node /home/node/lowdefy

WORKDIR /home/node/lowdefy

USER node

# Copy build artifacts
COPY --from=build --chown=node:node /lowdefy/node_modules ./node_modules

COPY --from=build --chown=node:node /lowdefy/dist ./dist

COPY --from=build --chown=node:node /lowdefy/.lowdefy/build ./build

# Copy default public files from shell
# These can be overridden by copying different files to ./public
RUN cp -R ./node_modules/@lowdefy/shell/dist/public ./public

CMD ["node", "/home/node/lowdefy/dist/server.js"]

<|start_filename|>packages/cli/src/utils/__mocks__/startUp.js<|end_filename|>
/*
  Copyright 2020-2021 Lowdefy, Inc

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

const mockStartUp = jest.fn().mockImplementation(mockStartUpImp);

async function mockStartUpImp({ context, options = {} }) {
  context.command = 'test';
  context.cliVersion = 'cliVersion';
  context.commandLineOptions = options;

  context.print = {
    info: jest.fn(),
    succeed: jest.fn(),
    log: jest.fn(),
  };

  context.baseDirectory = options.baseDirectory || 'baseDirectory';

  context.cliConfig = {};
  context.lowdefyVersion = 'lowdefyVersion';

  context.appId = 'appId';
  context.options = options;

  context.cacheDirectory = `${context.baseDirectory}/cacheDirectory`;
  context.outputDirectory = `${context.baseDirectory}/outputDirectory`;

  context.sendTelemetry = jest.fn();

  return context;
}

export default mockStartUp;


<|start_filename|>packages/servers/serverAwsLambda/src/server.js<|end_filename|>
/*
  Copyright 2020-2021 Lowdefy, Inc

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

import serverless from 'serverless-http';
import getServer from '@lowdefy/server';
import { shellDirectory } from '@lowdefy/shell';
import { createGetSecretsFromEnv } from '@lowdefy/node-utils';

const buildDirectory = process.env.LOWDEFY_SERVER_BUILD_DIRECTORY || './build';
const publicDirectory = process.env.LOWDEFY_SERVER_PUBLIC_DIRECTORY || './public';
const serverBasePath = process.env.LOWDEFY_SERVER_BASE_PATH;

const server = getServer({
  buildDirectory,
  development: false,
  getSecrets: createGetSecretsFromEnv(),
  logger: console,
  publicDirectory,
  serverBasePath,
  shellDirectory,
});

export const handler = serverless(server, {
  binary: ['image/png'],
});


<|start_filename|>packages/blocks/blocksAntd/src/blocks/CheckboxSwitch/CheckboxSwitch.js<|end_filename|>
/*
  Copyright 2020-2021 Lowdefy, Inc

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

import React from 'react';
import { Checkbox } from 'antd';
import { blockDefaultProps, renderHtml } from '@lowdefy/block-tools';

import Label from '../Label/Label';

const CheckboxSwitch = ({
  blockId,
  events,
  loading,
  properties,
  required,
  validation,
  value,
  methods,
}) => {
  return (
    <Label
      blockId={blockId}
      events={events}
      properties={{ title: properties.title, size: properties.size, ...properties.label }}
      validation={validation}
      required={required}
      loading={loading}
      content={{
        content: () => (
          <Checkbox
            id={`${blockId}_input`}
            checked={value}
            disabled={properties.disabled}
            className={methods.makeCssClass([
              properties.color && {
                '& > span.ant-checkbox-checked:not(.ant-checkbox-disabled) > span': {
                  backgroundColor: `${properties.color} !important`,
                  borderColor: `${properties.color} !important`,
                },
              },
              properties.description && {
                marginRight: '30px', // stops the checkbox description from overlapping with the validation symbol
              },
              properties.inputStyle,
            ])}
            onChange={(e) => {
              methods.setValue(e.target.checked);
              methods.triggerEvent({ name: 'onChange' });
            }}
          >
            {renderHtml({ html: properties.description, methods })}
          </Checkbox>
        ),
      }}
    />
  );
};

CheckboxSwitch.defaultProps = blockDefaultProps;

export default CheckboxSwitch;


<|start_filename|>packages/build/src/utils/meta/metaLocations.js<|end_filename|>
/*
  Copyright 2020-2021 Lowdefy, Inc

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

import { type } from '@lowdefy/helpers';
import packageJson from '../../../package.json';

const { version } = packageJson;

function defaultMetaLocations({ blocksServerUrl }) {
  let baseUrl = blocksServerUrl;
  if (type.isNone(baseUrl)) {
    baseUrl = `https://blocks-cdn.lowdefy.com/v${version}`;
  }
  return {
    // @lowdefy/blocks-basic
    Context: {
      url: `${baseUrl}/blocks-basic/meta/Context.json`,
    },
    Box: {
      url: `${baseUrl}/blocks-basic/meta/Box.json`,
    },
    DangerousHtml: {
      url: `${baseUrl}/blocks-basic/meta/DangerousHtml.json`,
    },
    Html: {
      url: `${baseUrl}/blocks-basic/meta/Html.json`,
    },
    Img: {
      url: `${baseUrl}/blocks-basic/meta/Img.json`,
    },
    List: {
      url: `${baseUrl}/blocks-basic/meta/List.json`,
    },
    Span: {
      url: `${baseUrl}/blocks-basic/meta/Span.json`,
    },

    // @lowdefy/blocks-antd
    Affix: {
      url: `${baseUrl}/blocks-antd/meta/Affix.json`,
    },
    Alert: {
      url: `${baseUrl}/blocks-antd/meta/Alert.json`,
    },
    Anchor: {
      url: `${baseUrl}/blocks-antd/meta/Anchor.json`,
    },
    AutoComplete: {
      url: `${baseUrl}/blocks-antd/meta/AutoComplete.json`,
    },
    Avatar: {
      url: `${baseUrl}/blocks-antd/meta/Avatar.json`,
    },
    Badge: {
      url: `${baseUrl}/blocks-antd/meta/Badge.json`,
    },
    Breadcrumb: {
      url: `${baseUrl}/blocks-antd/meta/Breadcrumb.json`,
    },
    Button: {
      url: `${baseUrl}/blocks-antd/meta/Button.json`,
    },
    ButtonSelector: {
      url: `${baseUrl}/blocks-antd/meta/ButtonSelector.json`,
    },
    Card: {
      url: `${baseUrl}/blocks-antd/meta/Card.json`,
    },
    CheckboxSelector: {
      url: `${baseUrl}/blocks-antd/meta/CheckboxSelector.json`,
    },
    CheckboxSwitch: {
      url: `${baseUrl}/blocks-antd/meta/CheckboxSwitch.json`,
    },
    Collapse: {
      url: `${baseUrl}/blocks-antd/meta/Collapse.json`,
    },
    Comment: {
      url: `${baseUrl}/blocks-antd/meta/Comment.json`,
    },
    ConfirmModal: {
      url: `${baseUrl}/blocks-antd/meta/ConfirmModal.json`,
    },
    Content: {
      url: `${baseUrl}/blocks-antd/meta/Content.json`,
    },
    ControlledList: {
      url: `${baseUrl}/blocks-antd/meta/ControlledList.json`,
    },
    DateRangeSelector: {
      url: `${baseUrl}/blocks-antd/meta/DateRangeSelector.json`,
    },
    DateSelector: {
      url: `${baseUrl}/blocks-antd/meta/DateSelector.json`,
    },
    DateTimeSelector: {
      url: `${baseUrl}/blocks-antd/meta/DateTimeSelector.json`,
    },
    Descriptions: {
      url: `${baseUrl}/blocks-antd/meta/Descriptions.json`,
    },
    Divider: {
      url: `${baseUrl}/blocks-antd/meta/Divider.json`,
    },
    Drawer: {
      url: `${baseUrl}/blocks-antd/meta/Drawer.json`,
    },
    Footer: {
      url: `${baseUrl}/blocks-antd/meta/Footer.json`,
    },
    Header: {
      url: `${baseUrl}/blocks-antd/meta/Header.json`,
    },
    Icon: {
      url: `${baseUrl}/blocks-antd/meta/Icon.json`,
    },
    Label: {
      url: `${baseUrl}/blocks-antd/meta/Label.json`,
    },
    Layout: {
      url: `${baseUrl}/blocks-antd/meta/Layout.json`,
    },
    Menu: {
      url: `${baseUrl}/blocks-antd/meta/Menu.json`,
    },
    Message: {
      url: `${baseUrl}/blocks-antd/meta/Message.json`,
    },
    MobileMenu: {
      url: `${baseUrl}/blocks-antd/meta/MobileMenu.json`,
    },
    Modal: {
      url: `${baseUrl}/blocks-antd/meta/Modal.json`,
    },
    MonthSelector: {
      url: `${baseUrl}/blocks-antd/meta/MonthSelector.json`,
    },
    MultipleSelector: {
      url: `${baseUrl}/blocks-antd/meta/MultipleSelector.json`,
    },
    Notification: {
      url: `${baseUrl}/blocks-antd/meta/Notification.json`,
    },
    NumberInput: {
      url: `${baseUrl}/blocks-antd/meta/NumberInput.json`,
    },
    PageHCF: {
      url: `${baseUrl}/blocks-antd/meta/PageHCF.json`,
    },
    PageHCSF: {
      url: `${baseUrl}/blocks-antd/meta/PageHCSF.json`,
    },
    PageHeaderMenu: {
      url: `${baseUrl}/blocks-antd/meta/PageHeaderMenu.json`,
    },
    PageHSCF: {
      url: `${baseUrl}/blocks-antd/meta/PageHSCF.json`,
    },
    PageSHCF: {
      url: `${baseUrl}/blocks-antd/meta/PageSHCF.json`,
    },
    PageSiderMenu: {
      url: `${baseUrl}/blocks-antd/meta/PageSiderMenu.json`,
    },
    Pagination: {
      url: `${baseUrl}/blocks-antd/meta/Pagination.json`,
    },
    Paragraph: {
      url: `${baseUrl}/blocks-antd/meta/Paragraph.json`,
    },
    ParagraphInput: {
      url: `${baseUrl}/blocks-antd/meta/ParagraphInput.json`,
    },
    PasswordInput: {
      url: `${baseUrl}/blocks-antd/meta/PasswordInput.json`,
    },
    Progress: {
      url: `${baseUrl}/blocks-antd/meta/Progress.json`,
    },
    RadioSelector: {
      url: `${baseUrl}/blocks-antd/meta/RadioSelector.json`,
    },
    RatingSlider: {
      url: `${baseUrl}/blocks-antd/meta/RatingSlider.json`,
    },
    Result: {
      url: `${baseUrl}/blocks-antd/meta/Result.json`,
    },
    S3UploadButton: {
      url: `${baseUrl}/blocks-antd/meta/S3UploadButton.json`,
    },
    Selector: {
      url: `${baseUrl}/blocks-antd/meta/Selector.json`,
    },
    Slider: {
      url: `${baseUrl}/blocks-antd/meta/Slider.json`,
    },
    Skeleton: {
      url: `${baseUrl}/blocks-antd/meta/Skeleton.json`,
    },
    Spin: {
      url: `${baseUrl}/blocks-antd/meta/Spin.json`,
    },
    Statistic: {
      url: `${baseUrl}/blocks-antd/meta/Statistic.json`,
    },
    Switch: {
      url: `${baseUrl}/blocks-antd/meta/Switch.json`,
    },
    Tabs: {
      url: `${baseUrl}/blocks-antd/meta/Tabs.json`,
    },
    TextArea: {
      url: `${baseUrl}/blocks-antd/meta/TextArea.json`,
    },
    TextInput: {
      url: `${baseUrl}/blocks-antd/meta/TextInput.json`,
    },
    TimelineList: {
      url: `${baseUrl}/blocks-antd/meta/TimelineList.json`,
    },
    Title: {
      url: `${baseUrl}/blocks-antd/meta/Title.json`,
    },
    TitleInput: {
      url: `${baseUrl}/blocks-antd/meta/TitleInput.json`,
    },
    Tooltip: {
      url: `${baseUrl}/blocks-antd/meta/Tooltip.json`,
    },
    UserAvatar: {
      url: `${baseUrl}/blocks-antd/meta/UserAvatar.json`,
    },
    WeekSelector: {
      url: `${baseUrl}/blocks-antd/meta/WeekSelector.json`,
    },

    // @lowdefy/blocks-color-selectors-selectors
    ColorSelector: {
      url: `${baseUrl}/blocks-color-selectors/meta/ColorSelector.json`,
    },
    ChromeColorSelector: {
      url: `${baseUrl}/blocks-color-selectors/meta/ChromeColorSelector.json`,
    },
    CircleColorSelector: {
      url: `${baseUrl}/blocks-color-selectors/meta/CircleColorSelector.json`,
    },
    CompactColorSelector: {
      url: `${baseUrl}/blocks-color-selectors/meta/CompactColorSelector.json`,
    },
    GithubColorSelector: {
      url: `${baseUrl}/blocks-color-selectors/meta/GithubColorSelector.json`,
    },
    SliderColorSelector: {
      url: `${baseUrl}/blocks-color-selectors/meta/SliderColorSelector.json`,
    },
    SwatchesColorSelector: {
      url: `${baseUrl}/blocks-color-selectors/meta/SwatchesColorSelector.json`,
    },
    TwitterColorSelector: {
      url: `${baseUrl}/blocks-color-selectors/meta/TwitterColorSelector.json`,
    },
    // @lowdefy/blocks-markdown
    Markdown: {
      url: `${baseUrl}/blocks-markdown/meta/Markdown.json`,
    },
    MarkdownWithCode: {
      url: `${baseUrl}/blocks-markdown/meta/MarkdownWithCode.json`,
    },
    DangerousMarkdown: {
      url: `${baseUrl}/blocks-markdown/meta/DangerousMarkdown.json`,
    },
    // @lowdefy/blocks-echarts
    EChart: {
      url: `${baseUrl}/blocks-echarts/meta/EChart.json`,
    },
  };
}

export default defaultMetaLocations;


<|start_filename|>packages/blocks/blocksAntd/src/blocks/RadioSelector/RadioSelector.js<|end_filename|>
/*
  Copyright 2020-2021 Lowdefy, Inc

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

import React from 'react';
import { Radio } from 'antd';
import { blockDefaultProps, renderHtml } from '@lowdefy/block-tools';
import { type } from '@lowdefy/helpers';

import Label from '../Label/Label';
import getValueIndex from '../../getValueIndex';
import getUniqueValues from '../../getUniqueValues';

const RadioGroup = Radio.Group;

const RadioSelector = ({
  blockId,
  events,
  loading,
  properties,
  required,
  validation,
  value,
  methods,
}) => {
  const uniqueValueOptions = getUniqueValues(properties.options || []);
  return (
    <Label
      blockId={blockId}
      events={events}
      properties={{ title: properties.title, size: properties.size, ...properties.label }}
      validation={validation}
      required={required}
      loading={loading}
      content={{
        content: () => (
          <RadioGroup
            id={`${blockId}_input`}
            className={methods.makeCssClass([
              properties.color && {
                '& > label > span.ant-radio-checked:not(.ant-radio-disabled) > span': {
                  borderColor: `${properties.color} !important`,
                  '&:after': {
                    backgroundColor: `${properties.color} !important`,
                  },
                },
              },
              properties.inputStyle,
            ])}
            disabled={properties.disabled}
            onChange={(event) => {
              methods.setValue(
                type.isPrimitive(uniqueValueOptions[event.target.value])
                  ? uniqueValueOptions[event.target.value]
                  : uniqueValueOptions[event.target.value].value
              );
              methods.triggerEvent({ name: 'onChange' });
            }}
            value={getValueIndex(value, uniqueValueOptions)}
          >
            {uniqueValueOptions.map((opt, i) =>
              type.isPrimitive(opt) ? (
                <Radio id={`${blockId}_${opt}`} key={i} value={i}>
                  {renderHtml({ html: `${opt}`, methods })}
                </Radio>
              ) : (
                <Radio
                  id={`${blockId}_${i}`}
                  key={i}
                  value={i}
                  disabled={opt.disabled}
                  className={methods.makeCssClass(opt.style)}
                >
                  {type.isNone(opt.label)
                    ? renderHtml({ html: `${opt.value}`, methods })
                    : renderHtml({ html: opt.label, methods })}
                </Radio>
              )
            )}
          </RadioGroup>
        ),
      }}
    />
  );
};

RadioSelector.defaultProps = blockDefaultProps;

export default RadioSelector;


<|start_filename|>packages/graphql/src/connections/Elasticsearch/ElasticsearchDeleteByQuery/ElasticsearchDeleteByQuery.test.js<|end_filename|>
/*
  Copyright 2020-2021 Lowdefy, Inc

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

import { Client } from '@elastic/elasticsearch';
import { validate } from '@lowdefy/ajv';
import ElasticsearchDeleteByQuery from './ElasticsearchDeleteByQuery';

const mockElasticsearchClient = jest.fn(() => mockElasticsearchClient);
mockElasticsearchClient.deleteByQuery = jest.fn(() => mockElasticsearchClient);
jest.mock('@elastic/elasticsearch', () => ({
  Client: jest.fn().mockImplementation(() => mockElasticsearchClient),
}));

const { resolver, schema, checkRead, checkWrite } = ElasticsearchDeleteByQuery;

const connection = {
  node: 'http://node',
  index: 'test',
};

test('valid request schema', () => {
  const request = {
    query: {
      match_all: {},
    },
  };
  expect(validate({ schema, data: request })).toEqual({ valid: true });
});

test('valid request schema, no query', () => {
  const request = {};
  expect(validate({ schema, data: request })).toEqual({ valid: true });
});

test('checkRead should be false', async () => {
  expect(checkRead).toBe(false);
});

test('checkWrite should be true', async () => {
  expect(checkWrite).toBe(true);
});

test('ElasticsearchDeleteByQuery', async () => {
  const responseData = {
    body: {
      took: 147,
      timed_out: false,
      total: 119,
      deleted: 119,
      batches: 1,
      version_conflicts: 0,
      noops: 0,
      retries: { bulk: 0, search: 0 },
      throttled_millis: 0,
      requests_per_second: -1.0,
      throttled_until_millis: 0,
      failures: [],
    },
  };
  mockElasticsearchClient.deleteByQuery.mockImplementationOnce(() => Promise.resolve(responseData));
  const request = {
    query: {
      term: {
        foo: 'bar',
      },
    },
  };
  const res = await resolver({ request, connection });
  expect(Client.mock.calls).toEqual([
    [
      {
        node: 'http://node',
        index: 'test',
      },
    ],
  ]);
  expect(mockElasticsearchClient.deleteByQuery.mock.calls).toEqual([
    [
      {
        index: 'test',
        query: {
          term: {
            foo: 'bar',
          },
        },
      },
    ],
  ]);
  expect(res).toEqual({
    response: {
      took: 147,
      timed_out: false,
      total: 119,
      deleted: 119,
      batches: 1,
      version_conflicts: 0,
      noops: 0,
      retries: { bulk: 0, search: 0 },
      throttled_millis: 0,
      requests_per_second: -1.0,
      throttled_until_millis: 0,
      failures: [],
    },
  });
});


<|start_filename|>packages/blocks/blocksAntd/src/index.js<|end_filename|>
/*
  Copyright 2020-2021 Lowdefy, Inc

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

import Affix from './blocks/Affix/Affix';
import Alert from './blocks/Alert/Alert';
import Anchor from './blocks/Anchor/Anchor';
import AutoComplete from './blocks/AutoComplete/AutoComplete';
import Avatar from './blocks/Avatar/Avatar';
import Badge from './blocks/Badge/Badge';
import Breadcrumb from './blocks/Breadcrumb/Breadcrumb';
import Button from './blocks/Button/Button';
import ButtonSelector from './blocks/ButtonSelector/ButtonSelector';
import Card from './blocks/Card/Card';
import CheckboxSelector from './blocks/CheckboxSelector/CheckboxSelector';
import CheckboxSwitch from './blocks/CheckboxSwitch/CheckboxSwitch';
import Collapse from './blocks/Collapse/Collapse';
import Comment from './blocks/Comment/Comment';
import ConfirmModal from './blocks/ConfirmModal/ConfirmModal';
import Content from './blocks/Content/Content';
import ControlledList from './blocks/ControlledList/ControlledList';
import DateRangeSelector from './blocks/DateRangeSelector/DateRangeSelector';
import DateSelector from './blocks/DateSelector/DateSelector';
import DateTimeSelector from './blocks/DateTimeSelector/DateTimeSelector';
import Descriptions from './blocks/Descriptions/Descriptions';
import Divider from './blocks/Divider/Divider';
import Drawer from './blocks/Drawer/Drawer';
import Footer from './blocks/Footer/Footer';
import Header from './blocks/Header/Header';
import Icon from './blocks/Icon/Icon';
import Label from './blocks/Label/Label';
import Layout from './blocks/Layout/Layout';
import Menu from './blocks/Menu/Menu';
import Message from './blocks/Message/Message';
import MobileMenu from './blocks/MobileMenu/MobileMenu';
import Modal from './blocks/Modal/Modal';
import MonthSelector from './blocks/MonthSelector/MonthSelector';
import MultipleSelector from './blocks/MultipleSelector/MultipleSelector';
import Notification from './blocks/Notification/Notification';
import NumberInput from './blocks/NumberInput/NumberInput';
import PageHCF from './blocks/PageHCF/PageHCF';
import PageHCSF from './blocks/PageHCSF/PageHCSF';
import PageHeaderMenu from './blocks/PageHeaderMenu/PageHeaderMenu';
import PageHSCF from './blocks/PageHSCF/PageHSCF';
import PageSHCF from './blocks/PageSHCF/PageSHCF';
import PageSiderMenu from './blocks/PageSiderMenu/PageSiderMenu';
import Pagination from './blocks/Pagination/Pagination';
import Paragraph from './blocks/Paragraph/Paragraph';
import ParagraphInput from './blocks/ParagraphInput/ParagraphInput';
import PasswordInput from './blocks/PasswordInput/PasswordInput';
import Progress from './blocks/Progress/Progress';
import RadioSelector from './blocks/RadioSelector/RadioSelector';
import RatingSlider from './blocks/RatingSlider/RatingSlider';
import Result from './blocks/Result/Result';
import S3UploadButton from './blocks/S3UploadButton/S3UploadButton';
import Selector from './blocks/Selector/Selector';
import Sider from './blocks/Sider/Sider';
import Skeleton from './blocks/Skeleton/Skeleton';
import Spin from './blocks/Spin/Spin';
import Statistic from './blocks/Statistic/Statistic';
import Switch from './blocks/Switch/Switch';
import Tabs from './blocks/Tabs/Tabs';
import TimelineList from './blocks/TimelineList/TimelineList';
import TextArea from './blocks/TextArea/TextArea';
import TextInput from './blocks/TextInput/TextInput';
import Title from './blocks/Title/Title';
import TitleInput from './blocks/TitleInput/TitleInput';
import Tooltip from './blocks/Tooltip/Tooltip';
import WeekSelector from './blocks/WeekSelector/WeekSelector';

export {
  Affix,
  Alert,
  Anchor,
  AutoComplete,
  Avatar,
  Badge,
  Breadcrumb,
  Button,
  ButtonSelector,
  Card,
  CheckboxSelector,
  CheckboxSwitch,
  Collapse,
  Comment,
  ConfirmModal,
  Content,
  ControlledList,
  DateRangeSelector,
  DateSelector,
  DateTimeSelector,
  Descriptions,
  Divider,
  Drawer,
  Footer,
  Header,
  Icon,
  Label,
  Layout,
  Menu,
  Message,
  MobileMenu,
  Modal,
  MonthSelector,
  MultipleSelector,
  Notification,
  NumberInput,
  PageHCF,
  PageHCSF,
  PageHeaderMenu,
  PageHSCF,
  PageSHCF,
  PageSiderMenu,
  Pagination,
  Paragraph,
  ParagraphInput,
  PasswordInput,
  Progress,
  RadioSelector,
  RatingSlider,
  Result,
  S3UploadButton,
  Selector,
  Sider,
  Skeleton,
  Spin,
  Statistic,
  Switch,
  Tabs,
  TimelineList,
  TextArea,
  TextInput,
  Title,
  TitleInput,
  Tooltip,
  WeekSelector,
};
export default {
  Affix,
  Alert,
  Anchor,
  AutoComplete,
  Avatar,
  Badge,
  Breadcrumb,
  Button,
  ButtonSelector,
  Card,
  CheckboxSelector,
  CheckboxSwitch,
  Collapse,
  Comment,
  ConfirmModal,
  Content,
  ControlledList,
  DateRangeSelector,
  DateSelector,
  DateTimeSelector,
  Descriptions,
  Divider,
  Drawer,
  Footer,
  Header,
  Icon,
  Label,
  Layout,
  Menu,
  Message,
  MobileMenu,
  Modal,
  MonthSelector,
  MultipleSelector,
  Notification,
  NumberInput,
  PageHCF,
  PageHCSF,
  PageHeaderMenu,
  PageHSCF,
  PageSHCF,
  PageSiderMenu,
  Pagination,
  Paragraph,
  ParagraphInput,
  PasswordInput,
  Progress,
  RadioSelector,
  RatingSlider,
  Result,
  S3UploadButton,
  Selector,
  Sider,
  Skeleton,
  Spin,
  Statistic,
  Switch,
  Tabs,
  TimelineList,
  TextArea,
  TextInput,
  Title,
  TitleInput,
  Tooltip,
  WeekSelector,
};


<|start_filename|>packages/docs/public/modules/index.js<|end_filename|>
import connectDocsearch from './connectDocsearch.js';
import filterDefaultValue from './filterDefaultValue.js';

window.lowdefy.registerJsAction('connectDocsearch', connectDocsearch);
window.lowdefy.registerJsOperator('filterDefaultValue', filterDefaultValue);


<|start_filename|>packages/build/src/test/testContext.js<|end_filename|>
/*
  Copyright 2020-2021 Lowdefy, Inc

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

function testContext({
  writeBuildArtifact,
  configDirectory,
  readConfigFile,
  logger = {},
  getMeta,
} = {}) {
  const defaultLogger = {
    info: () => {},
    log: () => {},
    warn: () => {},
    error: () => {},
    succeed: () => {},
  };

  const context = {
    configDirectory: configDirectory || '',
    getMeta: getMeta || (() => {}),
    writeBuildArtifact: writeBuildArtifact || (() => {}),
    readConfigFile: readConfigFile || (() => {}),
  };

  context.logger = {
    ...defaultLogger,
    ...logger,
  };
  return context;
}

export default testContext;


<|start_filename|>packages/blocks/blocksAntd/src/blocks/ConfirmModal/ConfirmModal.js<|end_filename|>
/*
  Copyright 2020-2021 Lowdefy, Inc

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

import React, { useEffect } from 'react';
import { Modal } from 'antd';
import { blockDefaultProps, renderHtml } from '@lowdefy/block-tools';
import Icon from '../Icon/Icon';

const ConfirmModal = ({ blockId, events, content, methods, properties }) => {
  useEffect(() => {
    methods.registerMethod('open', (args = {}) => {
      const additionalProps = {};
      if (properties.icon) {
        additionalProps.icon = (
          <Icon blockId={`${blockId}_icon`} events={events} properties={properties.icon} />
        );
      }
      methods.triggerEvent({ name: 'onOpen' });
      Modal[args.status || properties.status || 'confirm']({
        id: `${blockId}_confirm_modal`,
        title: renderHtml({ html: properties.title, methods }),
        content:
          (content.content && content.content()) ||
          renderHtml({ html: properties.content, methods }),
        className: methods.makeCssClass(properties.modalStyle),
        okText: properties.okText || 'Ok',
        okButtonProps: properties.okButton,
        cancelButtonProps: properties.cancelButton,
        cancelText: properties.cancelText || 'Cancel',
        centered: properties.centered || false,
        mask: properties.mask !== undefined ? properties.mask : true,
        maskClosable: properties.maskClosable || false,
        width: properties.width,
        zIndex: properties.zIndex,
        onOk: async () => {
          const response = await methods.triggerEvent({ name: 'onOk' });
          if (response.success === false && response.bounced !== true) throw response;
        },
        onCancel: async () => {
          const response = await methods.triggerEvent({ name: 'onCancel' });
          if (response.success === false && response.bounced !== true) throw response;
        },
        ...additionalProps,
      });
    });
  });
  return <div id={blockId} />;
};

ConfirmModal.defaultProps = blockDefaultProps;

export default ConfirmModal;


<|start_filename|>packages/helpers/src/serializer.js<|end_filename|>
/* eslint-disable no-param-reassign */

/*
  Copyright 2020-2021 Lowdefy, Inc

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

import type from './type';
import stableStringify from './stableStringify';

const makeReplacer = (customReplacer, isoStringDates) => (key, value) => {
  let dateReplacer = (date) => ({ _date: date.valueOf() });
  if (isoStringDates) {
    dateReplacer = (date) => ({ _date: date.toISOString() });
  }
  let newValue = value;
  if (customReplacer) {
    newValue = customReplacer(key, value);
  }
  if (type.isError(newValue)) {
    return {
      _error: {
        name: newValue.name,
        message: newValue.message,
        value: newValue.toString(),
      },
    };
  }
  if (type.isObject(newValue)) {
    Object.keys(newValue).forEach((k) => {
      if (type.isDate(newValue[k])) {
        // shallow copy original value before reassigning a value in order not to mutate original value
        newValue = { ...newValue };
        newValue[k] = dateReplacer(newValue[k]);
      }
    });
    return newValue;
  }
  if (type.isArray(newValue)) {
    return newValue.map((item) => {
      if (type.isDate(item)) {
        return dateReplacer(item);
      }
      return item;
    });
  }
  return newValue;
};

const makeReviver = (customReviver) => (key, value) => {
  let newValue = value;
  if (customReviver) {
    newValue = customReviver(key, value);
  }
  if (type.isObject(newValue) && !type.isUndefined(newValue._error)) {
    const error = new Error(newValue._error.message);
    error.name = newValue._error.name;
    return error;
  }
  if (type.isObject(newValue) && !type.isUndefined(newValue._date)) {
    if (type.isInt(newValue._date)) {
      return new Date(newValue._date);
    }
    if (newValue._date === 'now') {
      return newValue;
    }
    const result = new Date(newValue._date);
    if (!type.isDate(result)) {
      return newValue;
    }
    return result;
  }
  return newValue;
};

const serialize = (json, options = {}) => {
  if (type.isUndefined(json)) return json;
  if (type.isDate(json)) {
    if (options.isoStringDates) {
      return { _date: json.toISOString() };
    }
    return { _date: json.valueOf() };
  }
  return JSON.parse(JSON.stringify(json, makeReplacer(options.replacer, options.isoStringDates)));
};

const serializeToString = (json, options = {}) => {
  if (type.isUndefined(json)) return json;

  if (type.isDate(json)) {
    if (options.isoStringDates) {
      return `{ "_date": "${json.toISOString()}" }`;
    }
    return `{ "_date": ${json.valueOf()} }`;
  }
  if (options.stable) {
    return stableStringify(json, {
      replacer: makeReplacer(options.replacer),
      space: options.space,
    });
  }
  return JSON.stringify(
    json,
    makeReplacer(options.replacer, options.isoStringDates),
    options.space
  );
};

const deserialize = (json, options = {}) => {
  if (type.isUndefined(json)) return json;
  return JSON.parse(JSON.stringify(json), makeReviver(options.reviver));
};

const deserializeFromString = (str, options = {}) => {
  if (type.isUndefined(str)) return str;
  return JSON.parse(str, makeReviver(options.reviver));
};

const copy = (json, options = {}) => {
  if (type.isUndefined(json)) return undefined;
  if (type.isDate(json)) return new Date(json.valueOf());

  return JSON.parse(
    JSON.stringify(json, makeReplacer(options.replacer)),
    makeReviver(options.reviver)
  );
};

const serializer = { copy, serialize, serializeToString, deserialize, deserializeFromString };
export default serializer;


<|start_filename|>packages/docs/public/modules/connectDocsearch.js<|end_filename|>
/*
  Copyright 2020-2021 Lowdefy, Inc

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

function connectDocsearch() {
  let time = 100;
  try {
    // eslint-disable-next-line no-undef
    docsearch({
      apiKey: '4e88995ba28e39b8ed2bcfb6639379a1',
      indexName: 'lowdefy',
      inputSelector: '#docsearch_input',
      debug: true,
    });
  } catch (err) {
    setTimeout(connectDocsearch, time);
    time = time * 2;
  }
}

export default connectDocsearch;


<|start_filename|>packages/engine/test/createLink.test.js<|end_filename|>
import createLink from '../src/createLink';

const mockBackLink = jest.fn();
const mockNewOriginLink = jest.fn();
const mockSameOriginLink = jest.fn();

beforeEach(() => {
  mockBackLink.mockReset();
  mockNewOriginLink.mockReset();
  mockSameOriginLink.mockReset();
});

test('createLink, link with pageId', () => {
  const lowdefy = { inputs: {} };
  const link = createLink({
    backLink: mockBackLink,
    lowdefy,
    newOriginLink: mockNewOriginLink,
    sameOriginLink: mockSameOriginLink,
  });
  link({ pageId: 'page_1' });
  link({ pageId: 'page_1', urlQuery: { p: 3 } });
  expect(mockBackLink.mock.calls).toEqual([]);
  expect(mockNewOriginLink.mock.calls).toEqual([]);
  expect(mockSameOriginLink.mock.calls).toEqual([
    ['/page_1', undefined],
    ['/page_1?p=3', undefined],
  ]);
});

test('createLink, link with pageId new tab', () => {
  const lowdefy = { inputs: {} };
  const link = createLink({
    backLink: mockBackLink,
    lowdefy,
    newOriginLink: mockNewOriginLink,
    sameOriginLink: mockSameOriginLink,
  });
  link({ pageId: 'page_1', newTab: true });
  link({ pageId: 'page_1', newTab: true, urlQuery: { p: 3 } });
  expect(mockBackLink.mock.calls).toEqual([]);
  expect(mockNewOriginLink.mock.calls).toEqual([]);
  expect(mockSameOriginLink.mock.calls).toEqual([
    ['/page_1', true],
    ['/page_1?p=3', true],
  ]);
});

test('createLink, link with pageId with inputs', () => {
  const lowdefy = { inputs: {} };
  const link = createLink({
    backLink: mockBackLink,
    lowdefy,
    newOriginLink: mockNewOriginLink,
    sameOriginLink: mockSameOriginLink,
  });
  link({ pageId: 'page_1', input: { a: 1 } });
  link({ pageId: 'page_1', input: { a: 1 }, urlQuery: { p: 3 } });
  expect(mockBackLink.mock.calls).toEqual([]);
  expect(mockNewOriginLink.mock.calls).toEqual([]);
  expect(mockSameOriginLink.mock.calls).toEqual([
    ['/page_1', undefined],
    ['/page_1?p=3', undefined],
  ]);
  expect(lowdefy.inputs).toEqual({
    'page_1:page_1:{}': { a: 1 },
    'page_1:page_1:{"p":3}': { a: 1 },
  });
});

test('createLink, link with url', () => {
  const lowdefy = { inputs: {} };
  const link = createLink({
    backLink: mockBackLink,
    lowdefy,
    newOriginLink: mockNewOriginLink,
    sameOriginLink: mockSameOriginLink,
  });
  link({ url: 'http://localhost:8080/test' });
  link({ url: 'http://localhost:8080/test', urlQuery: { p: 3 } });
  expect(mockBackLink.mock.calls).toEqual([]);
  expect(mockNewOriginLink.mock.calls).toEqual([
    ['http://localhost:8080/test', undefined],
    ['http://localhost:8080/test?p=3', undefined],
  ]);
  expect(mockSameOriginLink.mock.calls).toEqual([]);
});

test('createLink, link with url new tab', () => {
  const lowdefy = { inputs: {} };
  const link = createLink({
    backLink: mockBackLink,
    lowdefy,
    newOriginLink: mockNewOriginLink,
    sameOriginLink: mockSameOriginLink,
  });
  link({ url: 'http://localhost:8080/test', newTab: true });
  link({ url: 'http://localhost:8080/test', newTab: true, urlQuery: { p: 3 } });
  expect(mockBackLink.mock.calls).toEqual([]);
  expect(mockNewOriginLink.mock.calls).toEqual([
    ['http://localhost:8080/test', true],
    ['http://localhost:8080/test?p=3', true],
  ]);
  expect(mockSameOriginLink.mock.calls).toEqual([]);
});

test('createLink, link with home', () => {
  const lowdefy = { inputs: {}, homePageId: 'home' };
  const link = createLink({
    backLink: mockBackLink,
    lowdefy,
    newOriginLink: mockNewOriginLink,
    sameOriginLink: mockSameOriginLink,
  });
  link({ home: true });
  link({ home: true, urlQuery: { p: 3 } });
  expect(mockBackLink.mock.calls).toEqual([]);
  expect(mockNewOriginLink.mock.calls).toEqual([]);
  expect(mockSameOriginLink.mock.calls).toEqual([
    ['/home', undefined],
    ['/home?p=3', undefined],
  ]);
});

test('createLink, link with home new tab', () => {
  const lowdefy = { inputs: {}, homePageId: 'home' };
  const link = createLink({
    backLink: mockBackLink,
    lowdefy,
    newOriginLink: mockNewOriginLink,
    sameOriginLink: mockSameOriginLink,
  });
  link({ home: true, newTab: true });
  link({ home: true, newTab: true, urlQuery: { p: 3 } });
  expect(mockBackLink.mock.calls).toEqual([]);
  expect(mockNewOriginLink.mock.calls).toEqual([]);
  expect(mockSameOriginLink.mock.calls).toEqual([
    ['/home', true],
    ['/home?p=3', true],
  ]);
});

test('createLink, link with home with inputs', () => {
  const lowdefy = { inputs: {}, homePageId: 'home' };
  const link = createLink({
    backLink: mockBackLink,
    lowdefy,
    newOriginLink: mockNewOriginLink,
    sameOriginLink: mockSameOriginLink,
  });
  link({ home: true, input: { a: 1 } });
  link({ home: true, input: { a: 1 }, urlQuery: { p: 3 } });
  expect(mockBackLink.mock.calls).toEqual([]);
  expect(mockNewOriginLink.mock.calls).toEqual([]);
  expect(mockSameOriginLink.mock.calls).toEqual([
    ['/home', undefined],
    ['/home?p=3', undefined],
  ]);
  expect(lowdefy.inputs).toEqual({
    'home:home:{}': { a: 1 },
    'home:home:{"p":3}': { a: 1 },
  });
});

test('createLink, link to throw if no params', () => {
  const lowdefy = { inputs: {}, homePageId: 'home' };
  const link = createLink({
    backLink: mockBackLink,
    lowdefy,
    newOriginLink: mockNewOriginLink,
    sameOriginLink: mockSameOriginLink,
  });
  expect(() => link({})).toThrowErrorMatchingInlineSnapshot(`"Invalid Link."`);
});

test('createLink, link with back', () => {
  const lowdefy = { inputs: {} };
  const link = createLink({
    backLink: mockBackLink,
    lowdefy,
    newOriginLink: mockNewOriginLink,
    sameOriginLink: mockSameOriginLink,
  });
  link({ back: true });
  expect(mockBackLink.mock.calls).toEqual([[]]);
  expect(mockNewOriginLink.mock.calls).toEqual([]);
  expect(mockSameOriginLink.mock.calls).toEqual([]);
});

test('createLink, link with back equal to false is invalid', () => {
  const lowdefy = { inputs: {} };
  const link = createLink({
    backLink: mockBackLink,
    lowdefy,
    newOriginLink: mockNewOriginLink,
    sameOriginLink: mockSameOriginLink,
  });
  expect(() => link({ back: false })).toThrowErrorMatchingInlineSnapshot(`"Invalid Link."`);
});


<|start_filename|>packages/blocks/blocksAntd/src/blocks/Button/Button.js<|end_filename|>
/*
  Copyright 2020-2021 Lowdefy, Inc

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

import React from 'react';
import { Button } from 'antd';
import color from '@lowdefy/color';
import { get, type } from '@lowdefy/helpers';
import { blockDefaultProps, renderHtml } from '@lowdefy/block-tools';
import Icon from '../Icon/Icon';

const ButtonBlock = ({ blockId, events, loading, methods, onClick, properties, rename }) => {
  const onClickActionName = get(rename, 'events.onClick', { default: 'onClick' });
  return (
    <Button
      block={properties.block}
      className={methods.makeCssClass([
        {
          backgroundColor: properties.color,
          borderColor: properties.color,
          '&:active': properties.color && {
            backgroundColor: color(properties.color, 7),
            borderColor: color(properties.color, 7),
          },
          '&:hover': properties.color && {
            backgroundColor: color(properties.color, 5),
            borderColor: color(properties.color, 5),
          },
        },
        properties.style,
      ])}
      disabled={properties.disabled || get(events, `${onClickActionName}.loading`)}
      ghost={properties.ghost}
      danger={properties.danger}
      href={properties.href}
      id={blockId}
      loading={loading || get(events, `${onClickActionName}.loading`)}
      shape={properties.shape}
      size={properties.size}
      type={get(properties, 'type', { default: 'primary' })}
      icon={
        properties.icon && (
          <Icon blockId={`${blockId}_icon`} events={events} properties={properties.icon} />
        )
      }
      onClick={onClick || (() => methods.triggerEvent({ name: onClickActionName }))}
    >
      {!properties.hideTitle &&
        renderHtml({
          html: type.isNone(properties.title) ? blockId : properties.title,
          methods,
        })}
    </Button>
  );
};

ButtonBlock.defaultProps = blockDefaultProps;

export default ButtonBlock;


<|start_filename|>packages/engine/test/Requests.test.js<|end_filename|>
/*
  Copyright 2020-2021 Lowdefy, Inc

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

import testContext from './testContext';

const mockReqResponses = {
  req_one: {
    data: {
      request: {
        id: 'req_one',
        success: true,
        response: 1,
      },
    },
  },
  req_watch: {
    data: {
      request: {
        id: 'req_watch',
        success: true,
        response: 2,
      },
    },
  },
  req_error: new Error('mock error'),
};

const mockQuery = jest.fn();
const mockQueryImp = ({ variables }) => {
  const { input } = variables;
  const { requestId } = input;
  return new Promise((resolve, reject) => {
    if (requestId === 'req_error') {
      reject(mockReqResponses[requestId]);
    }
    resolve(mockReqResponses[requestId]);
  });
};
const client = { query: mockQuery };

const rootBlock = {
  blockId: 'page1',
  meta: {
    category: 'context',
  },
  requests: [
    {
      requestId: 'req_one',
    },
    {
      requestId: 'req_error',
    },
    {
      requestId: 'req_watch',
    },
  ],
};

const pageId = 'page1';
const initState = { state: true };

const lowdefy = {
  client,
  lowdefyGlobal: { lowdefyGlobal: true },
  inputs: { test: { input: true } },
  pageId,
  urlQuery: { urlQuery: true },
};

// Comment out to use console
console.log = () => {};
console.error = () => {};

beforeEach(() => {
  mockQuery.mockReset();
  mockQuery.mockImplementation(mockQueryImp);
});

test('callRequest', async () => {
  const context = await testContext({
    lowdefy,
    rootBlock,
  });
  await context.Requests.callRequest({ requestId: 'req_one' });
  expect(context.requests).toEqual({
    req_one: {
      error: [],
      loading: false,
      response: 1,
    },
  });
});

test('callRequest, pass variables to qraphql', async () => {
  const context = await testContext({
    lowdefy,
    rootBlock,
    initState,
  });
  await context.Requests.callRequest({
    requestId: 'req_one',
    event: { event: true },
    arrayIndices: [1],
  });
  expect(mockQuery.mock.calls[0][0].variables).toEqual({
    input: {
      arrayIndices: [1],
      blockId: 'page1',
      event: { event: true },
      input: { input: true },
      lowdefyGlobal: { lowdefyGlobal: true },
      pageId: 'page1',
      requestId: 'req_one',
      state: { state: true },
      urlQuery: { urlQuery: true },
    },
  });
});

test('callRequests all requests', async () => {
  const context = await testContext({
    lowdefy,
    rootBlock,
  });
  const promise = context.Requests.callRequests();
  expect(context.requests).toEqual({
    req_one: {
      error: [],
      loading: true,
      response: null,
    },
    req_error: {
      error: [],
      loading: true,
      response: null,
    },
    req_watch: {
      error: [],
      loading: true,
      response: null,
    },
  });
  try {
    await promise;
  } catch (e) {
    // catch thrown errors
  }
  expect(context.requests).toEqual({
    req_one: {
      error: [],
      loading: false,
      response: 1,
    },
    req_error: {
      error: [new Error('mock error')],
      loading: false,
      response: null,
    },
    req_watch: {
      error: [],
      loading: false,
      response: 2,
    },
  });
  expect(mockQuery).toHaveBeenCalledTimes(3);
});

test('callRequests', async () => {
  const context = await testContext({
    lowdefy,
    rootBlock,
  });
  const promise = context.Requests.callRequests({ requestIds: ['req_one'] });
  expect(context.requests).toEqual({
    req_one: {
      error: [],
      loading: true,
      response: null,
    },
  });
  await promise;
  expect(context.requests).toEqual({
    req_one: {
      error: [],
      loading: false,
      response: 1,
    },
  });
  expect(mockQuery).toHaveBeenCalledTimes(1);
});

test('callRequest error', async () => {
  const context = await testContext({
    lowdefy,
    rootBlock,
  });
  await expect(context.Requests.callRequest({ requestId: 'req_error' })).rejects.toThrow();
  expect(context.requests).toEqual({
    req_error: {
      error: [new Error('mock error')],
      loading: false,
      response: null,
    },
  });
  await expect(context.Requests.callRequest({ requestId: 'req_error' })).rejects.toThrow();
  expect(context.requests).toEqual({
    req_error: {
      error: [new Error('mock error'), new Error('mock error')],
      loading: false,
      response: null,
    },
  });
});

test('callRequest that is not on root block', async () => {
  const context = await testContext({
    lowdefy,
    rootBlock,
  });
  await expect(context.Requests.callRequest({ requestId: 'req_does_not_exist' })).rejects.toThrow(
    'Configuration Error: Request req_does_not_exist not defined on context.'
  );
});

test('callRequest on root block with no requests', async () => {
  const context = await testContext({
    lowdefy,
    rootBlock,
  });
  await expect(context.Requests.callRequest({ requestId: 'req_does_not_exist' })).rejects.toThrow(
    'Configuration Error: Request req_does_not_exist not defined on context.'
  );
});

test('callRequest request does not exist', async () => {
  const context = await testContext({
    lowdefy,
    rootBlock,
  });
  await expect(context.Requests.callRequest({ requestId: 'req_two' })).rejects.toThrow(
    'Configuration Error: Request req_two not defined on context.'
  );
  expect(context.requests).toEqual({
    req_two: {
      error: [new Error('Configuration Error: Request req_two not defined on context.')],
      loading: false,
      response: null,
    },
  });
});

test('update function should be called', async () => {
  const updateFunction = jest.fn();
  const context = await testContext({
    lowdefy,
    rootBlock,
  });
  context.update = updateFunction;
  await context.Requests.callRequest({ requestId: 'req_one' });
  expect(updateFunction).toHaveBeenCalledTimes(1);
});

test('update function should be called if error', async () => {
  const updateFunction = jest.fn();
  const context = await testContext({
    lowdefy,
    rootBlock,
  });
  context.update = updateFunction;
  try {
    await context.Requests.callRequest({ requestId: 'req_error' });
  } catch (e) {
    // catch thrown errors
    console.log(e);
  }
  expect(updateFunction).toHaveBeenCalledTimes(1);
});

test('fetch should set blocks loading and call query every time it is called', async () => {
  const context = await testContext({
    lowdefy,
    rootBlock,
  });
  const setBlocksLoadingCacheFunction = jest.fn();
  context.RootBlocks = {
    setBlocksLoadingCache: setBlocksLoadingCacheFunction,
    update: jest.fn(),
  };
  await context.Requests.callRequest({ requestId: 'req_one', onlyNew: true });
  expect(setBlocksLoadingCacheFunction).toHaveBeenCalledTimes(1);
  expect(mockQuery).toHaveBeenCalledTimes(1);
  context.Requests.fetch({ requestId: 'req_one' });
  expect(setBlocksLoadingCacheFunction).toHaveBeenCalledTimes(2);
  expect(mockQuery).toHaveBeenCalledTimes(2);
});


<|start_filename|>packages/build/src/build/buildRefs/getRefsFromFile.js<|end_filename|>
/*
  Copyright 2020-2021 Lowdefy, Inc

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/
import { type } from '@lowdefy/helpers';
import makeRefDefinition from './makeRefDefinition';

function getRefsFromFile(fileContent) {
  const foundRefs = [];
  const reviver = (key, value) => {
    if (type.isObject(value)) {
      if (!type.isUndefined(value._ref)) {
        const def = makeRefDefinition(value._ref);
        foundRefs.push(def);
        return {
          _ref: def,
        };
      }
    }
    return value;
  };
  const fileContentBuiltRefs = JSON.parse(JSON.stringify(fileContent), reviver);
  return { foundRefs, fileContentBuiltRefs };
}

export default getRefsFromFile;


<|start_filename|>packages/blocks/blocksAntd/src/blocks/S3UploadButton/S3UploadButton.js<|end_filename|>
import React, { useEffect, useState } from 'react';
import { blockDefaultProps } from '@lowdefy/block-tools';
import { get } from '@lowdefy/helpers';
import { Upload } from 'antd';

import Button from '../Button/Button';

const makeFileValue = (file, s3Parameters) => {
  const { lastModified, name, percent, size, status, type, uid } = file;
  const { bucket, key } = get(s3Parameters, uid, { default: {} });
  return { bucket, key, lastModified, name, percent, size, status, type, uid };
};

const makeOnChangeValue = (s3Parameters, changeEvent) => {
  const { file, fileList } = changeEvent;
  return {
    file: makeFileValue(file, s3Parameters),
    fileList: fileList.map((fl) => makeFileValue(fl, s3Parameters)),
  };
};

const getDisabled = ({ properties, value }) => {
  if (properties.disabled) return true;
  if (properties.singleFile && value && (value.fileList || []).length >= 1) {
    return true;
  }
  return false;
};

const getCustomRequest =
  ({ methods, setS3Parameters }) =>
  async ({ file, onError, onProgress, onSuccess }) => {
    try {
      const { name, size, type, uid } = file;

      const s3PostPolicyResponse = await methods.triggerEvent({
        name: '__getS3PostPolicy',
        event: { filename: name, size, type, uid },
      });

      if (s3PostPolicyResponse.success !== true) {
        throw new Error('S3 post policy request error.');
      }

      const { url, fields } = s3PostPolicyResponse.responses.__getS3PostPolicy.response[0];
      const { bucket, key } = fields;

      setS3Parameters((prevState) => {
        const ret = { ...prevState };
        ret[uid] = { bucket, key };
        return ret;
      });

      // Set 20 % progress on policy is acquired else user waits to long before progress is reported
      onProgress({ percent: 20 });

      // Create FormData with all required fields in S3 policy
      const formData = new FormData();
      Object.keys(fields).forEach((field) => {
        formData.append(field, fields[field]);
      });
      // file needs to be the last field in the form
      formData.append('file', file);

      const xhr = new XMLHttpRequest();
      xhr.upload.onprogress = (event) => {
        if (event.lengthComputable) {
          onProgress({ percent: (event.loaded / event.total) * 80 + 20 });
        }
      };
      xhr.addEventListener('error', onError);
      xhr.addEventListener('load', onSuccess);
      xhr.open('post', url);
      xhr.send(formData);
    } catch (error) {
      console.error(error);
      onError(error);
    }
  };

const S3UploadButtonBlock = ({ blockId, events, methods, properties, value }) => {
  // Use state here because we need to set s3 bucket and key as block value
  // The customRequest function does not have access to the updated block value,
  // so it cannot set the value directly. customRequest sets the parameters to s3Parameters state,
  // and then onChange updates the block value.
  const [s3Parameters, setS3Parameters] = useState(value);
  const customRequest = getCustomRequest({ methods, setS3Parameters });
  useEffect(() => {
    methods.setValue({ file: null, fileList: [] });
    methods.registerEvent({
      name: '__getS3PostPolicy',
      actions: [
        {
          id: '__getS3PostPolicy',
          type: 'Request',
          params: [properties.s3PostPolicyRequestId],
        },
      ],
    });
  }, []);

  const disabled = getDisabled({ properties, value });
  return (
    <Upload
      accept={properties.accept}
      customRequest={customRequest}
      disabled={disabled}
      id={blockId}
      multiple={!properties.singleFile} // Allows selection of multiple files at once, does not block multiple uploads
      showUploadList={properties.showUploadList}
      onChange={(event) => {
        methods.setValue(makeOnChangeValue(s3Parameters, event));
        methods.triggerEvent({ name: 'onChange' });
      }}
    >
      <Button
        blockId={`${blockId}_button`}
        events={events}
        properties={{
          disabled,
          icon: 'UploadOutlined',
          title: 'Upload',
          type: 'default',
          ...properties.button,
        }}
        methods={methods}
      />
    </Upload>
  );
};

S3UploadButtonBlock.defaultProps = blockDefaultProps;

export default S3UploadButtonBlock;


<|start_filename|>packages/build/src/build/buildRefs/makeRefDefinition.js<|end_filename|>
/*
  Copyright 2020-2021 Lowdefy, Inc

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

import { get } from '@lowdefy/helpers';
import { v1 as uuid } from 'uuid';

import getRefPath from './getRefPath';

function makeRefDefinition(refDefinition) {
  return {
    id: uuid(),
    original: refDefinition,
    path: getRefPath(refDefinition),
    resolver: get(refDefinition, 'resolver'),
    transformer: get(refDefinition, 'transformer'),
    vars: get(refDefinition, 'vars', { default: {} }),
  };
}

export default makeRefDefinition;


<|start_filename|>packages/blockTools/src/renderHtml.js<|end_filename|>
/*
  Copyright 2020-2021 Lowdefy, Inc

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

import React from 'react';
import { type } from '@lowdefy/helpers';

import HtmlComponent from './HtmlComponent';

const renderHtml = ({ div, html, id, methods, style }) =>
  type.isNone(html) ? undefined : (
    <HtmlComponent div={div} html={html} id={id} methods={methods} style={style} />
  );

export default renderHtml;


<|start_filename|>packages/blockTools/src/renderHtml.test.js<|end_filename|>
/*
  Copyright 2020-2021 Lowdefy, Inc

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

import { renderHtml } from '../src';

import { configure, mount } from 'enzyme';
import Adapter from '@wojtekmaj/enzyme-adapter-react-17';
configure({ adapter: new Adapter() });

const mockCMakeCssClass = jest.fn(() => 'test-class');
const methods = {
  makeCssClass: mockCMakeCssClass,
};

test('renderHtml html is undefined', () => {
  expect(renderHtml({ methods })).toBe(undefined);
  expect(renderHtml({ html: undefined, methods })).toBe(undefined);
});

test('renderHtml html string', async () => {
  const wrapper = await mount(renderHtml({ html: '<p>Hello</p>', methods }));
  await wrapper.instance().componentDidMount();
  await wrapper.update();
  expect(wrapper.html()).toMatchInlineSnapshot(
    `"<span class=\\"test-class\\"><p>Hello</p></span>"`
  );
});

test('renderHtml html number 0', async () => {
  const wrapper = await mount(renderHtml({ html: 0, methods }));
  await wrapper.instance().componentDidMount();
  await wrapper.update();
  expect(wrapper.html()).toMatchInlineSnapshot(`"<span class=\\"test-class\\">0</span>"`);
});

test('renderHtml html number 123', async () => {
  const wrapper = await mount(renderHtml({ html: 123, methods }));
  await wrapper.instance().componentDidMount();
  await wrapper.update();
  expect(wrapper.html()).toMatchInlineSnapshot(`"<span class=\\"test-class\\">123</span>"`);
});

test('renderHtml html boolean false', async () => {
  const wrapper = await mount(renderHtml({ html: false, methods }));
  await wrapper.instance().componentDidMount();
  await wrapper.update();
  expect(wrapper.html()).toMatchInlineSnapshot(`"<span class=\\"test-class\\">false</span>"`);
});

test('renderHtml html boolean true', async () => {
  const wrapper = await mount(renderHtml({ html: true, methods }));
  await wrapper.instance().componentDidMount();
  await wrapper.update();
  expect(wrapper.html()).toMatchInlineSnapshot(`"<span class=\\"test-class\\">true</span>"`);
});


<|start_filename|>packages/operators/src/web/js.js<|end_filename|>
/*
  Copyright 2020-2021 Lowdefy, Inc

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

import { type } from '@lowdefy/helpers';

function _js({ context, params, location, methodName }) {
  if (!type.isFunction(context.lowdefy.imports.jsOperators[methodName])) {
    throw new Error(`Operator Error: _js.${methodName} is not a function.`);
  }
  if (!type.isNone(params) && !type.isArray(params)) {
    throw new Error(`Operator Error: _js.${methodName} takes an array as input at ${location}.`);
  }
  return context.lowdefy.imports.jsOperators[methodName](...(params || []));
}

export default _js;


<|start_filename|>packages/cli/src/commands/cleanCache/cleanCache.test.js<|end_filename|>
/*
  Copyright 2020-2021 Lowdefy, Inc

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/
import path from 'path';
import fse from 'fs-extra';

import cleanCache from './cleanCache';
import startUp from '../../utils/startUp';

jest.mock('fs-extra', () => {
  const emptyDir = jest.fn();
  return { emptyDir };
});

jest.mock('../../utils/startUp');

beforeEach(() => {
  fse.emptyDir.mockReset();
});

test('cleanCache', async () => {
  const context = {};
  await startUp({ context, options: {}, command: {} });
  await cleanCache({ context });
  expect(fse.emptyDir.mock.calls).toEqual([['baseDirectory/cacheDirectory']]);
});

test('cleanCache baseDir', async () => {
  const context = {};
  await startUp({ context, options: { baseDirectory: 'baseDir' }, command: {} });
  await cleanCache({ context });
  expect(fse.emptyDir.mock.calls).toEqual([['baseDir/cacheDirectory']]);
});


<|start_filename|>packages/operators/test/web/js.test.js<|end_filename|>
/*
  Copyright 2020-2021 Lowdefy, Inc

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

import _js from '../../src/web/js';
import { context } from '../testContext';

const location = 'location';

test('_js.test_fn and params to return a value', () => {
  const params = [12, 14];
  const test_fn = (a, b) => a + b;
  const mockFn = jest.fn().mockImplementation(test_fn);
  context.lowdefy.imports.jsOperators.test_fn = mockFn;
  expect(_js({ context, location, params, methodName: 'test_fn' })).toEqual(26);
});

test('_js.test_fn no params to return a value', () => {
  const test_fn = () => 'some value';
  const mockFn = jest.fn().mockImplementation(test_fn);
  context.lowdefy.imports.jsOperators.test_fn = mockFn;
  expect(_js({ context, location, params: undefined, methodName: 'test_fn' })).toEqual(
    'some value'
  );
});

test('_js.test_fn and params to return a function', () => {
  const params = [12, 14];
  const test_fn = (a, b) => (c) => a + b + c;
  const mockFn = jest.fn().mockImplementation(test_fn);
  context.lowdefy.imports.jsOperators.test_fn = mockFn;
  const fn = _js({ context, location, params, methodName: 'test_fn' });
  expect(fn).toBeInstanceOf(Function);
  expect(fn(4)).toEqual(30);
});

test('_js.test_fn params not an array', () => {
  const params = 10;
  const test_fn = (a, b) => a + b;
  const mockFn = jest.fn().mockImplementation(test_fn);
  context.lowdefy.imports.jsOperators.test_fn = mockFn;
  expect(() => _js({ context, location, params, methodName: 'test_fn' })).toThrow(
    new Error('Operator Error: _js.test_fn takes an array as input at location.')
  );
});

test('_js.not_a_function', () => {
  const params = 10;
  const test_fn = (a, b) => a + b;
  const mockFn = jest.fn().mockImplementation(test_fn);
  context.lowdefy.imports.jsOperators.test_fn = mockFn;
  expect(() => _js({ context, location, params, methodName: 'not_a_function' })).toThrow(
    new Error('Operator Error: _js.not_a_function is not a function.')
  );
});


<|start_filename|>packages/engine/test/Actions/Actions.test.js<|end_filename|>
/* eslint-disable prefer-promise-reject-errors */

/*
  Copyright 2020-2021 Lowdefy, Inc

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

import testContext from '../testContext';
import actions from '../../src/actions/index.js';

jest.mock('../../src/actions/index.js', () => ({
  ActionSync: jest.fn(({ params }) => params),
  ActionAsync: jest.fn(async ({ params }) => {
    await timeout(params.ms || 1);
    return params;
  }),
  ActionError: jest.fn(() => {
    throw new Error('Test error');
  }),
  CatchActionError: jest.fn(() => {
    throw new Error('Test catch error');
  }),
  ActionAsyncError: jest.fn(async ({ params }) => {
    await timeout(params.ms || 1);
    throw new Error('Test error');
  }),
}));

const timeout = (ms) => {
  return new Promise((resolve) => setTimeout(resolve, ms));
};

const pageId = 'one';

const RealDate = Date;
const mockDate = jest.fn(() => ({ date: 0 }));
mockDate.now = jest.fn(() => 0);

const closeLoader = jest.fn();
const displayMessage = jest.fn();
const lowdefy = {
  displayMessage,
  pageId,
};
const arrayIndices = [];
const eventName = 'eventName';

// Comment out to use console.log
console.log = () => {};
console.error = () => {};

beforeEach(() => {
  global.Date = mockDate;
  displayMessage.mockReset();
  closeLoader.mockReset();
  displayMessage.mockImplementation(() => closeLoader);
});

afterAll(() => {
  global.Date = RealDate;
});

test('call a synchronous action', async () => {
  const rootBlock = {
    blockId: 'root',
    meta: {
      category: 'context',
    },
  };
  const context = await testContext({
    lowdefy,
    rootBlock,
  });
  const Actions = context.Actions;
  const res = await Actions.callActions({
    actions: [{ id: 'test', type: 'ActionSync', params: 'params' }],
    arrayIndices,
    block: { blockId: 'blockId' },
    catchActions: [],
    event: {},
    eventName,
  });
  expect(res).toEqual({
    blockId: 'blockId',
    bounced: false,
    event: {},
    eventName: 'eventName',
    responses: {
      test: {
        type: 'ActionSync',
        index: 0,
        response: 'params',
      },
    },
    success: true,
    startTimestamp: { date: 0 },
    endTimestamp: { date: 0 },
  });
  expect(actions.ActionSync.mock.calls.length).toBe(1);
});

test('call a asynchronous action', async () => {
  const rootBlock = {
    blockId: 'root',
    meta: {
      category: 'context',
    },
  };
  const context = await testContext({
    lowdefy,
    rootBlock,
  });
  const Actions = context.Actions;
  const res = await Actions.callActions({
    actions: [{ id: 'test', type: 'ActionAsync', params: 'params' }],
    arrayIndices,
    block: { blockId: 'blockId' },
    catchActions: [],
    event: {},
    eventName,
  });
  expect(res).toEqual({
    blockId: 'blockId',
    bounced: false,
    event: {},
    eventName: 'eventName',
    responses: {
      test: {
        type: 'ActionAsync',
        index: 0,
        response: 'params',
      },
    },
    success: true,
    startTimestamp: { date: 0 },
    endTimestamp: { date: 0 },
  });
  expect(actions.ActionAsync.mock.calls.length).toBe(1);
});

test('call 2 actions', async () => {
  const rootBlock = {
    blockId: 'root',
    meta: {
      category: 'context',
    },
  };
  const context = await testContext({
    lowdefy,
    rootBlock,
  });
  const Actions = context.Actions;
  const res = await Actions.callActions({
    actions: [
      { id: 'test1', type: 'ActionSync', params: 'params1' },
      { id: 'test2', type: 'ActionAsync', params: 'params2' },
    ],
    arrayIndices,
    block: { blockId: 'blockId' },
    catchActions: [],
    event: {},
    eventName,
  });
  expect(res).toEqual({
    blockId: 'blockId',
    bounced: false,
    event: {},
    eventName: 'eventName',
    responses: {
      test1: {
        type: 'ActionSync',
        index: 0,
        response: 'params1',
      },
      test2: {
        type: 'ActionAsync',
        index: 1,
        response: 'params2',
      },
    },
    success: true,
    startTimestamp: { date: 0 },
    endTimestamp: { date: 0 },
  });
});

test('operators are evaluated in params, skip and messages', async () => {
  const rootBlock = {
    blockId: 'root',
    meta: {
      category: 'context',
    },
  };
  const context = await testContext({
    lowdefy,
    rootBlock,
  });
  const Actions = context.Actions;
  await Actions.callActions({
    actions: [
      {
        id: 'test',
        type: 'ActionSync',
        params: { _event: 'arr.$' },
        skip: { _event: 'skip' },
        messages: { _event: 'messages' },
      },
      {
        id: 'test',
        type: 'ActionError',
        params: { _event: 'arr.$' },
        skip: { _event: 'skip' },
        messages: { _event: 'messages' },
      },
    ],
    catchActions: [],
    arrayIndices: [1],
    block: { blockId: 'blockId' },
    event: {
      arr: [1, 2, 3],
      skip: false,
      messages: {
        success: 'suc',
        error: 'err',
        loading: 'load',
      },
    },
    eventName,
  });
  expect(actions.ActionSync.mock.calls[0][0].params).toBe(2);
  expect(displayMessage.mock.calls).toEqual([
    [
      {
        content: 'load',
        duration: 0,
        status: 'loading',
      },
    ],
    [
      {
        content: 'suc',
        duration: undefined,
        status: 'success',
      },
    ],
    [
      {
        content: 'load',
        duration: 0,
        status: 'loading',
      },
    ],
    [
      {
        content: 'err',
        duration: 6,
        status: 'error',
      },
    ],
  ]);
});

test('operators are evaluated in error messages after error', async () => {
  const rootBlock = {
    blockId: 'root',
    meta: {
      category: 'context',
    },
  };
  const context = await testContext({
    lowdefy,
    rootBlock,
  });
  const Actions = context.Actions;
  await Actions.callActions({
    actions: [
      {
        id: 'test',
        type: 'ActionError',
        messages: {
          success: 'suc',
          error: {
            '_json.stringify': [
              {
                data: 1234,
              },
            ],
          },
        },
      },
    ],
    catchActions: [],
    arrayIndices: [1],
    block: { blockId: 'blockId' },
    event: {},
    eventName,
  });
  expect(displayMessage.mock.calls).toEqual([
    [
      {
        content: `{
  \"data\": 1234
}`,
        duration: 6,
        status: 'error',
      },
    ],
  ]);
});

test('action error in error messages from same action id', async () => {
  const rootBlock = {
    blockId: 'root',
    meta: {
      category: 'context',
    },
  };
  const context = await testContext({
    lowdefy,
    rootBlock,
  });
  const Actions = context.Actions;
  await Actions.callActions({
    actions: [
      {
        id: 'one',
        type: 'ActionSync',
        params: 'one response',
      },
      {
        id: 'two',
        type: 'ActionError',
        messages: {
          success: 'suc',
          error: {
            '_string.concat': [
              'Result one: ',
              {
                _actions: 'one.response',
              },
              ' - Result two: ',
              {
                _actions: 'two.error',
              },
            ],
          },
        },
      },
    ],
    catchActions: [],
    arrayIndices: [1],
    block: { blockId: 'blockId' },
    event: {},
    eventName,
  });
  expect(displayMessage.mock.calls).toEqual([
    [
      {
        content: 'Result one: one response - Result two: Error: Test error',
        duration: 6,
        status: 'error',
      },
    ],
  ]);
});

test('action error in error parser', async () => {
  const rootBlock = {
    blockId: 'root',
    meta: {
      category: 'context',
    },
  };
  const context = await testContext({
    lowdefy,
    rootBlock,
  });
  const Actions = context.Actions;
  const res = await Actions.callActions({
    actions: [
      {
        id: 'two',
        type: 'ActionError',
        messages: {
          success: 'suc',
          error: {
            _divide: [
              3,
              {
                _if_none: [
                  {
                    _actions: 'two.error',
                  },
                  1,
                ],
              },
            ],
          },
        },
      },
    ],
    catchActions: [],
    arrayIndices: [1],
    block: { blockId: 'blockId' },
    event: {},
    eventName,
  });
  expect(res.responses.two.error).toEqual(
    new Error(
      'Operator Error: _divide takes an array of 2 numbers. Received: [3,{"name":"Error"}] at blockId.'
    )
  );
  expect(res.error.error.error).toEqual(
    new Error(
      'Operator Error: _divide takes an array of 2 numbers. Received: [3,{"name":"Error"}] at blockId.'
    )
  );
});

test('error with messages undefined', async () => {
  const rootBlock = {
    blockId: 'root',
    meta: {
      category: 'context',
    },
  };
  const context = await testContext({
    lowdefy,
    rootBlock,
  });
  const Actions = context.Actions;
  await Actions.callActions({
    actions: [
      {
        id: 'test',
        type: 'ActionError',
      },
    ],
    catchActions: [],
    arrayIndices: [1],
    block: { blockId: 'blockId' },
    event: {},
    eventName,
  });
  expect(displayMessage.mock.calls).toEqual([
    [
      {
        content: 'Test error',
        duration: 6,
        status: 'error',
      },
    ],
  ]);
});

test('skip a action', async () => {
  const rootBlock = {
    blockId: 'root',
    meta: {
      category: 'context',
    },
  };
  const context = await testContext({
    lowdefy,
    rootBlock,
  });
  const Actions = context.Actions;
  const res = await Actions.callActions({
    actions: [{ id: 'test', type: 'ActionSync', skip: true }],
    arrayIndices,
    block: { blockId: 'blockId' },
    catchActions: [],
    event: {},
    eventName,
  });
  expect(res).toEqual({
    blockId: 'blockId',
    bounced: false,
    event: {},
    eventName: 'eventName',
    responses: {
      test: {
        type: 'ActionSync',
        index: 0,
        skipped: true,
      },
    },
    success: true,
    startTimestamp: { date: 0 },
    endTimestamp: { date: 0 },
  });
  expect(actions.ActionSync.mock.calls.length).toBe(0);
});

test('action throws a error', async () => {
  const rootBlock = {
    blockId: 'root',
    meta: {
      category: 'context',
    },
  };
  const context = await testContext({
    lowdefy,
    rootBlock,
  });
  const Actions = context.Actions;
  const res = await Actions.callActions({
    actions: [{ id: 'test', type: 'ActionError', params: 'params' }],
    arrayIndices,
    block: { blockId: 'blockId' },
    catchActions: [],
    event: {},
    eventName,
  });
  expect(res).toEqual({
    blockId: 'blockId',
    bounced: false,
    event: {},
    eventName: 'eventName',
    error: {
      action: {
        id: 'test',
        params: 'params',
        type: 'ActionError',
      },
      error: {
        error: new Error('Test error'),
        index: 0,
        type: 'ActionError',
      },
    },
    responses: {
      test: {
        type: 'ActionError',
        index: 0,
        error: new Error('Test error'),
      },
    },
    success: false,
    startTimestamp: { date: 0 },
    endTimestamp: { date: 0 },
  });
  expect(actions.ActionError.mock.calls.length).toBe(1);
});

test('actions after a error are not called throws a error', async () => {
  const rootBlock = {
    blockId: 'root',
    meta: {
      category: 'context',
    },
  };
  const context = await testContext({
    lowdefy,
    rootBlock,
  });
  const Actions = context.Actions;
  const res = await Actions.callActions({
    actions: [
      { id: 'test', type: 'ActionError', params: 'params' },
      { id: 'test', type: 'ActionSync', params: 'params' },
    ],
    arrayIndices,
    block: { blockId: 'blockId' },
    catchActions: [],
    event: {},
    eventName,
  });
  expect(res).toEqual({
    blockId: 'blockId',
    bounced: false,
    event: {},
    eventName: 'eventName',
    error: {
      action: {
        id: 'test',
        params: 'params',
        type: 'ActionError',
      },
      error: {
        error: new Error('Test error'),
        index: 0,
        type: 'ActionError',
      },
    },
    responses: {
      test: {
        type: 'ActionError',
        index: 0,
        error: new Error('Test error'),
      },
    },
    success: false,
    startTimestamp: { date: 0 },
    endTimestamp: { date: 0 },
  });
  expect(actions.ActionError.mock.calls.length).toBe(1);
  expect(actions.ActionSync.mock.calls.length).toBe(0);
});

test('Invalid action type', async () => {
  const rootBlock = {
    blockId: 'root',
    meta: {
      category: 'context',
    },
  };
  const context = await testContext({
    lowdefy,
    rootBlock,
  });
  const Actions = context.Actions;
  const res = await Actions.callActions({
    actions: [{ id: 'test', type: 'Invalid', params: 'params' }],
    arrayIndices,
    block: { blockId: 'blockId' },
    catchActions: [],
    event: {},
    eventName,
  });
  expect(res).toEqual({
    blockId: 'blockId',
    bounced: false,
    event: {},
    eventName: 'eventName',
    error: {
      action: {
        id: 'test',
        params: 'params',
        type: 'Invalid',
      },
      error: {
        error: new Error('Invalid action type "Invalid" at "blockId".'),
        index: 0,
        type: 'Invalid',
      },
    },
    responses: {
      test: {
        type: 'Invalid',
        index: 0,
        error: new Error('Invalid action type "Invalid" at "blockId".'),
      },
    },
    success: false,
    startTimestamp: { date: 0 },
    endTimestamp: { date: 0 },
  });
});

test('Parser error in action', async () => {
  const rootBlock = {
    blockId: 'root',
    meta: {
      category: 'context',
    },
  };
  const context = await testContext({
    lowdefy,
    rootBlock,
  });
  const Actions = context.Actions;
  const res = await Actions.callActions({
    actions: [{ id: 'test', type: 'ActionSync', params: { _state: [] } }],
    arrayIndices,
    block: { blockId: 'blockId' },
    catchActions: [],
    event: {},
    eventName,
  });
  expect(res).toEqual({
    blockId: 'blockId',
    bounced: false,
    event: {},
    eventName: 'eventName',
    error: {
      action: {
        id: 'test',
        params: {
          _state: [],
        },
        type: 'ActionSync',
      },
      error: {
        error: new Error(
          'Operator Error: _state params must be of type string, integer, boolean or object. Received: [] at blockId.'
        ),
        index: 0,
        type: 'ActionSync',
      },
    },
    responses: {
      test: {
        type: 'ActionSync',
        index: 0,
        error: new Error(
          'Operator Error: _state params must be of type string, integer, boolean or object. Received: [] at blockId.'
        ),
      },
    },
    success: false,
    startTimestamp: { date: 0 },
    endTimestamp: { date: 0 },
  });
});

test('Display default loading and success messages when value == true ', async () => {
  const rootBlock = {
    blockId: 'root',
    meta: {
      category: 'context',
    },
  };
  const context = await testContext({
    lowdefy,
    rootBlock,
  });
  const Actions = context.Actions;
  await Actions.callActions({
    actions: [
      {
        id: 'test',
        type: 'ActionSync',
        messages: { loading: true, success: true },
      },
    ],
    arrayIndices,
    block: { blockId: 'blockId' },
    catchActions: [],
    event: {},
    eventName,
  });
  expect(displayMessage.mock.calls).toEqual([
    [
      {
        content: 'Loading',
        duration: 0,
        status: 'loading',
      },
    ],
    [
      {
        content: 'Success',
        duration: undefined,
        status: 'success',
      },
    ],
  ]);
  expect(closeLoader.mock.calls).toEqual([[]]);
});

test('Display custom loading and success messages when value is a string ', async () => {
  const rootBlock = {
    blockId: 'root',
    meta: {
      category: 'context',
    },
  };
  const context = await testContext({
    lowdefy,
    rootBlock,
  });
  const Actions = context.Actions;
  await Actions.callActions({
    actions: [
      {
        id: 'test',
        type: 'ActionSync',
        messages: { loading: 'My loading', success: 'My success' },
      },
    ],
    arrayIndices,
    block: { blockId: 'blockId' },
    catchActions: [],
    event: {},
    eventName,
  });
  expect(displayMessage.mock.calls).toEqual([
    [
      {
        content: 'My loading',
        duration: 0,
        status: 'loading',
      },
    ],
    [
      {
        content: 'My success',
        duration: undefined,
        status: 'success',
      },
    ],
  ]);
  expect(closeLoader.mock.calls).toEqual([[]]);
});

test('Do not display loading and success messages by default', async () => {
  const rootBlock = {
    blockId: 'root',
    meta: {
      category: 'context',
    },
  };
  const context = await testContext({
    lowdefy,
    rootBlock,
  });
  const Actions = context.Actions;
  await Actions.callActions({
    actions: [
      {
        id: 'test',
        type: 'ActionSync',
      },
    ],
    arrayIndices,
    block: { blockId: 'blockId' },
    catchActions: [],
    event: {},
    eventName,
  });
  expect(displayMessage.mock.calls).toEqual([]);
  expect(closeLoader.mock.calls).toEqual([]);
});

test('Display error message by default', async () => {
  const rootBlock = {
    blockId: 'root',
    meta: {
      category: 'context',
    },
  };
  const context = await testContext({
    lowdefy,
    rootBlock,
  });
  const Actions = context.Actions;
  await Actions.callActions({
    actions: [
      {
        id: 'test',
        type: 'ActionError',
      },
    ],
    arrayIndices,
    block: { blockId: 'blockId' },
    catchActions: [],
    event: {},
    eventName,
  });
  expect(displayMessage.mock.calls).toEqual([
    [
      {
        content: 'Test error',
        duration: 6,
        status: 'error',
      },
    ],
  ]);
});

test('Display custom error message', async () => {
  const rootBlock = {
    blockId: 'root',
    meta: {
      category: 'context',
    },
  };
  const context = await testContext({
    lowdefy,
    rootBlock,
  });
  const Actions = context.Actions;
  await Actions.callActions({
    actions: [
      {
        id: 'test',
        type: 'ActionError',
        messages: {
          error: 'My error',
        },
      },
    ],
    arrayIndices,
    block: { blockId: 'blockId' },
    catchActions: [],
    event: {},
    eventName,
  });
  expect(displayMessage.mock.calls).toEqual([
    [
      {
        content: 'My error',
        duration: 6,
        status: 'error',
      },
    ],
  ]);
});

test('Do not display an error message if message === false', async () => {
  const rootBlock = {
    blockId: 'root',
    meta: {
      category: 'context',
    },
  };
  const context = await testContext({
    lowdefy,
    rootBlock,
  });
  const Actions = context.Actions;
  await Actions.callActions({
    actions: [
      {
        id: 'test',
        type: 'ActionError',
        messages: {
          error: false,
        },
      },
    ],
    arrayIndices,
    block: { blockId: 'blockId' },
    catchActions: [],
    event: {},
    eventName,
  });
  expect(displayMessage.mock.calls).toEqual([]);
});

test('Call catchActions when actions throws error', async () => {
  const rootBlock = {
    blockId: 'root',
    meta: {
      category: 'context',
    },
  };
  const context = await testContext({
    lowdefy,
    rootBlock,
  });
  const Actions = context.Actions;
  const res = await Actions.callActions({
    actions: [
      {
        id: 'try_error',
        type: 'ActionError',
        messages: {
          error: false,
        },
      },
    ],
    arrayIndices,
    block: { blockId: 'blockId' },
    catchActions: [
      {
        id: 'catch_test',
        type: 'ActionAsync',
        params: 'params',
      },
    ],
    event: {},
    eventName,
  });
  expect(res).toEqual({
    blockId: 'blockId',
    bounced: false,
    endTimestamp: {
      date: 0,
    },
    error: {
      action: {
        id: 'try_error',
        messages: {
          error: false,
        },
        type: 'ActionError',
      },
      error: {
        error: new Error('Test error'),
        index: 0,
        type: 'ActionError',
      },
    },
    event: {},
    eventName: 'eventName',
    responses: {
      catch_test: {
        index: 0,
        response: 'params',
        type: 'ActionAsync',
      },
      try_error: {
        error: new Error('Test error'),
        index: 0,
        type: 'ActionError',
      },
    },
    startTimestamp: {
      date: 0,
    },
    success: false,
  });
  expect(actions.ActionAsync.mock.calls.length).toBe(1);
});

test('Call catchActions when actions throws error and catchActions throws error', async () => {
  const rootBlock = {
    blockId: 'root',
    meta: {
      category: 'context',
    },
  };
  const context = await testContext({
    lowdefy,
    rootBlock,
  });
  const Actions = context.Actions;
  const res = await Actions.callActions({
    actions: [
      {
        id: 'try_error',
        type: 'ActionError',
        messages: {
          error: false,
        },
      },
    ],
    arrayIndices,
    block: { blockId: 'blockId' },

    catchActions: [
      {
        id: 'catch_test',
        type: 'ActionAsync',
        params: 'params',
      },
      {
        id: 'catch_error',
        type: 'CatchActionError',
        messages: {
          error: false,
        },
      },
    ],
    event: {},
    eventName,
  });
  expect(res).toEqual({
    blockId: 'blockId',
    bounced: false,
    endTimestamp: {
      date: 0,
    },
    error: {
      action: {
        id: 'try_error',
        messages: {
          error: false,
        },
        type: 'ActionError',
      },
      error: {
        error: new Error('Test error'),
        index: 0,
        type: 'ActionError',
      },
    },
    errorCatch: {
      action: {
        id: 'catch_error',
        messages: {
          error: false,
        },
        type: 'CatchActionError',
      },
      error: {
        error: new Error('Test catch error'),
        index: 1,
        type: 'CatchActionError',
      },
    },
    event: {},
    eventName: 'eventName',
    responses: {
      catch_test: {
        index: 0,
        response: 'params',
        type: 'ActionAsync',
      },
      try_error: {
        error: new Error('Test error'),
        index: 0,
        type: 'ActionError',
      },
      catch_error: {
        error: new Error('Test catch error'),
        index: 1,
        type: 'CatchActionError',
      },
    },
    startTimestamp: {
      date: 0,
    },
    success: false,
  });
  expect(actions.ActionAsync.mock.calls.length).toBe(1);
});

test('call 2 actions, first with async: true', async () => {
  const rootBlock = {
    blockId: 'root',
    meta: {
      category: 'context',
    },
  };
  const context = await testContext({
    lowdefy,
    rootBlock,
  });
  const Actions = context.Actions;
  const res = await Actions.callActions({
    actions: [
      { id: 'test1', type: 'ActionAsync', async: true, params: { ms: 100 } },
      { id: 'test2', type: 'ActionSync', params: 'params2' },
    ],
    arrayIndices,
    block: { blockId: 'blockId' },
    catchActions: [],
    event: {},
    eventName,
  });
  expect(res).toEqual({
    blockId: 'blockId',
    bounced: false,
    event: {},
    eventName: 'eventName',
    responses: {
      test2: {
        type: 'ActionSync',
        index: 1,
        response: 'params2',
      },
    },
    success: true,
    startTimestamp: { date: 0 },
    endTimestamp: { date: 0 },
  });
  await timeout(110);
  expect(res).toEqual({
    blockId: 'blockId',
    bounced: false,
    event: {},
    eventName: 'eventName',
    responses: {
      test1: {
        type: 'ActionAsync',
        index: 0,
        response: { ms: 100 },
      },
      test2: {
        type: 'ActionSync',
        index: 1,
        response: 'params2',
      },
    },
    success: true,
    startTimestamp: { date: 0 },
    endTimestamp: { date: 0 },
  });
});

test('call async: true with error', async () => {
  const rootBlock = {
    blockId: 'root',
    meta: {
      category: 'context',
    },
  };
  const context = await testContext({
    lowdefy,
    rootBlock,
  });
  const Actions = context.Actions;
  const res = await Actions.callActions({
    actions: [
      { id: 'test1', type: 'ActionAsyncError', async: true, params: { ms: 100 } },
      { id: 'test2', type: 'ActionSync', params: 'params2' },
    ],
    arrayIndices,
    block: { blockId: 'blockId' },
    catchActions: [],
    event: {},
    eventName,
  });
  expect(res).toEqual({
    blockId: 'blockId',
    bounced: false,
    event: {},
    eventName: 'eventName',
    responses: {
      test2: {
        type: 'ActionSync',
        response: 'params2',
        index: 1,
      },
    },
    endTimestamp: { date: 0 },
    startTimestamp: { date: 0 },
    success: true,
  });
  await timeout(110);
  expect(res).toEqual({
    blockId: 'blockId',
    bounced: false,
    event: {},
    eventName: 'eventName',
    responses: {
      test2: {
        type: 'ActionSync',
        response: 'params2',
        index: 1,
      },
      test1: {
        type: 'ActionAsyncError',
        error: new Error('Test error'),
        index: 0,
      },
    },
    success: true,
    startTimestamp: { date: 0 },
    endTimestamp: { date: 0 },
  });
});

test('call 2 actions, first with async: false', async () => {
  const rootBlock = {
    blockId: 'root',
    meta: {
      category: 'context',
    },
  };
  const context = await testContext({
    lowdefy,
    rootBlock,
  });
  const Actions = context.Actions;
  const res = await Actions.callActions({
    actions: [
      { id: 'test1', type: 'ActionAsync', async: false, params: { ms: 100 } },
      { id: 'test2', type: 'ActionSync', params: 'params2' },
    ],
    arrayIndices,
    block: { blockId: 'blockId' },
    catchActions: [],
    event: {},
    eventName,
  });
  expect(res).toEqual({
    blockId: 'blockId',
    bounced: false,
    event: {},
    eventName: 'eventName',
    responses: {
      test1: {
        type: 'ActionAsync',
        index: 0,
        response: { ms: 100 },
      },
      test2: {
        type: 'ActionSync',
        index: 1,
        response: 'params2',
      },
    },
    success: true,
    startTimestamp: { date: 0 },
    endTimestamp: { date: 0 },
  });
});

test('call 2 actions, first with async: null', async () => {
  const rootBlock = {
    blockId: 'root',
    meta: {
      category: 'context',
    },
  };
  const context = await testContext({
    lowdefy,
    rootBlock,
  });
  const Actions = context.Actions;
  const res = await Actions.callActions({
    actions: [
      { id: 'test1', type: 'ActionAsync', async: null, params: { ms: 100 } },
      { id: 'test2', type: 'ActionSync', params: 'params2' },
    ],
    arrayIndices,
    block: { blockId: 'blockId' },
    catchActions: [],
    event: {},
    eventName,
  });
  expect(res).toEqual({
    blockId: 'blockId',
    bounced: false,
    event: {},
    eventName: 'eventName',
    responses: {
      test1: {
        type: 'ActionAsync',
        index: 0,
        response: { ms: 100 },
      },
      test2: {
        type: 'ActionSync',
        index: 1,
        response: 'params2',
      },
    },
    success: true,
    startTimestamp: { date: 0 },
    endTimestamp: { date: 0 },
  });
});


<|start_filename|>packages/blocks/blocksAntd/src/blocks/PageSiderMenu/PageSiderMenu.js<|end_filename|>
/*
  Copyright 2020-2021 Lowdefy, Inc

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

import React, { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import { get, mergeObjects, type } from '@lowdefy/helpers';
import { blockDefaultProps } from '@lowdefy/block-tools';

import Affix from '../Affix/Affix';
import Breadcrumb from '../Breadcrumb/Breadcrumb';
import Button from '../Button/Button';
import Content from '../Content/Content';
import Footer from '../Footer/Footer';
import Header from '../Header/Header';
import Layout from '../Layout/Layout';
import Menu from '../Menu/Menu';
import MobileMenu from '../MobileMenu/MobileMenu';
import Sider from '../Sider/Sider';

const PageSiderMenu = ({
  basePath,
  blockId,
  events,
  content,
  homePageId,
  menus,
  methods,
  pageId,
  properties,
}) => {
  const [openSiderState, setSiderOpen] = useState(
    !(properties.sider && properties.sider.initialCollapsed)
  );
  useEffect(() => {
    methods.registerMethod('toggleSiderOpen', () => {
      methods._toggleSiderOpen({ open: !openSiderState });
      setSiderOpen(!openSiderState);
    });
    methods.registerMethod('setSiderOpen', ({ open }) => {
      methods._toggleSiderOpen({ open });
      setSiderOpen(open);
    });
  });
  const styles = {
    layout: { minHeight: '100vh' },
    header: {
      display: 'flex',
      alignItems: 'center',
      padding: '0 46px',
      sm: { padding: '0 15px' },
      md: { padding: '0 30px' },
      lg: { padding: '0 46px' },
      flexDirection: 'row-reverse',
    },
    headerContent: {
      alignItems: 'center',
      flex: '1 1 auto',
      display: 'flex',
      justifyContent: 'flex-end',
    },
    logo: {
      margin: '0 30px 0 0',
      flex: '0 1 auto',
      sm: { margin: '0 10px 0 0' },
      md: { margin: '0 15px 0 0' },
    },
    desktop: {
      display: 'none',
      lg: { display: 'block' },
    },
    mobile: {
      display: 'block',
      lg: { display: 'none' },
    },
    mdMenu: {
      paddingLeft: '1rem',
    },
    body: {
      padding: '0 40px 40px 40px',
      sm: { padding: '0 10px 10px 10px' },
      md: { padding: '0 20px 20px 20px' },
      lg: { padding: '0 40px 40px 40px' },
    },
    sider: {
      display: 'flex',
      height: '100%',
      flexDirection: 'column',
    },
    content: {},
    breadcrumb: {
      margin: '16px 0',
    },
    noBreadcrumb: {
      padding: '20px 0',
      sm: { padding: '5px 0' },
      md: { padding: '10px 0' },
    },
  };
  return (
    <Layout
      blockId={blockId}
      events={events}
      properties={{ style: mergeObjects([{ minHeight: '100vh' }, properties.style]) }}
      content={{
        content: () => (
          <>
            <Header
              blockId={`${blockId}_header`}
              events={events}
              properties={mergeObjects([{ style: styles.header }, properties.header])}
              content={{
                content: () => (
                  <>
                    <div className={methods.makeCssClass(styles.headerContent)}>
                      {content.header &&
                        content.header(
                          mergeObjects([
                            { width: 'auto' },
                            properties.header && properties.header.contentStyle,
                          ])
                        )}
                      <div className={methods.makeCssClass([styles.mobile, styles.mdMenu])}>
                        <MobileMenu
                          blockId={`${blockId}_mobile_menu`}
                          basePath={basePath}
                          events={events}
                          methods={methods}
                          menus={menus}
                          pageId={pageId}
                          properties={mergeObjects([
                            {
                              mode: 'inline',
                              theme: get(properties, 'sider.theme') || 'light',
                              backgroundColor: get(properties, 'sider.color'),
                            },
                            properties.menu,
                            properties.menuMd,
                          ])}
                          rename={{
                            methods: {
                              toggleOpen: 'toggleMobileMenuOpen',
                              setOpen: 'setMobileMenuOpen',
                            },
                            events: {
                              onClose: 'onMobileMenuClose',
                              onOpen: 'onMobileMenuOpen',
                            },
                          }}
                        />
                      </div>
                    </div>
                    <Link to={`${basePath}/${homePageId}`}>
                      <img
                        src={
                          (properties.logo && properties.logo.src) ||
                          (get(properties, 'header.theme') === 'light'
                            ? `${basePath}/public/logo-light-theme.png`
                            : `${basePath}/public/logo-dark-theme.png`)
                        }
                        srcSet={
                          (properties.logo && (properties.logo.srcSet || properties.logo.src)) ||
                          (get(properties, 'header.theme') === 'light'
                            ? `${basePath}/public/logo-square-light-theme.png 40w, ${basePath}/public/logo-light-theme.png 577w`
                            : `${basePath}/public/logo-square-dark-theme.png 40w, ${basePath}/public/logo-dark-theme.png 577w`)
                        }
                        sizes={
                          (properties.logo && properties.logo.sizes) ||
                          '(max-width: 576px) 40px, 577px'
                        }
                        alt={(properties.logo && properties.logo.alt) || 'Lowdefy'}
                        className={methods.makeCssClass([
                          {
                            width: 130,
                            sm: {
                              width:
                                properties.logo && properties.logo.src && !properties.logo.srcSet
                                  ? 130
                                  : 40,
                            },
                            md: { width: 130 },
                          },
                          styles.logo,
                          properties.logo && properties.logo.style,
                        ])}
                      />
                    </Link>
                  </>
                ),
              }}
            />
            <Layout
              blockId={`${blockId}_layout`}
              events={events}
              properties={properties.layout}
              content={{
                content: () => (
                  <>
                    <Sider
                      blockId={`${blockId}_sider`}
                      events={events}
                      methods={methods}
                      properties={mergeObjects([
                        {
                          theme: get(properties, 'sider.theme') || 'light',
                          style: styles.desktop,
                        },
                        properties.sider,
                      ])}
                      rename={{
                        methods: {
                          toggleOpen: '_toggleSiderOpen',
                          setOpen: '_setSiderOpen',
                        },
                      }}
                      content={{
                        content: () => (
                          <div style={styles.sider}>
                            <Menu
                              blockId={`${blockId}_menu`}
                              basePath={basePath}
                              events={events}
                              methods={methods}
                              menus={menus}
                              pageId={pageId}
                              properties={mergeObjects([
                                {
                                  mode: 'inline',
                                  theme: get(properties, 'sider.theme') || 'light',
                                  backgroundColor: get(properties, 'sider.color'),
                                  collapsed: !openSiderState,
                                },
                                properties.menu,
                                properties.menuLg,
                                { style: styles.desktop },
                              ])}
                              rename={{
                                methods: {
                                  toggleOpen: 'toggleMobileMenuOpen',
                                  setOpen: 'setMobileMenuOpen',
                                },
                                events: {
                                  onClick: 'onMenuItemClick',
                                  onSelect: 'onMenuItemCSelect',
                                  onToggleMenuGroup: 'onToggleMenuGroup',
                                },
                              }}
                            />
                            <div style={{ flex: '1 0 auto' }}>
                              {content.sider && content.sider()}
                            </div>
                            {!get(properties, 'sider.hideToggleButton') && (
                              <Affix
                                blockId={`${blockId}_toggle_sider_affix`}
                                events={events}
                                properties={{ offsetBottom: 0 }}
                                methods={methods}
                                rename={{
                                  events: {
                                    onChange: 'onChangeToggleSiderAffix',
                                  },
                                }}
                                content={{
                                  content: () => (
                                    <div
                                      style={{
                                        background:
                                          get(properties, 'sider.color') ||
                                          (get(properties, 'sider.theme') === 'dark'
                                            ? '#30393e'
                                            : 'white'),
                                      }}
                                    >
                                      <Button
                                        blockId={`${blockId}_toggle_sider`}
                                        events={events}
                                        properties={{
                                          hideTitle: true,
                                          type: 'link',
                                          block: true,
                                          icon: {
                                            name: openSiderState
                                              ? 'MenuFoldOutlined'
                                              : 'MenuUnfoldOutlined',
                                          },
                                          ...(properties.toggleSiderButton || {}),
                                        }}
                                        methods={methods}
                                        onClick={() => methods.toggleSiderOpen()}
                                        rename={{
                                          events: {
                                            onClick: 'onToggleSider',
                                          },
                                        }}
                                      />
                                    </div>
                                  ),
                                }}
                              />
                            )}
                          </div>
                        ),
                      }}
                    />
                    <Content
                      blockId={`${blockId}_content`}
                      events={events}
                      properties={mergeObjects([{ style: styles.body }, properties.content])}
                      content={{
                        content: () => (
                          <>
                            {!type.isNone(properties.breadcrumb) ? (
                              <Breadcrumb
                                blockId={`${blockId}_breadcrumb`}
                                basePath={basePath}
                                events={events}
                                methods={methods}
                                properties={mergeObjects([
                                  { style: { padding: '16px 0' } },
                                  properties.breadcrumb,
                                ])}
                                rename={{
                                  events: {
                                    onClick: 'onBreadcrumbClick',
                                  },
                                }}
                              />
                            ) : (
                              <div className={methods.makeCssClass(styles.noBreadcrumb)} />
                            )}
                            {content.content && content.content()}
                            {content.footer && (
                              <Footer
                                blockId={`${blockId}_footer`}
                                events={events}
                                properties={properties.footer}
                                content={{
                                  content: () => content.footer(),
                                }}
                              />
                            )}
                          </>
                        ),
                      }}
                    />
                  </>
                ),
              }}
            />
          </>
        ),
      }}
    />
  );
};

PageSiderMenu.defaultProps = blockDefaultProps;

export default PageSiderMenu;


<|start_filename|>packages/engine/test/Actions/JsAction.test.js<|end_filename|>
/*
  Copyright 2020-2021 Lowdefy, Inc

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

import testContext from '../testContext';
import actionFns from '../../src/actions/index.js';

const pageId = 'one';
const lowdefy = { pageId };

const RealDate = Date;
const mockDate = jest.fn(() => ({ date: 0 }));
mockDate.now = jest.fn(() => 0);

// Comment out to use console
console.log = () => {};
console.error = () => {};

beforeAll(() => {
  global.Date = mockDate;
});

afterAll(() => {
  global.Date = RealDate;
});

test('JsAction with no args, synchronous fn', async () => {
  const rootBlock = {
    blockId: 'root',
    meta: {
      category: 'context',
    },
    areas: {
      content: {
        blocks: [
          {
            blockId: 'button',
            type: 'Button',
            meta: {
              category: 'display',
              valueType: 'string',
            },
            events: {
              onClick: [
                {
                  id: 'a',
                  type: 'JsAction',
                  params: {
                    name: 'test_fn',
                  },
                },
              ],
            },
          },
        ],
      },
    },
  };
  const context = await testContext({
    lowdefy,
    rootBlock,
  });
  const mockFn = jest.fn(() => 'js_fn');
  context.lowdefy.imports.jsActions.test_fn = mockFn;
  const { button } = context.RootBlocks.map;

  const res = await button.triggerEvent({ name: 'onClick' });
  expect(res).toEqual({
    blockId: 'button',
    bounced: false,
    event: undefined,
    eventName: 'onClick',
    responses: {
      a: {
        type: 'JsAction',
        index: 0,
        response: 'js_fn',
      },
    },
    success: true,
    startTimestamp: { date: 0 },
    endTimestamp: { date: 0 },
  });
  expect(mockFn).toHaveBeenCalledTimes(1);
});

test('JsAction with no args, async fn', async () => {
  const rootBlock = {
    blockId: 'root',
    meta: {
      category: 'context',
    },
    areas: {
      content: {
        blocks: [
          {
            blockId: 'button',
            type: 'Button',
            meta: {
              category: 'display',
              valueType: 'string',
            },
            events: {
              onClick: [
                {
                  id: 'a',
                  type: 'JsAction',
                  params: {
                    name: 'test_fn',
                  },
                },
              ],
            },
          },
        ],
      },
    },
  };
  const context = await testContext({
    lowdefy,
    rootBlock,
  });
  const timeout = (ms) => {
    return new Promise((resolve) => setTimeout(resolve, ms));
  };
  const fn = async () => {
    await timeout(300);
    return 'js_fn';
  };
  const mockFn = jest.fn().mockImplementation(fn);
  context.lowdefy.imports.jsActions.test_fn = mockFn;
  const { button } = context.RootBlocks.map;

  const res = await button.triggerEvent({ name: 'onClick' });
  expect(res).toEqual({
    blockId: 'button',
    bounced: false,
    event: undefined,
    eventName: 'onClick',
    responses: {
      a: {
        type: 'JsAction',
        index: 0,
        response: 'js_fn',
      },
    },
    success: true,
    startTimestamp: { date: 0 },
    endTimestamp: { date: 0 },
  });
  expect(mockFn).toHaveBeenCalledTimes(1);
});

test('JsAction with args, synchronous fn', async () => {
  const rootBlock = {
    blockId: 'root',
    meta: {
      category: 'context',
    },
    areas: {
      content: {
        blocks: [
          {
            blockId: 'button',
            type: 'Button',
            meta: {
              category: 'display',
              valueType: 'string',
            },
            events: {
              onClick: [
                {
                  id: 'a',
                  type: 'JsAction',
                  params: {
                    name: 'test_fn',
                    args: [1, '2', new Date()],
                  },
                },
              ],
            },
          },
        ],
      },
    },
  };
  const context = await testContext({
    lowdefy,
    rootBlock,
  });
  const mockFn = jest.fn((...args) => args);
  context.lowdefy.imports.jsActions.test_fn = mockFn;
  const { button } = context.RootBlocks.map;

  const res = await button.triggerEvent({ name: 'onClick' });
  expect(res).toMatchInlineSnapshot(`
    Object {
      "blockId": "button",
      "bounced": false,
      "endTimestamp": Object {
        "date": 0,
      },
      "event": undefined,
      "eventName": "onClick",
      "responses": Object {
        "a": Object {
          "index": 0,
          "response": Array [
            Object {
              "actions": Object {
                "CallMethod": [Function],
                "JsAction": [Function],
                "Link": [Function],
                "Login": [Function],
                "Logout": [Function],
                "Message": [Function],
                "Request": [Function],
                "Reset": [Function],
                "ResetValidation": [Function],
                "ScrollTo": [Function],
                "SetGlobal": [Function],
                "SetState": [Function],
                "Throw": [Function],
                "Validate": [Function],
                "Wait": [Function],
              },
              "contextId": "test",
              "input": Object {},
              "pageId": "root",
              "requests": Object {},
              "state": Object {},
              "urlQuery": Object {},
            },
            1,
            "2",
            Object {
              "date": 0,
            },
          ],
          "type": "JsAction",
        },
      },
      "startTimestamp": Object {
        "date": 0,
      },
      "success": true,
    }
  `);
  expect(mockFn).toHaveBeenCalledTimes(1);
});

test('JsAction name not a string', async () => {
  const rootBlock = {
    blockId: 'root',
    meta: {
      category: 'context',
    },
    areas: {
      content: {
        blocks: [
          {
            blockId: 'button',
            type: 'Button',
            meta: {
              category: 'display',
              valueType: 'string',
            },
            events: {
              onClick: [
                {
                  id: 'a',
                  type: 'JsAction',
                  params: {
                    name: 1,
                  },
                },
              ],
            },
          },
        ],
      },
    },
  };
  const context = await testContext({
    lowdefy,
    rootBlock,
  });
  const { button } = context.RootBlocks.map;
  const res = await button.triggerEvent({ name: 'onClick' });
  expect(res).toEqual({
    blockId: 'button',
    bounced: false,
    event: undefined,
    eventName: 'onClick',
    error: {
      action: {
        id: 'a',
        params: {
          name: 1,
        },
        type: 'JsAction',
      },
      error: {
        error: new Error(`JsAction requires a string for 'params.name'.`),
        index: 0,
        type: 'JsAction',
      },
    },
    responses: {
      a: {
        type: 'JsAction',
        index: 0,
        error: new Error(`JsAction requires a string for 'params.name'.`),
      },
    },
    success: false,
    startTimestamp: { date: 0 },
    endTimestamp: { date: 0 },
  });
});

test('JsAction args not an array', async () => {
  const rootBlock = {
    blockId: 'root',
    meta: {
      category: 'context',
    },
    areas: {
      content: {
        blocks: [
          {
            blockId: 'button',
            type: 'Button',
            meta: {
              category: 'display',
              valueType: 'string',
            },
            events: {
              onClick: [
                {
                  id: 'a',
                  type: 'JsAction',
                  params: {
                    name: 'js_fn',
                    args: { a: 1 },
                  },
                },
              ],
            },
          },
        ],
      },
    },
  };
  const context = await testContext({
    lowdefy,
    rootBlock,
  });
  const { button } = context.RootBlocks.map;
  const res = await button.triggerEvent({ name: 'onClick' });
  expect(res).toEqual({
    blockId: 'button',
    bounced: false,
    event: undefined,
    eventName: 'onClick',
    error: {
      action: {
        id: 'a',
        params: {
          args: {
            a: 1,
          },
          name: 'js_fn',
        },
        type: 'JsAction',
      },
      error: {
        error: new Error(`JsAction requires a array for 'params.args'.`),
        index: 0,
        type: 'JsAction',
      },
    },
    responses: {
      a: {
        type: 'JsAction',
        index: 0,
        error: new Error(`JsAction requires a array for 'params.args'.`),
      },
    },
    success: false,
    startTimestamp: { date: 0 },
    endTimestamp: { date: 0 },
  });
});

test('JsAction args not a function', async () => {
  const rootBlock = {
    blockId: 'root',
    meta: {
      category: 'context',
    },
    areas: {
      content: {
        blocks: [
          {
            blockId: 'button',
            type: 'Button',
            meta: {
              category: 'display',
              valueType: 'string',
            },
            events: {
              onClick: [
                {
                  id: 'a',
                  type: 'JsAction',
                  params: {
                    name: 'js_not_fn',
                  },
                },
              ],
            },
          },
        ],
      },
    },
  };
  const context = await testContext({
    lowdefy,
    rootBlock,
  });
  const { button } = context.RootBlocks.map;
  const res = await button.triggerEvent({ name: 'onClick' });
  expect(res).toEqual({
    blockId: 'button',
    bounced: false,
    event: undefined,
    eventName: 'onClick',
    error: {
      action: {
        id: 'a',
        params: {
          name: 'js_not_fn',
        },
        type: 'JsAction',
      },
      error: {
        error: new Error(`JsAction js_not_fn is not a function.`),
        index: 0,
        type: 'JsAction',
      },
    },
    responses: {
      a: {
        type: 'JsAction',
        index: 0,
        error: new Error(`JsAction js_not_fn is not a function.`),
      },
    },
    success: false,
    startTimestamp: { date: 0 },
    endTimestamp: { date: 0 },
  });
});

test('JsAction can use Lowdefy actions', async () => {
  const rootBlock = {
    blockId: 'root',
    meta: {
      category: 'context',
    },
    areas: {
      content: {
        blocks: [
          {
            blockId: 'button',
            type: 'Button',
            meta: {
              category: 'display',
              valueType: 'string',
            },
            events: {
              onClick: [
                {
                  id: 'a',
                  type: 'JsAction',
                  params: {
                    name: 'test_fn',
                  },
                },
              ],
            },
          },
        ],
      },
    },
  };
  const context = await testContext({
    lowdefy,
    rootBlock,
  });

  const fn = async ({ actions }) => {
    actions.SetState({ answer: 42 });
    return actions;
  };

  const mockFn = jest.fn().mockImplementation(fn);
  context.lowdefy.imports.jsActions.test_fn = mockFn;
  const { button } = context.RootBlocks.map;

  const res = await button.triggerEvent({ name: 'onClick' });
  expect(context.state).toEqual({ answer: 42 });
  expect(Object.keys(res.responses.a.response)).toEqual(Object.keys(actionFns));
});


<|start_filename|>packages/build/src/utils/files/readConfigFile.test.js<|end_filename|>
/*
  Copyright 2020-2021 Lowdefy, Inc

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

import path from 'path';

import createReadConfigFile from './readConfigFile';
import { readFile } from '@lowdefy/node-utils';

jest.mock('@lowdefy/node-utils', () => {
  return {
    readFile: jest.fn(),
  };
});

const configDirectory = './config';

beforeEach(() => {
  readFile.mockReset();
});

test('readConfigFile reads a file from the correct dir', async () => {
  const readConfigFile = createReadConfigFile({ configDirectory });
  readFile.mockImplementation(() => 'Text file content');
  const res = await readConfigFile('file.txt');
  expect(res).toEqual('Text file content');
  expect(readFile.mock.calls).toEqual([[path.resolve(configDirectory, 'file.txt')]]);
});

test('readConfigFile memoizes results', async () => {
  const readConfigFile = createReadConfigFile({ configDirectory });
  readFile.mockImplementation(() => 'Text file content');
  const res1 = await readConfigFile('file.txt');
  expect(res1).toEqual('Text file content');
  expect(readFile.mock.calls).toEqual([[path.resolve(configDirectory, 'file.txt')]]);
  const res2 = await readConfigFile('file.txt');
  expect(res2).toEqual('Text file content');
  expect(readFile.mock.calls).toEqual([[path.resolve(configDirectory, 'file.txt')]]);
});


<|start_filename|>packages/operators/test/NodeParser.test.js<|end_filename|>
/*
  Copyright 2020-2021 Lowdefy, Inc

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

/* eslint-disable max-classes-per-file */
import NodeParser from '../src/nodeParser';

const state = {
  string: 'Some String',
};

test('parse input undefined', async () => {
  const parser = new NodeParser({ state });
  await parser.init();
  const res = parser.parse({});
  expect(res.output).toEqual();
  expect(res.errors).toMatchInlineSnapshot(`Array []`);
});

test('parse object', async () => {
  const input = { a: { _state: 'string' } };
  const parser = new NodeParser({ state });
  await parser.init();
  const res = parser.parse({ input, location: 'locationId' });
  expect(res.output).toEqual({ a: 'Some String' });
  expect(res.errors).toMatchInlineSnapshot(`Array []`);
});

test('parse array', async () => {
  const input = [{ _state: 'string' }];
  const parser = new NodeParser({ state });
  await parser.init();
  const res = parser.parse({ input, location: 'locationId' });
  expect(res.output).toEqual(['Some String']);
  expect(res.errors).toMatchInlineSnapshot(`Array []`);
});

test('parse string', async () => {
  const input = 'string';
  const parser = new NodeParser({ state });
  await parser.init();
  const res = parser.parse({ input, location: 'locationId' });
  expect(res.output).toBe('string');
  expect(res.errors).toMatchInlineSnapshot(`Array []`);
});

test('parse number', async () => {
  const input = 42;
  const parser = new NodeParser({ state });
  await parser.init();
  const res = parser.parse({ input, location: 'locationId' });
  expect(res.output).toBe(42);
  expect(res.errors).toMatchInlineSnapshot(`Array []`);
});

test('parse true', async () => {
  const input = true;
  const parser = new NodeParser({ state });
  await parser.init();
  const res = parser.parse({ input, location: 'locationId' });
  expect(res.output).toBe(true);
  expect(res.errors).toMatchInlineSnapshot(`Array []`);
});

test('parse false', async () => {
  const input = false;
  const parser = new NodeParser({ state });
  await parser.init();
  const res = parser.parse({ input, location: 'locationId' });
  expect(res.output).toBe(false);
  expect(res.errors).toMatchInlineSnapshot(`Array []`);
});

test('parse null', async () => {
  const input = null;
  const parser = new NodeParser({ state });
  await parser.init();
  const res = parser.parse({ input, location: 'locationId' });
  expect(res.output).toBe(null);
  expect(res.errors).toMatchInlineSnapshot(`Array []`);
});

test('parse undefined', async () => {
  const input = undefined;
  const parser = new NodeParser({ state });
  await parser.init();
  const res = parser.parse({ input, location: 'locationId' });
  expect(res.output).toBe(undefined);
  expect(res.errors).toMatchInlineSnapshot(`Array []`);
});

test('operator input with more than one key is ignored.', async () => {
  const input = { a: { _state: 'string', key: 'value' } };
  const parser = new NodeParser({ state });
  await parser.init();
  const res = parser.parse({ input, location: 'locationId' });
  expect(res.output).toEqual({ a: { _state: 'string', key: 'value' } });
  expect(res.errors).toMatchInlineSnapshot(`Array []`);
});

test('parse event not an object', async () => {
  const input = { _state: 'string' };
  const parser = new NodeParser({ state });
  await parser.init();
  expect(() => parser.parse({ input, event: 'String' })).toThrow(
    'Operator parser event must be a object.'
  );
});

test('parse args not an array', async () => {
  const input = { _state: 'string' };
  const parser = new NodeParser({ state });
  await parser.init();
  expect(() => parser.parse({ input, args: 'String' })).toThrow(
    'Operator parser args must be an array.'
  );
});

test('parse location not a string', async () => {
  const input = { _state: 'string' };
  const parser = new NodeParser({ state });
  await parser.init();
  expect(() => parser.parse({ input, location: true })).toThrow(
    'Operator parser location must be a string.'
  );
});

test('parse js dates', async () => {
  const input = { a: new Date(1), b: [new Date(2)] };
  const parser = new NodeParser({ state });
  await parser.init();
  const res = parser.parse({ input, location: 'locationId' });
  expect(res.output).toEqual({ a: new Date(1), b: [new Date(2)] });
  expect(res.errors).toMatchInlineSnapshot(`Array []`);
});

test('parse js dates, do not modify input', async () => {
  const input = { a: new Date(1) };
  const parser = new NodeParser({ state });
  await parser.init();
  const res = parser.parse({ input, location: 'locationId' });
  expect(input).toEqual({ a: new Date(1) });
  expect(res.errors).toMatchInlineSnapshot(`Array []`);
});
describe('parse operators', () => {
  test('parse _base64.encode operator', async () => {
    const input = { a: { '_base64.encode': 'A string value' } };
    const parser = new NodeParser({ state });
    await parser.init();
    const res = parser.parse({ input, location: 'locationId' });
    expect(res.output).toEqual({ a: 'QSBzdHJpbmcgdmFsdWU=' });
    expect(res.errors).toMatchInlineSnapshot(`Array []`);
  });

  test('parse _base64.decode operator', async () => {
    const input = { a: { '_base64.decode': 'QSBzdHJpbmcgdmFsdWU=' } };
    const parser = new NodeParser({ state });
    await parser.init();
    const res = parser.parse({ input, location: 'locationId' });
    expect(res.output).toEqual({ a: 'A string value' });
    expect(res.errors).toMatchInlineSnapshot(`Array []`);
  });

  test('parse _uri.encode operator', async () => {
    const input = { a: { '_uri.encode': 'ABC abc 123' } };
    const parser = new NodeParser({ state });
    await parser.init();
    const res = parser.parse({ input, location: 'locationId' });
    expect(res.output).toEqual({ a: 'ABC%20abc%20123' });
    expect(res.errors).toMatchInlineSnapshot(`Array []`);
  });

  test('parse _uri.decode operator', async () => {
    const input = { a: { '_uri.decode': 'ABC%20abc%20123' } };
    const parser = new NodeParser({ state });
    await parser.init();
    const res = parser.parse({ input, location: 'locationId' });
    expect(res.output).toEqual({ a: 'ABC abc 123' });
    expect(res.errors).toMatchInlineSnapshot(`Array []`);
  });

  test('parse _lt operator', async () => {
    const input = { a: { _lt: [4, 5] } };
    const parser = new NodeParser({ state });
    await parser.init();
    const res = parser.parse({ input, location: 'locationId' });
    expect(res.output).toEqual({ a: true });
    expect(res.errors).toMatchInlineSnapshot(`Array []`);
  });

  test('parse _lte operator', async () => {
    const input = { a: { _lte: [5, 5] } };
    const parser = new NodeParser({ state });
    await parser.init();
    const res = parser.parse({ input, location: 'locationId' });
    expect(res.output).toEqual({ a: true });
    expect(res.errors).toMatchInlineSnapshot(`Array []`);
  });

  test('parse _gt operator', async () => {
    const input = { a: { _gt: [5, 3] } };
    const parser = new NodeParser({ state });
    await parser.init();
    const res = parser.parse({ input, location: 'locationId' });
    expect(res.output).toEqual({ a: true });
    expect(res.errors).toMatchInlineSnapshot(`Array []`);
  });

  test('parse _gte operator', async () => {
    const input = { a: { _gte: [5, 5] } };
    const parser = new NodeParser({ state });
    await parser.init();
    const res = parser.parse({ input, location: 'locationId' });
    expect(res.output).toEqual({ a: true });
    expect(res.errors).toMatchInlineSnapshot(`Array []`);
  });

  test('parse _if_none operator', async () => {
    const input = { a: { _if_none: [null, 'default'] } };
    const parser = new NodeParser({ state });
    await parser.init();
    const res = parser.parse({ input, location: 'locationId' });
    expect(res.output).toEqual({ a: 'default' });
    expect(res.errors).toMatchInlineSnapshot(`Array []`);
  });

  test('parse _random operator', async () => {
    const mathRandomFn = Math.random;
    Math.random = () => 0.5678;
    const input = { a: { _random: 'string' } };
    const parser = new NodeParser({ state });
    await parser.init();
    const res = parser.parse({ input, location: 'locationId' });
    expect(res.output).toEqual({ a: 'kfv9yqdp' });
    expect(res.errors).toMatchInlineSnapshot(`Array []`);
    Math.random = mathRandomFn;
  });

  test('parse _uuid operator', async () => {
    const input = { a: { _uuid: true } };
    const parser = new NodeParser({ state });
    await parser.init();
    const res = parser.parse({ input, location: 'locationId' });
    expect(res.output.a.length).toEqual(36);
    expect(res.errors).toMatchInlineSnapshot(`Array []`);
  });

  test('parse _math operator', async () => {
    const input = { a: { '_math.min': [9, 4, 2] } };
    const parser = new NodeParser({ state });
    await parser.init();
    const res = parser.parse({ input, location: 'locationId' });
    expect(res.output).toEqual({ a: 2 });
    expect(res.errors).toMatchInlineSnapshot(`Array []`);
  });

  test('parse _sum operator', async () => {
    const input = { a: { _sum: [1, 1] } };
    const parser = new NodeParser({ state });
    await parser.init();
    const res = parser.parse({ input, location: 'locationId' });
    expect(res.output).toEqual({ a: 2 });
    expect(res.errors).toMatchInlineSnapshot(`Array []`);
  });

  test('parse _product operator', async () => {
    const input = { a: { _product: [2, -3] } };
    const parser = new NodeParser({ state });
    await parser.init();
    const res = parser.parse({ input, location: 'locationId' });
    expect(res.output).toEqual({ a: -6 });
    expect(res.errors).toMatchInlineSnapshot(`Array []`);
  });

  test('parse _subtract operator', async () => {
    const input = { a: { _subtract: [2, -3] } };
    const parser = new NodeParser({ state });
    await parser.init();
    const res = parser.parse({ input, location: 'locationId' });
    expect(res.output).toEqual({ a: 5 });
    expect(res.errors).toMatchInlineSnapshot(`Array []`);
  });

  test('parse _divide operator', async () => {
    const input = { a: { _divide: [2, 4] } };
    const parser = new NodeParser({ state });
    await parser.init();
    const res = parser.parse({ input, location: 'locationId' });
    expect(res.output).toEqual({ a: 0.5 });
    expect(res.errors).toMatchInlineSnapshot(`Array []`);
  });

  test('parse _array operator', async () => {
    const input = { a: { '_array.length': [2, 4] } };
    const parser = new NodeParser({ state });
    await parser.init();
    const res = parser.parse({ input, location: 'locationId' });
    expect(res.output).toEqual({ a: 2 });
    expect(res.errors).toMatchInlineSnapshot(`Array []`);
  });

  test('parse _object operator', async () => {
    const input = { a: { '_object.keys': { a: 1, b: 2 } } };
    const parser = new NodeParser({ state });
    await parser.init();
    const res = parser.parse({ input, location: 'locationId' });
    expect(res.output).toEqual({ a: ['a', 'b'] });
    expect(res.errors).toMatchInlineSnapshot(`Array []`);
  });

  test('parse _string operator', async () => {
    const input = { a: { '_string.concat': ['a new ', 'string'] } };
    const parser = new NodeParser({ state });
    await parser.init();
    const res = parser.parse({ input, location: 'locationId' });
    expect(res.output).toEqual({ a: 'a new string' });
    expect(res.errors).toMatchInlineSnapshot(`Array []`);
  });

  test('_json.stringify then _json.parse', async () => {
    const value = {
      a: [
        { b: 1, c: false, d: new Date(0) },
        { b: 2, c: true, d: new Date(1) },
      ],
      e: 'null',
      f: 'undefined',
      g: 0,
    };
    const input = { x: { '_json.parse': { '_json.stringify': [value] } } };
    const parser = new NodeParser({ state });
    await parser.init();
    const res = parser.parse({ input, location: 'locationId' });
    expect(res.output).toEqual({ x: value });
    expect(res.errors).toMatchInlineSnapshot(`Array []`);
  });

  test('_json.stringify then _json.parse date', async () => {
    const value = new Date();
    const input = { '_json.parse': { '_json.stringify': [value] } };
    const parser = new NodeParser({ state });
    await parser.init();
    const res = parser.parse({ input, location: 'locationId' });
    expect(res.output).toEqual(value);
    expect(res.errors).toMatchInlineSnapshot(`Array []`);
  });

  test('_yaml.stringify then _yaml.parse', async () => {
    const value = {
      a: [
        { b: 1, c: false, d: new Date(0) },
        { b: 2, c: true, d: new Date(1) },
      ],
      e: 'null',
      f: 'undefined',
      g: 0,
    };
    const input = { x: { '_yaml.parse': { '_yaml.stringify': [value] } } };
    const parser = new NodeParser({ state });
    await parser.init();
    const res = parser.parse({ input, location: 'locationId' });
    expect(res.output).toEqual({ x: value });
    expect(res.errors).toMatchInlineSnapshot(`Array []`);
  });

  test('_yaml.stringify then _yaml.parse date', async () => {
    const value = new Date();
    const input = { '_yaml.parse': { '_yaml.stringify': [value] } };
    const parser = new NodeParser({ state });
    await parser.init();
    const res = parser.parse({ input, location: 'locationId' });
    expect(res.output).toEqual(value);
    expect(res.errors).toMatchInlineSnapshot(`Array []`);
  });

  test('parse _mql operator', async () => {
    const input = { '_mql.test': { on: { _state: true }, test: { string: 'Some String' } } };
    const parser = new NodeParser({ state });
    await parser.init();
    const res = parser.parse({ input, location: 'locationId' });
    expect(res.output).toEqual(true);
    expect(res.errors).toMatchInlineSnapshot(`Array []`);
  });

  test('parse _diff operator', async () => {
    const input = { '_diff.deep': { rhs: { a: 1 }, lhs: { b: 2 } } };
    const parser = new NodeParser({ state });
    await parser.init();
    const res = parser.parse({ input, location: 'locationId' });
    expect(res.output).toEqual([
      { kind: 'D', lhs: 2, path: ['b'] },
      { kind: 'N', path: ['a'], rhs: 1 },
    ]);
    expect(res.errors).toMatchInlineSnapshot(`Array []`);
  });

  test('parse _global operator', async () => {
    const input = { _global: 'key' };
    const parser = new NodeParser({ lowdefyGlobal: { key: 'value' } });
    await parser.init();
    const res = parser.parse({ input, location: 'locationId' });
    expect(res.output).toEqual('value');
    expect(res.errors).toMatchInlineSnapshot(`Array []`);
  });

  test('parse _input operator', async () => {
    const input = { _input: 'key' };
    const parser = new NodeParser({ input: { key: 'value' } });
    await parser.init();
    const res = parser.parse({ input, location: 'locationId' });
    expect(res.output).toEqual('value');
    expect(res.errors).toMatchInlineSnapshot(`Array []`);
  });

  test('parse _secret operator', async () => {
    const input = { _secret: 'key' };
    const parser = new NodeParser({ secrets: { key: 'value' } });
    await parser.init();
    const res = parser.parse({ input, location: 'locationId' });
    expect(res.output).toEqual('value');
    expect(res.errors).toMatchInlineSnapshot(`Array []`);
  });

  test('parse _state operator', async () => {
    const input = { _state: 'key' };
    const parser = new NodeParser({ state: { key: 'value' } });
    await parser.init();
    const res = parser.parse({ input, location: 'locationId' });
    expect(res.output).toEqual('value');
    expect(res.errors).toMatchInlineSnapshot(`Array []`);
  });

  test('parse _url_query operator', async () => {
    const input = { _url_query: 'key' };
    const parser = new NodeParser({ urlQuery: { key: 'value' } });
    await parser.init();
    const res = parser.parse({ input, location: 'locationId' });
    expect(res.output).toEqual('value');
    expect(res.errors).toMatchInlineSnapshot(`Array []`);
  });

  test('parse _user operator', async () => {
    const input = { _user: 'name' };
    const parser = new NodeParser({ user: { name: 'user' } });
    await parser.init();
    const res = parser.parse({ input, location: 'locationId' });
    expect(res.output).toEqual('user');
    expect(res.errors).toMatchInlineSnapshot(`Array []`);
  });

  test('parse _get operator', async () => {
    const input = { _get: { key: 'key', from: { key: 'value' } } };
    const parser = new NodeParser({});
    await parser.init();
    const res = parser.parse({ input, location: 'locationId' });
    expect(res.output).toEqual('value');
    expect(res.errors).toMatchInlineSnapshot(`Array []`);
  });

  test('parse _function operator', async () => {
    const input = { _function: { state: { __state: 'key' }, args: { __args: true } } };
    const parser = new NodeParser({ state: { key: 'value' } });
    await parser.init();
    const { output, errors } = parser.parse({ input, location: 'locationId' });
    expect(output).toBeInstanceOf(Function);
    expect(output(1, 2)).toEqual({ state: 'value', args: [1, 2] });
    expect(errors).toEqual([]);
  });

  test('parse _index operator', async () => {
    const input = { _index: 0 };
    const parser = new NodeParser({ input: { key: 'value' }, arrayIndices: [3, 2] });
    await parser.init();
    const res = parser.parse({ input, location: 'locationId' });
    expect(res.output).toEqual(3);
    expect(res.errors).toMatchInlineSnapshot(`Array []`);
  });

  test('parse _change_case operator', async () => {
    const input = { '_change_case.camelCase': { on: 'test string' } };
    const parser = new NodeParser({ state });
    await parser.init();
    const res = parser.parse({ input, location: 'locationId' });
    expect(res.output).toEqual('testString');
    expect(res.errors).toMatchInlineSnapshot(`Array []`);
  });

  test('parse _number operator', async () => {
    const input = { '_number.toFixed': { on: 12.33666, digits: 2 } };
    const parser = new NodeParser({ state });
    await parser.init();
    const res = parser.parse({ input, location: 'locationId' });
    expect(res.output).toEqual('12.34');
    expect(res.errors).toMatchInlineSnapshot(`Array []`);
  });

  test('parse _switch operator', async () => {
    const input = {
      _switch: {
        branches: [
          { if: true, then: 'A' },
          { if: false, then: 'B' },
        ],
        default: 'C',
      },
    };
    const parser = new NodeParser({ state });
    await parser.init();
    const res = parser.parse({ input, location: 'locationId' });
    expect(res.output).toEqual('A');
    expect(res.errors).toMatchInlineSnapshot(`Array []`);
  });
});


<|start_filename|>packages/blockTools/src/HtmlComponent.test.js<|end_filename|>
/*
  Copyright 2020-2021 Lowdefy, Inc

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

import React from 'react';

import { HtmlComponent } from '../src';

import { configure, mount } from 'enzyme';
import Adapter from '@wojtekmaj/enzyme-adapter-react-17';
configure({ adapter: new Adapter() });

const mockCMakeCssClass = jest.fn(() => 'test-class');
const methods = {
  makeCssClass: mockCMakeCssClass,
};

beforeEach(() => {
  mockCMakeCssClass.mockReset();
  mockCMakeCssClass.mockImplementation((obj) => JSON.stringify(obj));
});

test('Render default', async () => {
  const wrapper = await mount(<HtmlComponent methods={methods} />);
  await wrapper.instance().componentDidMount();
  await wrapper.update();
  expect(wrapper.html()).toMatchInlineSnapshot(`"<span></span>"`);
  await wrapper.instance().componentDidUpdate();
  await wrapper.update();
  expect(wrapper.html()).toMatchInlineSnapshot(`"<span></span>"`);
});

test('Render default and id', async () => {
  const wrapper = await mount(<HtmlComponent id="test-id" methods={methods} />);
  await wrapper.instance().componentDidMount();
  await wrapper.update();
  expect(wrapper.html()).toMatchInlineSnapshot(
    `"<span id=\\"test-id\\" data-testid=\\"test-id\\"></span>"`
  );
});

test('Render string', async () => {
  const wrapper = await mount(<HtmlComponent html="A string value" methods={methods} />);
  await wrapper.instance().componentDidMount();
  await wrapper.update();
  expect(wrapper.html()).toMatchInlineSnapshot(`"<span>A string value</span>"`);
});

test('Render number', async () => {
  const wrapper = await mount(<HtmlComponent html={123} methods={methods} />);
  await wrapper.instance().componentDidMount();
  await wrapper.update();
  expect(wrapper.html()).toMatchInlineSnapshot(`"<span>123</span>"`);
});

test('Render number 0', async () => {
  const wrapper = await mount(<HtmlComponent html={0} methods={methods} />);
  await wrapper.instance().componentDidMount();
  await wrapper.update();
  expect(wrapper.html()).toMatchInlineSnapshot(`"<span>0</span>"`);
});

test('Render boolean true', async () => {
  const wrapper = await mount(<HtmlComponent html={true} methods={methods} />);
  await wrapper.instance().componentDidMount();
  await wrapper.update();
  expect(wrapper.html()).toMatchInlineSnapshot(`"<span>true</span>"`);
});

test('Render boolean false', async () => {
  const wrapper = await mount(<HtmlComponent html={false} methods={methods} />);
  await wrapper.instance().componentDidMount();
  await wrapper.update();
  expect(wrapper.html()).toMatchInlineSnapshot(`"<span>false</span>"`);
});

test('Render null', async () => {
  const wrapper = await mount(<HtmlComponent html={null} methods={methods} />);
  await wrapper.instance().componentDidMount();
  await wrapper.update();
  expect(wrapper.html()).toMatchInlineSnapshot(`"<span></span>"`);
});

test('Render html', async () => {
  const wrapper = await mount(
    <HtmlComponent
      html={'<div style="background: green; padding: 10px;">Content green background</div>'}
      methods={methods}
    />
  );
  await wrapper.instance().componentDidMount();
  await wrapper.update();
  expect(wrapper.html()).toMatchInlineSnapshot(
    `"<span><div style=\\"background: green; padding: 10px;\\">Content green background</div></span>"`
  );
});

test('Render html div', async () => {
  const wrapper = await mount(
    <HtmlComponent
      html={'<div style="background: green; padding: 10px;">Content green background</div>'}
      methods={methods}
      div={true}
    />
  );
  await wrapper.instance().componentDidMount();
  await wrapper.update();
  expect(wrapper.html()).toMatchInlineSnapshot(
    `"<div><div style=\\"background: green; padding: 10px;\\">Content green background</div></div>"`
  );
});

test('Render html and style', async () => {
  const wrapper = await mount(
    <HtmlComponent
      html={'<div style="background: green; padding: 10px;">Content green background</div>'}
      methods={methods}
      style={{ color: 'red' }}
    />
  );
  await wrapper.instance().componentDidMount();
  await wrapper.update();
  expect(wrapper.html()).toMatchInlineSnapshot(
    `"<span class=\\"{&quot;color&quot;:&quot;red&quot;}\\"><div style=\\"background: green; padding: 10px;\\">Content green background</div></span>"`
  );
});

test('Render html iframe', async () => {
  const wrapper = await mount(
    <HtmlComponent
      html={
        '<iframe width="560" height="315" src="https://www.youtube.com/embed/7N7GWdlQJlU" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>'
      }
      methods={methods}
    />
  );
  await wrapper.instance().componentDidMount();
  await wrapper.update();
  expect(wrapper.html()).toMatchInlineSnapshot(`"<span></span>"`);
});

test('Render bad html', async () => {
  const wrapper = await mount(
    <HtmlComponent
      html={`
      <h1>Link<h1>
      <a href="https://lowdefy.com">Lowdefy link</a>
      <font size="+10">Description</font>
      <h1>Bad HTML</h1>
      <div onmouseover="alert('alpha')">
        <a href="javascript:alert('bravo')">delta</a>
        <img src="x" onerror="alert('charlie')"><iframe src="javascript:alert('delta')"></iframe>
        <math><mi xlink:href="data:x,<script>alert('echo')</script>"></mi></math>
      </div><script>alert('script tag');</script>
      `}
      methods={methods}
    />
  );
  await wrapper.instance().componentDidMount();
  await wrapper.update();
  expect(wrapper.html()).toMatchInlineSnapshot(`
    "<span>
          <h1>Link</h1><h1>
          <a href=\\"https://lowdefy.com\\">Lowdefy link</a>
          <font size=\\"+10\\">Description</font>
          </h1><h1>Bad HTML</h1>
          <div>
            <a>delta</a>
            <img src=\\"x\\">
            <math><mi></mi></math>
          </div>
          </span>"
  `);
});


<|start_filename|>packages/cli/src/utils/getSendTelemetry.js<|end_filename|>
/*
  Copyright 2020-2021 Lowdefy, Inc

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

import axios from 'axios';

function getSendTelemetry({ appId, cliVersion, command, lowdefyVersion, options }) {
  if (options.disableTelemetry) {
    return () => {};
  }
  async function sendTelemetry({ data = {} } = {}) {
    try {
      await axios.request({
        method: 'post',
        url: 'https://api.lowdefy.net/telemetry/cli',
        headers: {
          'User-Agent': `Lowdefy CLI v${cliVersion}`,
        },
        data: {
          ...data,
          app_id: appId,
          cli_version: cliVersion,
          command,
          lowdefy_version: lowdefyVersion,
        },
      });
    } catch (error) {
      // pass
    }
  }
  return sendTelemetry;
}

export default getSendTelemetry;


<|start_filename|>packages/build/src/build/buildPages/setBlockMeta.js<|end_filename|>
/*
  Copyright 2020-2021 Lowdefy, Inc

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

import { type } from '@lowdefy/helpers';

async function setBlockMeta(block, { getMeta, pageId }) {
  if (type.isNone(block.type)) {
    throw new Error(`Block type is not defined at ${block.blockId} on page ${pageId}.`);
  }
  if (!type.isString(block.type)) {
    throw new Error(
      `Block type is not a string at ${block.blockId} on page ${pageId}. Received ${JSON.stringify(
        block.type
      )}`
    );
  }
  const meta = await getMeta(block.type);
  if (!meta) {
    throw new Error(
      `Invalid Block type at ${block.blockId} on page ${pageId}. Received ${JSON.stringify(
        block.type
      )}`
    );
  }
  const { category, loading, moduleFederation, valueType } = meta;
  block.meta = { category, loading, moduleFederation };
  if (category === 'input') {
    block.meta.valueType = valueType;
  }

  if (category === 'list') {
    // include valueType to ensure block has value on init
    block.meta.valueType = 'array';
  }
  // Add user defined loading
  if (block.loading) {
    block.meta.loading = block.loading;
  }
}

export default setBlockMeta;


<|start_filename|>packages/build/src/build/buildPages/buildRequests.js<|end_filename|>
/*
  Copyright 2020-2021 Lowdefy, Inc

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

import { type } from '@lowdefy/helpers';

function buildRequest(request, blockContext) {
  const { auth, contextId, pageId } = blockContext;
  if (!type.isString(request.id)) {
    if (type.isUndefined(request.id)) {
      throw new Error(`Request id missing at page "${pageId}".`);
    }
    throw new Error(
      `Request id is not a string at page "${pageId}". Received ${JSON.stringify(request.id)}.`
    );
  }
  if (request.id.includes('.')) {
    throw new Error(`Request id "${request.id}" should not include a period (".").`);
  }

  request.auth = auth;
  request.requestId = request.id;
  request.contextId = contextId;
  request.id = `request:${pageId}:${contextId}:${request.id}`;
  blockContext.requests.push(request);
}

function buildRequests(block, blockContext) {
  if (!type.isNone(block.requests)) {
    if (!type.isArray(block.requests)) {
      throw new Error(
        `Requests is not an array at ${block.blockId} on page ${
          blockContext.pageId
        }. Received ${JSON.stringify(block.requests)}`
      );
    }
    block.requests.forEach((request) => {
      buildRequest(request, blockContext);
    });
    delete block.requests;
  }
}

export default buildRequests;


<|start_filename|>packages/blocks/blocksAntd/src/blocks/Alert/Alert.js<|end_filename|>
/*
  Copyright 2020-2021 Lowdefy, Inc

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

import React from 'react';
import { Alert } from 'antd';
import { blockDefaultProps, renderHtml } from '@lowdefy/block-tools';
import { type } from '@lowdefy/helpers';

import Icon from '../Icon/Icon';

const AlertBlock = ({ blockId, events, methods, properties }) => {
  const additionalProps = {};
  if (properties.icon) {
    additionalProps.icon = (
      <Icon blockId={`${blockId}_icon`} events={events} properties={properties.icon} />
    );
  }
  return (
    <Alert
      afterClose={() => methods.triggerEvent({ name: 'afterClose' })}
      banner={properties.banner}
      closable={properties.closable}
      closeText={properties.closeText}
      description={renderHtml({ html: properties.description, methods })}
      id={blockId}
      message={
        renderHtml({ html: properties.message, methods }) ||
        (type.isNone(properties.description) && <div style={{ height: '1.5175em' }}></div>)
      }
      onClose={() => methods.triggerEvent({ name: 'onClose' })}
      showIcon={properties.showIcon === false ? false : true}
      type={properties.type}
      {...additionalProps}
    />
  );
};

AlertBlock.defaultProps = blockDefaultProps;

export default AlertBlock;


<|start_filename|>packages/cli/src/commands/dev/dev.js<|end_filename|>
/*
  Copyright 2020-2021 Lowdefy, Inc

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

import opener from 'opener';

import buildWatcher from './buildWatcher';
import envWatcher from './envWatcher';
import getBuild from './getBuild';
import getExpress from './getExpress';
import getGraphQL from './getGraphQL';
import prepare from './prepare';
import versionWatcher from './versionWatcher';

async function initialBuild({ context }) {
  const build = await getBuild({ context });
  try {
    await build();
    // eslint-disable-next-line no-empty
  } catch (error) {}
  return build;
}

async function serverSetup({ context }) {
  const gqlServer = await getGraphQL({ context });
  return getExpress({ context, gqlServer });
}

async function dev({ context }) {
  await prepare({ context });
  const initialBuildPromise = initialBuild({ context });
  const serverSetupPromise = serverSetup({ context });

  const [build, { expressApp, reloadFn }] = await Promise.all([
    initialBuildPromise,
    serverSetupPromise,
  ]);

  buildWatcher({ build, context, reloadFn });
  envWatcher({ context });
  versionWatcher({ context });

  context.print.log('Starting Lowdefy development server.');

  const port = expressApp.get('port');
  expressApp.listen(port, function () {
    context.print.info(`Development server listening on port ${port}`);
  });
  opener(`http://localhost:${port}`);

  await context.sendTelemetry({
    data: {
      type: 'startup',
    },
  });
}

export default dev;


<|start_filename|>packages/build/src/utils/meta/getMeta.js<|end_filename|>
/*
  Copyright 2020-2021 Lowdefy, Inc

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

/*
Steps to fetch meta
- check in cache
  - in cache
    - return
  - not in cache
    - fetch url
    - write cache
    - return
*/

import { type as typeHelper } from '@lowdefy/helpers';

import cachedPromises from '../cachePromises';
import createFetchMetaCache from './fetchMetaCache';
import createWriteMetaCache from './writeMetaCache';
import metaLocations from './metaLocations';
import fetchMetaUrl from './fetchMetaUrl';

function createGetMeta({ components, context }) {
  const { blocksServerUrl, cacheDirectory } = context;
  const { types } = components;
  const allMetaLocations = {
    ...metaLocations({ blocksServerUrl }),
    ...types,
  };
  const fetchMetaCache = createFetchMetaCache({ cacheDirectory });
  const writeMetaCache = createWriteMetaCache({ cacheDirectory });
  async function getMeta(type) {
    const location = allMetaLocations[type];
    if (!location) {
      throw new Error(
        `Block type ${JSON.stringify(type)} is not defined. Specify type url in types array.`
      );
    }

    const cacheMeta = !location.url.startsWith('http://localhost:');
    let meta;

    if (cacheMeta) {
      meta = await fetchMetaCache(location);
      if (meta) {
        return meta;
      }
    }

    meta = await fetchMetaUrl({ location, type });
    // TODO: implement Ajv schema check. Use testAjvSchema func from @lowdefy/ajv
    if (meta && typeHelper.isString(meta.category) && meta.moduleFederation) {
      if (cacheMeta) {
        await writeMetaCache({ location, meta });
      }
      return meta;
    }
    throw new Error(
      `Block type ${JSON.stringify(type)} has invalid block meta at ${JSON.stringify(location)}.`
    );
  }

  return cachedPromises(getMeta);
}

export default createGetMeta;


<|start_filename|>packages/engine/src/getContext.js<|end_filename|>
/*
  Copyright 2020-2021 Lowdefy, Inc

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

import { WebParser } from '@lowdefy/operators';

import Actions from './Actions';
import Blocks from './Blocks';
import Requests from './Requests';
import State from './State';

const blockData = ({
  areas,
  blockId,
  blocks,
  events,
  field,
  id,
  layout,
  meta,
  operators,
  pageId,
  properties,
  requests,
  required,
  style,
  type,
  validate,
  visible,
}) => ({
  areas,
  blockId,
  blocks,
  events,
  field,
  id,
  layout,
  meta,
  operators,
  pageId,
  properties,
  requests,
  required,
  style,
  type,
  validate,
  visible,
});

const getContext = async ({ block, contextId, lowdefy }) => {
  if (lowdefy.contexts[contextId]) {
    lowdefy.contexts[contextId].update();
    return lowdefy.contexts[contextId];
  }
  if (!block) {
    throw new Error('A block must be provided to get context.');
  }
  // eslint-disable-next-line no-param-reassign
  if (!lowdefy.inputs[contextId]) {
    lowdefy.inputs[contextId] = {};
  }
  const operatorsSet = new Set([...block.operators, '_not', '_type']);
  lowdefy.contexts[contextId] = {
    id: contextId,
    blockId: block.blockId,
    eventLog: [],
    requests: {},
    operators: [...operatorsSet],
    lowdefy,
    pageId: lowdefy.pageId,
    rootBlock: blockData(block), // filter block to prevent circular structure
    state: {},
    update: () => {}, // Initialize update since Requests might call it during context creation
    updateListeners: new Set(),
  };
  const ctx = lowdefy.contexts[contextId];
  ctx.parser = new WebParser({ context: ctx, contexts: lowdefy.contexts });
  await ctx.parser.init();
  ctx.State = new State(ctx);
  ctx.Actions = new Actions(ctx);
  ctx.Requests = new Requests(ctx);
  ctx.RootBlocks = new Blocks({
    areas: { root: { blocks: [ctx.rootBlock] } },
    context: ctx,
  });
  ctx.RootBlocks.init();
  ctx.update = () => {
    ctx.RootBlocks.update();
    [...ctx.updateListeners].forEach((listenId) => {
      // Will loop infinitely if update is called on self
      if (!lowdefy.contexts[listenId] || listenId === contextId) {
        ctx.updateListeners.delete(listenId);
      } else {
        lowdefy.contexts[listenId].update();
      }
    });
  };
  await ctx.RootBlocks.map[ctx.blockId].triggerEvent({ name: 'onInit' });
  ctx.update();
  ctx.State.freezeState();
  ctx.RootBlocks.map[ctx.blockId].triggerEvent({ name: 'onInitAsync' });
  return ctx;
};

export default getContext;


<|start_filename|>packages/cli/src/utils/getDirectories.js<|end_filename|>
/*
  Copyright 2020-2021 Lowdefy, Inc

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

import path from 'path';

function getDirectories({ baseDirectory, options }) {
  const cacheDirectory = path.resolve(baseDirectory, './.lowdefy/.cache');

  let outputDirectory;
  if (options.outputDirectory) {
    outputDirectory = path.resolve(options.outputDirectory);
  } else {
    outputDirectory = path.resolve(baseDirectory, './.lowdefy/build');
  }
  return { cacheDirectory, outputDirectory };
}

export default getDirectories;


<|start_filename|>packages/graphql/src/controllers/requestController.js<|end_filename|>
/*
  Copyright 2020-2021 Lowdefy, Inc

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

import { validate } from '@lowdefy/ajv';
import { serializer } from '@lowdefy/helpers';
import { NodeParser } from '@lowdefy/operators';
import { ConfigurationError, RequestError } from '../context/errors';
import resolvers from '../connections/resolvers';

class RequestController {
  constructor({ getController, getLoader, getSecrets, user }) {
    this.authorizationController = getController('authorization');
    this.connectionLoader = getLoader('connection');
    this.getSecrets = getSecrets;
    this.requestLoader = getLoader('request');
    this.user = user;
  }

  async callRequest(requestInput) {
    // get variables needed to load request/connection from requestInput
    const { arrayIndices, blockId, pageId, requestId } = requestInput;

    const request = await this.loadRequest({
      pageId,
      contextId: blockId,
      requestId,
    });

    // authorizeRequest will throw if not authorized
    this.authorizeRequest({ request });
    const connection = await this.loadConnection({ connectionId: request.connectionId });

    // Get definitions early to throw and avoid parsing if request/connection type is invalid
    const connectionDefinition = this.getConnectionDefinition({ connection, request });
    const requestDefinition = this.getRequestDefinition({ connectionDefinition, request });

    // Get parser variables from requestInput and deserialize
    const { event, input, lowdefyGlobal, state, urlQuery } = this.deserializeInputs(requestInput);

    const { connectionProperties, requestProperties } = await this.parseOperators({
      event,
      arrayIndices,
      connection,
      input,
      lowdefyGlobal,
      request,
      state,
      urlQuery,
    });

    this.checkConnectionRead({
      connectionId: request.connectionId,
      connectionProperties,
      checkRead: requestDefinition.checkRead,
    });
    this.checkConnectionWrite({
      connectionId: request.connectionId,
      connectionProperties,
      checkWrite: requestDefinition.checkWrite,
    });

    try {
      validate({ schema: connectionDefinition.schema, data: connectionProperties });
      validate({ schema: requestDefinition.schema, data: requestProperties });
    } catch (error) {
      throw new ConfigurationError(error.message);
    }

    const response = await this.callResolver({
      connectionProperties,
      requestProperties,
      resolver: requestDefinition.resolver,
    });

    return {
      id: request.id,
      success: true,
      type: request.type,
      response: serializer.serialize(response),
    };
  }

  async loadRequest({ pageId, contextId, requestId }) {
    const request = await this.requestLoader.load({ pageId, contextId, requestId });
    if (!request) {
      throw new ConfigurationError(`Request "${requestId}" does not exist.`);
    }
    if (!request.connectionId) {
      throw new ConfigurationError(`Request "${requestId}" does not specify a connection.`);
    }

    return request;
  }

  authorizeRequest({ request }) {
    if (!this.authorizationController.authorize(request)) {
      throw new ConfigurationError(`Request "${request.requestId}" does not exist.`);
    }
  }

  async loadConnection({ connectionId }) {
    const connection = await this.connectionLoader.load(connectionId);
    if (!connection) {
      throw new ConfigurationError(`Connection "${connectionId}" does not exist.`);
    }
    return connection;
  }

  getConnectionDefinition({ connection, request }) {
    const connectionDefinition = resolvers[connection.type];
    if (!connectionDefinition) {
      throw new ConfigurationError(
        `Request "${request.requestId}" has invalid connection type "${connection.type}".`
      );
    }
    return connectionDefinition;
  }

  getRequestDefinition({ connectionDefinition, request }) {
    const requestDefinition = connectionDefinition.requests[request.type];
    if (!requestDefinition) {
      throw new ConfigurationError(
        `Request "${request.requestId}" has invalid request type "${request.type}".`
      );
    }
    return requestDefinition;
  }

  deserializeInputs({ event, input, lowdefyGlobal, state, urlQuery }) {
    return serializer.deserialize({ event, input, lowdefyGlobal, state, urlQuery });
  }

  async parseOperators({
    event,
    arrayIndices,
    connection,
    input,
    lowdefyGlobal,
    request,
    state,
    urlQuery,
  }) {
    const secrets = await this.getSecrets();
    const operatorsParser = new NodeParser({
      arrayIndices,
      input,
      lowdefyGlobal,
      secrets,
      state,
      urlQuery,
      user: this.user,
    });
    await operatorsParser.init();
    const { output: connectionProperties, errors: connectionErrors } = operatorsParser.parse({
      event,
      input: connection.properties || {},
      location: connection.connectionId,
    });
    if (connectionErrors.length > 0) {
      throw new RequestError(connectionErrors[0]);
    }

    const { output: requestProperties, errors: requestErrors } = operatorsParser.parse({
      event,
      input: request.properties || {},
      location: request.requestId,
    });
    if (requestErrors.length > 0) {
      throw new RequestError(requestErrors[0]);
    }

    return {
      connectionProperties,
      requestProperties,
    };
  }

  checkConnectionRead({ connectionId, connectionProperties, checkRead }) {
    if (checkRead && connectionProperties.read === false) {
      throw new ConfigurationError(`Connection "${connectionId}" does not allow reads.`);
    }
  }

  checkConnectionWrite({ connectionId, connectionProperties, checkWrite }) {
    if (checkWrite && connectionProperties.write !== true) {
      throw new ConfigurationError(`Connection "${connectionId}" does not allow writes.`);
    }
  }

  async callResolver({ connectionProperties, requestProperties, resolver }) {
    try {
      const response = await resolver({
        request: requestProperties,
        connection: connectionProperties,
      });
      return response;
    } catch (err) {
      if (err instanceof ConfigurationError || err instanceof RequestError) {
        throw err;
      }
      throw new RequestError(err.message);
    }
  }
}

function createRequestController(context) {
  return new RequestController(context);
}

export { RequestController };

export default createRequestController;


<|start_filename|>packages/operators/src/node/hash.js<|end_filename|>
/*
  Copyright 2020-2021 Lowdefy, Inc

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

import crypto from 'crypto';
import runClass from '../runClass';

function hash(algorithm, data) {
  return crypto.createHash(algorithm).update(data).digest('hex');
}

function md5(data) {
  return hash('md5', data);
}

function sha1(data) {
  return hash('sha1', data);
}

function sha256(data) {
  return hash('sha256', data);
}

function sha512(data) {
  return hash('sha512', data);
}

function ripemd160(data) {
  return hash('ripemd160', data);
}

const functions = { md5, sha1, sha256, sha512, ripemd160 };

const meta = {
  md5: { validTypes: ['string'], singleArg: true },
  sha1: { validTypes: ['string'], singleArg: true },
  sha256: { validTypes: ['string'], singleArg: true },
  sha512: { validTypes: ['string'], singleArg: true },
  ripemd160: { validTypes: ['string'], singleArg: true },
};

function _hash({ params, location, methodName }) {
  return runClass({
    functions,
    location,
    meta,
    methodName,
    operator: '_hash',
    params,
  });
}

export default _hash;


<|start_filename|>packages/build/src/build/addDefaultPages/addDefaultPages.js<|end_filename|>
/*
  Copyright 2020-2021 Lowdefy, Inc

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

import { type } from '@lowdefy/helpers';
import page404 from './404.json';

async function addDefaultPages({ components }) {
  // If not copied, the same object is mutated by build every time
  // build runs for dev server. See #647
  const defaultPages = [JSON.parse(JSON.stringify(page404))];

  if (type.isNone(components.pages)) {
    components.pages = [];
  }
  if (!type.isArray(components.pages)) {
    throw new Error('lowdefy.pages is not an array.');
  }

  const pageIds = components.pages.map((page) => page.id);
  // deep copy to avoid mutating defaultConfig
  const filteredDefaultPages = defaultPages.filter(
    (defaultPage) => !pageIds.includes(defaultPage.id)
  );
  components.pages = [...components.pages, ...filteredDefaultPages];
  return components;
}

export default addDefaultPages;


<|start_filename|>packages/operators/test/node/hash.test.js<|end_filename|>
/*
  Copyright 2020-2021 Lowdefy, Inc

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

import hash from '../../src/node/hash';

console.error = () => {};

test('_hash.md5 a string', () => {
  expect(hash({ params: 'A string value', location: 'locationId', methodName: 'md5' })).toEqual(
    '55cff4fd4227c899e8fc194e55152c24'
  );
});

test('_hash.md5 a number', () => {
  expect(() => hash({ params: 10, location: 'locationId', methodName: 'md5' }))
    .toThrowErrorMatchingInlineSnapshot(`
    "Operator Error: _hash.md5 accepts one of the following types: string.
          Received: {\\"_hash.md5\\":10} at locationId."
  `);
});

test('_hash.md5 a boolean', () => {
  expect(() => hash({ params: true, location: 'locationId', methodName: 'md5' }))
    .toThrowErrorMatchingInlineSnapshot(`
    "Operator Error: _hash.md5 accepts one of the following types: string.
          Received: {\\"_hash.md5\\":true} at locationId."
  `);
});

test('_hash.md5 a object', () => {
  expect(() => hash({ params: { a: 1 }, location: 'locationId', methodName: 'md5' }))
    .toThrowErrorMatchingInlineSnapshot(`
    "Operator Error: _hash.md5 accepts one of the following types: string.
          Received: {\\"_hash.md5\\":{\\"a\\":1}} at locationId."
  `);
});

test('_hash.md5 a array', () => {
  expect(() => hash({ params: ['a', 'b'], location: 'locationId', methodName: 'md5' }))
    .toThrowErrorMatchingInlineSnapshot(`
    "Operator Error: _hash.md5 accepts one of the following types: string.
          Received: {\\"_hash.md5\\":[\\"a\\",\\"b\\"]} at locationId."
  `);
});

test('_hash.md5 undefined', () => {
  expect(() => hash({ location: 'locationId', methodName: 'md5' }))
    .toThrowErrorMatchingInlineSnapshot(`
    "Operator Error: _hash.md5 accepts one of the following types: string.
          Received: {\\"_hash.md5\\":undefined} at locationId."
  `);
});

test('_hash.md5 null', () => {
  expect(() => hash({ params: null, location: 'locationId', methodName: 'md5' }))
    .toThrowErrorMatchingInlineSnapshot(`
    "Operator Error: _hash.md5 accepts one of the following types: string.
          Received: {\\"_hash.md5\\":null} at locationId."
  `);
});

test('_hash.sha1 a string', () => {
  expect(hash({ params: 'A string value', location: 'locationId', methodName: 'sha1' })).toEqual(
    'ab185e961a1ff20ad9240f102dd0fc7a00565ccb'
  );
});

test('_hash.sha1 a number', () => {
  expect(() => hash({ params: 10, location: 'locationId', methodName: 'sha1' }))
    .toThrowErrorMatchingInlineSnapshot(`
    "Operator Error: _hash.sha1 accepts one of the following types: string.
          Received: {\\"_hash.sha1\\":10} at locationId."
  `);
});

test('_hash.sha1 a boolean', () => {
  expect(() => hash({ params: true, location: 'locationId', methodName: 'sha1' }))
    .toThrowErrorMatchingInlineSnapshot(`
    "Operator Error: _hash.sha1 accepts one of the following types: string.
          Received: {\\"_hash.sha1\\":true} at locationId."
  `);
});

test('_hash.sha1 a object', () => {
  expect(() => hash({ params: { a: 1 }, location: 'locationId', methodName: 'sha1' }))
    .toThrowErrorMatchingInlineSnapshot(`
    "Operator Error: _hash.sha1 accepts one of the following types: string.
          Received: {\\"_hash.sha1\\":{\\"a\\":1}} at locationId."
  `);
});

test('_hash.sha1 a array', () => {
  expect(() => hash({ params: ['a', 'b'], location: 'locationId', methodName: 'sha1' }))
    .toThrowErrorMatchingInlineSnapshot(`
    "Operator Error: _hash.sha1 accepts one of the following types: string.
          Received: {\\"_hash.sha1\\":[\\"a\\",\\"b\\"]} at locationId."
  `);
});

test('_hash.sha1 undefined', () => {
  expect(() => hash({ location: 'locationId', methodName: 'sha1' }))
    .toThrowErrorMatchingInlineSnapshot(`
    "Operator Error: _hash.sha1 accepts one of the following types: string.
          Received: {\\"_hash.sha1\\":undefined} at locationId."
  `);
});

test('_hash.sha1 null', () => {
  expect(() => hash({ params: null, location: 'locationId', methodName: 'sha1' }))
    .toThrowErrorMatchingInlineSnapshot(`
    "Operator Error: _hash.sha1 accepts one of the following types: string.
          Received: {\\"_hash.sha1\\":null} at locationId."
  `);
});

test('_hash.sha256 a string', () => {
  expect(hash({ params: 'A string value', location: 'locationId', methodName: 'sha256' })).toEqual(
    '67fa88551dc1becff8e9facba582cea0c4e4f1268094297824f266e87e98e014'
  );
});

test('_hash.sha256 a number', () => {
  expect(() => hash({ params: 10, location: 'locationId', methodName: 'sha256' }))
    .toThrowErrorMatchingInlineSnapshot(`
    "Operator Error: _hash.sha256 accepts one of the following types: string.
          Received: {\\"_hash.sha256\\":10} at locationId."
  `);
});

test('_hash.sha256 a boolean', () => {
  expect(() => hash({ params: true, location: 'locationId', methodName: 'sha256' }))
    .toThrowErrorMatchingInlineSnapshot(`
    "Operator Error: _hash.sha256 accepts one of the following types: string.
          Received: {\\"_hash.sha256\\":true} at locationId."
  `);
});

test('_hash.sha256 a object', () => {
  expect(() => hash({ params: { a: 1 }, location: 'locationId', methodName: 'sha256' }))
    .toThrowErrorMatchingInlineSnapshot(`
    "Operator Error: _hash.sha256 accepts one of the following types: string.
          Received: {\\"_hash.sha256\\":{\\"a\\":1}} at locationId."
  `);
});

test('_hash.sha256 a array', () => {
  expect(() => hash({ params: ['a', 'b'], location: 'locationId', methodName: 'sha256' }))
    .toThrowErrorMatchingInlineSnapshot(`
    "Operator Error: _hash.sha256 accepts one of the following types: string.
          Received: {\\"_hash.sha256\\":[\\"a\\",\\"b\\"]} at locationId."
  `);
});

test('_hash.sha256 undefined', () => {
  expect(() => hash({ location: 'locationId', methodName: 'sha256' }))
    .toThrowErrorMatchingInlineSnapshot(`
    "Operator Error: _hash.sha256 accepts one of the following types: string.
          Received: {\\"_hash.sha256\\":undefined} at locationId."
  `);
});

test('_hash.sha256 null', () => {
  expect(() => hash({ params: null, location: 'locationId', methodName: 'sha256' }))
    .toThrowErrorMatchingInlineSnapshot(`
    "Operator Error: _hash.sha256 accepts one of the following types: string.
          Received: {\\"_hash.sha256\\":null} at locationId."
  `);
});

test('_hash.sha512 a string', () => {
  expect(hash({ params: 'A string value', location: 'locationId', methodName: 'sha512' })).toEqual(
    '4002438cc89dbe9092468aa10fa766fb977010a59879739fe77558d7685691e0ca0ccb70090267008e77447271106f490707b3f6f2d8efb3b6c8c6a63802c46a'
  );
});

test('_hash.sha512 a number', () => {
  expect(() => hash({ params: 10, location: 'locationId', methodName: 'sha512' }))
    .toThrowErrorMatchingInlineSnapshot(`
    "Operator Error: _hash.sha512 accepts one of the following types: string.
          Received: {\\"_hash.sha512\\":10} at locationId."
  `);
});

test('_hash.sha512 a boolean', () => {
  expect(() => hash({ params: true, location: 'locationId', methodName: 'sha512' }))
    .toThrowErrorMatchingInlineSnapshot(`
    "Operator Error: _hash.sha512 accepts one of the following types: string.
          Received: {\\"_hash.sha512\\":true} at locationId."
  `);
});

test('_hash.sha512 a object', () => {
  expect(() => hash({ params: { a: 1 }, location: 'locationId', methodName: 'sha512' }))
    .toThrowErrorMatchingInlineSnapshot(`
    "Operator Error: _hash.sha512 accepts one of the following types: string.
          Received: {\\"_hash.sha512\\":{\\"a\\":1}} at locationId."
  `);
});

test('_hash.sha512 a array', () => {
  expect(() => hash({ params: ['a', 'b'], location: 'locationId', methodName: 'sha512' }))
    .toThrowErrorMatchingInlineSnapshot(`
    "Operator Error: _hash.sha512 accepts one of the following types: string.
          Received: {\\"_hash.sha512\\":[\\"a\\",\\"b\\"]} at locationId."
  `);
});

test('_hash.sha512 undefined', () => {
  expect(() => hash({ location: 'locationId', methodName: 'sha512' }))
    .toThrowErrorMatchingInlineSnapshot(`
    "Operator Error: _hash.sha512 accepts one of the following types: string.
          Received: {\\"_hash.sha512\\":undefined} at locationId."
  `);
});

test('_hash.sha512 null', () => {
  expect(() => hash({ params: null, location: 'locationId', methodName: 'sha512' }))
    .toThrowErrorMatchingInlineSnapshot(`
    "Operator Error: _hash.sha512 accepts one of the following types: string.
          Received: {\\"_hash.sha512\\":null} at locationId."
  `);
});

test('_hash.ripemd160 a string', () => {
  expect(
    hash({ params: 'A string value', location: 'locationId', methodName: 'ripemd160' })
  ).toEqual('a6267e14418b2833da3a44ff2513e544e71e1242');
});

test('_hash.ripemd160 a number', () => {
  expect(() => hash({ params: 10, location: 'locationId', methodName: 'ripemd160' }))
    .toThrowErrorMatchingInlineSnapshot(`
    "Operator Error: _hash.ripemd160 accepts one of the following types: string.
          Received: {\\"_hash.ripemd160\\":10} at locationId."
  `);
});

test('_hash.ripemd160 a boolean', () => {
  expect(() => hash({ params: true, location: 'locationId', methodName: 'ripemd160' }))
    .toThrowErrorMatchingInlineSnapshot(`
    "Operator Error: _hash.ripemd160 accepts one of the following types: string.
          Received: {\\"_hash.ripemd160\\":true} at locationId."
  `);
});

test('_hash.ripemd160 a object', () => {
  expect(() => hash({ params: { a: 1 }, location: 'locationId', methodName: 'ripemd160' }))
    .toThrowErrorMatchingInlineSnapshot(`
    "Operator Error: _hash.ripemd160 accepts one of the following types: string.
          Received: {\\"_hash.ripemd160\\":{\\"a\\":1}} at locationId."
  `);
});

test('_hash.ripemd160 a array', () => {
  expect(() => hash({ params: ['a', 'b'], location: 'locationId', methodName: 'ripemd160' }))
    .toThrowErrorMatchingInlineSnapshot(`
    "Operator Error: _hash.ripemd160 accepts one of the following types: string.
          Received: {\\"_hash.ripemd160\\":[\\"a\\",\\"b\\"]} at locationId."
  `);
});

test('_hash.ripemd160 undefined', () => {
  expect(() => hash({ location: 'locationId', methodName: 'ripemd160' }))
    .toThrowErrorMatchingInlineSnapshot(`
    "Operator Error: _hash.ripemd160 accepts one of the following types: string.
          Received: {\\"_hash.ripemd160\\":undefined} at locationId."
  `);
});

test('_hash.ripemd160 null', () => {
  expect(() => hash({ params: null, location: 'locationId', methodName: 'ripemd160' }))
    .toThrowErrorMatchingInlineSnapshot(`
    "Operator Error: _hash.ripemd160 accepts one of the following types: string.
          Received: {\\"_hash.ripemd160\\":null} at locationId."
  `);
});
