sinon#SinonStub TypeScript Examples

The following examples show how to use sinon#SinonStub. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: ScheduleTest.ts    From discord-bot with MIT License 6 votes vote down vote up
describe("@Schedule()", () => {
	let sandbox: SinonSandbox;
	let scheduleJobStub: SinonStub;

	beforeEach(() => {
		sandbox = createSandbox();
		scheduleJobStub = sandbox.stub(schedule, "scheduleJob").returns({});
	});

	afterEach(() => sandbox.restore());

	it("schedules a job with the given crontab", () => {
		class Test {
			@Schedule("0 0 * * *")
			doSomething() {
				console.log("test");
			}
		}

		expect(scheduleJobStub.calledOnce).to.be.true;
		expect(scheduleJobStub.args[0][0]).to.equal("0 0 * * *");
	});

	it("schedules a job to run the decorated method", () => {
		class Test {
			@Schedule("0 0 * * *")
			doSomething() {
				console.log("test");
			}
		}

		expect(scheduleJobStub.args[0][1]).to.equal(Test.prototype.doSomething);
	});
});
Example #2
Source File: index.unit.spec.tsx    From next-typescript-materialui-jest-starter with MIT License 6 votes vote down vote up
describe("Button Unit Tests", () => {
  afterEach(() => {
    sandbox.verifyAndRestore();
    cleanup();
  });

  it("should render", () => {
    // Arrange
    sandbox.spy(React, "createElement");

    // Act
    const { container } = render(
      <ThemeProvider theme={theme}>
        <Button color="primary" name={word()} />
      </ThemeProvider>
    );

    // Assert
    expect(container.querySelector("button")).toBeInTheDocument();
    expect((React.createElement as SinonStub).calledWith(MuiButton)).toBe(true);
  });
});
Example #3
Source File: StartServer.test.ts    From dendron with GNU Affero General Public License v3.0 6 votes vote down vote up
suite("StartServer", function () {
  let homeDirStub: SinonStub;

  setupBeforeAfter(this, {
    beforeHook: async () => {
      sinon.restore();
      await resetCodeWorkspace();
      homeDirStub = TestEngineUtils.mockHomeDir();
    },
    afterHook: async () => {
      homeDirStub.restore();
    },
  });

  describe("basic", function () {
    test("ok", function (done) {
      ServerUtils.execServerNode({
        scriptPath: path.join(__dirname, "..", "..", "server.js"),
        logPath: getExtension().context.logPath,
      }).then(({ port }) => {
        expect(port > 0).toBeTruthy();
        done();
      });
    });
  });
});
Example #4
Source File: engine.ts    From dendron with GNU Affero General Public License v3.0 5 votes vote down vote up
static mockHomeDir(dir?: string): SinonStub {
    if (_.isUndefined(dir)) dir = tmpDir().name;
    return sinon.stub(os, "homedir").returns(dir);
  }
Example #5
Source File: tx.ts    From algo-builder with Apache License 2.0 5 votes vote down vote up
describe("Deploy ASA without asa.yaml", () => {
	useFixtureProject("config-project");

	let deployer: Deployer;
	let algod: AlgoOperatorDryRunImpl;
	beforeEach(async () => {
		const env = mkEnv("network1");
		algod = new AlgoOperatorDryRunImpl();
		const deployerCfg = new DeployerConfig(env, algod);
		deployerCfg.asaDefs = { silver: mkASA() };
		deployer = new DeployerDeployMode(deployerCfg);
		stubAlgodGenesisAndTxParams(algod.algodClient);
	});

	afterEach(async () => {
		(algod.algodClient.getTransactionParams as SinonStub).restore();
		(algod.algodClient.genesis as SinonStub).restore();
	});

	it("should deploy asa without asa.yaml", async () => {
		const exp = {
			total: 10000,
			decimals: 0,
			defaultFrozen: false,
			unitName: "SLV",
			url: "url",
			metadataHash: "12312442142141241244444411111133",
			note: "note",
		};
		const execParams: wtypes.ExecParams = {
			type: wtypes.TransactionType.DeployASA,
			sign: wtypes.SignType.SecretKey,
			fromAccount: bobAcc,
			asaName: "silver-1",
			asaDef: exp,
			payFlags: {},
		};

		await deployer.executeTx([execParams]);

		const res = deployer.getASAInfo("silver-1");
		assert.isDefined(res);
		assert.deepEqual(res.assetDef, exp);
	});
});
Example #6
Source File: tx.ts    From algo-builder with Apache License 2.0 5 votes vote down vote up
describe("ASA modify fields", () => {
	useFixtureProject("config-project");
	let deployer: Deployer;
	let execParams: wtypes.ModifyAssetParam;
	let algod: AlgoOperatorDryRunImpl;
	let assetFields: wtypes.AssetModFields;
	beforeEach(async () => {
		const env = mkEnv("network1");
		algod = new AlgoOperatorDryRunImpl();
		const deployerCfg = new DeployerConfig(env, algod);
		deployer = new DeployerDeployMode(deployerCfg);
		assetFields = {
			manager: "",
			clawback: bobAcc.addr,
		};
		execParams = {
			type: wtypes.TransactionType.ModifyAsset,
			sign: wtypes.SignType.SecretKey,
			payFlags: {},
			fromAccount: bobAcc,
			assetID: 1,
			fields: assetFields,
		};
		stubAlgodGenesisAndTxParams(algod.algodClient);
	});

	afterEach(async () => {
		(algod.algodClient.getTransactionParams as SinonStub).restore();
		(algod.algodClient.genesis as SinonStub).restore();
	});

	/**
	 * Verifies correct asset fields are sent to network
	 * @param rawTxns rawTxns Signed transactions in Uint8Array
	 */
	function checkTx(rawTxns: Uint8Array | Uint8Array[]): Promise<ConfirmedTxInfo> {
		if (Array.isArray(rawTxns)) {
			// verify here if group tx
		} else {
			const tx: Transaction = decodeSignedTransaction(rawTxns).txn;
			// Verify if fields are set correctly
			assert.isUndefined(tx.assetManager);
			assert.isUndefined(tx.assetReserve);
			assert.equal(encodeAddress(tx.assetFreeze.publicKey), mockAssetInfo.params.freeze);
			assert.equal(encodeAddress(tx.assetClawback.publicKey), assetFields.clawback);
		}
		(algod.sendAndWait as SinonStub).restore();
		return algod.sendAndWait(rawTxns);
	}

	it("Should set fields, freeze is not sent, therefore it should be picked from assetInfo", async () => {
		// Manager should be set to ""(sent as undefined to network)
		// Clawback should be updated
		stub(algod, "sendAndWait").callsFake(checkTx);

		await deployer.executeTx([execParams]);
	});
});
Example #7
Source File: tx.ts    From algo-builder with Apache License 2.0 5 votes vote down vote up
describe("Opt-In to ASA", () => {
	useFixtureProject("config-project");

	let deployer: Deployer;
	let execParams: wtypes.OptInASAParam;
	let algod: AlgoOperatorDryRunImpl;
	let expected: ConfirmedTxInfo[];
	beforeEach(async () => {
		const env = mkEnv("network1");
		algod = new AlgoOperatorDryRunImpl();
		const deployerCfg = new DeployerConfig(env, algod);
		deployerCfg.asaDefs = { silver: mkASA() };
		deployer = new DeployerDeployMode(deployerCfg);
		await deployer.deployASA("silver", { creator: deployer.accounts[0] });
		execParams = {
			type: wtypes.TransactionType.OptInASA,
			sign: wtypes.SignType.SecretKey,
			payFlags: {},
			fromAccount: bobAcc,
			assetID: 1,
		};
		stubAlgodGenesisAndTxParams(algod.algodClient);

		expected = [
			{
				"confirmed-round": 1,
				"asset-index": 1,
				"application-index": 1,
				"global-state-delta": "string",
				"local-state-delta": "string",
			},
		];
	});

	afterEach(() => {
		(algod.algodClient.getTransactionParams as SinonStub).restore();
		(algod.algodClient.genesis as SinonStub).restore();
	});

	it("should opt-in to asa using asset id as number", async () => {
		const res = await deployer.executeTx([execParams]);

		assert.deepEqual(res, expected);
	});

	it("Should fail if asset name is passed but not found in checkpoints", async () => {
		execParams.assetID = "unknown";

		await expectBuilderErrorAsync(
			async () => await deployer.executeTx([execParams]),
			ERRORS.BUILTIN_TASKS.DEPLOYER_ASA_NOT_DEFINED,
			"unknown"
		);
	});

	it("Should set asset id to asset id of asset name passed", async () => {
		execParams.assetID = "silver";

		const res = await deployer.executeTx([execParams]);

		assert.deepEqual(res, expected);
	});
});
Example #8
Source File: DataLink.test.ts    From viewer-components-react with MIT License 5 votes vote down vote up
describe("DataLink", () => {
  const connectionMock = Moq.Mock.ofType<IModelConnection>();
  const defaultStoryId: string = "testId";
  const defaultClipAtSpaces: boolean = true;
  const defaultRowFormat = { rowFormat: 0 };
  let queryStub: SinonStub;

  beforeEach(() => {
    queryStub = sinon.stub(IModelConnection.prototype, "query");

    connectionMock.setup((x) => x.query(Moq.It.isAny(), undefined, defaultRowFormat))
      .callback(queryStub)
      .returns(queryResultGenerator);
  });

  afterEach(() => {
    queryStub.restore();
    connectionMock.reset();
  });

  it("should return correct data for queryStoryRange", async () => {
    const result = await DataLink.queryStoryRange(connectionMock.object, defaultStoryId, defaultClipAtSpaces);

    expect(result).to.eql(new Range3d(range3DProps.minX, range3DProps.minY, range3DProps.minZ, range3DProps.maxX, range3DProps.maxY, range3DProps.maxZ));
  });

  it("should execute query with correct params", async () => {
    await DataLink.queryStoryRange(connectionMock.object, defaultStoryId, defaultClipAtSpaces);

    const expectedQuery = "select MIN(spel.minX) as minX, MIN(spel.minY) as minY, MIN(spel.minZ) as minZ, MAX(spel.maxX) as maxX, MAX(spel.maxY) as maxY, MAX(spel.maxZ) as maxZ\n\
        from spatialcomposition.CompositeComposesSubComposites ovr join biscore.spatialIndex spel on spel.ECinstanceId = ovr.targetecinstanceid\n\
        where sourceECInstanceId in\n\
        (select sp.ecinstanceid from buildingspatial.story s join buildingspatial.space sp on s.ecinstanceid=sp.composingelement.id where s.ecinstanceid=testId\n\
        union\n\
        select s.ecinstanceid from buildingspatial.story s where s.ecinstanceid=testId)";

    expect(queryStub.calledWith(expectedQuery, undefined, defaultRowFormat)).to.true;
  });

  it("should execute correct query when clipAtSpaces is false", async () => {
    await DataLink.queryStoryRange(connectionMock.object, defaultStoryId, !defaultClipAtSpaces);

    const expectedQuery = "select MIN(spel.minX) as minX, MIN(spel.minY) as minY, MIN(spel.minZ) as minZ, MAX(spel.maxX) as maxX, MAX(spel.maxY) as maxY, MAX(spel.maxZ) as maxZ\n\
        from spatialcomposition.CompositeOverlapsSpatialElements ovr join biscore.spatialIndex spel on spel.ECinstanceId = ovr.targetecinstanceid\n\
        where sourceECInstanceId in\n\
        (select sp.ecinstanceid from buildingspatial.story s join buildingspatial.space sp on s.ecinstanceid=sp.composingelement.id where s.ecinstanceid=testId\n\
        union\n\
        select s.ecinstanceid from buildingspatial.story s where s.ecinstanceid=testId)";

    expect(queryStub.calledWith(expectedQuery, undefined, defaultRowFormat)).to.true;
  });

  it("should run the query again with compositeOverlaps class if compositeCompose returns undefined", async () => {
    connectionMock.reset();
    connectionMock.setup((x) => x.query(Moq.It.isAny(), undefined, defaultRowFormat))
      .callback(queryStub)
      .returns(emptyQueryResultGenerator);

    await DataLink.queryStoryRange(connectionMock.object, defaultStoryId, defaultClipAtSpaces);

    const expectedQuery = "select MIN(spel.minX) as minX, MIN(spel.minY) as minY, MIN(spel.minZ) as minZ, MAX(spel.maxX) as maxX, MAX(spel.maxY) as maxY, MAX(spel.maxZ) as maxZ\n\
        from spatialcomposition.CompositeOverlapsSpatialElements ovr join biscore.spatialIndex spel on spel.ECinstanceId = ovr.targetecinstanceid\n\
        where sourceECInstanceId in\n\
        (select sp.ecinstanceid from buildingspatial.story s join buildingspatial.space sp on s.ecinstanceid=sp.composingelement.id where s.ecinstanceid=testId\n        union\n        select s.ecinstanceid from buildingspatial.story s where s.ecinstanceid=testId)";

    expect(queryStub.callCount).to.eql(2);
    expect(queryStub.calledWith(expectedQuery, undefined, defaultRowFormat));
  });
});
Example #9
Source File: NoteLookupCommand.test.ts    From dendron with GNU Affero General Public License v3.0 5 votes vote down vote up
suite("stateService", function () {
  let homeDirStub: SinonStub;
  const ctx: vscode.ExtensionContext = setupBeforeAfter(this, {
    beforeHook: async (ctx) => {
      new StateService(ctx);
      await resetCodeWorkspace();
      homeDirStub = TestEngineUtils.mockHomeDir();
    },
    afterHook: async () => {
      homeDirStub.restore();
    },
    noSetInstallStatus: true,
  });
  describe("GIVEN user accepts lookup for the first time", () => {
    test("THEN global states firstLookupTime and lastLookupTime are set correctly", (done) => {
      runLegacyMultiWorkspaceTest({
        ctx,
        preSetupHook: ENGINE_HOOKS.setupBasic,
        onInit: async ({ vaults }) => {
          VSCodeUtils.closeAllEditors();

          const cmd = new NoteLookupCommand();
          stubVaultPick(vaults);

          let metaData = MetadataService.instance().getMeta();
          expect(_.isUndefined(metaData.firstLookupTime)).toBeTruthy();
          expect(_.isUndefined(metaData.lastLookupTime)).toBeTruthy();
          await cmd.run({
            noConfirm: true,
          });
          metaData = MetadataService.instance().getMeta();
          expect(_.isUndefined(metaData.firstLookupTime)).toBeFalsy();
          expect(_.isUndefined(metaData.lastLookupTime)).toBeFalsy();

          cmd.cleanUp();
          done();
        },
      });
    });
  });

  describe("GIVEN user accepts subsequent lookup", () => {
    test("THEN global state lastLookupTime is set correctly", (done) => {
      runLegacyMultiWorkspaceTest({
        ctx,
        preSetupHook: ENGINE_HOOKS.setupBasic,
        onInit: async ({ vaults }) => {
          VSCodeUtils.closeAllEditors();

          const cmd = new NoteLookupCommand();
          stubVaultPick(vaults);

          await cmd.run({
            noConfirm: true,
          });
          let metaData = MetadataService.instance().getMeta();
          const firstLookupTime = metaData.firstLookupTime;
          const lastLookupTime = metaData.lastLookupTime;
          expect(_.isUndefined(firstLookupTime)).toBeFalsy();
          expect(_.isUndefined(lastLookupTime)).toBeFalsy();

          await cmd.run({
            noConfirm: true,
          });

          metaData = MetadataService.instance().getMeta();
          expect(metaData.firstLookupTime).toEqual(firstLookupTime);
          expect(metaData.lastLookupTime).toNotEqual(lastLookupTime);

          cmd.cleanUp();
          done();
        },
      });
    });
  });
});
Example #10
Source File: Extension-PostInstall.test.ts    From dendron with GNU Affero General Public License v3.0 5 votes vote down vote up
suite("GIVEN keybindings conflict", function () {
  let promptSpy: SinonSpy;
  let installStatusStub: SinonStub;
  describeMultiWS(
    "GIVEN initial install",
    {
      beforeHook: async () => {
        installStatusStub = sinon
          .stub(VSCodeUtils, "getInstallStatusForExtension")
          .returns(InstallStatus.INITIAL_INSTALL);
        promptSpy = sinon.spy(KeybindingUtils, "maybePromptKeybindingConflict");
      },
      noSetInstallStatus: true,
    },
    () => {
      beforeEach(() => {
        installStatusStub = sinon
          .stub(
            KeybindingUtils,
            "getInstallStatusForKnownConflictingExtensions"
          )
          .returns([{ id: "dummyExt", installed: true }]);
      });

      afterEach(() => {
        installStatusStub.restore();
      });

      after(() => {
        promptSpy.restore();
      });

      test("THEN maybePromptKeybindingConflict is called", async () => {
        expect(promptSpy.called).toBeTruthy();
      });
    }
  );

  describeMultiWS(
    "GIVEN not initial install",
    {
      beforeHook: async () => {
        promptSpy = sinon.spy(KeybindingUtils, "maybePromptKeybindingConflict");
      },
    },
    () => {
      beforeEach(() => {
        installStatusStub = sinon
          .stub(
            KeybindingUtils,
            "getInstallStatusForKnownConflictingExtensions"
          )
          .returns([{ id: "dummyExt", installed: true }]);
      });

      afterEach(() => {
        installStatusStub.restore();
      });

      after(() => {
        promptSpy.restore();
      });

      test("THEN maybePromptKeybindingConflict is not called", async () => {
        expect(promptSpy.called).toBeFalsy();
      });
    }
  );
});
Example #11
Source File: NoteLookupCommand.test.ts    From dendron with GNU Affero General Public License v3.0 4 votes vote down vote up
suite("NoteLookupCommand", function () {
  const ctx: vscode.ExtensionContext = setupBeforeAfter(this, {});

  const getTodayInScratchDateFormat = () => {
    const dateFormat = DendronExtension.configuration().get<string>(
      CONFIG["DEFAULT_SCRATCH_DATE_FORMAT"].key
    ) as string;
    const today = Time.now().toFormat(dateFormat);
    return today.split(".").slice(0, -1).join(".");
  };

  describe("enrichInputs", () => {
    test("edge, quickpick cleans up when hidden", (done) => {
      runLegacyMultiWorkspaceTest({
        ctx,
        preSetupHook: async ({ wsRoot, vaults }) => {
          await ENGINE_HOOKS.setupBasic({ wsRoot, vaults });
        },
        onInit: async () => {
          const cmd = new NoteLookupCommand();
          const opts = await cmd.gatherInputs();
          const out = cmd.enrichInputs(opts);
          expect(
            HistoryService.instance().subscribersv2.lookupProvider.length
          ).toEqual(1);
          // delicate test
          setTimeout(async () => {
            opts.quickpick.hide();
            await out;
            expect(
              HistoryService.instance().subscribersv2.lookupProvider.length
            ).toEqual(0);
            cmd.cleanUp();
            done();
          }, 1000);
          // await out;
        },
      });
    });
  });

  // NOTE: think these tests are wrong
  describe("updateItems", () => {
    test("empty querystring", (done) => {
      runLegacyMultiWorkspaceTest({
        ctx,
        preSetupHook: async ({ wsRoot, vaults }) => {
          await ENGINE_HOOKS.setupBasic({ wsRoot, vaults });
        },
        onInit: async ({ vaults }) => {
          const cmd = new NoteLookupCommand();
          stubVaultPick(vaults);
          const opts = (await cmd.run({
            noConfirm: true,
            initialValue: "",
          }))!;
          expect(
            !_.isUndefined(
              _.find(opts.quickpick.selectedItems, { fname: "root" })
            )
          ).toBeTruthy();
          expect(
            !_.isUndefined(
              _.find(opts.quickpick.selectedItems, { fname: "foo" })
            )
          ).toBeTruthy();
          cmd.cleanUp();
          done();
        },
      });
    });

    test("star query", (done) => {
      runLegacyMultiWorkspaceTest({
        ctx,
        preSetupHook: async ({ wsRoot, vaults }) => {
          await ENGINE_HOOKS.setupBasic({ wsRoot, vaults });
        },
        onInit: async ({ vaults }) => {
          const cmd = new NoteLookupCommand();
          stubVaultPick(vaults);
          const opts = (await cmd.run({
            noConfirm: true,
            initialValue: "*",
          }))!;
          expect(opts.quickpick.selectedItems.length).toEqual(6);
          cmd.cleanUp();
          done();
        },
      });
    });

    test(`WHEN partial match but not exact match THEN bubble up 'Create New'`, (done) => {
      runLegacyMultiWorkspaceTest({
        ctx,
        preSetupHook: async ({ wsRoot, vaults }) => {
          await ENGINE_HOOKS.setupBasic({ wsRoot, vaults });
        },
        onInit: async ({ vaults }) => {
          const cmd = new NoteLookupCommand();
          stubVaultPick(vaults);
          const opts = (await cmd.run({
            noConfirm: true,
            initialValue: "foo.ch",
          }))!;
          // Check that Create New comes first.
          expectCreateNew({ item: opts.quickpick.selectedItems[0] });

          // Check that its not just create new in the quick pick.
          expect(opts.quickpick.selectedItems.length > 1).toBeTruthy();
          cmd.cleanUp();
          done();
        },
      });
    });

    test("domain query with schema", (done) => {
      runLegacyMultiWorkspaceTest({
        ctx,
        preSetupHook: async ({ wsRoot, vaults }) => {
          await ENGINE_HOOKS.setupBasic({ wsRoot, vaults });
        },
        onInit: async ({ vaults, engine }) => {
          const cmd = new NoteLookupCommand();
          stubVaultPick(vaults);
          await cmd.run({
            noConfirm: true,
            initialValue: "foo",
          })!;
          const editor = VSCodeUtils.getActiveTextEditor();
          const actualNote = WSUtils.getNoteFromDocument(editor!.document);
          const expectedNote = engine.notes["foo"];
          expect(actualNote).toEqual(expectedNote);
          expect(actualNote!.schema).toEqual({
            moduleId: "foo",
            schemaId: "foo",
          });
          cmd.cleanUp();
          done();
        },
      });
    });

    test("child query with schema", (done) => {
      runLegacyMultiWorkspaceTest({
        ctx,
        preSetupHook: async ({ wsRoot, vaults }) => {
          await ENGINE_HOOKS.setupBasic({ wsRoot, vaults });
        },
        onInit: async ({ vaults, engine }) => {
          const cmd = new NoteLookupCommand();
          stubVaultPick(vaults);
          await cmd.run({
            noConfirm: true,
            initialValue: "foo.ch1",
          })!;
          const editor = VSCodeUtils.getActiveTextEditor();
          const actualNote = WSUtils.getNoteFromDocument(editor!.document);
          const expectedNote = engine.notes["foo.ch1"];
          expect(actualNote).toEqual(expectedNote);
          expect(actualNote!.schema).toEqual({
            moduleId: "foo",
            schemaId: "ch1",
          });

          cmd.cleanUp();
          done();
        },
      });
    });

    test("direct child filter", (done) => {
      runLegacyMultiWorkspaceTest({
        ctx,
        preSetupHook: async ({ wsRoot, vaults }) => {
          await ENGINE_HOOKS.setupBasic({ wsRoot, vaults });
          await NOTE_PRESETS_V4.NOTE_SIMPLE_GRANDCHILD.create({
            wsRoot,
            vault: TestEngineUtils.vault1(vaults),
          });
          await NoteTestUtilsV4.createNote({
            wsRoot,
            fname: "foo.ch2.gch1",
            vault: vaults[0],
            props: { stub: true },
          });
        },
        onInit: async ({ vaults }) => {
          const cmd = new NoteLookupCommand();
          stubVaultPick(vaults);
          const opts = (await cmd.run({
            noConfirm: true,
            initialValue: "foo.",
            filterMiddleware: ["directChildOnly"],
          }))!;
          // Doesn't find grandchildren
          expect(
            _.find(opts.quickpick.selectedItems, { fname: "foo.ch1.gch1" })
          ).toEqual(undefined);
          // Doesn't find stubs
          expect(
            _.find(opts.quickpick.selectedItems, { fname: "foo.ch2" })
          ).toEqual(undefined);
          expect(_.isUndefined(opts.quickpick.filterMiddleware)).toBeFalsy();
          cmd.cleanUp();
          done();
        },
      });
    });

    test("picker has value of opened note by default", (done) => {
      runLegacyMultiWorkspaceTest({
        ctx,
        preSetupHook: ENGINE_HOOKS_MULTI.setupBasicMulti,
        onInit: async ({ vaults, engine }) => {
          const cmd = new NoteLookupCommand();
          stubVaultPick(vaults);
          await WSUtils.openNote(engine.notes["foo"]);
          const opts = (await cmd.run({ noConfirm: true }))!;
          expect(opts.quickpick.value).toEqual("foo");
          expect(_.first(opts.quickpick.selectedItems)?.fname).toEqual("foo");
          cmd.cleanUp();
          done();
        },
      });
    });

    test("schema suggestions basic", (done) => {
      runLegacyMultiWorkspaceTest({
        ctx,
        postSetupHook: async ({ wsRoot, vaults }) => {
          await ENGINE_HOOKS.setupBasic({ wsRoot, vaults });
          const vpath = vault2Path({ vault: vaults[0], wsRoot });
          fs.removeSync(path.join(vpath, "foo.ch1.md"));
        },
        onInit: async ({ vaults }) => {
          const cmd = new NoteLookupCommand();
          stubVaultPick(vaults);

          const { controller, provider, quickpick } = await cmd.gatherInputs({
            noteType: LookupNoteTypeEnum.journal,
          });

          quickpick.value = "foo.";
          await provider.onUpdatePickerItems({
            picker: quickpick,
            token: controller.cancelToken.token,
            // fuzzThreshold: controller.fuzzThreshold,
          });
          const schemaItem = _.pick(
            _.find(quickpick.items, { fname: "foo.ch1" }),
            ["fname", "schemaStub"]
          );
          expect(schemaItem).toEqual({
            fname: "foo.ch1",
            schemaStub: true,
          });
          cmd.cleanUp();
          done();
        },
      });
    });
  });

  /**
   * Notes to choose from (root.md excluded):
   *
   <pre>
   vault1/
   ├── bar.ch1.gch1.ggch1.md
   ├── bar.ch1.gch1.md
   ├── bar.ch1.md
   ├── bar.md
   ├── foo.ch1.gch1.ggch1.md
   ├── foo.ch1.gch1.md
   ├── foo.ch1.gch2.md
   ├── foo.ch1.md
   ├── foo.ch2.md
   ├── goo.ends-with-ch1.no-ch1-by-itself.md
   └── foo.md
   </pre>
   * */
  async function runLookupInHierarchyTestWorkspace(
    initialValue: string,
    assertions: (out: CommandOutput) => void,
    done: Done
  ) {
    await runLegacyMultiWorkspaceTest({
      ctx,
      preSetupHook: async ({ wsRoot, vaults }) => {
        await ENGINE_HOOKS.setupHierarchyForLookupTests({ wsRoot, vaults });
      },
      onInit: async () => {
        const cmd = new NoteLookupCommand();

        const out: CommandOutput = (await cmd.run({
          noConfirm: true,
          initialValue,
        }))!;

        assertions(out);
        cmd.cleanUp();
        done();
      },
    });
  }

  describe(`GIVEN default note look up settings:`, () => {
    test("WHEN running simplest query THEN find the matching value", (done) => {
      runLookupInHierarchyTestWorkspace(
        "ends-with-ch1",
        (out) => {
          expectQuickPick(out.quickpick).toIncludeFname(
            "goo.ends-with-ch1.no-ch1-by-itself"
          );
        },
        done
      );
    });

    describe(`Test: Queries ending with dot`, () => {
      test("WHEN querying with 'with-ch1.' THEN find partial match within hierarchy and show its children..", (done) => {
        runLookupInHierarchyTestWorkspace(
          "with-ch1.",
          (out) => {
            expectQuickPick(out.quickpick).toIncludeFname(
              "goo.ends-with-ch1.no-ch1-by-itself"
            );
            expectQuickPick(out.quickpick).toNotIncludeFname("foo.ch1.gch1");
          },
          done
        );
      });

      test("WHEN querying with 'ch1.gch1.' THEN finds direct match within hierarchy.", (done) => {
        runLookupInHierarchyTestWorkspace(
          "ch1.gch1.",
          (out) => {
            // Showing direct children of matches in different hierarchies:
            expectQuickPick(out.quickpick).toIncludeFname("bar.ch1.gch1.ggch1");
            expectQuickPick(out.quickpick).toIncludeFname("foo.ch1.gch1.ggch1");
            // Not showing our own match
            expectQuickPick(out.quickpick).toNotIncludeFname("bar.ch1.gch1");
          },
          done
        );
      });

      // Closest candidate is 'goo.ends-with-ch1.no-ch1-by-itself' which does contain 'ends-with-'
      // however since we add the dot to the query we expect at least the postfix of the part
      // of the hierarchy to match such as with 'with-ch1.' test. Here we deem it as not matching anything.
      test("WHEN querying with 'ends-with-.' THEN empty quick pick", (done) => {
        runLookupInHierarchyTestWorkspace(
          "ends-with-.",
          (out) => {
            expectQuickPick(out.quickpick).toBeEmpty();
          },
          done
        );
      });
    });

    describe(`Test extended search`, () => {
      test("WHEN running query with exclusion THEN exclude unwanted but keep others", (done) => {
        runLookupInHierarchyTestWorkspace(
          "!bar ch1",
          (out) => {
            expectQuickPick(out.quickpick).toIncludeFname("foo.ch1");
            expectQuickPick(out.quickpick).toNotIncludeFname("bar.ch1");
          },
          done
        );
      });

      test("WHEN running `ends with query` THEN filter to values that end with desired query.", (done) => {
        runLookupInHierarchyTestWorkspace(
          "foo$",
          (out) => {
            expectQuickPick(out.quickpick).toIncludeFname("foo");
            expectQuickPick(out.quickpick).toNotIncludeFname("foo.ch1");
          },
          done
        );
      });

      test("WHEN running query with (|) THEN match both values", (done) => {
        runLookupInHierarchyTestWorkspace(
          "foo | bar",
          (out) => {
            expectQuickPick(out.quickpick).toIncludeFname("foo.ch1");
            expectQuickPick(out.quickpick).toIncludeFname("bar.ch1");
          },
          done
        );
      });
    });

    describe(`WHEN user look up a note (without using a space) where the query doesn't match the note's case`, () => {
      test("THEN lookup result must cantain all matching values irrespective of case", (done) => {
        runLookupInHierarchyTestWorkspace(
          "bar.CH1",
          (out) => {
            expectQuickPick(out.quickpick).toIncludeFname("bar.ch1");
          },
          done
        );
      });
    });
  });

  describe("onAccept", () => {
    describeMultiWS(
      "WHEN new NODE",
      {
        ctx,
        preSetupHook: ENGINE_HOOKS_MULTI.setupBasicMulti,
      },
      () => {
        test("THEN create new item has name of quickpick value", async () => {
          const { vaults } = ExtensionProvider.getDWorkspace();
          const cmd = new NoteLookupCommand();
          stubVaultPick(vaults);
          const opts = (await cmd.run({
            noConfirm: true,
            initialValue: "foobar",
          }))!;
          expect(opts.quickpick.selectedItems.length).toEqual(1);
          const lastItem = _.last(opts.quickpick.selectedItems);
          expect(_.pick(lastItem, ["id", "fname"])).toEqual({
            id: "Create New",
            fname: "foobar",
          });
          expect(
            WSUtils.getNoteFromDocument(
              VSCodeUtils.getActiveTextEditorOrThrow().document
            )?.fname
          ).toEqual("foobar");
        });
      }
    );

    describeMultiWS(
      "WHEN a new note with .md in its name is created",
      {
        ctx,
        preSetupHook: ENGINE_HOOKS_MULTI.setupBasicMulti,
      },
      () => {
        test("THEN its title generation should not break", async () => {
          const { vaults } = ExtensionProvider.getDWorkspace();
          const cmd = new NoteLookupCommand();
          stubVaultPick(vaults);
          const opts = (await cmd.run({
            noConfirm: true,
            initialValue: "learn.mdone.test",
          }))!;
          expect(opts.quickpick.selectedItems.length).toEqual(1);
          const lastItem = _.last(opts.quickpick.selectedItems);
          expect(_.pick(lastItem, ["id", "fname"])).toEqual({
            id: "Create New",
            fname: "learn.mdone.test",
          });
          const note = ExtensionProvider.getWSUtils().getNoteFromDocument(
            VSCodeUtils.getActiveTextEditorOrThrow().document
          );
          expect(note?.fname).toEqual("learn.mdone.test");
          expect(note?.title).toEqual("Test");
        });
      }
    );

    describeMultiWS(
      "WHEN user lookup a note where the query doesn't match the note's case in multi-vault setup ",
      {
        ctx,
        preSetupHook: ENGINE_HOOKS_MULTI.setupBasicMulti,
      },
      () => {
        test("THEN result must include note irresepective of casing", async () => {
          const cmd = new NoteLookupCommand();
          const opts = (await cmd.run({
            noConfirm: true,
            initialValue: "FOOCH1",
          }))!;
          expectQuickPick(opts.quickpick).toIncludeFname("foo.ch1");
        });
      }
    );

    describeMultiWS(
      "WHEN new node is stub",
      {
        ctx,
        preSetupHook: async ({ vaults, wsRoot }) => {
          const vault = TestEngineUtils.vault1(vaults);
          await NOTE_PRESETS_V4.NOTE_SIMPLE_CHILD.create({
            vault,
            wsRoot,
          });
        },
      },
      () => {
        test("THEN a note is created and stub property is removed", async () => {
          const { vaults, engine, wsRoot } = ExtensionProvider.getDWorkspace();
          const cmd = new NoteLookupCommand();
          const vault = TestEngineUtils.vault1(vaults);
          stubVaultPick(vaults);
          const opts = (await cmd.run({
            noConfirm: true,
            initialValue: "foo",
          }))!;
          expect(_.first(opts.quickpick.selectedItems)?.fname).toEqual("foo");
          const fooNote = NoteUtils.getNoteOrThrow({
            fname: "foo",
            notes: engine.notes,
            vault,
            wsRoot,
          });
          expect(fooNote.stub).toBeFalsy();
        });
      }
    );

    test("new domain", (done) => {
      runLegacyMultiWorkspaceTest({
        ctx,
        preSetupHook: ENGINE_HOOKS.setupBasic,
        onInit: async ({ vaults, engine }) => {
          const cmd = new NoteLookupCommand();
          stubVaultPick(vaults);
          await cmd.run({
            noConfirm: true,
            initialValue: "bar",
          })!;
          const barFromEngine = engine.notes["bar"];
          const editor = VSCodeUtils.getActiveTextEditor()!;
          const activeNote = WSUtils.getNoteFromDocument(editor.document);
          expect(activeNote).toEqual(barFromEngine);
          expect(
            DNodeUtils.isRoot(engine.notes[barFromEngine.parent as string])
          );
          done();
        },
      });
    });

    test("regular multi-select, no pick new", (done) => {
      runLegacyMultiWorkspaceTest({
        ctx,
        preSetupHook: ENGINE_HOOKS_MULTI.setupBasicMulti,
        onInit: async ({ vaults, wsRoot }) => {
          const vault = _.find(vaults, { fsPath: "vault2" });
          const cmd = new NoteLookupCommand();
          sinon.stub(PickerUtilsV2, "getVaultForOpenEditor").returns(vault!);

          const opts = (await cmd.run({
            noConfirm: true,
            initialValue: "foobar",
            multiSelect: true,
          }))!;
          expect(opts.quickpick.selectedItems.length).toEqual(0);
          expect(_.last(opts.quickpick.selectedItems)?.title).toNotEqual(
            "Create New"
          );
          expect(
            await EngineTestUtilsV4.checkVault({
              wsRoot,
              vault: vault!,
              match: ["foobar.md"],
            })
          ).toBeTruthy();
          done();
        },
      });
    });

    test("lookupConfirmVaultOnCreate = true, existing vault", (done) => {
      runLegacyMultiWorkspaceTest({
        ctx,
        preSetupHook: ENGINE_HOOKS_MULTI.setupBasicMulti,
        onInit: async ({ wsRoot, vaults }) => {
          withConfig(
            (config) => {
              ConfigUtils.setNoteLookupProps(
                config,
                "confirmVaultOnCreate",
                true
              );
              return config;
            },
            { wsRoot }
          );

          const fname = NOTE_PRESETS_V4.NOTE_SIMPLE_OTHER.fname;
          const vault = _.find(vaults, { fsPath: "vault2" });
          const cmd = new NoteLookupCommand();
          sinon
            .stub(PickerUtilsV2, "promptVault")
            .returns(Promise.resolve(vault));
          const { quickpick } = (await cmd.run({
            noConfirm: true,
            initialValue: fname,
            fuzzThreshold: 1,
          }))!;
          // should have next pick
          expect(_.isUndefined(quickpick?.nextPicker)).toBeFalsy();
          // One item for our file name and the other for 'CreateNew' since there
          // are multiple vaults in this test.
          expect(quickpick.selectedItems.length).toEqual(2);
          expect(_.pick(quickpick.selectedItems[0], ["id", "vault"])).toEqual({
            id: fname,
            vault,
          });
          done();
        },
      });
    });

    describeMultiWS(
      "WHEN user creates new note with enableFullHierarchyNoteTitle == true",
      {
        modConfigCb: (config) => {
          ConfigUtils.setWorkspaceProp(
            config,
            "enableFullHierarchyNoteTitle",
            true
          );
          return config;
        },
        ctx,
        preSetupHook: ENGINE_HOOKS_MULTI.setupBasicMulti,
      },

      () => {
        test("THEN the new note title should reflect the full hierarchy name", async () => {
          const cmd = new NoteLookupCommand();
          await cmd.run({
            noConfirm: true,
            initialValue: "one.two.three",
          });

          const editor = VSCodeUtils.getActiveTextEditor()!;
          const activeNote = WSUtilsV2.instance().getNoteFromDocument(
            editor.document
          );

          expect(activeNote?.title).toEqual("One Two Three");
        });
      }
    );

    test("new node with schema template", (done) => {
      runLegacyMultiWorkspaceTest({
        ctx,
        postSetupHook: async ({ wsRoot, vaults }) => {
          await ENGINE_HOOKS.setupSchemaPreseet({ wsRoot, vaults });
        },
        onInit: async ({ vaults }) => {
          const cmd = new NoteLookupCommand();
          stubVaultPick(vaults);
          await cmd.run({
            initialValue: "bar.ch1",
            noConfirm: true,
          });
          const document = VSCodeUtils.getActiveTextEditor()?.document;
          const newNote = WSUtils.getNoteFromDocument(document!);
          expect(_.trim(newNote!.body)).toEqual("ch1 template");
          expect(newNote?.tags).toEqual("tag-foo");

          done();
        },
      });
    });

    describeMultiWS(
      "WHEN schema template references to a template note that lies in a different vault",
      {
        ctx,
        preSetupHook: ENGINE_HOOKS_MULTI.setupBasicMulti,
        postSetupHook: async ({ wsRoot, vaults }) => {
          // Schema is in vault1
          const vault = vaults[0];
          // Template is in vault2
          await NoteTestUtilsV4.createNote({
            wsRoot,
            body: "food ch2 template",
            fname: "template.ch2",
            vault: vaults[1],
          });
          const template: SchemaTemplate = {
            id: "template.ch2",
            type: "note",
          };
          await setupSchemaCrossVault({ wsRoot, vault, template });
        },
      },
      () => {
        test("THEN template body gets applied to new note FROM other vault", async () => {
          const cmd = new NoteLookupCommand();
          await cmd.run({
            initialValue: "food.ch2",
            noConfirm: true,
          });
          const { engine, vaults } = ExtensionProvider.getDWorkspace();

          const newNote = NoteUtils.getNoteByFnameFromEngine({
            fname: "food.ch2",
            engine,
            vault: vaults[0],
          });
          expect(_.trim(newNote?.body)).toEqual("food ch2 template");

          cmd.cleanUp();
        });
      }
    );

    describeMultiWS(
      "WHEN schema template references to a template note that lies in a different vault using xvault notation",
      {
        ctx,
        preSetupHook: ENGINE_HOOKS_MULTI.setupBasicMulti,
        postSetupHook: async ({ wsRoot, vaults }) => {
          // Schema is in vault1 and specifies template in vaultThree
          const vault = vaults[0];
          // Template is in vault2 and vaultThree
          await NoteTestUtilsV4.createNote({
            wsRoot,
            genRandomId: true,
            body: "food ch2 template in vault 2",
            fname: "template.ch2",
            vault: vaults[1],
          });
          await NoteTestUtilsV4.createNote({
            wsRoot,
            genRandomId: true,
            body: "food ch2 template in vaultThree",
            fname: "template.ch2",
            vault: vaults[2],
          });
          const template: SchemaTemplate = {
            id: `dendron://${VaultUtils.getName(vaults[2])}/template.ch2`,
            type: "note",
          };
          await setupSchemaCrossVault({ wsRoot, vault, template });
        },
      },
      () => {
        test("THEN correct template body FROM vault referred to be xvault link gets applied to new note", async () => {
          const cmd = new NoteLookupCommand();
          await cmd.run({
            initialValue: "food.ch2",
            noConfirm: true,
          });
          const { engine, vaults } = ExtensionProvider.getDWorkspace();

          const newNote = NoteUtils.getNoteByFnameFromEngine({
            fname: "food.ch2",
            engine,
            vault: vaults[0],
          });
          expect(_.trim(newNote?.body)).toEqual(
            "food ch2 template in vaultThree"
          );

          cmd.cleanUp();
        });
      }
    );

    describeMultiWS(
      "WHEN schema template references to a template note and there exists a stub with the same name",
      {
        ctx,
        preSetupHook: ENGINE_HOOKS_MULTI.setupBasicMulti,
        postSetupHook: async ({ wsRoot, vaults }) => {
          // Schema is in vault1
          const vault = vaults[0];
          // Template is in vault1
          await NoteTestUtilsV4.createNote({
            wsRoot,
            body: "food ch2 template",
            fname: "template.ch2",
            vault,
          });
          // template.ch2 is now a stub in vault2
          await NoteTestUtilsV4.createNote({
            wsRoot,
            body: "food ch2 child note",
            fname: "template.ch2.child",
            vault: vaults[1],
          });
          const template: SchemaTemplate = {
            id: "template.ch2",
            type: "note",
          };
          await setupSchemaCrossVault({ wsRoot, vault, template });
        },
      },
      () => {
        let showQuickPick: sinon.SinonStub;

        beforeEach(() => {
          showQuickPick = sinon.stub(vscode.window, "showQuickPick");
        });
        afterEach(() => {
          showQuickPick.restore();
        });

        test("THEN user does not get prompted with stub suggesstion and template note body gets applied to new note", async () => {
          const cmd = new NoteLookupCommand();
          await cmd.run({
            initialValue: "food.ch2",
            noConfirm: true,
          });
          const { engine, vaults } = ExtensionProvider.getDWorkspace();

          const newNote = NoteUtils.getNoteByFnameFromEngine({
            fname: "food.ch2",
            engine,
            vault: vaults[0],
          });
          expect(showQuickPick.calledOnce).toBeFalsy();
          expect(_.trim(newNote?.body)).toEqual("food ch2 template");
          cmd.cleanUp();
        });
      }
    );

    describeMultiWS(
      "WHEN schema template references to a template note that lies in multiple vaults without cross vault notation",
      {
        ctx,
        preSetupHook: ENGINE_HOOKS_MULTI.setupBasicMulti,
        postSetupHook: async ({ wsRoot, vaults }) => {
          // Schema is in vault1
          const vault = vaults[0];
          // Template is in vault2 and vaultThree
          await NoteTestUtilsV4.createNote({
            wsRoot,
            genRandomId: true,
            body: "food ch2 template in vault 2",
            fname: "template.ch2",
            vault: vaults[1],
          });
          await NoteTestUtilsV4.createNote({
            wsRoot,
            genRandomId: true,
            body: "food ch2 template in vaultThree",
            fname: "template.ch2",
            vault: vaults[2],
          });
          const template: SchemaTemplate = {
            id: "template.ch2",
            type: "note",
          };
          await setupSchemaCrossVault({ wsRoot, vault, template });
        },
      },
      () => {
        let showQuickPick: sinon.SinonStub;

        beforeEach(() => {
          showQuickPick = sinon.stub(vscode.window, "showQuickPick");
        });
        afterEach(() => {
          showQuickPick.restore();
        });

        test("AND user picks from prompted vault, THEN template body gets applied to new note", async () => {
          const { engine, vaults } = ExtensionProvider.getDWorkspace();

          // Pick vault 2
          showQuickPick.onFirstCall().returns(
            Promise.resolve({
              label: "vault2",
              vault: vaults[1],
            }) as Thenable<vscode.QuickPickItem>
          );
          const cmd = new NoteLookupCommand();
          cmd
            .run({
              initialValue: "food.ch2",
              noConfirm: true,
            })
            .then(() => {
              const newNote = NoteUtils.getNoteByFnameFromEngine({
                fname: "food.ch2",
                engine,
                vault: vaults[0],
              });
              expect(showQuickPick.calledOnce).toBeTruthy();
              expect(_.trim(newNote?.body)).toEqual(
                "food ch2 template in vault 2"
              );
            });

          cmd.cleanUp();
        });
      }
    );

    describeMultiWS(
      "WHEN schema template references to a template note that lies in multiple vaults without cross vault notation",
      {
        ctx,
        preSetupHook: ENGINE_HOOKS_MULTI.setupBasicMulti,
        postSetupHook: async ({ wsRoot, vaults }) => {
          // Schema is in vault1
          const vault = vaults[0];
          // Template is in vault2 and vaultThree
          await NoteTestUtilsV4.createNote({
            wsRoot,
            genRandomId: true,
            body: "food ch2 template in vault 2",
            fname: "template.ch2",
            vault: vaults[1],
          });
          await NoteTestUtilsV4.createNote({
            wsRoot,
            genRandomId: true,
            body: "food ch2 template in vaultThree",
            fname: "template.ch2",
            vault: vaults[2],
          });
          const template: SchemaTemplate = {
            id: "template.ch2",
            type: "note",
          };
          await setupSchemaCrossVault({ wsRoot, vault, template });
        },
      },
      () => {
        let showQuickPick: sinon.SinonStub;

        beforeEach(() => {
          showQuickPick = sinon.stub(vscode.window, "showQuickPick");
        });
        afterEach(() => {
          showQuickPick.restore();
        });

        test("AND user escapes from prompted vault, THEN no template gets applied to new note", async () => {
          const { engine, vaults } = ExtensionProvider.getDWorkspace();

          // Escape out, leading to undefined note
          showQuickPick.onFirstCall().returns(Promise.resolve(undefined));
          const cmd = new NoteLookupCommand();
          cmd
            .run({
              initialValue: "food.ch2",
              noConfirm: true,
            })
            .then(() => {
              const newNote = NoteUtils.getNoteByFnameFromEngine({
                fname: "food.ch2",
                engine,
                vault: vaults[0],
              });
              expect(showQuickPick.calledOnce).toBeTruthy();
              expect(_.trim(newNote?.body)).toEqual("");
            });

          cmd.cleanUp();
        });
      }
    );

    describeMultiWS(
      "WHEN schema template references to a template note that lies in a different vault using xvault notation that points to the wrong vault",
      {
        ctx,
        preSetupHook: ENGINE_HOOKS_MULTI.setupBasicMulti,
        postSetupHook: async ({ wsRoot, vaults }) => {
          // Schema is in vault1
          const vault = vaults[0];
          // Template is in vault2
          await NoteTestUtilsV4.createNote({
            wsRoot,
            body: "food ch2 template",
            fname: "template.ch2",
            vault: vaults[1],
          });
          const template: SchemaTemplate = {
            id: `dendron://missingVault/template.ch2`,
            type: "note",
          };
          await setupSchemaCrossVault({ wsRoot, vault, template });
        },
      },
      () => {
        test("THEN warning message gets shown about missing vault", async () => {
          const windowSpy = sinon.spy(vscode.window, "showWarningMessage");
          const cmd = new NoteLookupCommand();

          await cmd.run({
            initialValue: "food.ch2",
            noConfirm: true,
          });
          const warningMsg = windowSpy.getCall(0).args[0];
          expect(warningMsg).toEqual(
            `Warning: Problem with food.ch2 schema. No vault found for missingVault`
          );

          cmd.cleanUp();
        });
      }
    );

    describeMultiWS(
      "WHEN schema template references to a template note that lies in a different vault using incorrect xvault notation",
      {
        ctx,
        preSetupHook: ENGINE_HOOKS_MULTI.setupBasicMulti,
        postSetupHook: async ({ wsRoot, vaults }) => {
          // Schema is in vault1
          const vault = vaults[0];
          // Template is in vault2
          await NoteTestUtilsV4.createNote({
            wsRoot,
            body: "food ch2 template",
            fname: "template.ch2",
            vault: vaults[1],
          });
          const template: SchemaTemplate = {
            id: `blah://${VaultUtils.getName(vaults[1])}/template.ch2`,
            type: "note",
          };
          await setupSchemaCrossVault({ wsRoot, vault, template });
        },
      },
      () => {
        test("THEN warning message gets shown about missing template", async () => {
          const windowSpy = sinon.spy(vscode.window, "showWarningMessage");
          const cmd = new NoteLookupCommand();

          await cmd.run({
            initialValue: "food.ch2",
            noConfirm: true,
          });
          const warningMsg = windowSpy.getCall(0).args[0];
          expect(warningMsg).toEqual(
            `Warning: Problem with food.ch2 schema. No note found for blah`
          );

          cmd.cleanUp();
        });
      }
    );

    describeMultiWS(
      "WHEN schema template references to a missing template note",
      {
        ctx,
        preSetupHook: ENGINE_HOOKS.setupBasic,
        postSetupHook: async ({ wsRoot, vaults }) => {
          const vault = vaults[0];
          const template: SchemaTemplate = { id: "food.missing", type: "note" };
          await setupSchemaCrossVault({ wsRoot, vault, template });
        },
      },
      () => {
        test("THEN warning message gets shown about missing note", async () => {
          const windowSpy = sinon.spy(vscode.window, "showWarningMessage");
          const cmd = new NoteLookupCommand();

          await cmd.run({
            initialValue: "food.ch2",
            noConfirm: true,
          });
          const warningMsg = windowSpy.getCall(0).args[0];
          expect(warningMsg).toEqual(
            "Warning: Problem with food.ch2 schema. No note found for food.missing"
          );

          cmd.cleanUp();
        });
      }
    );

    test("new node matching schema prefix defaults to first matching schema child name", (done) => {
      runLegacyMultiWorkspaceTest({
        ctx,
        postSetupHook: async ({ wsRoot, vaults }) => {
          await ENGINE_HOOKS.setupSchemaPreseet({ wsRoot, vaults });
        },

        onInit: async ({ vaults }) => {
          const cmd = new NoteLookupCommand();
          stubVaultPick(vaults);
          await cmd.run({
            initialValue: "foo.",
            noConfirm: true,
          });
          const document = VSCodeUtils.getActiveTextEditor()?.document;
          const newNote = WSUtils.getNoteFromDocument(document!);
          expect(newNote?.fname).toEqual("foo.ch1");

          cmd.cleanUp();
          done();
        },
      });
    });

    test("new node with schema template on namespace", (done) => {
      runLegacyMultiWorkspaceTest({
        ctx,
        postSetupHook: async ({ wsRoot, vaults }) => {
          await ENGINE_HOOKS.setupSchemaPresetWithNamespaceTemplate({
            wsRoot,
            vaults,
          });
        },
        onInit: async ({ vaults }) => {
          const cmd = new NoteLookupCommand();
          stubVaultPick(vaults);
          const gatherOut = await cmd.gatherInputs({
            initialValue: "daily.journal.2021.08.10",
            noConfirm: true,
          });

          const enrichOut = await cmd.enrichInputs(gatherOut);
          const mockQuickPick = createMockQuickPick({
            value: "daily.journal.2021.08.10",
            selectedItems: [createNoActiveItem(vaults[0])],
          });
          mockQuickPick.showNote = enrichOut?.quickpick.showNote;

          await cmd.execute({
            ...enrichOut!,
            quickpick: mockQuickPick,
          });
          const document = VSCodeUtils.getActiveTextEditor()?.document;
          const newNote = WSUtils.getNoteFromDocument(document!);
          expect(_.trim(newNote!.body)).toEqual("Template text");

          cmd.cleanUp();
          done();
        },
      });
    });

    test("on accept, nothing selected", (done) => {
      runLegacyMultiWorkspaceTest({
        ctx,
        preSetupHook: async ({ wsRoot, vaults }) => {
          await ENGINE_HOOKS.setupBasic({ wsRoot, vaults });
        },
        onInit: async ({ vaults }) => {
          const cmd = new NoteLookupCommand();
          stubVaultPick(vaults);
          const spyFetchPickerResultsNoInput = sinon.spy(
            NotePickerUtils,
            "fetchPickerResultsNoInput"
          );
          const { quickpick, provider, controller } = await cmd.gatherInputs({
            noConfirm: true,
            initialValue: "foo",
          });
          await provider.onDidAccept({
            quickpick,
            cancellationToken: controller.cancelToken,
          })();
          expect(spyFetchPickerResultsNoInput.calledOnce).toBeTruthy();
          cmd.cleanUp();
          done();
        },
      });
    });
  });

  describe("onAccept with lookupConfirmVaultOnCreate", () => {
    const modConfigCb = (config: IntermediateDendronConfig) => {
      ConfigUtils.setNoteLookupProps(config, "confirmVaultOnCreate", true);
      return config;
    };
    test("turned off, existing note", (done) => {
      runLegacyMultiWorkspaceTest({
        ctx,
        preSetupHook: async ({ wsRoot, vaults }) => {
          await ENGINE_HOOKS.setupBasic({ wsRoot, vaults });
        },
        onInit: async ({ vaults }) => {
          const cmd = new NoteLookupCommand();
          const promptVaultSpy = stubVaultPick(vaults);
          await cmd.run({ noConfirm: true, initialValue: "foo" });
          expect(promptVaultSpy.calledOnce).toBeFalsy();
          cmd.cleanUp();
          done();
        },
      });
    });
    test("turned off, new note", (done) => {
      runLegacyMultiWorkspaceTest({
        ctx,
        preSetupHook: async ({ wsRoot, vaults }) => {
          await ENGINE_HOOKS.setupBasic({ wsRoot, vaults });
        },
        onInit: async ({ vaults }) => {
          const cmd = new NoteLookupCommand();
          const promptVaultSpy = stubVaultPick(vaults);
          await cmd.run({ noConfirm: true, initialValue: "foo" });
          expect(promptVaultSpy.calledOnce).toBeFalsy();

          cmd.cleanUp();
          done();
        },
      });
    });

    test("turned on, existing note", (done) => {
      runLegacyMultiWorkspaceTest({
        ctx,
        modConfigCb,
        preSetupHook: async ({ wsRoot, vaults }) => {
          await ENGINE_HOOKS.setupBasic({ wsRoot, vaults });
        },
        onInit: async ({ vaults }) => {
          const cmd = new NoteLookupCommand();
          const promptVaultSpy = stubVaultPick(vaults);
          await cmd.run({ noConfirm: true, initialValue: "foo" });
          expect(promptVaultSpy.calledOnce).toBeFalsy();

          cmd.cleanUp();
          done();
        },
      });
    });

    test("turned on, new note", (done) => {
      runLegacyMultiWorkspaceTest({
        ctx,
        modConfigCb,
        preSetupHook: async ({ wsRoot, vaults }) => {
          await ENGINE_HOOKS.setupBasic({ wsRoot, vaults });
        },
        onInit: async ({ vaults }) => {
          const cmd = new NoteLookupCommand();
          const promptVaultSpy = stubVaultPick(vaults);
          await cmd.run({ noConfirm: true, initialValue: "gamma" });
          expect(promptVaultSpy.calledOnce).toBeTruthy();

          cmd.cleanUp();
          done();
        },
      });
    });
  });

  describe("modifiers", () => {
    test("journal note basic", (done) => {
      runLegacyMultiWorkspaceTest({
        ctx,
        preSetupHook: async ({ wsRoot, vaults }) => {
          await ENGINE_HOOKS.setupBasic({ wsRoot, vaults });
        },
        onInit: async ({ vaults, engine }) => {
          const cmd = new NoteLookupCommand();
          stubVaultPick(vaults);
          // with journal note modifier enabled,
          await WSUtils.openNote(engine.notes["foo"]);
          const out = (await cmd.run({
            noteType: LookupNoteTypeEnum.journal,
            noConfirm: true,
          })) as CommandOutput;

          const dateFormat = ConfigUtils.getJournal(engine.config).dateFormat;
          expect(dateFormat).toEqual("y.MM.dd");
          // quickpick value should be `foo.journal.yyyy.mm.dd`
          const today = Time.now().toFormat(dateFormat);
          const noteName = `foo.journal.${today}`;
          expect(out.quickpick.value).toEqual(noteName);

          // note title should be overriden.
          const note = WSUtils.getNoteFromDocument(
            VSCodeUtils.getActiveTextEditor()!.document
          );

          expect(note?.fname).toEqual(noteName);

          const titleOverride = today.split(".").join("-");
          expect(note!.title).toEqual(titleOverride);

          cmd.cleanUp();
          done();
        },
      });
    });

    test("scratch note basic", (done) => {
      runLegacyMultiWorkspaceTest({
        ctx,
        preSetupHook: async ({ wsRoot, vaults }) => {
          await ENGINE_HOOKS.setupBasic({ wsRoot, vaults });
        },
        onInit: async ({ vaults, engine }) => {
          const cmd = new NoteLookupCommand();
          stubVaultPick(vaults);

          // with scratch note modifier enabled,
          await WSUtils.openNote(engine.notes["foo"]);
          const out = (await cmd.run({
            noteType: LookupNoteTypeEnum.scratch,
            noConfirm: true,
          })) as CommandOutput;

          // quickpick value should be `scratch.yyyy.mm.dd.ts`
          const dateFormat = DendronExtension.configuration().get<string>(
            CONFIG["DEFAULT_SCRATCH_DATE_FORMAT"].key
          ) as string;
          const today = Time.now().toFormat(dateFormat);
          const todayFormatted = today.split(".").slice(0, -1).join(".");
          expect(
            out.quickpick.value.startsWith(`scratch.${todayFormatted}.`)
          ).toBeTruthy();

          cmd.cleanUp();
          done();
        },
      });
    });

    test("Scratch notes created at different times are differently named", (done) => {
      runLegacyMultiWorkspaceTest({
        ctx,
        preSetupHook: async ({ wsRoot, vaults }) => {
          await ENGINE_HOOKS.setupBasic({ wsRoot, vaults });
        },
        onInit: async ({ vaults, engine }) => {
          const cmd = new NoteLookupCommand();
          stubVaultPick(vaults);

          // with scratch note modifier enabled,
          await WSUtils.openNote(engine.notes["foo"]);

          const createScratch = async () => {
            const out = (await cmd.run({
              noteType: LookupNoteTypeEnum.scratch,
              noConfirm: true,
            })) as CommandOutput;

            return out.quickpick.value;
          };

          const scratch1Name = await createScratch();
          await waitInMilliseconds(1000);
          const scratch2Name = await createScratch();

          expect(scratch1Name).toNotEqual(scratch2Name);

          cmd.cleanUp();
          done();
        },
      });
    });

    test("task note basic", (done) => {
      runLegacyMultiWorkspaceTest({
        ctx,
        preSetupHook: async ({ wsRoot, vaults }) => {
          await ENGINE_HOOKS.setupBasic({ wsRoot, vaults });
        },
        onInit: async ({ vaults, engine }) => {
          const cmd = new NoteLookupCommand();
          stubVaultPick(vaults);

          // with scratch note modifier enabled,
          await WSUtils.openNote(engine.notes["foo"]);
          const out = (await cmd.run({
            noteType: LookupNoteTypeEnum.task,
            noConfirm: true,
          })) as CommandOutput;

          expect(out.quickpick.value.startsWith(`task`)).toBeTruthy();

          cmd.cleanUp();
          done();
        },
      });
    });

    // not working
    test.skip("journal note with initial value override", (done) => {
      runLegacyMultiWorkspaceTest({
        ctx,
        preSetupHook: async ({ wsRoot, vaults }) => {
          await ENGINE_HOOKS.setupBasic({ wsRoot, vaults });
        },
        onInit: async ({ vaults, engine }) => {
          const cmd = new NoteLookupCommand();
          stubVaultPick(vaults);
          // with journal note modifier enabled,
          await WSUtils.openNote(engine.notes["foo"]);
          const out = (await cmd.run({
            noteType: LookupNoteTypeEnum.journal,
            initialValue: "gamma",
            noConfirm: true,
          })) as CommandOutput;

          const dateFormat = ConfigUtils.getJournal(engine.config).dateFormat;
          expect(dateFormat).toEqual("y.MM.dd");
          // quickpick value should be `foo.journal.yyyy.mm.dd`
          const today = Time.now().toFormat(dateFormat);
          const noteName = `gamma.journal.${today}`;
          expect(out.quickpick.value).toEqual(noteName);

          // note title should be overriden.
          const note = WSUtils.getNoteFromDocument(
            VSCodeUtils.getActiveTextEditor()!.document
          );
          const titleOverride = today.split(".").join("-");
          expect(note!.title).toEqual(titleOverride);

          cmd.cleanUp();
          done();
        },
      });
    });

    test("journal modifier toggle", (done) => {
      runLegacyMultiWorkspaceTest({
        ctx,
        preSetupHook: async ({ wsRoot, vaults }) => {
          await ENGINE_HOOKS.setupBasic({ wsRoot, vaults });
        },
        onInit: async ({ vaults, engine }) => {
          await WSUtils.openNote(engine.notes["foo"]);

          const cmd = new NoteLookupCommand();
          stubVaultPick(vaults);

          const { controller } = await cmd.gatherInputs({
            noteType: LookupNoteTypeEnum.journal,
          });

          const { journalBtn, scratchBtn } = getNoteTypeButtons(
            controller.quickPick.buttons
          );

          expect(journalBtn.pressed).toBeTruthy();
          expect(scratchBtn.pressed).toBeFalsy();
          expect(controller.quickPick.value.startsWith("foo.journal."));

          cmd.cleanUp();
          done();
        },
      });
    });

    test("scratch modifier toggle", (done) => {
      runLegacyMultiWorkspaceTest({
        ctx,
        preSetupHook: async ({ wsRoot, vaults }) => {
          await ENGINE_HOOKS.setupBasic({ wsRoot, vaults });
        },
        onInit: async ({ vaults, engine }) => {
          await WSUtils.openNote(engine.notes["foo"]);

          const cmd = new NoteLookupCommand();
          stubVaultPick(vaults);

          const { controller } = await cmd.gatherInputs({
            noteType: LookupNoteTypeEnum.scratch,
          });

          const { journalBtn, scratchBtn } = getNoteTypeButtons(
            controller.quickPick.buttons
          );

          expect(journalBtn.pressed).toBeFalsy();
          expect(scratchBtn.pressed).toBeTruthy();
          expect(controller.quickPick.value.startsWith("scratch."));

          cmd.cleanUp();
          done();
        },
      });
    });

    test("task modifier toggle", (done) => {
      runLegacyMultiWorkspaceTest({
        ctx,
        preSetupHook: async ({ wsRoot, vaults }) => {
          await ENGINE_HOOKS.setupBasic({ wsRoot, vaults });
        },
        onInit: async ({ vaults, engine }) => {
          await WSUtils.openNote(engine.notes["foo"]);

          const cmd = new NoteLookupCommand();
          stubVaultPick(vaults);

          const { controller } = await cmd.gatherInputs({
            noteType: LookupNoteTypeEnum.task,
          });

          const { journalBtn, scratchBtn, taskBtn } = getNoteTypeButtons(
            controller.quickPick.buttons
          );

          expect(journalBtn.pressed).toBeFalsy();
          expect(scratchBtn.pressed).toBeFalsy();
          expect(taskBtn.pressed).toBeTruthy();
          expect(controller.quickPick.value.startsWith("task."));

          cmd.cleanUp();
          done();
        },
      });
    });

    test("selection modifier set to none in configs", (done) => {
      runLegacyMultiWorkspaceTest({
        ctx,
        modConfigCb: (config: IntermediateDendronConfig) => {
          ConfigUtils.setNoteLookupProps(
            config,
            "selectionMode",
            LookupSelectionModeEnum.none
          );
          return config;
        },
        preSetupHook: async ({ wsRoot, vaults }) => {
          await ENGINE_HOOKS.setupBasic({ wsRoot, vaults });
        },
        onInit: async ({ vaults }) => {
          const cmd = new NoteLookupCommand();
          stubVaultPick(vaults);
          const gatherOut = await cmd.gatherInputs({});
          const { selection2linkBtn, selectionExtractBtn } =
            getSelectionTypeButtons(gatherOut.quickpick.buttons);
          expect(selection2linkBtn.pressed).toBeFalsy();
          expect(selectionExtractBtn.pressed).toBeFalsy();
          cmd.cleanUp();
          done();
        },
      });
    });

    test("selectionType: none in args", (done) => {
      runLegacyMultiWorkspaceTest({
        ctx,
        preSetupHook: async ({ wsRoot, vaults }) => {
          await ENGINE_HOOKS.setupBasic({ wsRoot, vaults });
        },
        onInit: async ({ vaults }) => {
          const cmd = new NoteLookupCommand();
          stubVaultPick(vaults);
          const gatherOut = await cmd.gatherInputs({
            selectionType: LookupSelectionTypeEnum.none,
          });
          const { selection2linkBtn, selectionExtractBtn } =
            getSelectionTypeButtons(gatherOut.quickpick.buttons);
          expect(selection2linkBtn.pressed).toBeFalsy();
          expect(selectionExtractBtn.pressed).toBeFalsy();
          cmd.cleanUp();
          done();
        },
      });
    });

    test("selectionType is selectionExtract by default", (done) => {
      runLegacyMultiWorkspaceTest({
        ctx,
        preSetupHook: async ({ wsRoot, vaults }) => {
          await ENGINE_HOOKS.setupBasic({ wsRoot, vaults });
        },
        onInit: async ({ vaults }) => {
          const cmd = new NoteLookupCommand();
          stubVaultPick(vaults);
          const gatherOut = await cmd.gatherInputs({});
          const { selection2linkBtn, selectionExtractBtn } =
            getSelectionTypeButtons(gatherOut.quickpick.buttons);
          expect(selection2linkBtn.pressed).toBeFalsy();
          expect(selectionExtractBtn.pressed).toBeTruthy();
          cmd.cleanUp();
          done();
        },
      });
    });

    test("selection2link basic", (done) => {
      runLegacyMultiWorkspaceTest({
        ctx,
        preSetupHook: async ({ wsRoot, vaults }) => {
          await ENGINE_HOOKS.setupBasic({ wsRoot, vaults });
        },
        onInit: async ({ vaults, engine }) => {
          const cmd = new NoteLookupCommand();
          stubVaultPick(vaults);
          const fooNoteEditor = await WSUtils.openNote(engine.notes["foo"]);

          // selects "foo body"
          fooNoteEditor.selection = new vscode.Selection(7, 0, 7, 12);
          const { text } = VSCodeUtils.getSelection();
          expect(text).toEqual("foo body");

          await cmd.run({
            selectionType: "selection2link",
            noConfirm: true,
          });

          // should create foo.foo-body.md with an empty body.
          expect(getActiveEditorBasename().endsWith("foo.foo-body.md"));
          const newNoteEditor = VSCodeUtils.getActiveTextEditorOrThrow();
          const newNote = WSUtils.getNoteFromDocument(newNoteEditor.document);
          expect(newNote?.body).toEqual("");

          // should change selection to link with alais.
          const changedText = fooNoteEditor.document.getText();
          expect(changedText.endsWith("[[foo body|foo.foo-body]]\n"));

          // Note should have its links updated, since selection2link put a link in it

          // TODO: Re-enable checks below. There's currently a race condition
          // with the check, where it needs to wait for NoteSyncService to
          // finish its callback before we should check the engine state. The
          // test should subscribe to OnNoteChange event and do the check upon
          // event firing. However, NoteSyncService is currently not exposed in
          // the test infrastructure.

          // const oldNote = engine.notes["foo"];
          // expect(oldNote.links.length).toEqual(1);
          // expect(oldNote.links[0].value).toEqual("foo.foo-body");
          cmd.cleanUp();
          done();
        },
      });
    });

    describeSingleWS(
      "WHEN selection2link is used with a multi-line string",
      {
        ctx,
        postSetupHook: async ({ vaults, wsRoot }) => {
          NoteTestUtilsV4.createNote({
            fname: "multi-line",
            vault: vaults[0],
            wsRoot,
            body: "test\ning\n",
          });
        },
      },
      () => {
        test("THEN it produces a valid string", async () => {
          const { vaults, engine } = ExtensionProvider.getDWorkspace();
          const cmd = new NoteLookupCommand();
          stubVaultPick(vaults);
          const fooNoteEditor = await ExtensionProvider.getWSUtils().openNote(
            engine.notes["multi-line"]
          );

          // selects "test \n ing \n"
          fooNoteEditor.selection = new vscode.Selection(7, 0, 9, 0);
          const { text } = VSCodeUtils.getSelection();
          expect(text).toEqual("test\ning\n");

          await cmd.run({
            selectionType: "selection2link",
            noConfirm: true,
          });

          // should create foo.foo-body.md with an empty body.
          expect(getActiveEditorBasename().endsWith("multi-line.testing.md"));
          const newNoteEditor = VSCodeUtils.getActiveTextEditorOrThrow();
          const newNote = ExtensionProvider.getWSUtils().getNoteFromDocument(
            newNoteEditor.document
          );
          expect(newNote?.body).toEqual("");

          // should change selection to link with alais.
          const changedText = fooNoteEditor.document.getText();
          expect(changedText.endsWith("[[testing|multi-line.testing]]\n"));

          cmd.cleanUp();
        });
      }
    );

    test("selection2link modifier toggle", (done) => {
      runLegacyMultiWorkspaceTest({
        ctx,
        preSetupHook: async ({ wsRoot, vaults }) => {
          await ENGINE_HOOKS.setupBasic({ wsRoot, vaults });
        },
        onInit: async ({ vaults, engine }) => {
          const cmd = new NoteLookupCommand();
          stubVaultPick(vaults);
          const fooNoteEditor = await WSUtils.openNote(engine.notes["foo"]);

          fooNoteEditor.selection = new vscode.Selection(7, 0, 7, 12);
          const { text } = VSCodeUtils.getSelection();
          expect(text).toEqual("foo body");

          const { controller } = await cmd.gatherInputs({
            selectionType: "selection2link",
          });

          const { selection2linkBtn, selectionExtractBtn } =
            getSelectionTypeButtons(controller.quickPick.buttons);

          expect(selection2linkBtn?.pressed).toBeTruthy();
          expect(selectionExtractBtn.pressed).toBeFalsy();
          expect(controller.quickPick.value).toEqual("foo.foo-body");

          cmd.cleanUp();
          done();
        },
      });
    });

    test("selectionExtract basic", (done) => {
      runLegacyMultiWorkspaceTest({
        ctx,
        preSetupHook: async ({ wsRoot, vaults }) => {
          await ENGINE_HOOKS.setupBasic({ wsRoot, vaults });
        },
        onInit: async ({ vaults, engine }) => {
          const cmd = new NoteLookupCommand();
          stubVaultPick(vaults);
          const fooNoteEditor = await WSUtils.openNote(engine.notes["foo"]);

          // selects "foo body"
          fooNoteEditor.selection = new vscode.Selection(7, 0, 7, 12);
          const { text } = VSCodeUtils.getSelection();
          expect(text).toEqual("foo body");

          await cmd.run({
            selectionType: "selectionExtract",
            initialValue: "foo.extracted",
            noConfirm: true,
          });

          // should create foo.extracted.md with an selected text as body.
          expect(getActiveEditorBasename().endsWith("foo.extracted.md"));
          const newNoteEditor = VSCodeUtils.getActiveTextEditorOrThrow();
          const newNote = WSUtils.getNoteFromDocument(newNoteEditor.document);
          expect(newNote?.body.trim()).toEqual("foo body");

          // should remove selection
          const changedText = fooNoteEditor.document.getText();
          expect(changedText.includes("foo body")).toBeFalsy();
          cmd.cleanUp();
          done();
        },
      });
    });
    test("leave trace on selectionExtract", (done) => {
      runLegacyMultiWorkspaceTest({
        ctx,
        preSetupHook: async ({ wsRoot, vaults }) => {
          await ENGINE_HOOKS.setupBasic({ wsRoot, vaults });
        },
        onInit: async ({ wsRoot, vaults, engine }) => {
          withConfig(
            (config) => {
              ConfigUtils.setNoteLookupProps(config, "leaveTrace", true);
              return config;
            },
            { wsRoot }
          );
          const cmd = new NoteLookupCommand();
          stubVaultPick(vaults);
          const fooNoteEditor = await WSUtils.openNote(engine.notes["foo"]);

          // selects "foo body"
          fooNoteEditor.selection = new vscode.Selection(7, 0, 7, 12);
          const { text } = VSCodeUtils.getSelection();
          expect(text).toEqual("foo body");

          await cmd.run({
            selectionType: "selectionExtract",
            initialValue: "foo.extracted",
            noConfirm: true,
          });

          // should create foo.extracted.md with an selected text as body.
          expect(getActiveEditorBasename().endsWith("foo.extracted.md"));
          const newNoteEditor = VSCodeUtils.getActiveTextEditorOrThrow();
          const newNote = WSUtils.getNoteFromDocument(newNoteEditor.document);
          expect(newNote?.body.trim()).toEqual("foo body");
          // should remove selection
          const changedText = fooNoteEditor.document.getText();
          expect(
            changedText.includes(`![[${newNote?.title}|${newNote?.fname}]]`)
          ).toBeTruthy();
          expect(changedText.includes("foo body")).toBeFalsy();
          cmd.cleanUp();
          done();
        },
      });
    });

    test("selectionExtract from file not in known vault", (done) => {
      runLegacyMultiWorkspaceTest({
        ctx,
        onInit: async ({ vaults }) => {
          const cmd = new NoteLookupCommand();
          stubVaultPick(vaults);

          // open and create a file outside of vault.
          const extDir = tmpDir().name;
          const extPath = "outside.md";
          const extBody = "non vault content";
          await FileTestUtils.createFiles(extDir, [
            { path: extPath, body: extBody },
          ]);
          const uri = vscode.Uri.file(path.join(extDir, extPath));
          const editor = (await VSCodeUtils.openFileInEditor(
            uri
          )) as vscode.TextEditor;
          editor.selection = new vscode.Selection(0, 0, 0, 17);

          await cmd.run({
            selectionType: "selectionExtract",
            initialValue: "from-outside",
            noConfirm: true,
          });

          const newNoteEditor = VSCodeUtils.getActiveTextEditorOrThrow();
          const newNote = WSUtils.getNoteFromDocument(newNoteEditor.document);
          expect(newNote?.body.trim()).toEqual("non vault content");

          const nonVaultFileEditor = (await VSCodeUtils.openFileInEditor(
            uri
          )) as vscode.TextEditor;
          expect(nonVaultFileEditor.document.getText()).toEqual(extBody);
          cmd.cleanUp();
          done();
        },
      });
    });

    test("selectionExtract modifier toggle", (done) => {
      runLegacyMultiWorkspaceTest({
        ctx,
        preSetupHook: async ({ wsRoot, vaults }) => {
          await ENGINE_HOOKS.setupBasic({ wsRoot, vaults });
        },
        onInit: async ({ vaults, engine }) => {
          const cmd = new NoteLookupCommand();
          stubVaultPick(vaults);
          const fooNoteEditor = await WSUtils.openNote(engine.notes["foo"]);

          fooNoteEditor.selection = new vscode.Selection(7, 0, 7, 12);
          const { text } = VSCodeUtils.getSelection();
          expect(text).toEqual("foo body");

          const { controller } = await cmd.gatherInputs({
            selectionType: "selectionExtract",
          });

          const { selection2linkBtn, selectionExtractBtn } =
            getSelectionTypeButtons(controller.quickPick.buttons);

          expect(selection2linkBtn?.pressed).toBeFalsy();
          expect(selectionExtractBtn.pressed).toBeTruthy();

          cmd.cleanUp();
          done();
        },
      });
    });

    test("horizontal split basic", (done) => {
      runLegacyMultiWorkspaceTest({
        ctx,
        preSetupHook: async ({ wsRoot, vaults }) => {
          await ENGINE_HOOKS.setupBasic({ wsRoot, vaults });
        },
        onInit: async ({ vaults, engine }) => {
          const cmd = new NoteLookupCommand();
          stubVaultPick(vaults);

          // close all editors before running.
          VSCodeUtils.closeAllEditors();

          await WSUtils.openNote(engine.notes["foo"]);
          await cmd.run({
            initialValue: "bar",
            splitType: "horizontal",
            noConfirm: true,
          });
          const barEditor = VSCodeUtils.getActiveTextEditor();
          expect(barEditor!.viewColumn).toEqual(2);

          await cmd.run({
            initialValue: "foo.ch1",
            splitType: "horizontal",
            noConfirm: true,
          });
          const fooChildEditor = VSCodeUtils.getActiveTextEditor();
          expect(fooChildEditor!.viewColumn).toEqual(3);

          cmd.cleanUp();
          done();
        },
      });
    });

    test("horizontal split modifier toggle", (done) => {
      runLegacyMultiWorkspaceTest({
        ctx,
        preSetupHook: async ({ wsRoot, vaults }) => {
          await ENGINE_HOOKS.setupBasic({ wsRoot, vaults });
        },
        onInit: async ({ vaults }) => {
          const cmd = new NoteLookupCommand();
          stubVaultPick(vaults);

          const { controller } = await cmd.gatherInputs({
            splitType: "horizontal",
          });

          const { horizontalSplitBtn } = getSplitTypeButtons(
            controller.quickPick.buttons
          );

          expect(horizontalSplitBtn?.pressed).toBeTruthy();

          cmd.cleanUp();
          done();
        },
      });
    });

    test("copyNoteLink basic", (done) => {
      runLegacyMultiWorkspaceTest({
        ctx,
        preSetupHook: async ({ wsRoot, vaults }) => {
          await ENGINE_HOOKS.setupBasic({ wsRoot, vaults });
        },
        onInit: async ({ vaults }) => {
          const cmd = new NoteLookupCommand();
          stubVaultPick(vaults);
          const out = await cmd.run({
            initialValue: "foo",
            noConfirm: true,
            copyNoteLink: true,
          });
          const content = await clipboard.readText();
          expect(content).toEqual("[[Foo|foo]]");
          expect(!_.isUndefined(out?.quickpick.copyNoteLinkFunc)).toBeTruthy();

          cmd.cleanUp();
          done();
        },
      });
    });
  });

  describe("journal + selection2link interactions", () => {
    const prepareCommandFunc = async ({ vaults, engine }: any) => {
      const cmd = new NoteLookupCommand();
      stubVaultPick(vaults);

      const fooNoteEditor = await WSUtils.openNote(engine.notes["foo"]);

      // selects "foo body"
      fooNoteEditor.selection = new vscode.Selection(7, 0, 7, 12);
      const { text } = VSCodeUtils.getSelection();
      expect(text).toEqual("foo body");

      const { controller } = await cmd.gatherInputs({
        noteType: LookupNoteTypeEnum.journal,
        selectionType: LookupSelectionTypeEnum.selection2link,
      });
      return { controller, cmd };
    };

    test("journal and selection2link both applied", (done) => {
      runLegacyMultiWorkspaceTest({
        ctx,
        preSetupHook: async ({ wsRoot, vaults }) => {
          await ENGINE_HOOKS.setupBasic({ wsRoot, vaults });
        },
        onInit: async ({ vaults, engine }) => {
          const { controller, cmd } = await prepareCommandFunc({
            vaults,
            engine,
          });

          const { journalBtn } = getNoteTypeButtons(
            controller.quickPick.buttons
          );

          const { selection2linkBtn } = getSelectionTypeButtons(
            controller.quickPick.buttons
          );

          expect(journalBtn.pressed).toBeTruthy();
          expect(selection2linkBtn.pressed).toBeTruthy();

          const dateFormat = ConfigUtils.getJournal(engine.config).dateFormat;
          const today = Time.now().toFormat(dateFormat);
          expect(controller.quickPick.value).toEqual(
            `foo.journal.${today}.foo-body`
          );

          cmd.cleanUp();
          done();
        },
      });
    });

    describe("scratch + selection2link interactions", () => {
      const prepareCommandFunc = async ({ vaults, engine }: any) => {
        const cmd = new NoteLookupCommand();
        stubVaultPick(vaults);

        const fooNoteEditor = await WSUtils.openNote(engine.notes["foo"]);

        // selects "foo body"
        fooNoteEditor.selection = new vscode.Selection(7, 0, 7, 12);
        const { text } = VSCodeUtils.getSelection();
        expect(text).toEqual("foo body");

        const { controller } = await cmd.gatherInputs({
          noteType: LookupNoteTypeEnum.scratch,
          selectionType: LookupSelectionTypeEnum.selection2link,
        });
        return { controller, cmd };
      };

      test("scratch and selection2link both applied", (done) => {
        runLegacyMultiWorkspaceTest({
          ctx,
          preSetupHook: async ({ wsRoot, vaults }) => {
            await ENGINE_HOOKS.setupBasic({ wsRoot, vaults });
          },
          onInit: async ({ vaults, engine }) => {
            const { controller, cmd } = await prepareCommandFunc({
              vaults,
              engine,
            });

            const { scratchBtn } = getNoteTypeButtons(
              controller.quickPick.buttons
            );

            const { selection2linkBtn } = getSelectionTypeButtons(
              controller.quickPick.buttons
            );

            expect(scratchBtn.pressed).toBeTruthy();
            expect(selection2linkBtn.pressed).toBeTruthy();

            const todayFormatted = getTodayInScratchDateFormat();
            const quickpickValue = controller.quickPick.value;
            expect(
              quickpickValue.startsWith(`scratch.${todayFormatted}`)
            ).toBeTruthy();
            expect(quickpickValue.endsWith(".foo-body")).toBeTruthy();

            cmd.cleanUp();
            done();
          },
        });
      });
    });

    describe("task + selection2link interactions", () => {
      const prepareCommandFunc = async ({ vaults, engine }: any) => {
        const cmd = new NoteLookupCommand();
        stubVaultPick(vaults);

        const fooNoteEditor = await WSUtils.openNote(engine.notes["foo"]);

        // selects "foo body"
        fooNoteEditor.selection = new vscode.Selection(7, 0, 7, 12);
        const { text } = VSCodeUtils.getSelection();
        expect(text).toEqual("foo body");

        const { controller } = await cmd.gatherInputs({
          noteType: LookupNoteTypeEnum.task,
          selectionType: LookupSelectionTypeEnum.selection2link,
        });
        return { controller, cmd };
      };

      test("task and selection2link both applied", (done) => {
        runLegacyMultiWorkspaceTest({
          ctx,
          preSetupHook: async ({ wsRoot, vaults }) => {
            await ENGINE_HOOKS.setupBasic({ wsRoot, vaults });
          },
          onInit: async ({ vaults, engine }) => {
            const { controller } = await prepareCommandFunc({
              vaults,
              engine,
            });

            const { taskBtn } = getNoteTypeButtons(
              controller.quickPick.buttons
            );

            const { selection2linkBtn } = getSelectionTypeButtons(
              controller.quickPick.buttons
            );

            expect(taskBtn.pressed).toBeTruthy();
            expect(selection2linkBtn.pressed).toBeTruthy();

            const quickpickValue = controller.quickPick.value;
            expect(quickpickValue.startsWith(`task.`)).toBeTruthy();
            expect(quickpickValue.endsWith(".foo-body")).toBeTruthy();

            controller.onHide();
            done();
          },
        });
      });
    });

    describe("note modifiers + selectionExtract interactions", () => {
      const prepareCommandFunc = async ({ vaults, engine, noteType }: any) => {
        const cmd = new NoteLookupCommand();
        stubVaultPick(vaults);

        const fooNoteEditor = await WSUtils.openNote(engine.notes["foo"]);

        // selects "foo body"
        fooNoteEditor.selection = new vscode.Selection(7, 0, 7, 12);
        const { text } = VSCodeUtils.getSelection();
        expect(text).toEqual("foo body");

        const cmdOut = await cmd.run({
          noteType,
          selectionType: LookupSelectionTypeEnum.selectionExtract,
          noConfirm: true,
        });
        return { cmdOut, selectedText: text, cmd };
      };

      test("journal + selectionExtract both applied", (done) => {
        runLegacyMultiWorkspaceTest({
          ctx,
          preSetupHook: async ({ wsRoot, vaults }) => {
            await ENGINE_HOOKS.setupBasic({ wsRoot, vaults });
          },
          onInit: async ({ wsRoot, vaults, engine }) => {
            const { selectedText, cmd } = await prepareCommandFunc({
              vaults,
              engine,
              noteType: LookupNoteTypeEnum.journal,
            });

            const dateFormat = ConfigUtils.getJournal(engine.config).dateFormat;
            const today = Time.now().toFormat(dateFormat);
            const newNote = NoteUtils.getNoteOrThrow({
              fname: `foo.journal.${today}`,
              notes: engine.notes,
              vault: vaults[0],
              wsRoot,
            });

            expect(newNote.body.trim()).toEqual(selectedText);

            cmd.cleanUp();
            done();
          },
        });
      });

      test("scratch + selectionExtract both applied", (done) => {
        runLegacyMultiWorkspaceTest({
          ctx,
          preSetupHook: async ({ wsRoot, vaults }) => {
            await ENGINE_HOOKS.setupBasic({ wsRoot, vaults });
          },
          onInit: async ({ wsRoot, vaults, engine }) => {
            const { cmdOut, selectedText, cmd } = await prepareCommandFunc({
              vaults,
              engine,
              noteType: LookupNoteTypeEnum.scratch,
            });

            const newNote = NoteUtils.getNoteOrThrow({
              fname: cmdOut!.quickpick.value,
              notes: engine.notes,
              vault: vaults[0],
              wsRoot,
            });
            expect(newNote.body.trim()).toEqual(selectedText);

            cmd.cleanUp();
            done();
          },
        });
      });
    });

    describe("multiselect interactions", () => {
      // TODO: there's gotta be a better way to mock this.
      const prepareCommandFunc = async ({
        wsRoot,
        vaults,
        engine,
        opts,
      }: any) => {
        const cmd = new NoteLookupCommand();
        const notesToSelect = ["foo.ch1", "bar", "lorem", "ipsum"].map(
          (fname) => engine.notes[fname]
        );
        const selectedItems = notesToSelect.map((note) => {
          return DNodeUtils.enhancePropForQuickInputV3({
            props: note,
            schemas: engine.schemas,
            wsRoot,
            vaults,
          });
        }) as NoteQuickInput[];

        const runOpts = {
          multiSelect: true,
          noConfirm: true,
          copyNoteLink: opts.copyLink ? true : undefined,
        } as CommandRunOpts;

        if (opts.split) runOpts.splitType = LookupSplitTypeEnum.horizontal;

        const gatherOut = await cmd.gatherInputs(runOpts);

        const mockQuickPick = createMockQuickPick({
          value: "",
          selectedItems,
          canSelectMany: true,
          buttons: gatherOut.quickpick.buttons,
        });

        mockQuickPick.showNote = gatherOut.quickpick.showNote;
        mockQuickPick.copyNoteLinkFunc = gatherOut.quickpick.copyNoteLinkFunc;

        sinon.stub(cmd, "enrichInputs").returns(
          Promise.resolve({
            quickpick: mockQuickPick,
            controller: gatherOut.controller,
            provider: gatherOut.provider,
            selectedItems,
          })
        );
        return { cmd };
      };

      test("split + multiselect: should have n+1 columns", (done) => {
        runLegacyMultiWorkspaceTest({
          ctx,
          preSetupHook: async ({ wsRoot, vaults }) => {
            await ENGINE_HOOKS.setupBasic({ wsRoot, vaults });
            await NoteTestUtilsV4.createNote({
              fname: "lorem",
              vault: vaults[0],
              wsRoot,
            });
            await NoteTestUtilsV4.createNote({
              fname: "ipsum",
              vault: vaults[0],
              wsRoot,
            });
          },
          onInit: async ({ wsRoot, vaults, engine }) => {
            // make clean slate.
            VSCodeUtils.closeAllEditors();

            await WSUtils.openNote(engine.notes["foo"]);
            const { cmd } = await prepareCommandFunc({
              wsRoot,
              vaults,
              engine,
              opts: { split: true },
            });

            await cmd.run({
              multiSelect: true,
              splitType: LookupSplitTypeEnum.horizontal,
              noConfirm: true,
            });
            const editor = VSCodeUtils.getActiveTextEditor();
            // one open, lookup with 2 selected. total 3 columns.
            expect(editor?.viewColumn).toEqual(5);
            sinon.restore();

            cmd.cleanUp();
            done();
          },
        });
      });

      test("copyNoteLink + multiselect: should copy link of all selected notes", (done) => {
        runLegacyMultiWorkspaceTest({
          ctx,
          preSetupHook: async ({ wsRoot, vaults }) => {
            await ENGINE_HOOKS.setupBasic({ wsRoot, vaults });
            await NoteTestUtilsV4.createNote({
              fname: "lorem",
              vault: vaults[0],
              wsRoot,
            });
            await NoteTestUtilsV4.createNote({
              fname: "ipsum",
              vault: vaults[0],
              wsRoot,
            });
          },
          onInit: async ({ wsRoot, vaults, engine }) => {
            // make clean slate.
            VSCodeUtils.closeAllEditors();

            await WSUtils.openNote(engine.notes["foo"]);

            const { cmd } = await prepareCommandFunc({
              wsRoot,
              vaults,
              engine,
              opts: { copyLink: true },
            });

            await cmd.run({
              multiSelect: true,
              noConfirm: true,
              copyNoteLink: true,
            });

            const content = await clipboard.readText();

            expect(content).toEqual(
              [
                "[[Ch1|foo.ch1]]",
                "[[Bar|bar]]",
                "[[Lorem|lorem]]",
                "[[Ipsum|ipsum]]",
              ].join("\n")
            );

            cmd.cleanUp();
            done();
          },
        });
      });
    });
  });
});
Example #12
Source File: Keybinding.test.ts    From dendron with GNU Affero General Public License v3.0 4 votes vote down vote up
suite("KeybindingUtils", function () {
  const DUMMY_KEYBINDING_CONFLICTS = [
    {
      extensionId: "dummyExt",
      commandId: "dummyExt.cmd",
      conflictsWith: "dendron.lookupNote",
    },
  ];
  describeMultiWS(
    "GIVEN conflicting extension installed AND keybinding exists",
    {},
    () => {
      let installStatusStub: SinonStub;
      let userConfigDirStub: SinonStub;
      beforeEach(() => {
        userConfigDirStub = mockUserConfigDir();
        installStatusStub = sinon
          .stub(
            KeybindingUtils,
            "getInstallStatusForKnownConflictingExtensions"
          )
          .returns([{ id: "dummyExt", installed: true }]);
      });

      afterEach(() => {
        installStatusStub.restore();
        userConfigDirStub.restore();
      });

      test("THEN conflict is detected", async () => {
        const out = KeybindingUtils.getConflictingKeybindings({
          knownConflicts: DUMMY_KEYBINDING_CONFLICTS,
        });
        expect(out).toEqual(DUMMY_KEYBINDING_CONFLICTS);
      });

      test("THEN conflict is detected if a non-resolving remap is in keybindings.json", async () => {
        const { keybindingConfigPath, osName } =
          KeybindingUtils.getKeybindingConfigPath();
        const keyCombo = osName === "Darwin" ? "cmd+l" : "ctrl+l";
        const remapCombo = osName === "Darwin" ? "cmd+shift+l" : "ctrl+shift+l";

        const remapConflictCaseConfig = `// This is my awesome Dendron Keybinding
        [
          {
            "key": "${remapCombo}",
            "command": "dummyExt.cmd",
          }
        ]`;
        const conflictWithMoreArgsCaseConfig = `// This is my awesome Dendron Keybinding
        [
          {
            "key": "${keyCombo}",
            "command": "dummyExt.cmd",
            "args": {
              "foo": "bar"
            }
          }
        ]`;
        const remapDendronCaseConfig = `// This is my awesome Dendron Keybinding
        [
          {
            "key": "${remapCombo}",
            "command": "dendron.lookupNote",
          }
        ]`;
        const dendronWithMoreArgsCaseConfig = `// This is my awesome Dendron Keybinding
        [
          {
            "key": "${keyCombo}",
            "command": "dendron.lookupNote",
            "args": {
              "initialValue": "foo"
            }
          }
        ]`;
        [
          remapConflictCaseConfig,
          conflictWithMoreArgsCaseConfig,
          remapDendronCaseConfig,
          dendronWithMoreArgsCaseConfig,
        ].forEach((config) => {
          fs.ensureFileSync(keybindingConfigPath);
          fs.writeFileSync(keybindingConfigPath, config);
          const out = KeybindingUtils.getConflictingKeybindings({
            knownConflicts: DUMMY_KEYBINDING_CONFLICTS,
          });
          expect(out).toEqual(DUMMY_KEYBINDING_CONFLICTS);
          fs.removeSync(keybindingConfigPath);
        });
      });

      test("THEN conflict is not detected if conflicting keybinding is disabled in keybindings.json", async () => {
        const { keybindingConfigPath, osName } =
          KeybindingUtils.getKeybindingConfigPath();
        const keyCombo = osName === "Darwin" ? "cmd+l" : "ctrl+l";
        const disableConflictCaseConfig = `// This is my awesome Dendron Keybinding
        [
          {
            "key": "${keyCombo}",
            "command": "-dummyExt.cmd",
          }
        ]`;
        fs.ensureFileSync(keybindingConfigPath);
        fs.writeFileSync(keybindingConfigPath, disableConflictCaseConfig);
        const out = KeybindingUtils.getConflictingKeybindings({
          knownConflicts: DUMMY_KEYBINDING_CONFLICTS,
        });
        expect(out).toEqual([]);
        fs.removeSync(keybindingConfigPath);
      });
    }
  );

  describeMultiWS("GIVEN no conflicting extension installed", {}, () => {
    test("THEN no conflict is detected", async () => {
      const out = KeybindingUtils.getConflictingKeybindings({
        knownConflicts: KNOWN_KEYBINDING_CONFLICTS,
      });
      expect(out).toEqual([]);
    });
  });

  describe("GIVEN a keybinding entry", () => {
    test("THEN correct JSON for disable block is generated", () => {
      const disableBlock = KeybindingUtils.generateKeybindingBlockForCopy({
        entry: {
          key: "ctrl+l",
          command: "dummyExt.cmd",
        },
        disable: true,
      });
      expect(disableBlock).toEqual(
        `{\n  "key": "ctrl+l",\n  "command": "-dummyExt.cmd",\n}\n`
      );
    });
    test("THEN correct JSON for remap block is generated", () => {
      const disableBlock = KeybindingUtils.generateKeybindingBlockForCopy({
        entry: {
          key: "ctrl+l",
          command: "dummyExt.cmd",
        },
      });
      expect(disableBlock).toEqual(
        `{\n  "key": "",\n  "command": "dummyExt.cmd",\n}\n`
      );
    });
  });
});
Example #13
Source File: SetupWorkspace.test.ts    From dendron with GNU Affero General Public License v3.0 4 votes vote down vote up
suite("GIVEN SetupWorkspace Command", function () {
  let homeDirStub: SinonStub;
  let userConfigDirStub: SinonStub;
  let wsFoldersStub: SinonStub;
  this.timeout(6 * 1000);

  let ctx: ExtensionContext;
  beforeEach(async () => {
    ctx = VSCodeUtils.getOrCreateMockContext();
    // Required for StateService Singleton Init at the moment.
    // eslint-disable-next-line no-new
    new StateService({
      globalState: ctx.globalState,
      workspaceState: ctx.workspaceState,
    });
    await resetCodeWorkspace();
    homeDirStub = TestEngineUtils.mockHomeDir();
    userConfigDirStub = VSCodeTestUtils.mockUserConfigDir();
    wsFoldersStub = VSCodeTestUtils.stubWSFolders(undefined);
  });
  afterEach(() => {
    homeDirStub.restore();
    userConfigDirStub.restore();
    wsFoldersStub.restore();
  });
  const opts = {
    noSetInstallStatus: true,
  };

  // TODO: This test case fails in Windows if the logic in setupBeforeAfter (stubs) is not there. Look into why that is the case
  describeMultiWS("WHEN command is gathering inputs", opts, () => {
    let showOpenDialog: sinon.SinonStub;

    beforeEach(async () => {
      const cmd = new SetupWorkspaceCommand();
      showOpenDialog = sinon.stub(window, "showOpenDialog");
      await cmd.gatherInputs();
    });
    afterEach(() => {
      showOpenDialog.restore();
    });

    test("THEN file picker is opened", (done) => {
      expect(showOpenDialog.calledOnce).toBeTruthy();
      done();
    });
  });

  describe("WHEN initializing a CODE workspace", function () {
    this.timeout(6 * 1000);

    describe("AND workspace has not been set up yet", () => {
      test("THEN Dendon does not activate", async () => {
        const resp = await _activate(ctx);
        expect(resp).toBeFalsy();
        const dendronState = MetadataService.instance().getMeta();
        expect(isNotUndefined(dendronState.firstInstall)).toBeTruthy();
        expect(isNotUndefined(dendronState.firstWsInitialize)).toBeFalsy();
      });
    });

    describe("AND a new workspace is being created", () => {
      test("THEN Dendron creates the workspace correctly", async () => {
        const wsRoot = tmpDir().name;

        MetadataService.instance().setActivationContext(
          WorkspaceActivationContext.normal
        );

        const active = await _activate(ctx);
        // Not active yet, because there is no workspace
        expect(active).toBeFalsy();
        stubSetupWorkspace({
          wsRoot,
        });
        const cmd = new SetupWorkspaceCommand();
        await cmd.execute({
          rootDirRaw: wsRoot,
          skipOpenWs: true,
          skipConfirmation: true,
          workspaceInitializer: new BlankInitializer(),
          selfContained: false,
        });
        const resp = await readYAMLAsync(path.join(wsRoot, "dendron.yml"));
        expect(resp).toEqual(
          WorkspaceTestUtils.generateDefaultConfig({
            vaults: [{ fsPath: "vault" }],
            duplicateNoteBehavior: {
              action: "useVault",
              payload: ["vault"],
            },
          })
        );

        const dendronState = MetadataService.instance().getMeta();
        expect(isNotUndefined(dendronState.firstInstall)).toBeTruthy();
        expect(isNotUndefined(dendronState.firstWsInitialize)).toBeTruthy();
        expect(
          await fs.readdir(path.join(wsRoot, DEFAULT_LEGACY_VAULT_NAME))
        ).toEqual(genEmptyWSFiles());
      });
    });

    describe("AND a new workspace is being created with a template initializer", () => {
      test("setup with template initializer", async () => {
        const wsRoot = tmpDir().name;
        MetadataService.instance().setActivationContext(
          WorkspaceActivationContext.normal
        );
        const out = await _activate(ctx);
        // Not active yet, because there is no workspace
        expect(out).toBeFalsy();
        stubSetupWorkspace({
          wsRoot,
        });

        const cmd = new SetupWorkspaceCommand();
        await cmd.execute({
          rootDirRaw: wsRoot,
          skipOpenWs: true,
          skipConfirmation: true,
          workspaceInitializer: new TemplateInitializer(),
          selfContained: false,
        } as SetupWorkspaceOpts);

        const resp = await readYAMLAsync(path.join(wsRoot, "dendron.yml"));
        expect(resp).toContain({
          workspace: {
            vaults: [
              {
                fsPath: "templates",
                name: "dendron.templates",
                seed: "dendron.templates",
              },
              {
                fsPath: "vault",
              },
            ],
            seeds: {
              "dendron.templates": {},
            },
          },
        });
        const dendronState = MetadataService.instance().getMeta();
        expect(isNotUndefined(dendronState.firstInstall)).toBeTruthy();
        expect(isNotUndefined(dendronState.firstWsInitialize)).toBeTruthy();
        expect(
          await fs.readdir(path.join(wsRoot, DEFAULT_LEGACY_VAULT_NAME))
        ).toEqual(genEmptyWSFiles());
      });
    });

    describeSingleWS(
      "WHEN a workspace exists",
      {
        preSetupHook: async () => {
          DendronExtension.version = () => "0.0.1";
        },
      },
      () => {
        test("THEN Dendron initializes", async () => {
          const { wsRoot, vaults, engine } = ExtensionProvider.getDWorkspace();
          // check for meta
          const port = EngineUtils.getPortFilePathForWorkspace({ wsRoot });
          const fpath = getWSMetaFilePath({ wsRoot });
          const meta = openWSMetaFile({ fpath });
          expect(
            _.toInteger(fs.readFileSync(port, { encoding: "utf8" })) > 0
          ).toBeTruthy();
          expect(meta.version).toEqual("0.0.1");
          expect(meta.activationTime < Time.now().toMillis()).toBeTruthy();
          expect(_.values(engine.notes).length).toEqual(1);
          const vault = path.join(wsRoot, VaultUtils.getRelPath(vaults[0]));

          const settings = fs.readJSONSync(
            path.join(wsRoot, "dendron.code-workspace")
          );
          expect(settings).toEqual(genDefaultSettings());
          expect(fs.readdirSync(vault)).toEqual(
            [CONSTANTS.DENDRON_CACHE_FILE].concat(genEmptyWSFiles())
          );
        });
      }
    );

    describeSingleWS(
      "WHEN a workspace exists, but it is missing the root.schema.yml",
      {
        postSetupHook: async ({ vaults, wsRoot }) => {
          const vault = path.join(wsRoot, VaultUtils.getRelPath(vaults[0]));
          fs.removeSync(path.join(vault, "root.schema.yml"));
        },
      },
      () => {
        // Question mark because I'm not sure what this test is actually testing for.
        test("THEN it still initializes?", async () => {
          const { wsRoot, vaults } = ExtensionProvider.getDWorkspace();
          const vault = path.join(wsRoot, VaultUtils.getRelPath(vaults[0]));
          expect(fs.readdirSync(vault)).toEqual(
            [CONSTANTS.DENDRON_CACHE_FILE].concat(genEmptyWSFiles())
          );
        });
      }
    );

    describe("test conditions for displaying lapsed user message", () => {
      test("Workspace Not Initialized; Message Never Sent; > 1 Day ago", (done) => {
        lapsedMessageTest({
          done,
          firstInstall: 1,
          shouldDisplayMessage: true,
        });
      });

      test("Workspace Not Initialized; Message Never Sent; < 1 Day ago", (done) => {
        lapsedMessageTest({
          done,
          firstInstall: Time.now().toSeconds(),
          shouldDisplayMessage: false,
        });
      });

      test("Workspace Not Initialized; Message Sent < 1 week ago", (done) => {
        lapsedMessageTest({
          done,
          firstInstall: 1,
          lapsedUserMsgSendTime: Time.now().toSeconds(),
          shouldDisplayMessage: false,
        });
      });

      test("Workspace Not Initialized; Message Sent > 1 week ago", (done) => {
        lapsedMessageTest({
          done,
          firstInstall: 1,
          lapsedUserMsgSendTime: 1,
          shouldDisplayMessage: true,
        });
      });

      test("Workspace Already Initialized", (done) => {
        lapsedMessageTest({
          done,
          firstInstall: 1,
          firstWsInitialize: 1,
          shouldDisplayMessage: false,
        });
      });
    });

    describe("firstWeekSinceInstall", () => {
      describe("GIVEN first week", () => {
        test("THEN isFirstWeek is true", (done) => {
          const svc = MetadataService.instance();
          svc.setInitialInstall();

          const actual = AnalyticsUtils.isFirstWeek();
          expect(actual).toBeTruthy();
          done();
        });
      });
      describe("GIVEN not first week", () => {
        test("THEN isFirstWeek is false", (done) => {
          const svc = MetadataService.instance();
          const ONE_WEEK = 604800;
          const NOW = Time.now().toSeconds();
          const TWO_WEEKS_BEFORE = NOW - 2 * ONE_WEEK;
          svc.setMeta("firstInstall", TWO_WEEKS_BEFORE);

          const actual = AnalyticsUtils.isFirstWeek();
          expect(actual).toBeFalsy();
          done();
        });
      });
    });
  });

  describe("WHEN initializing a NATIVE workspace", function () {
    this.timeout(6 * 1000);

    test("not active, initial create ws", async () => {
      const wsRoot = tmpDir().name;

      MetadataService.instance().setActivationContext(
        WorkspaceActivationContext.normal
      );

      const out = await _activate(ctx);
      // Shouldn't have activated because there is no workspace yet
      expect(out).toBeFalsy();

      stubSetupWorkspace({
        wsRoot,
      });
      const cmd = new SetupWorkspaceCommand();
      await cmd.execute({
        workspaceType: WorkspaceType.NATIVE,
        rootDirRaw: wsRoot,
        skipOpenWs: true,
        skipConfirmation: true,
        workspaceInitializer: new BlankInitializer(),
        selfContained: false,
      });
      expect(
        await fs.pathExists(path.join(wsRoot, CONSTANTS.DENDRON_CONFIG_FILE))
      ).toBeTruthy();
      expect(
        await fs.pathExists(path.join(wsRoot, CONSTANTS.DENDRON_WS_NAME))
      ).toBeFalsy();
    });
  });

  describe("WHEN initializing a self contained vault as a workspace", () => {
    test("THEN Dendron correctly creates a workspace", async () => {
      const wsRoot = tmpDir().name;

      MetadataService.instance().setActivationContext(
        WorkspaceActivationContext.normal
      );

      const out = await _activate(ctx);
      // Shouldn't have activated because there is no workspace yet
      expect(out).toBeFalsy();

      stubSetupWorkspace({
        wsRoot,
      });
      const cmd = new SetupWorkspaceCommand();
      await cmd.execute({
        workspaceType: WorkspaceType.CODE,
        rootDirRaw: wsRoot,
        skipOpenWs: true,
        skipConfirmation: true,
        workspaceInitializer: new BlankInitializer(),
        selfContained: true,
      });
      const firstFile = await fs.pathExists(
        path.join(wsRoot, CONSTANTS.DENDRON_CONFIG_FILE)
      );
      expect(firstFile).toBeTruthy();
      const secondFile = await fs.pathExists(
        path.join(wsRoot, CONSTANTS.DENDRON_WS_NAME)
      );
      expect(secondFile).toBeTruthy();
    });
  });
});
Example #14
Source File: CommandFactoryTest.ts    From discord-bot with MIT License 4 votes vote down vote up
describe("CommandFactory", () => {
	let factory: CommandFactory;
	let commandName: string;
	let commandWithAliasesName: string;
	let aliases: string[];

	before(() => {
		commandName = new MockCommand().getName();
		commandWithAliasesName = new MockCommandWithAlias().getName();
		aliases = new MockCommandWithAlias().getAliases();

		factory = new CommandFactory();
		factory.commands = {};
		factory.commands[commandName] = () => new MockCommand();
		factory.commands[commandWithAliasesName] = () => new MockCommandWithAlias();

		aliases.forEach(alias => {
			factory.commands[alias] = () => new MockCommandWithAlias();
		});
	});

	describe("loadCommands()", () => {
		let sandbox: SinonSandbox;
		let emptyFactory: CommandFactory;
		let getFilesStub: SinonStub;

		beforeEach(() => {
			sandbox = createSandbox();
			emptyFactory = new CommandFactory();

			getFilesStub = sandbox.stub(DirectoryUtils, "getFilesInDirectory").callsFake(async () => [
				// eslint-disable-next-line global-require
				require("../MockCommand"),

				// eslint-disable-next-line global-require
				require("../MockCommandWithAlias")
			]);
		});

		it("should call DirectoryUtils::getFilesInDirectory()", async () => {
			await emptyFactory.loadCommands();

			expect(getFilesStub.called).to.be.true;
		});

		it("should load commands", async () => {
			expect(emptyFactory.commandExists(new MockCommand().getName())).to.be.false;

			await emptyFactory.loadCommands();

			expect(emptyFactory.commandExists(new MockCommand().getName())).to.be.true;
		});

		it("should load aliases", async () => {
			expect(emptyFactory.commandExists(new MockCommandWithAlias().getName())).to.be.false;

			await emptyFactory.loadCommands();

			expect(emptyFactory.commandExists(new MockCommandWithAlias().getName())).to.be.true;
		});

		afterEach(() => {
			sandbox.restore();
		});
	});

	describe("commandExists()", () => {
		it("checks to see a command exists", () => {
			expect(factory.commandExists(commandName)).to.be.true;
		});

		it("checks to see a command exists - lowercase", () => {
			expect(factory.commandExists(commandName.toLowerCase())).to.be.true;
		});

		it("checks to see a command exists - uppercase", () => {
			expect(factory.commandExists(commandName.toUpperCase())).to.be.true;
		});

		it("checks to see a command doesn't exist", () => {
			expect(factory.commandExists("bad command")).to.be.false;
		});

		it("checks to see a command exists by it's aliases", () => {
			aliases.forEach(alias => {
				expect(factory.commandExists(alias)).to.be.true;
			});
		});
	});

	describe("getCommand()", () => {
		it("gets a mocked command", () => {
			expect(factory.getCommand(commandName)).to.be.a("object");
		});

		it("gets a mocked command - lowercase", () => {
			expect(factory.getCommand(commandName.toLowerCase())).to.be.a("object");
		});

		it("gets a mocked command - uppercase", () => {
			expect(factory.getCommand(commandName.toUpperCase())).to.be.a("object");
		});

		it("gets a mocked command by it's aliases", () => {
			aliases.forEach(alias => {
				expect(factory.getCommand(alias)).to.be.a("object");
			});
		});
	});
});
Example #15
Source File: Survey.test.ts    From dendron with GNU Affero General Public License v3.0 4 votes vote down vote up
suite("SurveyUtils", function () {
  describe("showInitialSurvey", () => {
    let homeDirStub: SinonStub;
    let stateStub: SinonStub;
    let surveySpy: SinonSpy;
    describeMultiWS(
      "GIVEN INITIAL_SURVEY_SUBMITTED is not set",
      {
        beforeHook: async () => {
          await resetCodeWorkspace();
          homeDirStub = TestEngineUtils.mockHomeDir();
        },
      },
      () => {
        after(() => {
          homeDirStub.restore();
        });

        beforeEach(() => {
          stateStub = sinon
            .stub(StateService.instance(), "getGlobalState")
            .resolves(undefined);
          surveySpy = sinon.spy(SurveyUtils, "showInitialSurvey");
        });

        afterEach(() => {
          stateStub.restore();
          surveySpy.restore();
        });

        describe("AND initialSurveyStatus is not set", () => {
          test("THEN showInitialSurvey is called", async () => {
            const tutorialInitializer = new TutorialInitializer();
            const ws = ExtensionProvider.getDWorkspace();
            expect(
              MetadataService.instance().getMeta().initialSurveyStatus
            ).toEqual(undefined);
            expect(
              await StateService.instance().getGlobalState(
                GLOBAL_STATE.INITIAL_SURVEY_SUBMITTED
              )
            ).toEqual(undefined);
            await tutorialInitializer.onWorkspaceOpen({ ws });
            expect(surveySpy.calledOnce).toBeTruthy();
          });
        });

        describe("AND initialSurveyStatus is set to cancelled", () => {
          test("THEN showInitialSurvey is called", async () => {
            const tutorialInitializer = new TutorialInitializer();
            const ws = ExtensionProvider.getDWorkspace();
            MetadataService.instance().setInitialSurveyStatus(
              InitialSurveyStatusEnum.cancelled
            );
            expect(
              await StateService.instance().getGlobalState(
                GLOBAL_STATE.INITIAL_SURVEY_SUBMITTED
              )
            ).toEqual(undefined);
            await tutorialInitializer.onWorkspaceOpen({ ws });
            expect(surveySpy.calledOnce).toBeTruthy();
          });
        });

        describe("AND initialSurveyStatus is set to submitted", () => {
          test("THEN showInitialSurvey is not called", async () => {
            const tutorialInitializer = new TutorialInitializer();
            const ws = ExtensionProvider.getDWorkspace();
            MetadataService.instance().setInitialSurveyStatus(
              InitialSurveyStatusEnum.submitted
            );
            expect(
              await StateService.instance().getGlobalState(
                GLOBAL_STATE.INITIAL_SURVEY_SUBMITTED
              )
            ).toEqual(undefined);
            await tutorialInitializer.onWorkspaceOpen({ ws });
            expect(surveySpy.calledOnce).toBeFalsy();
          });
        });
      }
    );

    describeMultiWS(
      "GIVEN INITIAL_SURVEY_SUBMITTED is set",
      {
        beforeHook: async () => {
          await resetCodeWorkspace();
          homeDirStub = TestEngineUtils.mockHomeDir();
        },
      },
      () => {
        after(() => {
          homeDirStub.restore();
        });

        beforeEach(() => {
          stateStub = sinon
            .stub(StateService.instance(), "getGlobalState")
            .resolves("submitted");
          surveySpy = sinon.spy(SurveyUtils, "showInitialSurvey");
        });

        afterEach(() => {
          stateStub.restore();
          surveySpy.restore();
        });

        describe("AND initialSurveyStatus is not set", () => {
          test("THEN metadata is backfilled AND showInitialSurvey is not called", async () => {
            const tutorialInitializer = new TutorialInitializer();
            const ws = ExtensionProvider.getDWorkspace();
            // metadata is not set yet, we expect this to be backfilled
            expect(
              MetadataService.instance().getMeta().initialSurveyStatus
            ).toEqual(undefined);
            // global state is already set.
            expect(
              await StateService.instance().getGlobalState(
                GLOBAL_STATE.INITIAL_SURVEY_SUBMITTED
              )
            ).toEqual("submitted");

            await tutorialInitializer.onWorkspaceOpen({ ws });

            expect(surveySpy.calledOnce).toBeFalsy();

            // metadata is backfilled.
            expect(
              MetadataService.instance().getMeta().initialSurveyStatus
            ).toEqual(InitialSurveyStatusEnum.submitted);
          });
        });

        describe("AND initialSurveyStatus is set to submitted", () => {
          test("THEN showInitialSurvey is not called", async () => {
            const tutorialInitializer = new TutorialInitializer();
            const ws = ExtensionProvider.getDWorkspace();

            MetadataService.instance().setInitialSurveyStatus(
              InitialSurveyStatusEnum.submitted
            );
            expect(
              await StateService.instance().getGlobalState(
                GLOBAL_STATE.INITIAL_SURVEY_SUBMITTED
              )
            ).toEqual("submitted");
            await tutorialInitializer.onWorkspaceOpen({ ws });
            expect(surveySpy.calledOnce).toBeFalsy();
          });
        });
      }
    );
  });

  describe("showLapsedUserSurvey", () => {
    let homeDirStub: SinonStub;
    let stateStub: SinonStub;
    let surveySpy: SinonSpy;
    let infoMsgStub: SinonStub;

    describeMultiWS(
      "GIVEN LAPSED_USER_SURVEY_SUBMITTED is not set",
      {
        beforeHook: async () => {
          await resetCodeWorkspace();
          homeDirStub = TestEngineUtils.mockHomeDir();
        },
      },
      (ctx) => {
        after(() => {
          homeDirStub.restore();
        });

        beforeEach(() => {
          stateStub = sinon
            .stub(StateService.instance(), "getGlobalState")
            .resolves(undefined);
          surveySpy = sinon.spy(SurveyUtils, "showLapsedUserSurvey");
          infoMsgStub = sinon
            .stub(vscode.window, "showInformationMessage")
            .resolves({ title: "foo" });
        });

        afterEach(() => {
          stateStub.restore();
          surveySpy.restore();
          infoMsgStub.restore();
        });

        describe("AND lapsedUserSurveyStatus is not set", () => {
          test("THEN showLapsedUserSurvey is called", async () => {
            await StartupUtils.showLapsedUserMessage(
              VSCodeUtils.getAssetUri(ctx)
            );
            await new Promise((resolve: any) => {
              setTimeout(() => {
                resolve();
              }, 1);
            });
            expect(surveySpy.calledOnce).toBeTruthy();
          });
        });

        describe("AND lapsedUserSurveyStatus is set to submitted", () => {
          test("THEN showLapsedUserSurvey is not called", async () => {
            MetadataService.instance().setLapsedUserSurveyStatus(
              LapsedUserSurveyStatusEnum.submitted
            );
            await StartupUtils.showLapsedUserMessage(
              VSCodeUtils.getAssetUri(ctx)
            );
            await new Promise((resolve: any) => {
              setTimeout(() => {
                resolve();
              }, 1);
            });
            expect(surveySpy.calledOnce).toBeFalsy();
          });
        });
      }
    );

    describeMultiWS(
      "GIVEN LAPSED_USER_SURVEY_SUBMITTED is set",
      {
        beforeHook: async () => {
          await resetCodeWorkspace();
          homeDirStub = TestEngineUtils.mockHomeDir();
        },
      },
      (ctx) => {
        after(() => {
          homeDirStub.restore();
        });

        beforeEach(() => {
          stateStub = sinon
            .stub(StateService.instance(), "getGlobalState")
            .resolves("submitted");
          surveySpy = sinon.spy(SurveyUtils, "showLapsedUserSurvey");
          infoMsgStub = sinon
            .stub(vscode.window, "showInformationMessage")
            .resolves({ title: "foo" });
        });

        afterEach(() => {
          stateStub.restore();
          surveySpy.restore();
          infoMsgStub.restore();
        });

        describe("AND lapsedUserSurveyStatus is not set", () => {
          test("THEN metadata is backfilled AND showLapsedUserSurvye is not called", async () => {
            // metadata is not set yet, we expect this to be backfilled.
            expect(
              MetadataService.instance().getLapsedUserSurveyStatus()
            ).toEqual(undefined);
            // global state is already set.
            expect(
              await StateService.instance().getGlobalState(
                GLOBAL_STATE.LAPSED_USER_SURVEY_SUBMITTED
              )
            ).toEqual("submitted");

            await StartupUtils.showLapsedUserMessage(
              VSCodeUtils.getAssetUri(ctx)
            );
            await new Promise((resolve: any) => {
              setTimeout(() => {
                resolve();
              }, 1);
            });

            expect(surveySpy.calledOnce).toBeFalsy();

            // metadata is backfilled.
            expect(
              MetadataService.instance().getLapsedUserSurveyStatus()
            ).toEqual(LapsedUserSurveyStatusEnum.submitted);
          });
        });

        describe("AND lapsedUserSurveyStatus is set to submitted", () => {
          test("THEN showLapsedUserSurvey is not called", async () => {
            expect(
              MetadataService.instance().getLapsedUserSurveyStatus()
            ).toEqual(LapsedUserSurveyStatusEnum.submitted);
            expect(
              await StateService.instance().getGlobalState(
                GLOBAL_STATE.LAPSED_USER_SURVEY_SUBMITTED
              )
            ).toEqual("submitted");

            await StartupUtils.showLapsedUserMessage(
              VSCodeUtils.getAssetUri(ctx)
            );
            await new Promise((resolve: any) => {
              setTimeout(() => {
                resolve();
              }, 1);
            });

            expect(surveySpy.calledOnce).toBeFalsy();
          });
        });
      }
    );
  });
});
Example #16
Source File: appTest.ts    From discord-bot with MIT License 4 votes vote down vote up
describe("App", () => {
	let sandbox: SinonSandbox;
	let loginStub: SinonStub;
	let getStub: SinonStub;

	beforeEach(() => {
		sandbox = createSandbox();

		loginStub = sandbox.stub(Client.prototype, "login");
		getStub = sandbox.stub(axios, "get").resolves();

		process.env.DISCORD_TOKEN = "FAKE_TOKEN";
		process.env.HEALTH_CHECK_URL = "https://health-check.com";
	});

	describe("constructor()", () => {
		it("should throw error if DISCORD_TOKEN is not set", async () => {
			process.env.DISCORD_TOKEN = undefined;

			try {
				await new App();
			} catch ({ message }) {
				expect(message).to.equal("You must supply the DISCORD_TOKEN environment variable.");
			}
		});
	});

	describe("reportHealth()", () => {
		it("sends a GET request to a health check endpoint", () => {
			new App().reportHealth();

			expect(getStub.calledOnce).to.be.true;
			expect(getStub.calledWith("https://health-check.com")).to.be.true;
		});
	});

	describe("init()", () => {
		it("should login with the provided DISCORD_TOKEN", async () => {
			sandbox.stub(DirectoryUtils, "getFilesInDirectory").resolves([]);

			await new App().init();

			expect(loginStub.calledWith("FAKE_TOKEN")).to.be.true;
		});

		it("should look for slash commands", async () => {
			const getFilesStub = sandbox.stub(DirectoryUtils, "getFilesInDirectory").resolves([]);

			await new App().init();

			expect(getFilesStub.args[0][1]).to.equal("Command.js");
		});

		it("should look for handler files", async () => {
			const getFilesStub = sandbox.stub(DirectoryUtils, "getFilesInDirectory").resolves([]);

			await new App().init();

			expect(getFilesStub.args[1][1]).to.equal("Handler.js");
		});

		it("should bind handlers to events", async () => {
			// eslint-disable-next-line global-require
			sandbox.stub(DirectoryUtils, "getFilesInDirectory").callsFake(async () => [require("./MockHandler")]);
			const onStub = sandbox.stub(Client.prototype, "on");

			await new App().init();

			const mockHandler = new MockHandler();

			expect(onStub.calledWith(mockHandler.getEvent())).to.be.true;
		});

		it("should fetch auth channel and messages in production environment", async () => {
			const testEnv = process.env.NODE_ENV;

			process.env.NODE_ENV = PRODUCTION_ENV;

			sandbox.stub(DirectoryUtils, "getFilesInDirectory").callsFake(() => []);

			const textChannel = BaseMocks.getTextChannel();

			const fetchChannelsStub = sandbox.stub(ChannelManager.prototype, "fetch").callsFake(async () => textChannel);
			const fetchMessagesStub = sandbox.stub(textChannel.messages, "fetch");

			await new App().init();

			expect(fetchChannelsStub.calledWith(AUTHENTICATION_MESSAGE_CHANNEL)).to.be.true;
			expect(fetchMessagesStub.calledWith(AUTHENTICATION_MESSAGE_ID)).to.be.true;

			process.env.NODE_ENV = testEnv;
		});
	});

	afterEach(() => {
		sandbox.restore();
	});
});
Example #17
Source File: dryrun.ts    From algo-builder with Apache License 2.0 4 votes vote down vote up
describe("Debugging TEAL code using tealdbg", () => {
	useFixtureProject("config-project");
	let deployer: Deployer;
	let algod: AlgoOperatorDryRunImpl;
	let txParam: ExecParams;
	let tealDebugger: TealDbgMock;

	beforeEach(async () => {
		const env = mkEnv("network1");
		algod = new AlgoOperatorDryRunImpl();
		const deployerCfg = new DeployerConfig(env, algod);
		deployer = new DeployerRunMode(deployerCfg);
		stub(algod.algodClient, "getTransactionParams").returns({
			do: async () => mockSuggestedParam,
		} as ReturnType<algosdk.Algodv2["getTransactionParams"]>);
		stub(algod.algodClient, "genesis").returns({
			do: async () => mockGenesisInfo,
		} as ReturnType<algosdk.Algodv2["genesis"]>);

		(stub(algod.algodClient, "dryrun") as any).returns({
			do: async () => mockDryRunResponse,
		}) as ReturnType<algosdk.Algodv2["dryrun"]>;

		(stub(algod.algodClient, "accountInformation") as any).returns({
			do: async () => mockAccountInformation,
		}) as ReturnType<algosdk.Algodv2["accountInformation"]>;

		(stub(algod.algodClient, "getApplicationByID") as any).returns({
			do: async () => mockApplicationResponse,
		}) as ReturnType<algosdk.Algodv2["getApplicationByID"]>;

		txParam = {
			type: types.TransactionType.TransferAsset,
			sign: types.SignType.LogicSignature,
			fromAccountAddr: generateAccount().addr,
			toAccountAddr: generateAccount().addr,
			amount: 500,
			assetID: 1,
			lsig: mockLsig,
			payFlags: { totalFee: 1000 },
		};
		tealDebugger = new TealDbgMock(deployer, txParam);
	});

	afterEach(async () => {
		(algod.algodClient.getTransactionParams as SinonStub).restore();
		(algod.algodClient.dryrun as SinonStub).restore();
		(algod.algodClient.accountInformation as SinonStub).restore();
		(algod.algodClient.getApplicationByID as SinonStub).restore();
		(algod.algodClient.genesis as SinonStub).restore();
	});

	it("dump dryrunResponse in assets/<file>", async () => {
		const resp = await tealDebugger.dryRunResponse();

		// assert recieved response
		assert.deepEqual(resp, mockDryRunResponse);

		// dump response to  file in assets
		await tealDebugger.dryRunResponse("dryrun-resp.json");

		// verify path and data
		const outPath = path.join(process.cwd(), ASSETS_DIR, "dryrun-resp.json");
		assert.isTrue(pathExistsSync(outPath));
		const data = fs.readFileSync(outPath);
		assert.deepEqual(JSON.parse(Buffer.from(data).toString()), mockDryRunResponse);

		fs.rmSync(outPath);
	});

	it("should warn or overwrite existing dryRunResponse based on --force flag", async () => {
		const stub = console.error as SinonStub;
		stub.reset();

		await tealDebugger.dryRunResponse("response.json");
		await tealDebugger.dryRunResponse("response.json"); // running again with same outfile

		const warnMsg =
			"File assets/response.json already exists. Aborting. Use --force flag if you want to overwrite it";
		assert.isTrue(stub.calledWith(warnMsg));

		// if --force == true is passed then file is overwritten
		await tealDebugger.dryRunResponse("response.json", true);
		assert.isTrue(
			(console.log as SinonStub).calledWith(`Data written succesfully to assets/response.json`)
		);

		fs.rmSync(path.join(process.cwd(), ASSETS_DIR, "response.json"));
	});

	it("should write --dryrun-dump in `cache/dryrun` and run debugger with provided args", async () => {
		assert.equal(tealDebugger.writtenFiles.length, 0);
		await tealDebugger.run();

		// verify .msdp (dryrun dump) is present in cache/dryrun
		assert.equal(tealDebugger.writtenFiles.length, 1);
		// eg. artifacts/cache/dryrun/dump-1626204870.msgp
		const pathToMsgpDump = tealDebugger.writtenFiles[0];
		assert.equal(path.dirname(pathToMsgpDump), "artifacts/cache/dryrun");

		// verify dump arg (-d)
		assert.include(tealDebugger.debugArgs, "-d");
		assert.include(tealDebugger.debugArgs, pathToMsgpDump);

		// verify path to teal file is present(if passed)
		await tealDebugger.run({ tealFile: "gold-asa.teal" });
		assert.include(tealDebugger.debugArgs, "assets/gold-asa.teal");

		// verify mode and groupIndex arg (--mode, --group-index)
		await tealDebugger.run({ mode: ExecutionMode.APPLICATION, groupIndex: 0 });
		assert.include(tealDebugger.debugArgs, "--mode");
		assert.include(tealDebugger.debugArgs, "application");
		assert.include(tealDebugger.debugArgs, "--group-index");
		assert.include(tealDebugger.debugArgs, "0");
	});

	it("should throw error if groupIndex is greator than txGroup length", async () => {
		tealDebugger.execParams = [txParam, { ...txParam, payFlags: { note: "Algrand" } }];

		try {
			await tealDebugger.run({ mode: ExecutionMode.APPLICATION, groupIndex: 5 });
		} catch (error) {
			if (error instanceof Error) {
				assert.equal(error.message, "groupIndex(= 5) exceeds transaction group length(= 2)");
			}
			console.error("An unexpected error occurred:", error);
		}
	});

	it("should run debugger using pyteal files as well", async () => {
		const writtenFilesBeforeLen = tealDebugger.writtenFiles.length;
		await tealDebugger.run({ tealFile: "gold-asa.py" });

		// 2 files should be written: (1. --dryrun-dump and 2. compiled teal code from pyTEAL)
		assert.equal(tealDebugger.writtenFiles.length, writtenFilesBeforeLen + 2);
	});

	it("should run debugger from cached TEAL code", async () => {
		const stub = console.info as SinonStub;
		stub.reset();
		const writtenFilesBeforeLen = tealDebugger.writtenFiles.length;

		// write to cache
		const pathToWrite = path.join(CACHE_DIR, "gold-asa.py.yaml");
		fs.writeFileSync(
			pathToWrite,
			YAML.stringify({
				tealCode: getProgram("gold-asa.py"),
			})
		);

		await tealDebugger.run({ tealFile: "gold-asa.py" });

		// verify cached console.info is true
		assert.isTrue(
			(console.info as SinonStub).calledWith(
				"\x1b[33m%s\x1b[0m",
				`Using cached TEAL code for gold-asa.py`
			)
		);

		// 2 files should be written: (1. --dryrun-dump and 2. cached teal code from pyTEAL)
		assert.equal(tealDebugger.writtenFiles.length, writtenFilesBeforeLen + 2);
		fs.rmSync(pathToWrite);
	});
});
Example #18
Source File: Extension-PostInstall.test.ts    From dendron with GNU Affero General Public License v3.0 4 votes vote down vote up
/**
 * This is for testing functionality that is only triggered when activating
 * a workspace after installation
 */

suite("GIVEN Dendron plugin activation", function () {
  let setInitialInstallSpy: sinon.SinonSpy;
  let showTelemetryNoticeSpy: sinon.SinonSpy;
  let mockHomeDirStub: sinon.SinonStub;

  function stubDendronWhenNotFirstInstall() {
    MetadataService.instance().setInitialInstall();
  }

  function stubDendronWhenFirstInstall(ctx: ExtensionContext) {
    ctx.globalState.update(GLOBAL_STATE.VERSION, undefined);
    MetadataService.instance().setMeta(
      "welcomeClickedTime",
      Time.now().toMillis()
    );
  }

  function setupSpies() {
    setInitialInstallSpy = sinon.spy(
      MetadataService.instance(),
      "setInitialInstall"
    );
    showTelemetryNoticeSpy = sinon.spy(AnalyticsUtils, "showTelemetryNotice");
  }

  async function afterHook() {
    mockHomeDirStub.restore();
    sinon.restore();
  }

  describe("AND WHEN not first install", () => {
    describeMultiWS(
      "AND WHEN activate",
      {
        preActivateHook: async () => {
          mockHomeDirStub = TestEngineUtils.mockHomeDir();
          stubDendronWhenNotFirstInstall();
          setupSpies();
        },
        afterHook,
        timeout: 1e4,
      },
      () => {
        test("THEN set initial install not called", () => {
          expect(setInitialInstallSpy.called).toBeFalsy();
        });

        test("THEN do not show telemetry notice", () => {
          expect(showTelemetryNoticeSpy.called).toBeFalsy();
        });
      }
    );
    describeMultiWS(
      "AND WHEN firstInstall not set for old user",
      {
        preActivateHook: async () => {
          mockHomeDirStub = TestEngineUtils.mockHomeDir();
          stubDendronWhenNotFirstInstall();
          setupSpies();
          // when check for first install, should be empty
          MetadataService.instance().deleteMeta("firstInstall");
        },
        afterHook,
        timeout: 1e5,
      },
      () => {
        test("THEN set initial install called", () => {
          expect(
            setInitialInstallSpy.calledWith(
              Time.DateTime.fromISO("2021-06-22").toSeconds()
            )
          ).toBeTruthy();
        });

        test("THEN do not show telemetry notice", () => {
          expect(showTelemetryNoticeSpy.called).toBeFalsy();
        });
      }
    );
  });

  describe("AND WHEN first install", () => {
    describeMultiWS(
      "AND WHEN activate",
      {
        preActivateHook: async ({ ctx }) => {
          mockHomeDirStub = TestEngineUtils.mockHomeDir();
          setupSpies();
          stubDendronWhenFirstInstall(ctx);
        },
        noSetInstallStatus: true,
        timeout: 1e5,
      },
      () => {
        test("THEN set initial install called", () => {
          expect(setInitialInstallSpy.called).toBeTruthy();
        });

        test("THEN global version set", () => {
          expect(MetadataService.instance().getGlobalVersion()).toNotEqual(
            undefined
          );
        });
        test("THEN show telemetry notice", () => {
          expect(showTelemetryNoticeSpy.called).toBeTruthy();
        });

        this.afterAll(afterHook);
      }
    );
  });

  describe("AND WHEN secondary install on a fresh vscode instance", () => {
    describeMultiWS(
      "AND WHEN activate",
      {
        preActivateHook: async ({ ctx }) => {
          mockHomeDirStub = TestEngineUtils.mockHomeDir();
          // new instance, so fresh user-data. global storage is clean slate.
          stubDendronWhenFirstInstall(ctx);
          // but we have first install already recorded in metadata.
          stubDendronWhenNotFirstInstall();
          setupSpies();
        },
        afterHook,
        timeout: 1e4,
        noSetInstallStatus: true,
      },
      () => {
        // we prevent this from happening in new vscode instances.
        test("THEN set initial install is not called", () => {
          expect(setInitialInstallSpy.called).toBeFalsy();
        });

        // but stil want to set this in the fresh globalStorage of the new vscode instance
        test("THEN global version set", () => {
          expect(MetadataService.instance().getGlobalVersion()).toNotEqual(
            undefined
          );
        });
      }
    );
  });
});
Example #19
Source File: engine.ts    From dendron with GNU Affero General Public License v3.0 4 votes vote down vote up
/**
 *
 * To create empty workspace, initilizae with `vaults = []`
 * See [[Run Engine Test|dendron://dendron.docs/pkg.engine-test-utils.ref.run-engine-test]]
 * @param func
 * @param opts.vaults: By default, initiate 3 vaults {vault1, vault2, (vault3, "vaultThree")}
 * @param opts.preSetupHook: By default, initiate empty
 * @param opts.wsRoot: Override the randomly generated test directory for the wsRoot
 * @returns
 */
export async function runEngineTestV5(
  func: RunEngineTestFunctionV5,
  opts: RunEngineTestV5Opts
): Promise<any> {
  const {
    preSetupHook,
    extra,
    vaults: vaultsInit,
    createEngine,
    initGit,
    workspaces,
    addVSWorkspace,
    git,
  } = _.defaults(opts, {
    preSetupHook: async () => {},
    postSetupHook: async () => {},
    createEngine: createEngineFromEngine,
    extra: {},
    // third vault has diff name
    vaults: [
      { fsPath: "vault1" },
      { fsPath: "vault2" },
      { fsPath: "vault3", name: "vaultThree" },
    ],
    addVSWorkspace: false,
  });

  let homeDirStub: sinon.SinonStub | undefined;
  let server: Server | undefined;

  try {
    // --- begin ws setup
    // make sure tests don't overwrite local homedir contents
    homeDirStub = TestEngineUtils.mockHomeDir();
    const { wsRoot, vaults } = await setupWS({
      vaults: vaultsInit,
      workspaces,
      wsRoot: opts.wsRoot,
      modConfigCb: opts.modConfigCb,
    });
    if ((opts.initHooks, vaults)) {
      fs.ensureDirSync(path.join(wsRoot, CONSTANTS.DENDRON_HOOKS_BASE));
    }
    if (addVSWorkspace) {
      fs.writeJSONSync(
        path.join(wsRoot, CONSTANTS.DENDRON_WS_NAME),
        {
          folders: vaults.map((ent) => ({
            path: ent.fsPath,
            name: ent.name,
          })) as WorkspaceFolderRaw[],
          settings: {},
          extensions: {},
        } as WorkspaceSettings,
        { spaces: 4 }
      );
    }

    // --- begin engine setup
    await preSetupHook({ wsRoot, vaults });
    const resp = await createEngine({ wsRoot, vaults });
    const engine = resp.engine;
    server = resp.server;
    const start = process.hrtime();
    const initResp = await engine.init();
    const engineInitDuration = getDurationMilliseconds(start);
    const testOpts = {
      wsRoot,
      vaults,
      engine,
      initResp,
      port: resp.port,
      extra,
      config: engine,
      engineInitDuration,
    };
    if (initGit) {
      await GitTestUtils.createRepoForWorkspace(wsRoot);
      await Promise.all(
        vaults.map((vault) => {
          return GitTestUtils.createRepoWithReadme(
            vault2Path({ vault, wsRoot }),
            { remote: git?.initVaultWithRemote, branchName: git?.branchName }
          );
        })
      );
    }
    if (opts.setupOnly) {
      return testOpts;
    }
    const results = (await func(testOpts)) || [];
    await runJestHarnessV2(results, expect);
    return { opts: testOpts, resp: undefined, wsRoot };
  } finally {
    // restore sinon so other tests can keep running
    if (homeDirStub) {
      homeDirStub.restore();
    }
    if (server) {
      server.close();
    }
  }
}
Example #20
Source File: tx.ts    From algo-builder with Apache License 2.0 4 votes vote down vote up
describe("Delete ASA and SSC", () => {
	useFixtureProjectCopy("stateful");
	let deployer: Deployer;
	let algod: AlgoOperatorDryRunImpl;
	beforeEach(async () => {
		const env = mkEnv("network1");
		algod = new AlgoOperatorDryRunImpl();
		const deployerCfg = new DeployerConfig(env, algod);
		deployerCfg.asaDefs = { silver: mkASA() };
		deployer = new DeployerDeployMode(deployerCfg);
		await deployer.deployASA("silver", { creator: deployer.accounts[0] });
		stubAlgodGenesisAndTxParams(algod.algodClient);
	});

	afterEach(async () => {
		(algod.algodClient.getTransactionParams as SinonStub).restore();
		(algod.algodClient.genesis as SinonStub).restore();
	});

	it("Should delete ASA, and set delete boolean in ASAInfo", async () => {
		const execParams: wtypes.DestroyAssetParam = {
			type: wtypes.TransactionType.DestroyAsset,
			sign: wtypes.SignType.SecretKey,
			payFlags: {},
			fromAccount: bobAcc,
			assetID: "silver",
		};
		await deployer.executeTx([execParams]);

		const res = deployer.getASAInfo("silver");
		assert.equal(res.deleted, true);
	});

	it("Should delete ASA If asset index is used, instead of asset name", async () => {
		const execParams: wtypes.DestroyAssetParam = {
			type: wtypes.TransactionType.DestroyAsset,
			sign: wtypes.SignType.SecretKey,
			payFlags: {},
			fromAccount: bobAcc,
			assetID: 1,
		};
		await deployer.executeTx([execParams]);

		const res = deployer.getASAInfo("silver");
		assert.equal(res.deleted, true);
	});

	it("Should not fail if ASA is not in checkpoints", async () => {
		const execParams: wtypes.DestroyAssetParam = {
			type: wtypes.TransactionType.DestroyAsset,
			sign: wtypes.SignType.SecretKey,
			payFlags: {},
			fromAccount: bobAcc,
			assetID: 2,
		};
		await deployer.executeTx([execParams]);
	});

	it("Should delete SSC, set delete boolean in latest AppInfo", async () => {
		const appDefinition: wtypes.AppDefinitionFromFile = {
			appName: "app",
			metaType: wtypes.MetaType.FILE,
			localBytes: 1,
			localInts: 1,
			globalBytes: 1,
			globalInts: 1,
			approvalProgramFilename: "approval.teal",
			clearProgramFilename: "clear.teal",
		};
		const info = await deployer.deployApp(bobAcc, appDefinition, {});
		const execParams: wtypes.AppCallsParam = {
			type: wtypes.TransactionType.DeleteApp,
			sign: wtypes.SignType.SecretKey,
			payFlags: {},
			fromAccount: bobAcc,
			appID: info.appID,
		};

		await deployer.executeTx([execParams]);

		const res = deployer.getApp("app");
		assert.isDefined(res);
		if (res) assert.equal(res.deleted, true);
	});

	it("Should not fail if SSC is not in checkpoints", async () => {
		const execParams: wtypes.AppCallsParam = {
			type: wtypes.TransactionType.DeleteApp,
			sign: wtypes.SignType.SecretKey,
			payFlags: {},
			fromAccount: bobAcc,
			appID: 23,
		};
		await deployer.executeTx([execParams]);
	});
});
Example #21
Source File: tx.ts    From algo-builder with Apache License 2.0 4 votes vote down vote up
describe("Delete ASA and SSC transaction flow(with functions and executeTx)", () => {
	useFixtureProject("stateful");
	let deployer: Deployer;
	let algod: AlgoOperatorDryRunImpl;
	let appID: number;
	let assetID: number;
	const assetName = "silver";
	beforeEach(async () => {
		const env = mkEnv("network1");
		algod = new AlgoOperatorDryRunImpl();
		const deployerCfg = new DeployerConfig(env, algod);
		deployerCfg.asaDefs = { silver: mkASA() };
		deployer = new DeployerDeployMode(deployerCfg);
		stubAlgodGenesisAndTxParams(algod.algodClient);

		// deploy  and delete asset
		const asaInfo = await deployer.deployASA(assetName, { creator: deployer.accounts[0] });
		assetID = asaInfo.assetIndex;
		const execParams: wtypes.DestroyAssetParam = {
			type: wtypes.TransactionType.DestroyAsset,
			sign: wtypes.SignType.SecretKey,
			payFlags: {},
			fromAccount: bobAcc,
			assetID: 1,
		};
		await deployer.executeTx([execParams]);

		// deploy and delete app
		const appDefinition: wtypes.AppDefinitionFromFile = {
			appName: "app",
			metaType: wtypes.MetaType.FILE,
			localBytes: 1,
			localInts: 1,
			globalBytes: 1,
			globalInts: 1,
			approvalProgramFilename: "approval.teal",
			clearProgramFilename: "clear.teal",
		};
		const info = await deployer.deployApp(bobAcc, appDefinition, {});
		appID = info.appID;
		const execParam: wtypes.AppCallsParam = {
			type: wtypes.TransactionType.DeleteApp,
			sign: wtypes.SignType.SecretKey,
			payFlags: {},
			fromAccount: bobAcc,
			appID: info.appID,
		};
		await deployer.executeTx([execParam]);
	});

	afterEach(async () => {
		(algod.algodClient.getTransactionParams as SinonStub).restore();
		(algod.algodClient.genesis as SinonStub).restore();
	});

	it("should throw error with opt-in asa functions, if asa exist and deleted", async () => {
		await expectBuilderErrorAsync(
			async () => await deployer.optInAccountToASA(assetName, "acc-name-1", {}),
			ERRORS.GENERAL.ASSET_DELETED
		);

		await expectBuilderErrorAsync(
			async () => await deployer.optInLsigToASA(assetName, mockLsig, {}),
			ERRORS.GENERAL.ASSET_DELETED
		);
	});

	it("should pass with opt-in asa functions, if asa doesn't exist in checkpoint", async () => {
		await deployer.optInAccountToASA("23", "acc-name-1", {});

		await deployer.optInLsigToASA("233212", mockLsig, {});
	});

	it("should throw error with opt-in app functions, if app exist and deleted", async () => {
		await expectBuilderErrorAsync(
			async () => await deployer.optInAccountToApp(bobAcc, appID, {}, {}),
			ERRORS.GENERAL.APP_DELETED
		);

		await expectBuilderErrorAsync(
			async () => await deployer.optInLsigToApp(appID, mockLsig, {}, {}),
			ERRORS.GENERAL.APP_DELETED
		);
	});

	it("should pass with opt-in app functions, if app doesn't exist in checkpoint", async () => {
		await deployer.optInAccountToApp(bobAcc, 122, {}, {});

		await deployer.optInLsigToApp(12223, mockLsig, {}, {});
	});

	it("should throw error with update app function, if app exist and deleted", async () => {
		await expectBuilderErrorAsync(
			async () =>
				await deployer.updateApp(
					"app",
					bobAcc,
					{},
					appID,
					{
						metaType: wtypes.MetaType.FILE,
						approvalProgramFilename: "approval.teal",
						clearProgramFilename: "clear.teal",
					},
					{}
				),
			ERRORS.GENERAL.APP_DELETED
		);
	});

	it("should pass with update app functions, if app doesn't exist in checkpoint", async () => {
		await deployer.updateApp(
			"app",
			bobAcc,
			{},
			123,
			{
				metaType: wtypes.MetaType.FILE,
				approvalProgramFilename: "approval.teal",
				clearProgramFilename: "clear.teal",
			},
			{}
		);
	});

	it("should fail if user tries to opt-in through execute tx", async () => {
		const execParam: wtypes.OptInASAParam = {
			type: wtypes.TransactionType.OptInASA,
			sign: wtypes.SignType.SecretKey,
			fromAccount: bobAcc,
			payFlags: {},
			assetID: assetID,
		};
		await expectBuilderErrorAsync(
			async () => await deployer.executeTx([execParam]),
			ERRORS.GENERAL.ASSET_DELETED
		);
	});

	it("should fail if user tries to modify through execute tx", async () => {
		const execParam: wtypes.ModifyAssetParam = {
			type: wtypes.TransactionType.ModifyAsset,
			sign: wtypes.SignType.SecretKey,
			fromAccount: bobAcc,
			payFlags: {},
			assetID: assetID,
			fields: {},
		};
		await expectBuilderErrorAsync(
			async () => await deployer.executeTx([execParam]),
			ERRORS.GENERAL.ASSET_DELETED
		);
	});

	it("should fail if user tries to freeze through execute tx", async () => {
		const execParam: wtypes.FreezeAssetParam = {
			type: wtypes.TransactionType.FreezeAsset,
			sign: wtypes.SignType.SecretKey,
			fromAccount: bobAcc,
			payFlags: {},
			assetID: assetID,
			freezeTarget: "acc-name-1",
			freezeState: true,
		};
		await expectBuilderErrorAsync(
			async () => await deployer.executeTx([execParam]),
			ERRORS.GENERAL.ASSET_DELETED
		);
	});

	it("should fail if user tries to revoke through execute tx", async () => {
		const execParam: wtypes.RevokeAssetParam = {
			type: wtypes.TransactionType.RevokeAsset,
			sign: wtypes.SignType.SecretKey,
			fromAccount: bobAcc,
			payFlags: {},
			assetID: assetID,
			recipient: bobAcc.addr,
			revocationTarget: "target",
			amount: 1000,
		};
		await expectBuilderErrorAsync(
			async () => await deployer.executeTx([execParam]),
			ERRORS.GENERAL.ASSET_DELETED
		);
	});

	it("should fail if user tries to destroy through execute tx", async () => {
		const execParam: wtypes.DestroyAssetParam = {
			type: wtypes.TransactionType.DestroyAsset,
			sign: wtypes.SignType.SecretKey,
			fromAccount: bobAcc,
			payFlags: {},
			assetID: assetID,
		};
		await expectBuilderErrorAsync(
			async () => await deployer.executeTx([execParam]),
			ERRORS.GENERAL.ASSET_DELETED
		);
	});

	it("should fail if user tries to transfer asa through execute tx", async () => {
		const execParam: wtypes.AssetTransferParam = {
			type: wtypes.TransactionType.TransferAsset,
			sign: wtypes.SignType.SecretKey,
			fromAccount: bobAcc,
			payFlags: {},
			assetID: assetID,
			toAccountAddr: aliceAcc.addr,
			amount: 12,
		};
		await expectBuilderErrorAsync(
			async () => await deployer.executeTx([execParam]),
			ERRORS.GENERAL.ASSET_DELETED
		);
	});

	it("should pass if user tries to opt-out through execute tx", async () => {
		const execParam: wtypes.AssetTransferParam = {
			type: wtypes.TransactionType.TransferAsset,
			sign: wtypes.SignType.SecretKey,
			fromAccount: bobAcc,
			payFlags: { closeRemainderTo: bobAcc.addr },
			assetID: assetID,
			toAccountAddr: aliceAcc.addr,
			amount: 12,
		};
		await deployer.executeTx([execParam]);
	});

	it("should throw error if user tries to delete deleted app", async () => {
		const execParam: wtypes.AppCallsParam = {
			type: wtypes.TransactionType.DeleteApp,
			sign: wtypes.SignType.SecretKey,
			fromAccount: bobAcc,
			payFlags: {},
			appID: appID,
		};
		await expectBuilderErrorAsync(
			async () => await deployer.executeTx([execParam]),
			ERRORS.GENERAL.APP_DELETED
		);
	});

	it("should throw error if user tries to update deleted app", async () => {
		const execParam: wtypes.UpdateAppParam = {
			type: wtypes.TransactionType.UpdateApp,
			sign: wtypes.SignType.SecretKey,
			fromAccount: bobAcc,
			payFlags: {},
			appName: "my-app",
			appID: appID,
			newAppCode: {
				metaType: wtypes.MetaType.FILE,
				approvalProgramFilename: "approval.teal",
				clearProgramFilename: "clear.teal",
			},
		};
		await expectBuilderErrorAsync(
			async () => await deployer.executeTx([execParam]),
			ERRORS.GENERAL.APP_DELETED
		);
	});

	it("should throw error if user tries to call deleted app", async () => {
		const execParam: wtypes.AppCallsParam = {
			type: wtypes.TransactionType.CallApp,
			sign: wtypes.SignType.SecretKey,
			fromAccount: bobAcc,
			payFlags: {},
			appID: appID,
		};
		await expectBuilderErrorAsync(
			async () => await deployer.executeTx([execParam]),
			ERRORS.GENERAL.APP_DELETED
		);
	});

	it("should throw error if user tries to opt-in deleted app", async () => {
		const execParam: wtypes.AppCallsParam = {
			type: wtypes.TransactionType.OptInToApp,
			sign: wtypes.SignType.SecretKey,
			fromAccount: bobAcc,
			payFlags: {},
			appID: appID,
		};
		await expectBuilderErrorAsync(
			async () => await deployer.executeTx([execParam]),
			ERRORS.GENERAL.APP_DELETED
		);
	});

	it("should pass if user tries to opt-out deleted app", async () => {
		const execParam: wtypes.AppCallsParam = {
			type: wtypes.TransactionType.CloseApp,
			sign: wtypes.SignType.SecretKey,
			fromAccount: bobAcc,
			payFlags: {},
			appID: appID,
		};
		await expectBuilderErrorAsync(
			async () => await deployer.executeTx([execParam]),
			ERRORS.GENERAL.APP_DELETED
		);

		const execParams: wtypes.AppCallsParam = {
			type: wtypes.TransactionType.ClearApp,
			sign: wtypes.SignType.SecretKey,
			fromAccount: bobAcc,
			payFlags: {},
			appID: appID,
		};
		await deployer.executeTx([execParams]);
	});

	it("should pass if user tries delete app that doesn't exist in checkpoint", async () => {
		const execParam: wtypes.DestroyAssetParam = {
			type: wtypes.TransactionType.DestroyAsset,
			sign: wtypes.SignType.SecretKey,
			fromAccount: bobAcc,
			payFlags: {},
			assetID: 123,
		};

		await deployer.executeTx([execParam]);
	});

	it("should pass if user tries delete (asset + app) that doesn't exist in checkpoint", async () => {
		const txGroup: wtypes.ExecParams[] = [
			{
				type: wtypes.TransactionType.DestroyAsset,
				sign: wtypes.SignType.SecretKey,
				fromAccount: bobAcc,
				payFlags: {},
				assetID: 123,
			},
			{
				type: wtypes.TransactionType.DeleteApp,
				sign: wtypes.SignType.SecretKey,
				fromAccount: bobAcc,
				payFlags: {},
				appID: 12213,
			},
		];

		await deployer.executeTx(txGroup);
	});
});
Example #22
Source File: tx.ts    From algo-builder with Apache License 2.0 4 votes vote down vote up
describe("Deploy, Delete transactions test in run mode", () => {
	useFixtureProject("stateful");
	let deployer: Deployer;
	let algod: AlgoOperatorDryRunImpl;
	let deployerCfg: DeployerConfig;
	beforeEach(async () => {
		const env = mkEnv("network1");
		algod = new AlgoOperatorDryRunImpl();
		deployerCfg = new DeployerConfig(env, algod);
		deployerCfg.asaDefs = { silver: mkASA() };
		deployer = new DeployerRunMode(deployerCfg);
		stubAlgodGenesisAndTxParams(algod.algodClient);
	});

	afterEach(async () => {
		(algod.algodClient.getTransactionParams as SinonStub).restore();
		(algod.algodClient.genesis as SinonStub).restore();
	});

	it("should deploy asa in run mode", async () => {
		const execParams: wtypes.ExecParams = {
			type: wtypes.TransactionType.DeployASA,
			sign: wtypes.SignType.SecretKey,
			fromAccount: bobAcc,
			asaName: "silver",
			payFlags: {},
		};

		await deployer.executeTx([execParams]);

		// should not be stored in checkpoint if in run mode
		expectBuilderError(
			() => deployer.getASAInfo("silver"),
			ERRORS.BUILTIN_TASKS.DEPLOYER_ASA_NOT_DEFINED
		);
	});

	it("should deploy application in run mode", async () => {
		const execParams: wtypes.ExecParams = {
			type: wtypes.TransactionType.DeployApp,
			sign: wtypes.SignType.SecretKey,
			fromAccount: bobAcc,
			appDefinition: {
				appName: "app",
				metaType: wtypes.MetaType.FILE,
				approvalProgramFilename: "approval.teal",
				clearProgramFilename: "clear.teal",
				localInts: 1,
				localBytes: 1,
				globalInts: 1,
				globalBytes: 1,
			},
			payFlags: {},
		};
		await deployer.executeTx([execParams]);

		// should not be stored in checkpoint if in run mode
		expectBuilderError(() => deployer.getApp("app"), ERRORS.GENERAL.APP_NOT_FOUND_IN_CP);
	});

	it("should deploy application in deploy mode and save info by name", async () => {
		deployer = new DeployerDeployMode(deployerCfg);
		const execParams: wtypes.ExecParams = {
			type: wtypes.TransactionType.DeployApp,
			sign: wtypes.SignType.SecretKey,
			fromAccount: bobAcc,
			appDefinition: {
				metaType: wtypes.MetaType.FILE,
				approvalProgramFilename: "approval.teal",
				clearProgramFilename: "clear.teal",
				localInts: 1,
				localBytes: 1,
				globalInts: 1,
				globalBytes: 1,
				appName: "dao-app",
			},
			payFlags: {},
		};
		await deployer.executeTx([execParams]);

		// able to retrieve info by "appName"
		assert.isDefined(deployer.getApp("dao-app"));
	});

	it("should delete application in run mode", async () => {
		deployer = new DeployerDeployMode(deployerCfg);
		let execParams: wtypes.ExecParams = {
			type: wtypes.TransactionType.DeployApp,
			sign: wtypes.SignType.SecretKey,
			fromAccount: bobAcc,
			appDefinition: {
				appName: "app",
				metaType: wtypes.MetaType.FILE,
				approvalProgramFilename: "approval.teal",
				clearProgramFilename: "clear.teal",
				localInts: 1,
				localBytes: 1,
				globalInts: 1,
				globalBytes: 1,
			},
			payFlags: {},
		};
		const [appInfo] = await deployer.executeTx([execParams]);

		deployer = new DeployerRunMode(deployerCfg);
		execParams = {
			type: wtypes.TransactionType.DeleteApp,
			sign: wtypes.SignType.SecretKey,
			fromAccount: bobAcc,
			appID: appInfo["application-index"],
			payFlags: {},
		};

		await deployer.executeTx([execParams]);

		const res = deployer.getApp("app");
		assert.isDefined(res);
		assert.equal(res?.deleted, false);
	});
});
Example #23
Source File: tx.ts    From algo-builder with Apache License 2.0 4 votes vote down vote up
describe("Update transaction test in run mode", () => {
	useFixtureProject("stateful");
	let deployer: Deployer;
	let algod: AlgoOperatorDryRunImpl;
	let deployerCfg: DeployerConfig;
	beforeEach(async () => {
		const env = mkEnv("network1");
		algod = new AlgoOperatorDryRunImpl();
		deployerCfg = new DeployerConfig(env, algod);
		deployer = new DeployerRunMode(deployerCfg);
		stubAlgodGenesisAndTxParams(algod.algodClient);
	});

	afterEach(async () => {
		(algod.algodClient.getTransactionParams as SinonStub).restore();
		(algod.algodClient.genesis as SinonStub).restore();
	});

	it("should update in run mode", async () => {
		let execParams: wtypes.ExecParams = {
			type: wtypes.TransactionType.DeployApp,
			sign: wtypes.SignType.SecretKey,
			fromAccount: bobAcc,
			appDefinition: {
				appName: "app",
				metaType: wtypes.MetaType.FILE,
				approvalProgramFilename: "approval.teal",
				clearProgramFilename: "clear.teal",
				localInts: 1,
				localBytes: 1,
				globalInts: 1,
				globalBytes: 1,
			},
			payFlags: {},
		};
		const [appInfo] = await deployer.executeTx([execParams]);

		// should not be stored in checkpoint if in run mode
		expectBuilderError(() => deployer.getApp("app"), ERRORS.GENERAL.APP_NOT_FOUND_IN_CP);

		execParams = {
			appName: "app",
			type: wtypes.TransactionType.UpdateApp,
			sign: wtypes.SignType.SecretKey,
			fromAccount: bobAcc,
			appID: appInfo["application-index"],
			newAppCode: {
				metaType: wtypes.MetaType.FILE,
				approvalProgramFilename: "approval.teal",
				clearProgramFilename: "clear.teal",
			},
			payFlags: {},
		};

		await deployer.executeTx([execParams]);
		// should not be stored in checkpoint if in run mode
		expectBuilderError(() => deployer.getApp("app"), ERRORS.GENERAL.APP_NOT_FOUND_IN_CP);
	});

	it("deploy in deploy mode, update in run mode", async () => {
		deployer = new DeployerDeployMode(deployerCfg);
		let execParams: wtypes.ExecParams = {
			type: wtypes.TransactionType.DeployApp,
			sign: wtypes.SignType.SecretKey,
			fromAccount: bobAcc,
			appDefinition: {
				appName: "app",
				metaType: wtypes.MetaType.FILE,
				approvalProgramFilename: "approval.teal",
				clearProgramFilename: "clear.teal",
				localInts: 1,
				localBytes: 1,
				globalInts: 1,
				globalBytes: 1,
			},
			payFlags: {},
		};
		await deployer.executeTx([execParams]);
		const appInfo = deployer.getApp("app");
		assert.isDefined(appInfo);

		deployer = new DeployerRunMode(deployerCfg);
		if (appInfo) {
			execParams = {
				appName: "app",
				type: wtypes.TransactionType.UpdateApp,
				sign: wtypes.SignType.SecretKey,
				fromAccount: bobAcc,
				appID: appInfo.appID,
				newAppCode: {
					metaType: wtypes.MetaType.FILE,
					approvalProgramFilename: "approval.teal",
					clearProgramFilename: "clear.teal",
				},
				payFlags: {},
			};

			await deployer.executeTx([execParams]);
			assert.deepEqual(appInfo, deployer.getApp("app"));
		}
	});

	it("deploy in run mode, update in deploy mode", async () => {
		let execParams: wtypes.ExecParams = {
			type: wtypes.TransactionType.DeployApp,
			sign: wtypes.SignType.SecretKey,
			fromAccount: bobAcc,
			appDefinition: {
				appName: "app",
				metaType: wtypes.MetaType.FILE,
				approvalProgramFilename: "approval.teal",
				clearProgramFilename: "clear.teal",
				localInts: 1,
				localBytes: 1,
				globalInts: 1,
				globalBytes: 1,
			},
			payFlags: {},
		};
		const [appInfo] = await deployer.executeTx([execParams]);
		expectBuilderError(() => deployer.getApp("app"), ERRORS.GENERAL.APP_NOT_FOUND_IN_CP);

		deployer = new DeployerDeployMode(deployerCfg);
		execParams = {
			type: wtypes.TransactionType.UpdateApp,
			sign: wtypes.SignType.SecretKey,
			fromAccount: bobAcc,
			appID: appInfo["application-index"],
			newAppCode: {
				metaType: wtypes.MetaType.FILE,
				approvalProgramFilename: "approval.teal",
				clearProgramFilename: "clear.teal",
			},
			appName: "app",
			payFlags: {},
		};

		await deployer.executeTx([execParams]);
		// checkpoint is stored for the update
		assert.isDefined(deployer.getApp("app"));
	});
});
Example #24
Source File: MessagePreviewServiceTest.ts    From discord-bot with MIT License 4 votes vote down vote up
describe("MessagePreviewService", () => {
	describe("::getInstance()", () => {
		it("returns an instance of MessagePreviewService", () => {
			const service = MessagePreviewService.getInstance();

			expect(service).to.be.instanceOf(MessagePreviewService);
		});
	});

	describe("generatePreview()", () => {
		let sandbox: SinonSandbox;
		let messagePreview: MessagePreviewService;
		let link: string;
		let callingMessage: Message;
		let channel: TextChannel;
		let getChannelMock: SinonStub;
		let sendMessageMock: SinonStub;
		let fetchMessageMock: SinonStub;

		beforeEach(() => {
			sandbox = createSandbox();

			messagePreview = MessagePreviewService.getInstance();

			const guild = CustomMocks.getGuild({
				id: "guild-id",
				channels: []
			});

			channel = CustomMocks.getTextChannel({
				id: "518817917438001152"
			}, guild);

			callingMessage = CustomMocks.getMessage({}, {
				channel
			});

			link = "https://discord.com/channels/guild-id/518817917438001152/732711501345062982";

			getChannelMock = sandbox.stub(callingMessage.guild.channels.cache, "get").returns(channel);
			sendMessageMock = sandbox.stub(callingMessage.channel, "send");

			fetchMessageMock = sandbox.stub(channel.messages, "fetch").resolves(callingMessage);
			sandbox.stub(callingMessage.member, "displayColor").get(() => "#FFFFFF");
		});

		it("gets the channel from the link", async () => {
			await messagePreview.generatePreview(link, callingMessage);

			expect(getChannelMock.calledOnce).to.be.true;
		});

		it("sends preview message", async () => {
			await messagePreview.generatePreview(link, callingMessage);

			expect(sendMessageMock.calledOnce).to.be.true;
		});

		it("escapes hyperlinks", async () => {
			const escapeHyperlinksMock = sandbox.stub(messagePreview, "escapeHyperlinks").returns("Parsed message");

			await messagePreview.generatePreview(link, callingMessage);

			expect(escapeHyperlinksMock.calledOnce);
		});

		it("doesn't send preview message if it is a bot message", async () => {
			callingMessage.author.bot = true;

			await messagePreview.generatePreview(link, callingMessage);

			expect(sendMessageMock.called).to.be.false;
		});

		it("doesn't send preview message if the channel ID is wrong", async () => {
			getChannelMock.restore();
			getChannelMock = sandbox.stub(callingMessage.guild.channels.cache, "get").returns(undefined);

			await messagePreview.generatePreview(link, callingMessage);

			expect(sendMessageMock.called).to.be.false;
		});

		it("doesn't send preview message if the message ID is wrong", async () => {
			fetchMessageMock.restore();
			fetchMessageMock = sandbox.stub(channel.messages, "fetch").returns(Promise.reject());

			await messagePreview.generatePreview(link, callingMessage);

			expect(sendMessageMock.called).to.be.false;
		});

		afterEach(() => {
			sandbox.restore();
		});
	});

	describe("verifyGuild()", () => {
		let sandbox: SinonSandbox;
		let messagePreview: MessagePreviewService;
		let message: Message;

		beforeEach(() => {
			sandbox = createSandbox();
			messagePreview = MessagePreviewService.getInstance();
			message = CustomMocks.getMessage();
		});

		it("should return true if message's guild and provided guild id match", () => {
			expect(messagePreview.verifyGuild(message, BaseMocks.getGuild().id)).to.be.true;
		});

		it("should return false if message's guild and provided guild id don't match", () => {
			expect(messagePreview.verifyGuild(message, "OTHER_GUILD_ID")).to.be.false;
		});

		afterEach(() => {
			sandbox.restore();
		});
	});

	describe("stripLink()", () => {
		let sandbox: SinonSandbox;
		let messagePreview: MessagePreviewService;
		let link: string;

		beforeEach(() => {
			sandbox = createSandbox();
			messagePreview = MessagePreviewService.getInstance();
			link = "https://ptb.discordapp.com/channels/240880736851329024/518817917438001152/732711501345062982";
		});

		it("strips link of unnecessary details", () => {
			const array = messagePreview.stripLink(link);

			expect(array).to.include("240880736851329024");
			expect(array).to.include("518817917438001152");
			expect(array).to.include("732711501345062982");
		});

		afterEach(() => {
			sandbox.restore();
		});
	});

	describe("escapeHyperlinks()", () => {
		let sandbox: SinonSandbox;
		let messagePreview: MessagePreviewService;

		beforeEach(() => {
			sandbox = createSandbox();
			messagePreview = MessagePreviewService.getInstance();
		});

		it("should return the string as it is if there are no hyperlinks", () => {
			expect(messagePreview.escapeHyperlinks("I am the night")).to.equal("I am the night");
		});

		it("should escape hyperlinks", () => {
			expect(messagePreview.escapeHyperlinks("Do you feel lucky, [punk](punkrock.com)?"))
				.to.equal("Do you feel lucky, \\[punk\\]\\(punkrock.com\\)?");
		});

		it("should scape all hyperlinks if there is more than one", () => {
			expect(messagePreview.escapeHyperlinks("[Link1](l1.com) and [Link2](l2.com)"))
				.to.equal("\\[Link1\\]\\(l1.com\\) and \\[Link2\\]\\(l2.com\\)");
		});

		it("should escape hyperlinks even if they are empty", () => {
			expect(messagePreview.escapeHyperlinks("[]()")).to.equal("\\[\\]\\(\\)");
			expect(messagePreview.escapeHyperlinks("[half]()")).to.equal("\\[half\\]\\(\\)");
			expect(messagePreview.escapeHyperlinks("[](half)")).to.equal("\\[\\]\\(half\\)");
		});

		afterEach(() => {
			sandbox.restore();
		});
	});
});