/**
 * @licstart The following is the entire license notice for the
 * JavaScript code in this page
 *
 * Copyright 2022 Mozilla Foundation
 *
 * 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.
 *
 * @licend The above is the entire license notice for the
 * JavaScript code in this page
 */
"use strict";

var _display_utils = require("../../display/display_utils.js");
const sandboxBundleSrc = "../../build/generic/build/pdf.sandbox.js";
describe("Scripting", function () {
  let sandbox, send_queue, test_id, ref, windowAlert;
  function getId() {
    const id = `${ref++}R`;
    return id;
  }
  function myeval(code) {
    const key = (test_id++).toString();
    return sandbox.eval(code, key).then(() => {
      const result = send_queue.get(key).result;
      send_queue.delete(key);
      return result;
    });
  }
  beforeAll(function () {
    test_id = 0;
    ref = 1;
    send_queue = new Map();
    window.dispatchEvent = event => {
      if (event.detail.command) {
        send_queue.set(event.detail.command, event.detail);
      } else if (send_queue.has(event.detail.id)) {
        const prev = send_queue.get(event.detail.id);
        Object.assign(prev, event.detail);
      } else {
        send_queue.set(event.detail.id, event.detail);
      }
    };
    windowAlert = window.alert;
    window.alert = value => {
      const command = "alert";
      send_queue.set(command, {
        command,
        value
      });
    };
    const promise = (0, _display_utils.loadScript)(sandboxBundleSrc).then(() => {
      return window.pdfjsSandbox.QuickJSSandbox();
    });
    sandbox = {
      createSandbox(data) {
        promise.then(sbx => sbx.create(data));
      },
      dispatchEventInSandbox(data) {
        return promise.then(sbx => sbx.dispatchEvent(data));
      },
      nukeSandbox() {
        promise.then(sbx => sbx.nukeSandbox());
      },
      eval(code, key) {
        return promise.then(sbx => sbx.evalForTesting(code, key));
      }
    };
  });
  afterAll(function () {
    sandbox.nukeSandbox();
    sandbox = null;
    send_queue = null;
    window.alert = windowAlert;
  });
  describe("Sandbox", function () {
    it("should send a value, execute an action and get back a new value", async () => {
      function compute(n) {
        let s = 0;
        for (let i = 0; i < n; i++) {
          s += i;
        }
        return s;
      }
      const number = 123;
      const expected = (number - 1) * number / 2;
      const refId = getId();
      const data = {
        objects: {
          field: [{
            id: refId,
            value: "",
            actions: {
              Keystroke: [`${compute.toString()}event.value = compute(parseInt(event.value));`]
            },
            type: "text"
          }]
        },
        calculationOrder: [],
        appInfo: {
          language: "en-US",
          platform: "Linux x86_64"
        }
      };
      sandbox.createSandbox(data);
      await sandbox.dispatchEventInSandbox({
        id: refId,
        value: `${number}`,
        name: "Keystroke",
        willCommit: true
      });
      expect(send_queue.has(refId)).toEqual(true);
      expect(send_queue.get(refId)).toEqual({
        id: refId,
        siblings: null,
        value: expected,
        formattedValue: null
      });
    });
  });
  describe("Doc", function () {
    it("should treat globalThis as the doc", async () => {
      const refId = getId();
      const data = {
        objects: {
          field: [{
            id: refId,
            value: "",
            actions: {},
            type: "text"
          }]
        },
        appInfo: {
          language: "en-US",
          platform: "Linux x86_64"
        },
        calculationOrder: [],
        dispatchEventName: "_dispatchMe"
      };
      sandbox.createSandbox(data);
      await myeval(`(this.foobar = 123456, 0)`);
      const value = await myeval(`this.getField("field").doc.foobar`);
      expect(value).toEqual(123456);
    });
    it("should get field using a path", async () => {
      const base = value => {
        return {
          id: getId(),
          value,
          actions: {},
          type: "text"
        };
      };
      const data = {
        objects: {
          A: [base(1)],
          "A.B": [base(2)],
          "A.B.C": [base(3)],
          "A.B.C.D": [base(4)],
          "A.B.C.D.E": [base(5)],
          "A.B.C.D.E.F": [base(6)],
          "A.B.C.D.G": [base(7)],
          C: [base(8)]
        },
        appInfo: {
          language: "en-US",
          platform: "Linux x86_64"
        },
        calculationOrder: [],
        dispatchEventName: "_dispatchMe"
      };
      sandbox.createSandbox(data);
      let value = await myeval(`this.getField("A").value`);
      expect(value).toEqual(1);
      value = await myeval(`this.getField("B.C").value`);
      expect(value).toEqual(3);
      value = await myeval(`this.getField("B.C").value`);
      expect(value).toEqual(3);
      value = await myeval(`this.getField("B.C.D#0").value`);
      expect(value).toEqual(5);
      value = await myeval(`this.getField("B.C.D#1").value`);
      expect(value).toEqual(7);
      value = await myeval(`this.getField("C").value`);
      expect(value).toEqual(8);
      value = await myeval(`this.getField("A.B.C.D").getArray().map((x) => x.value)`);
      expect(value).toEqual([4, 5, 6, 7]);
    });
  });
  describe("Util", function () {
    beforeAll(function () {
      sandbox.createSandbox({
        appInfo: {
          language: "en-US",
          platform: "Linux x86_64"
        },
        objects: {},
        calculationOrder: []
      });
    });
    describe("printd", function () {
      it("should print a date according to a format", async () => {
        const date = `new Date("Sun Apr 15 2007 03:14:15")`;
        let value = await myeval(`util.printd(0, ${date})`);
        expect(value).toEqual("D:20070415031415");
        value = await myeval(`util.printd(1, ${date})`);
        expect(value).toEqual("2007.04.15 03:14:15");
        value = await myeval(`util.printd(2, ${date})`);
        expect(value).toEqual("4/15/07 3:14:15 am");
        value = await myeval(`util.printd("mmmm mmm mm m", ${date})`);
        expect(value).toEqual("April Apr 04 4");
        value = await myeval(`util.printd("dddd ddd dd d", ${date})`);
        expect(value).toEqual("Sunday Sun 15 15");
      });
    });
    describe("scand", function () {
      it("should parse a date according to a format", async () => {
        const date = new Date("Sun Apr 15 2007 03:14:15");
        let value = await myeval(`util.scand(0, "D:20070415031415").toString()`);
        expect(new Date(value)).toEqual(date);
        value = await myeval(`util.scand(1, "2007.04.15 03:14:15").toString()`);
        expect(new Date(value)).toEqual(date);
        value = await myeval(`util.scand(2, "4/15/07 3:14:15 am").toString()`);
        expect(new Date(value)).toEqual(date);
      });
    });
    describe("printf", function () {
      it("should print some data according to a format", async () => {
        let value = await myeval(`util.printf("Integer numbers: %d, %d,...", 1.234, 56.789)`);
        expect(value).toEqual("Integer numbers: 1, 56,...");
        value = await myeval(`util.printf("Hex numbers: %x, %x,...", 1234, 56789)`);
        expect(value).toEqual("Hex numbers: 4D2, DDD5,...");
        value = await myeval(`util.printf("Hex numbers with 0x: %#x, %#x,...", 1234, 56789)`);
        expect(value).toEqual("Hex numbers with 0x: 0x4D2, 0xDDD5,...");
        value = await myeval(`util.printf("Decimal number: %,0+.3f", 1234567.89123)`);
        expect(value).toEqual("Decimal number: +1,234,567.891");
        value = await myeval(`util.printf("Decimal number: %,0+8.3f", 1.234567)`);
        expect(value).toEqual("Decimal number: +  1.235");
        value = await myeval(`util.printf("Decimal number: %,0.2f", -12.34567)`);
        expect(value).toEqual("Decimal number: -12.35");
      });
      it("should print a string with no argument", async () => {
        const value = await myeval(`util.printf("hello world")`);
        expect(value).toEqual("hello world");
      });
      it("print a string with a percent", async () => {
        const value = await myeval(`util.printf("%%s")`);
        expect(value).toEqual("%%s");
      });
    });
    describe("printx", function () {
      it("should print some data according to a format", async () => {
        const value = await myeval(`util.printx("9 (999) 999-9999", "aaa14159697489zzz")`);
        expect(value).toEqual("1 (415) 969-7489");
      });
    });
  });
  describe("Events", function () {
    it("should trigger an event and modify the source", async () => {
      const refId = getId();
      const data = {
        objects: {
          field: [{
            id: refId,
            value: "",
            actions: {
              test: [`event.source.value = "123";`]
            },
            type: "text"
          }]
        },
        appInfo: {
          language: "en-US",
          platform: "Linux x86_64"
        },
        calculationOrder: []
      };
      sandbox.createSandbox(data);
      await sandbox.dispatchEventInSandbox({
        id: refId,
        value: "",
        name: "test",
        willCommit: true
      });
      expect(send_queue.has(refId)).toEqual(true);
      expect(send_queue.get(refId)).toEqual({
        id: refId,
        value: "123"
      });
    });
    it("should trigger a Keystroke event and invalidate it", async () => {
      const refId = getId();
      const data = {
        objects: {
          field: [{
            id: refId,
            value: "",
            actions: {
              Keystroke: [`event.rc = false;`]
            },
            type: "text"
          }]
        },
        appInfo: {
          language: "en-US",
          platform: "Linux x86_64"
        },
        calculationOrder: []
      };
      sandbox.createSandbox(data);
      await sandbox.dispatchEventInSandbox({
        id: refId,
        value: "hell",
        name: "Keystroke",
        willCommit: false,
        change: "o",
        selStart: 4,
        selEnd: 4
      });
      expect(send_queue.has(refId)).toEqual(true);
      expect(send_queue.get(refId)).toEqual({
        id: refId,
        siblings: null,
        value: "hell",
        selRange: [4, 4]
      });
    });
    it("should trigger a Keystroke event and change it", async () => {
      const refId = getId();
      const data = {
        objects: {
          field: [{
            id: refId,
            value: "",
            actions: {
              Keystroke: [`event.change = "a";`]
            },
            type: "text"
          }]
        },
        appInfo: {
          language: "en-US",
          platform: "Linux x86_64"
        },
        calculationOrder: []
      };
      sandbox.createSandbox(data);
      await sandbox.dispatchEventInSandbox({
        id: refId,
        value: "hell",
        name: "Keystroke",
        willCommit: false,
        change: "o",
        selStart: 4,
        selEnd: 4
      });
      expect(send_queue.has(refId)).toEqual(true);
      expect(send_queue.get(refId)).toEqual({
        id: refId,
        siblings: null,
        value: "hella",
        selRange: [5, 5]
      });
    });
    it("should trigger an invalid commit Keystroke event", async () => {
      const refId = getId();
      const data = {
        objects: {
          field: [{
            id: refId,
            value: "",
            actions: {
              test: [`event.rc = false;`]
            },
            type: "text"
          }]
        },
        appInfo: {
          language: "en-US",
          platform: "Linux x86_64"
        },
        calculationOrder: []
      };
      sandbox.createSandbox(data);
      await sandbox.dispatchEventInSandbox({
        id: refId,
        value: "",
        name: "test",
        willCommit: true
      });
      expect(send_queue.has(refId)).toEqual(false);
    });
    it("should trigger a valid commit Keystroke event", async () => {
      const refId1 = getId();
      const refId2 = getId();
      const data = {
        objects: {
          field1: [{
            id: refId1,
            value: "",
            actions: {
              Validate: [`event.value = "world";`]
            },
            type: "text"
          }],
          field2: [{
            id: refId2,
            value: "",
            actions: {
              Calculate: [`event.value = "hello";`]
            },
            type: "text"
          }]
        },
        appInfo: {
          language: "en-US",
          platform: "Linux x86_64"
        },
        calculationOrder: [refId2]
      };
      sandbox.createSandbox(data);
      await sandbox.dispatchEventInSandbox({
        id: refId1,
        value: "hello",
        name: "Keystroke",
        willCommit: true
      });
      expect(send_queue.has(refId1)).toEqual(true);
      expect(send_queue.get(refId1)).toEqual({
        id: refId1,
        siblings: null,
        value: "world",
        formattedValue: null
      });
    });
  });
  describe("Color", function () {
    beforeAll(function () {
      sandbox.createSandbox({
        appInfo: {
          language: "en-US",
          platform: "Linux x86_64"
        },
        objects: {},
        calculationOrder: []
      });
    });
    function round(color) {
      return [color[0], ...color.slice(1).map(x => Math.round(x * 1000) / 1000)];
    }
    it("should convert RGB color for different color spaces", async () => {
      let value = await myeval(`color.convert(["RGB", 0.1, 0.2, 0.3], "T")`);
      expect(round(value)).toEqual(["T"]);
      value = await myeval(`color.convert(["RGB", 0.1, 0.2, 0.3], "G")`);
      expect(round(value)).toEqual(["G", 0.181]);
      value = await myeval(`color.convert(["RGB", 0.1, 0.2, 0.3], "RGB")`);
      expect(round(value)).toEqual(["RGB", 0.1, 0.2, 0.3]);
      value = await myeval(`color.convert(["RGB", 0.1, 0.2, 0.3], "CMYK")`);
      expect(round(value)).toEqual(["CMYK", 0.9, 0.8, 0.7, 0.7]);
    });
    it("should convert CMYK color for different color spaces", async () => {
      let value = await myeval(`color.convert(["CMYK", 0.1, 0.2, 0.3, 0.4], "T")`);
      expect(round(value)).toEqual(["T"]);
      value = await myeval(`color.convert(["CMYK", 0.1, 0.2, 0.3, 0.4], "G")`);
      expect(round(value)).toEqual(["G", 0.371]);
      value = await myeval(`color.convert(["CMYK", 0.1, 0.2, 0.3, 0.4], "RGB")`);
      expect(round(value)).toEqual(["RGB", 0.5, 0.3, 0.4]);
      value = await myeval(`color.convert(["CMYK", 0.1, 0.2, 0.3, 0.4], "CMYK")`);
      expect(round(value)).toEqual(["CMYK", 0.1, 0.2, 0.3, 0.4]);
    });
    it("should convert Gray color for different color spaces", async () => {
      let value = await myeval(`color.convert(["G", 0.1], "T")`);
      expect(round(value)).toEqual(["T"]);
      value = await myeval(`color.convert(["G", 0.1], "G")`);
      expect(round(value)).toEqual(["G", 0.1]);
      value = await myeval(`color.convert(["G", 0.1], "RGB")`);
      expect(round(value)).toEqual(["RGB", 0.1, 0.1, 0.1]);
      value = await myeval(`color.convert(["G", 0.1], "CMYK")`);
      expect(round(value)).toEqual(["CMYK", 0, 0, 0, 0.9]);
    });
    it("should convert Transparent color for different color spaces", async () => {
      let value = await myeval(`color.convert(["T"], "T")`);
      expect(round(value)).toEqual(["T"]);
      value = await myeval(`color.convert(["T"], "G")`);
      expect(round(value)).toEqual(["G", 0]);
      value = await myeval(`color.convert(["T"], "RGB")`);
      expect(round(value)).toEqual(["RGB", 0, 0, 0]);
      value = await myeval(`color.convert(["T"], "CMYK")`);
      expect(round(value)).toEqual(["CMYK", 0, 0, 0, 1]);
    });
  });
  describe("App", function () {
    beforeAll(function () {
      sandbox.createSandbox({
        appInfo: {
          language: "en-US",
          platform: "Linux x86_64"
        },
        objects: {},
        calculationOrder: []
      });
    });
    it("should test language", async () => {
      let value = await myeval(`app.language`);
      expect(value).toEqual("ENU");
      value = await myeval(`app.language = "hello"`);
      expect(value).toEqual("app.language is read-only");
    });
    it("should test platform", async () => {
      let value = await myeval(`app.platform`);
      expect(value).toEqual("UNIX");
      value = await myeval(`app.platform = "hello"`);
      expect(value).toEqual("app.platform is read-only");
    });
  });
  describe("AForm", function () {
    beforeAll(function () {
      sandbox.createSandbox({
        appInfo: {
          language: "en-US",
          platform: "Linux x86_64"
        },
        objects: {},
        calculationOrder: [],
        dispatchEventName: "_dispatchMe"
      });
    });
    describe("AFParseDateEx", function () {
      it("should parse a date with a format", async () => {
        const check = async (date, format, expected) => {
          const value = await myeval(`AFParseDateEx("${date}", "${format}").toISOString().replace(/T.*$/, "")`);
          expect(value).toEqual(new Date(expected).toISOString().replace(/T.*$/, ""));
        };
        await check("05", "dd", "2000/01/05");
        await check("12", "mm", "2000/12/01");
        await check("2022", "yyyy", "2022/01/01");
        await check("a1$9bbbb21", "dd/mm/yyyy", "2021/09/01");
      });
    });
    describe("AFExtractNums", function () {
      it("should extract numbers", async () => {
        let value = await myeval(`AFExtractNums("123 456 789")`);
        expect(value).toEqual(["123", "456", "789"]);
        value = await myeval(`AFExtractNums("123.456")`);
        expect(value).toEqual(["123", "456"]);
        value = await myeval(`AFExtractNums("123")`);
        expect(value).toEqual(["123"]);
        value = await myeval(`AFExtractNums(".123")`);
        expect(value).toEqual(["0", "123"]);
        value = await myeval(`AFExtractNums(",123")`);
        expect(value).toEqual(["0", "123"]);
      });
    });
    describe("AFMakeNumber", function () {
      it("should convert string to number", async () => {
        let value = await myeval(`AFMakeNumber("123.456")`);
        expect(value).toEqual(123.456);
        value = await myeval(`AFMakeNumber(123.456)`);
        expect(value).toEqual(123.456);
        value = await myeval(`AFMakeNumber("-123.456")`);
        expect(value).toEqual(-123.456);
        value = await myeval(`AFMakeNumber("-123,456")`);
        expect(value).toEqual(-123.456);
        value = await myeval(`AFMakeNumber("not a number")`);
        expect(value).toEqual(null);
      });
    });
    describe("AFMakeArrayFromList", function () {
      it("should split a string into an array of strings", async () => {
        const value = await myeval(`AFMakeArrayFromList("aaaa,  bbbbbbb,cc,ddd, e")`);
        expect(value).toEqual(["aaaa", " bbbbbbb", "cc", "ddd", "e"]);
      });
    });
    describe("AFNumber_format", function () {
      it("should format a number", async () => {
        const refId = getId();
        const data = {
          objects: {
            field: [{
              id: refId,
              value: "",
              actions: {
                test1: [`AFNumber_Format(2, 0, 0, 0, "€", false);` + `event.source.value = event.value;`],
                test2: [`AFNumber_Format(1, 3, 0, 0, "$", true);` + `event.source.value = event.value;`],
                test3: [`AFNumber_Format(2, 0, 1, 0, "€", false);` + `event.source.value = event.value;`],
                test4: [`AFNumber_Format(2, 0, 2, 0, "€", false);` + `event.source.value = event.value;`],
                test5: [`AFNumber_Format(2, 0, 3, 0, "€", false);` + `event.source.value = event.value;`]
              },
              type: "text"
            }]
          },
          appInfo: {
            language: "en-US",
            platform: "Linux x86_64"
          },
          calculationOrder: [],
          dispatchEventName: "_dispatchMe"
        };
        sandbox.createSandbox(data);
        await sandbox.dispatchEventInSandbox({
          id: refId,
          value: "123456.789",
          name: "test1"
        });
        expect(send_queue.has(refId)).toEqual(true);
        expect(send_queue.get(refId)).toEqual({
          id: refId,
          value: "123,456.79€"
        });
        send_queue.delete(refId);
        await sandbox.dispatchEventInSandbox({
          id: refId,
          value: "223456.789",
          name: "test2"
        });
        expect(send_queue.has(refId)).toEqual(true);
        expect(send_queue.get(refId)).toEqual({
          id: refId,
          value: "$223456,8"
        });
        send_queue.delete(refId);
        await sandbox.dispatchEventInSandbox({
          id: refId,
          value: "-323456.789",
          name: "test3"
        });
        expect(send_queue.has(refId)).toEqual(true);
        expect(send_queue.get(refId)).toEqual({
          id: refId,
          value: "323,456.79€",
          textColor: ["RGB", 1, 0, 0]
        });
        send_queue.delete(refId);
        await sandbox.dispatchEventInSandbox({
          id: refId,
          value: "-423456.789",
          name: "test4"
        });
        expect(send_queue.has(refId)).toEqual(true);
        expect(send_queue.get(refId)).toEqual({
          id: refId,
          value: "(423,456.79€)"
        });
        send_queue.delete(refId);
        await sandbox.dispatchEventInSandbox({
          id: refId,
          value: "-52345.678",
          name: "test5"
        });
        expect(send_queue.has(refId)).toEqual(true);
        expect(send_queue.get(refId)).toEqual({
          id: refId,
          value: "(52,345.68€)",
          textColor: ["RGB", 1, 0, 0]
        });
      });
    });
    describe("AFNumber_Keystroke", function () {
      it("should validate a number on a keystroke event", async () => {
        const refId = getId();
        const data = {
          objects: {
            field: [{
              id: refId,
              value: "",
              actions: {
                Validate: [`AFNumber_Keystroke(null, 0, null, null, null, null);`]
              },
              type: "text",
              name: "MyField"
            }]
          },
          appInfo: {
            language: "en-US",
            platform: "Linux x86_64"
          },
          calculationOrder: [],
          dispatchEventName: "_dispatchMe"
        };
        sandbox.createSandbox(data);
        await sandbox.dispatchEventInSandbox({
          id: refId,
          value: "123456.789",
          name: "Keystroke",
          willCommit: true
        });
        expect(send_queue.has(refId)).toEqual(true);
        expect(send_queue.get(refId)).toEqual({
          id: refId,
          siblings: null,
          value: "123456.789",
          formattedValue: null
        });
      });
      it("should not validate a number on a keystroke event", async () => {
        const refId = getId();
        const data = {
          objects: {
            field: [{
              id: refId,
              value: "",
              actions: {
                Validate: [`AFNumber_Keystroke(null, 0, null, null, null, null);`]
              },
              type: "text",
              name: "MyField"
            }]
          },
          appInfo: {
            language: "en-US",
            platform: "Linux x86_64"
          },
          calculationOrder: [],
          dispatchEventName: "_dispatchMe"
        };
        sandbox.createSandbox(data);
        await sandbox.dispatchEventInSandbox({
          id: refId,
          value: "123s456.789",
          name: "Keystroke",
          willCommit: true
        });
        expect(send_queue.has("alert")).toEqual(true);
        expect(send_queue.get("alert")).toEqual({
          command: "alert",
          value: "The value entered does not match the format of the field [ MyField ]"
        });
      });
    });
    describe("AFPercent_Format", function () {
      it("should format a percentage", async () => {
        const refId = getId();
        const data = {
          objects: {
            field: [{
              id: refId,
              value: "",
              actions: {
                test1: [`AFPercent_Format(2, 1, false);` + `event.source.value = event.value;`],
                test2: [`AFPercent_Format(2, 1, true);` + `event.source.value = event.value;`]
              },
              type: "text"
            }]
          },
          appInfo: {
            language: "en-US",
            platform: "Linux x86_64"
          },
          calculationOrder: [],
          dispatchEventName: "_dispatchMe"
        };
        sandbox.createSandbox(data);
        await sandbox.dispatchEventInSandbox({
          id: refId,
          value: "0.456789",
          name: "test1"
        });
        expect(send_queue.has(refId)).toEqual(true);
        expect(send_queue.get(refId)).toEqual({
          id: refId,
          value: "45.68%"
        });
        send_queue.delete(refId);
        await sandbox.dispatchEventInSandbox({
          id: refId,
          value: "0.456789",
          name: "test2"
        });
        expect(send_queue.has(refId)).toEqual(true);
        expect(send_queue.get(refId)).toEqual({
          id: refId,
          value: "%45.68"
        });
      });
    });
    describe("AFDate_Format", function () {
      it("should format a date", async () => {
        const refId = getId();
        const data = {
          objects: {
            field: [{
              id: refId,
              value: "",
              actions: {
                test1: [`AFDate_Format(0);event.source.value = event.value;`],
                test2: [`AFDate_Format(12);event.source.value = event.value;`]
              },
              type: "text"
            }]
          },
          appInfo: {
            language: "en-US",
            platform: "Linux x86_64"
          },
          calculationOrder: [],
          dispatchEventName: "_dispatchMe"
        };
        sandbox.createSandbox(data);
        await sandbox.dispatchEventInSandbox({
          id: refId,
          value: "Sun Apr 15 2007 03:14:15",
          name: "test1"
        });
        expect(send_queue.has(refId)).toEqual(true);
        expect(send_queue.get(refId)).toEqual({
          id: refId,
          value: "4/15"
        });
        send_queue.delete(refId);
        await sandbox.dispatchEventInSandbox({
          id: refId,
          value: "Sun Apr 15 2007 03:14:15",
          name: "test2"
        });
        expect(send_queue.has(refId)).toEqual(true);
        expect(send_queue.get(refId)).toEqual({
          id: refId,
          value: "4/15/07 3:14 am"
        });
      });
    });
    describe("AFRange_Validate", function () {
      it("should validate a number in range [a, b]", async () => {
        const refId = getId();
        const data = {
          objects: {
            field: [{
              id: refId,
              value: "",
              actions: {
                Validate: [`AFRange_Validate(true, 123, true, 456);`]
              },
              type: "text"
            }]
          },
          appInfo: {
            language: "en-US",
            platform: "Linux x86_64"
          },
          calculationOrder: [],
          dispatchEventName: "_dispatchMe"
        };
        sandbox.createSandbox(data);
        await sandbox.dispatchEventInSandbox({
          id: refId,
          value: "321",
          name: "Keystroke",
          willCommit: true
        });
        expect(send_queue.has(refId)).toEqual(true);
        expect(send_queue.get(refId)).toEqual({
          id: refId,
          siblings: null,
          value: "321",
          formattedValue: null
        });
      });
      it("should invalidate a number out of range [a, b]", async () => {
        const refId = getId();
        const data = {
          objects: {
            field: [{
              id: refId,
              value: "",
              actions: {
                Validate: [`AFRange_Validate(true, 123, true, 456);`]
              },
              type: "text"
            }]
          },
          appInfo: {
            language: "en-US",
            platform: "Linux x86_64"
          },
          calculationOrder: [],
          dispatchEventName: "_dispatchMe"
        };
        sandbox.createSandbox(data);
        await sandbox.dispatchEventInSandbox({
          id: refId,
          value: "12",
          name: "Keystroke",
          willCommit: true
        });
        expect(send_queue.has("alert")).toEqual(true);
        expect(send_queue.get("alert")).toEqual({
          command: "alert",
          value: "Invalid value: must be greater than or equal to 123 and less than or equal to 456."
        });
      });
    });
    describe("AFSimple_Calculate", function () {
      it("should compute the sum of several fields", async () => {
        const refIds = [0, 1, 2, 3, 4].map(_ => getId());
        const data = {
          objects: {
            field1: [{
              id: refIds[0],
              value: "",
              actions: {},
              type: "text"
            }],
            field2: [{
              id: refIds[1],
              value: "",
              actions: {},
              type: "text"
            }],
            field3: [{
              id: refIds[2],
              value: "",
              actions: {},
              type: "text"
            }],
            field4: [{
              id: refIds[3],
              value: "",
              actions: {
                Calculate: [`AFSimple_Calculate("SUM", ["field1", "field2", "field3", "unknown"]);`]
              },
              type: "text"
            }],
            field5: [{
              id: refIds[4],
              value: "",
              actions: {
                Calculate: [`AFSimple_Calculate("SUM", "field1, field2, field3, unknown");`]
              },
              type: "text"
            }]
          },
          appInfo: {
            language: "en-US",
            platform: "Linux x86_64"
          },
          calculationOrder: [refIds[3], refIds[4]],
          dispatchEventName: "_dispatchMe"
        };
        sandbox.createSandbox(data);
        await sandbox.dispatchEventInSandbox({
          id: refIds[0],
          value: "1",
          name: "Keystroke",
          willCommit: true
        });
        expect(send_queue.has(refIds[3])).toEqual(true);
        expect(send_queue.get(refIds[3])).toEqual({
          id: refIds[3],
          siblings: null,
          value: 1,
          formattedValue: null
        });
        await sandbox.dispatchEventInSandbox({
          id: refIds[1],
          value: "2",
          name: "Keystroke",
          willCommit: true
        });
        expect(send_queue.has(refIds[3])).toEqual(true);
        expect(send_queue.get(refIds[3])).toEqual({
          id: refIds[3],
          siblings: null,
          value: 3,
          formattedValue: null
        });
        await sandbox.dispatchEventInSandbox({
          id: refIds[2],
          value: "3",
          name: "Keystroke",
          willCommit: true
        });
        expect(send_queue.has(refIds[3])).toEqual(true);
        expect(send_queue.get(refIds[3])).toEqual({
          id: refIds[3],
          siblings: null,
          value: 6,
          formattedValue: null
        });
        expect(send_queue.has(refIds[4])).toEqual(true);
        expect(send_queue.get(refIds[4])).toEqual({
          id: refIds[4],
          siblings: null,
          value: 6,
          formattedValue: null
        });
      });
      it("should compute the sum of several fields in fields tree", async () => {
        const refIds = [0, 1, 2, 3, 4, 5].map(_ => getId());
        const data = {
          objects: {
            field1: [{
              id: refIds[0],
              kidIds: [refIds[1], refIds[2]]
            }],
            "field1.field2": [{
              id: refIds[1],
              kidIds: [refIds[3]]
            }],
            "field1.field3": [{
              id: refIds[2],
              value: "",
              actions: {},
              type: "text"
            }],
            "field1.field2.field4": [{
              id: refIds[3],
              kidIds: [refIds[4]]
            }],
            "field1.field2.field4.field5": [{
              id: refIds[4],
              value: "",
              actions: {},
              type: "text"
            }],
            field6: [{
              id: refIds[5],
              value: "",
              actions: {
                Calculate: [`AFSimple_Calculate("SUM", "field1");`]
              },
              type: "text"
            }]
          },
          appInfo: {
            language: "en-US",
            platform: "Linux x86_64"
          },
          calculationOrder: [refIds[5]],
          dispatchEventName: "_dispatchMe"
        };
        sandbox.createSandbox(data);
        await sandbox.dispatchEventInSandbox({
          id: refIds[2],
          value: "123",
          name: "Keystroke",
          willCommit: true
        });
        expect(send_queue.has(refIds[5])).toEqual(true);
        expect(send_queue.get(refIds[5])).toEqual({
          id: refIds[5],
          siblings: null,
          value: 123,
          formattedValue: null
        });
        await sandbox.dispatchEventInSandbox({
          id: refIds[4],
          value: "456",
          name: "Keystroke",
          willCommit: true
        });
        expect(send_queue.has(refIds[5])).toEqual(true);
        expect(send_queue.get(refIds[5])).toEqual({
          id: refIds[5],
          siblings: null,
          value: 579,
          formattedValue: null
        });
      });
    });
    describe("AFSpecial_KeystrokeEx", function () {
      it("should validate a phone number on a keystroke event", async () => {
        const refId = getId();
        const data = {
          objects: {
            field: [{
              id: refId,
              value: "",
              actions: {
                Keystroke: [`AFSpecial_KeystrokeEx("9AXO");`]
              },
              type: "text"
            }]
          },
          appInfo: {
            language: "en-US",
            platform: "Linux x86_64"
          },
          calculationOrder: [],
          dispatchEventName: "_dispatchMe"
        };
        sandbox.createSandbox(data);
        await sandbox.dispatchEventInSandbox({
          id: refId,
          value: "",
          change: "3",
          name: "Keystroke",
          willCommit: false,
          selStart: 0,
          selEnd: 0
        });
        expect(send_queue.has(refId)).toEqual(true);
        send_queue.delete(refId);
        await sandbox.dispatchEventInSandbox({
          id: refId,
          value: "3",
          change: "F",
          name: "Keystroke",
          willCommit: false,
          selStart: 1,
          selEnd: 1
        });
        expect(send_queue.has(refId)).toEqual(true);
        send_queue.delete(refId);
        await sandbox.dispatchEventInSandbox({
          id: refId,
          value: "3F",
          change: "?",
          name: "Keystroke",
          willCommit: false,
          selStart: 2,
          selEnd: 2
        });
        expect(send_queue.has(refId)).toEqual(true);
        send_queue.delete(refId);
        await sandbox.dispatchEventInSandbox({
          id: refId,
          value: "3F?",
          change: "@",
          name: "Keystroke",
          willCommit: false,
          selStart: 3,
          selEnd: 3
        });
        expect(send_queue.has(refId)).toEqual(true);
        expect(send_queue.get(refId)).toEqual({
          id: refId,
          siblings: null,
          value: "3F?",
          selRange: [3, 3]
        });
        send_queue.delete(refId);
        await sandbox.dispatchEventInSandbox({
          id: refId,
          value: "3F?",
          change: "0",
          name: "Keystroke",
          willCommit: false,
          selStart: 3,
          selEnd: 3
        });
        expect(send_queue.has(refId)).toEqual(true);
        send_queue.delete(refId);
        await sandbox.dispatchEventInSandbox({
          id: refId,
          value: "3F?0",
          name: "Keystroke",
          willCommit: true,
          selStart: 4,
          selEnd: 4
        });
        expect(send_queue.has(refId)).toEqual(true);
        expect(send_queue.get(refId)).toEqual({
          id: refId,
          siblings: null,
          value: "3F?0",
          formattedValue: null
        });
      });
    });
    describe("AFSpecial_Keystroke", function () {
      it("should validate a zip code on a keystroke event", async () => {
        const refId = getId();
        const data = {
          objects: {
            field: [{
              id: refId,
              value: "",
              actions: {
                Keystroke: [`AFSpecial_Keystroke(0);`]
              },
              type: "text"
            }]
          },
          appInfo: {
            language: "en-US",
            platform: "Linux x86_64"
          },
          calculationOrder: [],
          dispatchEventName: "_dispatchMe"
        };
        sandbox.createSandbox(data);
        let value = "";
        const changes = "12345";
        let i = 0;
        for (; i < changes.length; i++) {
          const change = changes.charAt(i);
          await sandbox.dispatchEventInSandbox({
            id: refId,
            value,
            change,
            name: "Keystroke",
            willCommit: false,
            selStart: i,
            selEnd: i
          });
          expect(send_queue.has(refId)).toEqual(true);
          send_queue.delete(refId);
          value += change;
        }
        await sandbox.dispatchEventInSandbox({
          id: refId,
          value,
          change: "A",
          name: "Keystroke",
          willCommit: false,
          selStart: i,
          selEnd: i
        });
        expect(send_queue.has(refId)).toEqual(true);
        expect(send_queue.get(refId)).toEqual({
          id: refId,
          siblings: null,
          value,
          selRange: [i, i]
        });
        send_queue.delete(refId);
      });
      it("should validate a US phone number (long) on a keystroke event", async () => {
        const refId = getId();
        const data = {
          objects: {
            field: [{
              id: refId,
              value: "",
              actions: {
                Keystroke: [`AFSpecial_Keystroke(2);`]
              },
              type: "text"
            }]
          },
          appInfo: {
            language: "en-US",
            platform: "Linux x86_64"
          },
          calculationOrder: [],
          dispatchEventName: "_dispatchMe"
        };
        sandbox.createSandbox(data);
        let value = "";
        const changes = "(123) 456-7890";
        let i = 0;
        for (; i < changes.length; i++) {
          const change = changes.charAt(i);
          await sandbox.dispatchEventInSandbox({
            id: refId,
            value,
            change,
            name: "Keystroke",
            willCommit: false,
            selStart: i,
            selEnd: i
          });
          expect(send_queue.has(refId)).toEqual(true);
          send_queue.delete(refId);
          value += change;
        }
        await sandbox.dispatchEventInSandbox({
          id: refId,
          value,
          change: "A",
          name: "Keystroke",
          willCommit: false,
          selStart: i,
          selEnd: i
        });
        expect(send_queue.has(refId)).toEqual(true);
        expect(send_queue.get(refId)).toEqual({
          id: refId,
          siblings: null,
          value,
          selRange: [i, i]
        });
        send_queue.delete(refId);
      });
      it("should validate a US phone number (short) on a keystroke event", async () => {
        const refId = getId();
        const data = {
          objects: {
            field: [{
              id: refId,
              value: "",
              actions: {
                Keystroke: [`AFSpecial_Keystroke(2);`]
              },
              type: "text"
            }]
          },
          appInfo: {
            language: "en-US",
            platform: "Linux x86_64"
          },
          calculationOrder: [],
          dispatchEventName: "_dispatchMe"
        };
        sandbox.createSandbox(data);
        let value = "";
        const changes = "123-4567";
        let i = 0;
        for (; i < changes.length; i++) {
          const change = changes.charAt(i);
          await sandbox.dispatchEventInSandbox({
            id: refId,
            value,
            change,
            name: "Keystroke",
            willCommit: false,
            selStart: i,
            selEnd: i
          });
          expect(send_queue.has(refId)).toEqual(true);
          send_queue.delete(refId);
          value += change;
        }
        await sandbox.dispatchEventInSandbox({
          id: refId,
          value,
          change: "A",
          name: "Keystroke",
          willCommit: false,
          selStart: i,
          selEnd: i
        });
        expect(send_queue.has(refId)).toEqual(true);
        expect(send_queue.get(refId)).toEqual({
          id: refId,
          siblings: null,
          value,
          selRange: [i, i]
        });
        send_queue.delete(refId);
      });
    });
    describe("eMailValidate", function () {
      it("should validate an e-mail address", async () => {
        let value = await myeval(`eMailValidate(123)`);
        expect(value).toEqual(false);
        value = await myeval(`eMailValidate("foo@bar.com")`);
        expect(value).toEqual(true);
        value = await myeval(`eMailValidate("foo bar")`);
        expect(value).toEqual(false);
      });
    });
    describe("AFExactMatch", function () {
      it("should check matching between regexs and a string", async () => {
        let value = await myeval(`AFExactMatch(/\\d+/, "123")`);
        expect(value).toEqual(true);
        value = await myeval(`AFExactMatch(/\\d+/, "foo")`);
        expect(value).toEqual(0);
        value = await myeval(`AFExactMatch([/\\d+/, /[fo]*/], "foo")`);
        expect(value).toEqual(2);
        value = await myeval(`AFExactMatch([/\\d+/, /[fo]*/], "bar")`);
        expect(value).toEqual(0);
      });
    });
  });
});