mocha#afterEach TypeScript Examples

The following examples show how to use mocha#afterEach. 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: createWskdeployProject.test.ts    From openwhisk-vscode-extension with Apache License 2.0 6 votes vote down vote up
suite('templateGenerator.createWskdeployProject', async function () {
    test('Create wskdeploy project files', async () => {
        const fakeConfirm = sinon.fake.returns(Promise.resolve({ action: 'confirm' }));
        sinon.replace(vscode.window, 'showInformationMessage', fakeConfirm);
        await createWskdeployProject();
        await timeout(1000);
        expect(existsSync(MANIFESTFILE_PATH)).to.be.true;
    });

    test('Show warning message if manifest file exists', async () => {
        writeFileSync(MANIFESTFILE_PATH, '');
        sinon.spy(vscode.window, 'showErrorMessage');
        await createWskdeployProject();
        // @ts-ignore
        const spyCall = vscode.window.showErrorMessage.getCall(0);
        expect(spyCall.args[0].includes('already exists')).to.be.true;
    });

    afterEach(() => {
        rimraf.sync(SRC_DIR_PATH);
        rimraf.sync(MANIFESTFILE_PATH);
    });
});
Example #2
Source File: RegisterNoteTraitCommand.test.ts    From dendron with GNU Affero General Public License v3.0 6 votes vote down vote up
suite("RegisterNoteTraitCommand tests", () => {
  describeSingleWS("GIVEN a new Note Trait", {}, () => {
    describe(`WHEN registering a new note trait`, () => {
      beforeEach(async () => {
        await VSCodeUtils.closeAllEditors();
      });

      afterEach(async () => {
        await VSCodeUtils.closeAllEditors();
      });

      const traitId = "new-test-trait";

      test(`THEN expect the note trait editor to be visible`, async () => {
        const registerCommand = sinon.stub(vscode.commands, "registerCommand");
        const { wsRoot } = ExtensionProvider.getDWorkspace();
        const cmd = new RegisterNoteTraitCommand();

        await cmd.execute({
          traitId,
        });

        expect(registerCommand.calledOnce).toBeTruthy();
        expect(registerCommand.args[0][0]).toEqual(
          "dendron.customCommand.new-test-trait"
        );
        expect(VSCodeUtils.getActiveTextEditor()?.document.uri.fsPath).toEqual(
          path.join(
            wsRoot,
            CONSTANTS.DENDRON_USER_NOTE_TRAITS_BASE,
            `${traitId}.js`
          )
        );
        registerCommand.restore();
      });
    });
  });
});
Example #3
Source File: NoteTraitService.test.ts    From dendron with GNU Affero General Public License v3.0 6 votes vote down vote up
//TODO: Expand coverage once other methods of NoteTraitManager are implemented
suite("NoteTraitManager tests", () => {
  describe(`GIVEN a NoteTraitManager`, () => {
    const TRAIT_ID = "test-trait";

    const trait: NoteTrait = {
      id: TRAIT_ID,
    };

    describeSingleWS("WHEN registering a new trait", {}, (ctx) => {
      let registrar: CommandRegistrar;
      afterEach(() => {
        if (registrar) {
          registrar.unregisterTrait(trait);
        }
      });

      test(`THEN expect the trait to be found by the manager`, () => {
        const registerCommand = sinon.stub(vscode.commands, "registerCommand");
        const { wsRoot, engine } = ExtensionProvider.getDWorkspace();
        const mockExtension = new MockDendronExtension({
          engine,
          wsRoot,
          context: ctx,
        });
        registrar = new CommandRegistrar(mockExtension);

        const traitManager = new NoteTraitManager(registrar);
        const resp = traitManager.registerTrait(trait);
        expect(resp.error).toBeFalsy();
        expect(registerCommand.calledOnce).toBeTruthy();
        expect(registerCommand.args[0][0]).toEqual(
          "dendron.customCommand.test-trait"
        );
        registerCommand.restore();
      });
    });
  });
});
Example #4
Source File: decoration-utils.test.ts    From vscode-code-review with MIT License 5 votes vote down vote up
suite('Decoration Utils', () => {
  let editorStub: TextEditor;
  let lastSetDecorationConf: TextEditorDecorationType | undefined;
  let lastSetDecorationSelection!: Selection[] | DecorationOptions[] | undefined;
  beforeEach(() => {
    editorStub = ({
      selections: [new Selection(new Position(0, 1), new Position(2, 6))],
      setDecorations: (conf: TextEditorDecorationType, selection: Selection[]) => {
        lastSetDecorationConf = conf;
        lastSetDecorationSelection = selection;
      },
    } as unknown) as TextEditor;
  });

  afterEach(() => {
    lastSetDecorationConf = undefined;
    lastSetDecorationSelection = undefined;
  });

  suite('underlineDecoration', () => {
    test('should underline comments', () => {
      const contextStub = {
        asAbsolutePath: (p) => p,
      } as ExtensionContext;
      const deco = new Decorations(contextStub);

      const csvEntries = [{ lines: '1:0-3:4|9:0-11:3' } as CsvEntry, { lines: '17:3-19:2' } as CsvEntry];
      const decoration = deco.underlineDecoration(csvEntries, editorStub);
      const decorationOptions = lastSetDecorationSelection as DecorationOptions[];

      assert.ok(deco);
      assert.deepStrictEqual(lastSetDecorationConf, deco.decorationDeclarationType);

      assert.strictEqual(decorationOptions[0].range.start.line, 0);
      assert.strictEqual(decorationOptions[0].range.start.character, 0);
      assert.strictEqual(decorationOptions[0].range.end.line, 2);
      assert.strictEqual(decorationOptions[0].range.end.character, 4);

      assert.strictEqual(decorationOptions[1].range.start.line, 8);
      assert.strictEqual(decorationOptions[1].range.start.character, 0);
      assert.strictEqual(decorationOptions[1].range.end.line, 10);
      assert.strictEqual(decorationOptions[1].range.end.character, 3);

      assert.strictEqual(decorationOptions[2].range.start.line, 16);
      assert.strictEqual(decorationOptions[2].range.start.character, 3);
      assert.strictEqual(decorationOptions[2].range.end.line, 18);
      assert.strictEqual(decorationOptions[2].range.end.character, 2);
    });
  });

  suite('commentIconDecoration', () => {
    test('should underline comments', () => {
      const contextStub = {
        asAbsolutePath: (p) => p,
      } as ExtensionContext;
      const deco = new Decorations(contextStub);

      const csvEntries = [{ lines: '1:0-3:4|9:0-11:3' } as CsvEntry, { lines: '17:3-19:2' } as CsvEntry];
      const decoration = deco.commentIconDecoration(csvEntries, editorStub);
      const decorationOptions = lastSetDecorationSelection as DecorationOptions[];

      assert.ok(deco);
      assert.deepStrictEqual(lastSetDecorationConf, deco.commentDecorationType);

      assert.strictEqual(decorationOptions[0].range.start.line, 0);
      assert.strictEqual(decorationOptions[0].range.start.character, 1024);
      assert.strictEqual(decorationOptions[0].range.end.line, 0);
      assert.strictEqual(decorationOptions[0].range.end.character, 1024);

      assert.strictEqual(decorationOptions[1].range.start.line, 8);
      assert.strictEqual(decorationOptions[1].range.start.character, 1024);
      assert.strictEqual(decorationOptions[1].range.end.line, 8);
      assert.strictEqual(decorationOptions[1].range.end.character, 1024);

      assert.strictEqual(decorationOptions[2].range.start.line, 16);
      assert.strictEqual(decorationOptions[2].range.start.character, 1024);
      assert.strictEqual(decorationOptions[2].range.end.line, 16);
      assert.strictEqual(decorationOptions[2].range.end.character, 1024);
    });
  });

  suite('colorizedBackgroundDecoration', () => {
    test('should colorize the given selection with decoration', () => {
      const selections = [new Range(new Position(2, 5), new Position(2, 5))];
      const decoration = colorizedBackgroundDecoration(selections, editorStub, '#fdfdfd');
      assert.ok(decoration);
      assert.deepStrictEqual(lastSetDecorationConf, decoration);
      assert.deepStrictEqual(lastSetDecorationSelection, selections);
    });
  });
});
Example #5
Source File: testUtilsV3.ts    From dendron with GNU Affero General Public License v3.0 5 votes vote down vote up
/**
 * @deprecated. If using {@link describeSingleWS} or {@link describeMultiWS}, this call is no longer necessary
 *
 * If you need before or after hooks, you can use `before()` and `after()` to set them up.
 * Timeout and `noSetInstallStatus` can be set on the options for the test harnesses.
 *
 * @param _this
 * @param opts.noSetInstallStatus: by default, we set install status to NO_CHANGE. use this when you need to test this logic
 */
export function setupBeforeAfter(
  _this: any,
  opts?: {
    beforeHook?: (ctx: ExtensionContext) => any;
    afterHook?: any;
    noSetInstallStatus?: boolean;
    noSetTimeout?: boolean;
  }
) {
  // allows for
  if (!opts?.noSetTimeout) {
    _this.timeout(TIMEOUT);
  }
  const ctx = VSCodeUtils.getOrCreateMockContext();
  beforeEach(async () => {
    // DendronWorkspace.getOrCreate(ctx);

    // workspace has not upgraded
    if (!opts?.noSetInstallStatus) {
      // try to remove any existing stub in case it exists
      // this is because we have tests that call `setupBeforeAfter` as well as
      // in describeMultiWS > [[../packages/plugin-core/src/test/testUtilsV3.ts#^lk3whwd4kh4k]]
      // TODO: keep in place until we completely remove `setupBeforeAndAfter`
      try {
        // @ts-ignore
        sinon
          .stub(VSCodeUtils, "getInstallStatusForExtension")
          .returns(InstallStatus.NO_CHANGE);
      } catch (e) {
        // eat it.
        sinon.restore();
        sinon
          .stub(VSCodeUtils, "getInstallStatusForExtension")
          .returns(InstallStatus.NO_CHANGE);
      }
    }

    sinon.stub(WorkspaceInitFactory, "create").returns(new BlankInitializer());

    if (opts?.beforeHook) {
      await opts.beforeHook(ctx);
    }
    Logger.configure(ctx, "info");
  });
  afterEach(async () => {
    HistoryService.instance().clearSubscriptions();
    if (opts?.afterHook) {
      await opts.afterHook();
    }
    sinon.restore();
  });
  return ctx;
}
Example #6
Source File: CreateNoteWithTraitCommand.test.ts    From dendron with GNU Affero General Public License v3.0 5 votes vote down vote up
suite("CreateNoteWithTraitCommand tests", () => {
  describeSingleWS("GIVEN a Note Trait", {}, (ctx) => {
    describe(`WHEN creating a note with that trait applied`, () => {
      beforeEach(() => {
        VSCodeUtils.closeAllEditors();
      });

      afterEach(() => {
        VSCodeUtils.closeAllEditors();
      });

      test(`THEN expect the title to have been modified`, async () => {
        const { engine, wsRoot, vaults } = getDWorkspace();
        const testTrait = new TestTrait();

        const mockExtension = new MockDendronExtension({
          engine,
          wsRoot,
          context: ctx,
        });

        const cmd = new CreateNoteWithTraitCommand(
          mockExtension,
          "test-create-note-with-trait",
          testTrait
        );

        await cmd.execute({ fname: "test" });

        const expectedFName = path.join(wsRoot, vaults[0].fsPath, "test.md");

        expect(VSCodeUtils.getActiveTextEditor()?.document.uri.fsPath).toEqual(
          expectedFName
        );

        const props = NoteUtils.getNoteByFnameV5({
          fname: "test",
          notes: engine.notes,
          vault: vaults[0],
          wsRoot,
        });

        expect(props?.title).toEqual(testTrait.TEST_TITLE_MODIFIER);
      });
    });
  });
});
Example #7
Source File: CommandRegistrar.test.ts    From dendron with GNU Affero General Public License v3.0 5 votes vote down vote up
suite("CommandRegistrar tests", () => {
  describe(`GIVEN a Command Registrar`, () => {
    const traitId = genUUID();
    const trait: NoteTrait = {
      id: traitId,
    };

    describeSingleWS(
      "WHEN registering a command for a new trait",
      {},
      (ctx) => {
        let _registrar: CommandRegistrar | undefined;
        afterEach(() => {
          _registrar?.unregisterTrait(trait);
        });

        test("THEN the command has been registered", async () => {
          const { engine, wsRoot } = ExtensionProvider.getDWorkspace();
          const mockExtension = new MockDendronExtension({
            engine,
            wsRoot,
            context: ctx,
          });
          _registrar = new CommandRegistrar(mockExtension);
          const expectedCmdName = _registrar.CUSTOM_COMMAND_PREFIX + traitId;

          const cmd = _registrar.registerCommandForTrait(trait);
          expect(cmd).toEqual(expectedCmdName);

          expect(_registrar.registeredCommands[traitId]).toEqual(
            expectedCmdName
          );

          const allCmds = await vscode.commands.getCommands(true);

          expect(allCmds.includes(expectedCmdName)).toBeTruthy();
        });
      }
    );

    describeSingleWS("WHEN unregistering a command", {}, (ctx) => {
      let _registrar: CommandRegistrar | undefined;

      test("THEN the command has been unregistered", async () => {
        const { engine, wsRoot } = ExtensionProvider.getDWorkspace();
        const mockExtension = new MockDendronExtension({
          engine,
          wsRoot,
          context: ctx,
        });

        _registrar = new CommandRegistrar(mockExtension);
        const expectedCmdName = _registrar.CUSTOM_COMMAND_PREFIX + traitId;

        _registrar.registerCommandForTrait(trait);
        _registrar.unregisterTrait(trait);
        expect(_registrar.registeredCommands[expectedCmdName]).toBeFalsy();
        const allCmds = await vscode.commands.getCommands();
        expect(allCmds.includes(expectedCmdName)).toBeFalsy();
      });
    });
  });
});
Example #8
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 #9
Source File: ChangeWorkspace.test.ts    From dendron with GNU Affero General Public License v3.0 5 votes vote down vote up
// eslint-disable-next-line prefer-arrow-callback
suite("GIVEN ChangeWorkspace command", function () {
  describeMultiWS("WHEN command is gathering inputs", {}, () => {
    let showOpenDialog: sinon.SinonStub;

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

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

  describeMultiWS("WHEN command is run", {}, (ctx) => {
    describe("AND a code workspace is selected", () => {
      let openWS: sinon.SinonStub;
      let newWSRoot: string;
      before(async () => {
        const { wsRoot: currentWSRoot } = getDWorkspace();
        openWS = sinon.stub(VSCodeUtils, "openWS").resolves();

        const out = await setupLegacyWorkspaceMulti({
          ctx,
          workspaceType: WorkspaceType.CODE,
        });
        newWSRoot = out.wsRoot;
        expect(newWSRoot).toNotEqual(currentWSRoot);

        const cmd = new ChangeWorkspaceCommand();
        sinon.stub(cmd, "gatherInputs").resolves({ rootDirRaw: newWSRoot });
        await cmd.run();
      });
      after(() => {
        openWS.restore();
      });

      test("THEN workspace is opened", (done) => {
        expect(openWS.calledOnce).toBeTruthy();
        expect(openWS.calledOnceWithExactly(newWSRoot));
        done();
      });
    });

    describe("AND a native workspace is selected", () => {
      let openWS: sinon.SinonStub;
      let newWSRoot: string;
      before(async () => {
        const { wsRoot: currentWSRoot } = getDWorkspace();
        openWS = sinon.stub(VSCodeUtils, "openWS").resolves();

        const out = await setupLegacyWorkspaceMulti({
          ctx,
          workspaceType: WorkspaceType.NATIVE,
        });
        newWSRoot = out.wsRoot;
        expect(newWSRoot).toNotEqual(currentWSRoot);

        const cmd = new ChangeWorkspaceCommand();
        sinon.stub(cmd, "gatherInputs").resolves({ rootDirRaw: newWSRoot });
        await cmd.run();
      });
      after(() => {
        openWS.restore();
      });

      test("THEN workspace is opened", (done) => {
        expect(openWS.calledOnce).toBeTruthy();
        expect(openWS.calledOnceWithExactly(newWSRoot));
        done();
      });
    });
  });
});
Example #10
Source File: storage-util.test.ts    From vscode-code-review with MIT License 5 votes vote down vote up
suite('Storage Utils', () => {
  const pathToFile = path.normalize('file1.csv');

  afterEach(() => {
    if (fs.existsSync(pathToFile)) {
      fs.unlinkSync(pathToFile);
    }
  });

  suite('cleanCsvStorage', () => {
    test('should remove a trailing slash from a string', () => {
      assert.deepStrictEqual(cleanCsvStorage(['foo', 'bar']), ['foo', 'bar']);
      assert.deepStrictEqual(cleanCsvStorage(['foo', '']), ['foo']);
      assert.deepStrictEqual(cleanCsvStorage(['foo', 'bar ']), ['foo', 'bar ']);
      assert.deepStrictEqual(cleanCsvStorage(['foo', ' bar ']), ['foo', ' bar ']);
      assert.deepStrictEqual(cleanCsvStorage(['foo', '    ']), ['foo']);
    });
  });

  suite('getCsvFileLinesAsIterable', () => {
    test('should yield CSV line values', () => {
      // create csv file
      fs.writeFileSync(pathToFile, `foo${EOL}bar${EOL}baz${EOL}`);

      // setup generator function
      const generator = getCsvFileLinesAsIterable(pathToFile);

      // check yielded values
      assert.strictEqual(generator.next().value, 'foo');
      assert.strictEqual(generator.next().value, 'bar');
      assert.strictEqual(generator.next().value, 'baz');
    });
  });

  suite('getCsvFileLinesAsArray', () => {
    test('should return all CSV file lines as an array of strings', () => {
      fs.writeFileSync(pathToFile, `foo${EOL}bar${EOL}baz${EOL}`);
      assert.deepStrictEqual(getCsvFileLinesAsArray(pathToFile), ['foo', 'bar', 'baz']);
    });
    test('should an empty array when CSV file is empty too', () => {
      fs.writeFileSync(pathToFile, '');
      assert.deepStrictEqual(getCsvFileLinesAsArray(pathToFile), []);
    });
  });

  suite('getCsvFileHeader', () => {
    test('should return the CSV header line as string', () => {
      fs.writeFileSync(pathToFile, `foo,foo1,foo2${EOL}bar,bar1,bar2${EOL}baz,baz1,baz2${EOL}`);
      assert.deepStrictEqual(getCsvFileHeader(pathToFile), 'foo,foo1,foo2');
    });
    test('should return an empty string, when CSV file is empty', () => {
      fs.writeFileSync(pathToFile, '');
      assert.deepStrictEqual(getCsvFileHeader(pathToFile), '');
    });
  });

  suite('setCsvFileLines', () => {
    test('should create the file when trying to append and file does not exist', () => {
      assert.ok(setCsvFileLines(pathToFile, ['foo', 'bar', 'baz'], false));
      assert.strictEqual(fs.readFileSync(pathToFile, 'utf8'), `foo${EOL}bar${EOL}baz${EOL}`);
    });
    test('should create the file when trying override and file does not exist', () => {
      assert.ok(setCsvFileLines(pathToFile, ['foo', 'bar', 'baz'], true));
      assert.strictEqual(fs.readFileSync(pathToFile, 'utf8'), `foo${EOL}bar${EOL}baz${EOL}`);
    });
    test('should append to an existing file with some content', () => {
      fs.writeFileSync(pathToFile, `foo${EOL}bar${EOL}baz${EOL}`);
      assert.ok(setCsvFileLines(pathToFile, ['foo1', 'bar1', 'baz1'], false));
      assert.strictEqual(
        fs.readFileSync(pathToFile, 'utf8'),
        `foo${EOL}bar${EOL}baz${EOL}foo1${EOL}bar1${EOL}baz1${EOL}`,
      );
    });
    test('should override an existing file with some content', () => {
      fs.writeFileSync(pathToFile, `foo${EOL}bar${EOL}baz${EOL}`);
      assert.ok(setCsvFileLines(pathToFile, ['foo1', 'bar1', 'baz1'], true));
      assert.strictEqual(fs.readFileSync(pathToFile, 'utf8'), `foo1${EOL}bar1${EOL}baz1${EOL}`);
    });
  });

  suite('setCsvFileContent', () => {
    const content = `foo${EOL}bar${EOL}baz${EOL}`;
    test('should create the file when trying override and file does not exist', () => {
      assert.ok(setCsvFileContent(pathToFile, content));
      assert.strictEqual(fs.readFileSync(pathToFile, 'utf8'), content);
    });
    test('should override content of a file when when it exist', () => {
      fs.writeFileSync(pathToFile, 'some-existing-content');
      assert.ok(setCsvFileContent(pathToFile, content));
      assert.strictEqual(fs.readFileSync(pathToFile, 'utf8'), content);
    });
  });
});
Example #11
Source File: editor-utils.test.ts    From vscode-code-review with MIT License 5 votes vote down vote up
suite('Editor Utils', () => {
  let editorStub: TextEditor;
  let lastSetDecorationConf: TextEditorDecorationType | undefined;
  let lastSetDecorationSelection: Selection[] | undefined;
  beforeEach(() => {
    editorStub = ({
      selections: [new Selection(new Position(0, 1), new Position(2, 6))],
      setDecorations: (conf: TextEditorDecorationType, selection: Selection[]) => {
        lastSetDecorationConf = conf;
        lastSetDecorationSelection = selection;
      },
    } as unknown) as TextEditor;
  });

  afterEach(() => {
    lastSetDecorationConf = undefined;
    lastSetDecorationSelection = undefined;
  });

  suite('clearSelection', () => {
    test('should mutate selection and reset everything to 0', () => {
      clearSelection(editorStub);
      assert.deepStrictEqual(editorStub.selections, [new Selection(new Position(0, 0), new Position(0, 0))]);
    });
  });

  suite('hasSelection', () => {
    test('should detect an active selection', () => {
      const result = hasSelection(editorStub);
      assert.ok(result);
    });
    test('should detect no active selection when no positions are set', () => {
      editorStub.selections = [];
      const result = hasSelection(editorStub);
      assert.strictEqual(result, false);
    });
    test('should detect no active selection when selection positions (start, end) are equal', () => {
      editorStub.selections = [new Selection(new Position(2, 5), new Position(2, 5))];
      const result = hasSelection(editorStub);
      assert.strictEqual(result, false);
    });
  });

  suite('getSelectionStringDefinition', () => {
    test('should return a string representing the current selection', () => {
      assert.strictEqual(getSelectionStringDefinition(editorStub), '1:1-3:6');
    });
    test('should return an empty string representing when no selection is active', () => {
      editorStub.selections = [];
      assert.strictEqual(getSelectionStringDefinition(editorStub), '');
    });
  });

  suite('getSelectionRanges', () => {
    test('should return a range for the current selection', () => {
      const ranges = getSelectionRanges(editorStub);
      assert.strictEqual(ranges.length, 1);
      assert.strictEqual(ranges[0].start.line, 0);
      assert.strictEqual(ranges[0].start.character, 1);
      assert.strictEqual(ranges[0].end.line, 2);
      assert.strictEqual(ranges[0].end.character, 6);
    });
  });

  suite('isValidColorDefinition', () => {
    test('should determine if a color value is RGBA or HEX', () => {
      // empty and invalid
      assert.strictEqual(isValidColorDefinition(''), false);
      assert.strictEqual(isValidColorDefinition('ffffff'), false);
      assert.strictEqual(isValidColorDefinition('#11'), false);
      assert.strictEqual(isValidColorDefinition('#22222'), false);
      assert.strictEqual(isValidColorDefinition('#3333333'), false);
      assert.strictEqual(isValidColorDefinition('some-other'), false);
      assert.strictEqual(isValidColorDefinition('rgb(10,20,44)'), false);
      assert.strictEqual(isValidColorDefinition('rgba(,20,44)'), false);
      assert.strictEqual(isValidColorDefinition('rgba(23)'), false);
      assert.strictEqual(isValidColorDefinition('rgba(23,34)'), false);
      assert.strictEqual(isValidColorDefinition('rgba(23,34)'), false);
      assert.strictEqual(isValidColorDefinition('rgba(10,20,44)'), false);
      assert.strictEqual(isValidColorDefinition('rgba(100,200,300,1.1)'), false);

      // hex
      assert.strictEqual(isValidColorDefinition('#fff'), true);
      assert.strictEqual(isValidColorDefinition('#FFF'), true);
      assert.strictEqual(isValidColorDefinition('#dddd'), true);
      assert.strictEqual(isValidColorDefinition('#DDDD'), true);
      assert.strictEqual(isValidColorDefinition('#ababab'), true);
      assert.strictEqual(isValidColorDefinition('#ABABAB'), true);
      assert.strictEqual(isValidColorDefinition('#ababab33'), true);
      assert.strictEqual(isValidColorDefinition('#ABABAB33'), true);

      // rgba
      assert.strictEqual(isValidColorDefinition('rgba(100,200,300,0.8)'), true);
    });
  });

  suite('themeColorForPriority', () => {
    test('should colorize the given selection with decoration', () => {
      assert.deepStrictEqual(themeColorForPriority(3), new ThemeColor('codereview.priority.red'));
      assert.deepStrictEqual(themeColorForPriority(2), new ThemeColor('codereview.priority.yellow'));
      assert.deepStrictEqual(themeColorForPriority(1), new ThemeColor('codereview.priority.green'));
      assert.strictEqual(themeColorForPriority(0), undefined);
    });
  });

  suite('symbolForPriority', () => {
    test('should colorize the given selection with decoration', () => {
      assert.strictEqual(symbolForPriority(3), '⇡');
      assert.strictEqual(symbolForPriority(2), '⇢');
      assert.strictEqual(symbolForPriority(1), '⇣');
      assert.strictEqual(symbolForPriority(0), undefined);
    });
  });
});
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: 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 #14
Source File: RefactorHierarchy.test.ts    From dendron with GNU Affero General Public License v3.0 4 votes vote down vote up
suite("RefactorHierarchy", function () {
  const ctx = setupBeforeAfter(this, {
    beforeHook: () => {},
  });

  /**
   * Setup
   * refactor.md
   * ```
   * - [[refactor.one]]
   * - [[refactor.two]]
   * ```
   *
   * refactor.one.md
   * ```
   * - [[refactor.two]]
   * ```
   *
   */
  describe("GIVEN a workspace with some notes with simple hierarchy", () => {
    let note: DNodeProps;
    let noteOne: DNodeProps;
    let noteTwo: DNodeProps;
    let preSetupHook: PreSetupHookFunction;

    beforeEach(() => {
      preSetupHook = async (opts: { wsRoot: string; vaults: DVault[] }) => {
        const { wsRoot, vaults } = opts;
        const vault = vaults[0];
        note = await NoteTestUtilsV4.createNote({
          vault,
          wsRoot,
          fname: "refactor",
          body: ["- [[refactor.one]]", "- [[refactor.two]]"].join("\n"),
        });
        noteOne = await NoteTestUtilsV4.createNote({
          vault,
          wsRoot,
          fname: "refactor.one",
          body: ["- [[refactor.two]]"].join("\n"),
        });
        noteTwo = await NoteTestUtilsV4.createNote({
          vault,
          wsRoot,
          fname: "refactor.two",
        });
      };
    });

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

    describe("WHEN scope is undefined", () => {
      /**
       * After test
       * refactor(.*) -> prefix$1
       *
       * refactor.md
       * ```
       * - [[prefix.one]]
       * - [[prefix.two]]
       * ```
       *
       * refactor.one.md
       * ```
       * - [[prefix.two]]
       */
      test("THEN scope is all existing notes, all notes and links refactored.", (done) => {
        runLegacyMultiWorkspaceTest({
          ctx,
          preSetupHook,
          onInit: async () => {
            const cmd = new RefactorHierarchyCommandV2();
            await cmd.execute({
              scope: undefined,
              match: "refactor(.*)",
              replace: "prefix$1",
              noConfirm: true,
            });

            const engine = getEngine();
            const { vaults, wsRoot } = engine;
            const vault = vaults[0];
            const vpath = vault2Path({ vault, wsRoot });
            const notes = fs.readdirSync(vpath).join("");
            const exist = ["prefix.md", "prefix.one.md", "prefix.two.md"];
            const notExist = [
              "refactor.md",
              "refactor.one.md",
              "refactor.two.md",
            ];
            expect(
              await AssertUtils.assertInString({
                body: notes,
                match: exist,
                nomatch: notExist,
              })
            ).toBeTruthy();

            const noteAfterRefactor = NoteUtils.getNoteByFnameV5({
              fname: "prefix",
              notes: engine.notes,
              vault,
              wsRoot,
            });
            expect(noteAfterRefactor?.body).toEqual(
              "- [[prefix.one]]\n- [[prefix.two]]\n"
            );
            done();
          },
        });
      });
    });

    describe("WHEN scoped to one note", () => {
      test("THEN only refactor that note and links to it.", (done) => {
        runLegacyMultiWorkspaceTest({
          ctx,
          preSetupHook,
          onInit: async () => {
            const cmd = new RefactorHierarchyCommandV2();
            const scope: NoteLookupProviderSuccessResp = {
              selectedItems: [
                {
                  ...noteTwo,
                  label: "refactor.two",
                },
              ],
              onAcceptHookResp: [],
            };
            await cmd.execute({
              scope,
              match: "refactor(.*)",
              replace: "prefix$1",
              noConfirm: true,
            });

            const engine = getEngine();
            const { vaults, wsRoot } = engine;
            const vault = vaults[0];
            const vpath = vault2Path({ vault, wsRoot });
            const notes = fs.readdirSync(vpath).join("");
            const exist = ["refactor.md", "refactor.one.md", "prefix.two.md"];
            const notExist = ["prefix.md", "prefix.one.md", "refactor.two.md"];
            expect(
              await AssertUtils.assertInString({
                body: notes,
                match: exist,
                nomatch: notExist,
              })
            ).toBeTruthy();

            const noteAfterRefactor = NoteUtils.getNoteByFnameV5({
              fname: "refactor",
              notes: engine.notes,
              vault,
              wsRoot,
            });
            expect(noteAfterRefactor?.body).toEqual(
              "- [[refactor.one]]\n- [[prefix.two]]\n"
            );

            const noteOneAfterRefactor = NoteUtils.getNoteByFnameV5({
              fname: "refactor.one",
              notes: engine.notes,
              vault,
              wsRoot,
            });
            expect(noteOneAfterRefactor?.body).toEqual("- [[prefix.two]]\n");
            done();
          },
        });
      });
    });

    describe("WHEN given simple regex match / replace text with capture group", () => {
      test("THEN correctly refactors fname", (done) => {
        runLegacyMultiWorkspaceTest({
          ctx,
          preSetupHook,
          onInit: async () => {
            const cmd = new RefactorHierarchyCommandV2();

            const engine = getEngine();
            const { wsRoot } = engine;

            const capturedNotes = [note, noteOne, noteTwo];
            const operations = cmd.getRenameOperations({
              capturedNotes,
              matchRE: new RegExp("(.*)"),
              replace: "prefix.$1.suffix",
              wsRoot,
            });

            operations.forEach((op) => {
              const newFname = path.basename(op.newUri.path, ".md");
              const oldFname = path.basename(op.oldUri.path, ".md");
              expect(newFname.startsWith("prefix.")).toBeTruthy();
              expect(newFname.endsWith(".suffix")).toBeTruthy();
              expect(newFname.includes(oldFname)).toBeTruthy();
            });
            done();
          },
        });
      });
    });
  });

  describe("GIVEN a workspace with some notes with complex hierarchy", () => {
    let refFooTest: DNodeProps;
    let refBarTest: DNodeProps;
    let refEggTest: DNodeProps;
    let preSetupHook: PreSetupHookFunction;

    beforeEach(() => {
      preSetupHook = async (opts: { wsRoot: string; vaults: DVault[] }) => {
        const { wsRoot, vaults } = opts;
        const vault = vaults[0];

        refFooTest = await NoteTestUtilsV4.createNote({
          vault,
          wsRoot,
          fname: "dendron.ref.foo.test",
        });

        refBarTest = await NoteTestUtilsV4.createNote({
          vault,
          wsRoot,
          fname: "dendron.ref.bar.test",
        });

        refEggTest = await NoteTestUtilsV4.createNote({
          vault,
          wsRoot,
          fname: "dendron.ref.egg.test",
        });
      };
    });

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

    describe("WHEN a complex regex match (lookaround) / replace text with (named) capture/non-capture group is given", () => {
      test("THEN correctly refactors fname", (done) => {
        runLegacyMultiWorkspaceTest({
          ctx,
          preSetupHook,
          onInit: async () => {
            const cmd = new RefactorHierarchyCommandV2();

            const engine = getEngine();
            const { wsRoot } = engine;

            const capturedNotes = [refFooTest, refBarTest, refEggTest];

            const operations = cmd.getRenameOperations({
              capturedNotes,
              // capture two depth of hierarchy if parent is "ref"
              // discard whatever comes before "ref"
              matchRE: new RegExp("(?:.*)(?<=ref)\\.(\\w*)\\.(?<rest>.*)"),
              replace: "pkg.$<rest>.$1.ref",
              wsRoot,
            });

            operations.forEach((op) => {
              const newFname = path.basename(op.newUri.path, ".md");
              const oldFname = path.basename(op.oldUri.path, ".md");
              expect(newFname.startsWith("pkg.test.")).toBeTruthy();
              expect(newFname.endsWith(".ref")).toBeTruthy();
              expect(oldFname.split(".")[2]).toEqual(newFname.split(".")[2]);
            });
            done();
          },
        });
      });
    });

    describe("WHEN match would capture fname of note that is a stub", () => {
      test("THEN: stub notes are not part of notes that are being refactored", (done) => {
        runLegacyMultiWorkspaceTest({
          ctx,
          preSetupHook,
          onInit: async () => {
            const cmd = new RefactorHierarchyCommandV2();
            const engine = getEngine();
            const capturedNotes = cmd.getCapturedNotes({
              scope: undefined,
              matchRE: new RegExp("dendron.ref"),
              engine,
            });

            // none of the captured notes should have stub: true
            // stub notes in this test are:
            // dendron.ref, dendron.ref.foo, dendron.ref.bar, dendron.ref.egg
            const numberOfNotesThatAreStubs = capturedNotes.filter(
              (note) => note.stub
            ).length;
            expect(numberOfNotesThatAreStubs).toEqual(0);

            done();
          },
        });
      });
    });
  });
});
Example #15
Source File: RenameHeader.test.ts    From dendron with GNU Affero General Public License v3.0 4 votes vote down vote up
suite("RenameNote", function () {
  const ctx = setupBeforeAfter(this, {});

  let target: NoteProps;
  describeMultiWS(
    "GIVEN a note, and another note that references it",
    {
      ctx,
      preSetupHook: async ({ wsRoot, vaults }) => {
        target = await NoteTestUtilsV4.createNote({
          fname: "target",
          wsRoot,
          vault: vaults[0],
          body: "## header\n\n## dummy",
        });
        await NoteTestUtilsV4.createNote({
          fname: "note-with-link-to-target",
          wsRoot,
          vault: vaults[0],
          body: "[[target]]",
        });
        await NoteTestUtilsV4.createNote({
          fname: "another-note-with-link-to-target",
          wsRoot,
          vault: vaults[0],
          body: "[[target#dummy]]",
        });
      },
    },
    () => {
      let sandbox: sinon.SinonSandbox;
      beforeEach(() => {
        sandbox = sinon.createSandbox();
      });

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

      test("THEN, if the reference isn't pointing to the header being renamed, the note that is referencing isn't updated.", async () => {
        const editor = await WSUtils.openNote(target);
        editor.selection = LocationTestUtils.getPresetWikiLinkSelection();

        sandbox
          .stub(vscode.window, "showInputBox")
          .returns(Promise.resolve("Foo Bar"));
        const out = (await new RenameHeaderCommand().run({})) as CommandOutput;

        const updateResps = out!.data?.filter((resp) => {
          return resp.status === "update";
        });
        expect(updateResps?.length).toEqual(0);
      });
    }
  );

  describe("using selection", () => {
    test("wikilink to other file", (done) => {
      let note: NoteProps;
      runLegacyMultiWorkspaceTest({
        ctx,
        preSetupHook: async ({ wsRoot, vaults }) => {
          note = await NoteTestUtilsV4.createNote({
            fname: "has-header",
            wsRoot,
            vault: vaults[0],
            body: "## Lorem ipsum dolor amet",
          });
          await NoteTestUtilsV4.createNote({
            fname: "has-link",
            wsRoot,
            vault: vaults[0],
            body: "[[has-header#lorem-ipsum-dolor-amet]]",
          });
        },
        onInit: async ({ engine, vaults, wsRoot }) => {
          const editor = await WSUtils.openNote(note);
          editor.selection = LocationTestUtils.getPresetWikiLinkSelection();

          const prompt = sinon
            .stub(vscode.window, "showInputBox")
            .returns(Promise.resolve("Foo Bar"));
          try {
            await new RenameHeaderCommand().run({});

            const afterRename = NoteUtils.getNoteByFnameV5({
              fname: "has-header",
              wsRoot,
              vault: vaults[0],
              notes: engine.notes,
            });
            expect(
              await AssertUtils.assertInString({
                body: afterRename!.body,
                match: ["## Foo Bar"],
                nomatch: ["Lorem", "ipsum", "dolor", "amet"],
              })
            ).toBeTruthy();
            const afterRenameLink = NoteUtils.getNoteByFnameV5({
              fname: "has-link",
              wsRoot,
              vault: vaults[0],
              notes: engine.notes,
            });
            expect(
              await AssertUtils.assertInString({
                body: afterRenameLink!.body,
                match: ["[[has-header#foo-bar]]"],
                nomatch: ["[[has-header#lorem-ipsum-dolor-amet]]"],
              })
            ).toBeTruthy();
            done();
          } finally {
            prompt.restore();
          }
        },
      });
    });

    test("updates default alias", (done) => {
      let note: NoteProps;
      runLegacyMultiWorkspaceTest({
        ctx,
        preSetupHook: async ({ wsRoot, vaults }) => {
          note = await NoteTestUtilsV4.createNote({
            fname: "has-header",
            wsRoot,
            vault: vaults[0],
            body: "## Lorem ipsum dolor amet",
          });
          await NoteTestUtilsV4.createNote({
            fname: "has-link",
            wsRoot,
            vault: vaults[0],
            body: "[[Lorem ipsum dolor amet|has-header#lorem-ipsum-dolor-amet]]",
          });
        },
        onInit: async ({ engine, vaults, wsRoot }) => {
          const editor = await WSUtils.openNote(note);
          editor.selection = LocationTestUtils.getPresetWikiLinkSelection();

          const prompt = sinon
            .stub(vscode.window, "showInputBox")
            .returns(Promise.resolve("Foo Bar"));
          try {
            await new RenameHeaderCommand().run({});

            const afterRename = NoteUtils.getNoteByFnameV5({
              fname: "has-header",
              wsRoot,
              vault: vaults[0],
              notes: engine.notes,
            });
            expect(
              await AssertUtils.assertInString({
                body: afterRename!.body,
                match: ["## Foo Bar"],
                nomatch: ["Lorem", "ipsum", "dolor", "amet"],
              })
            ).toBeTruthy();
            const afterRenameLink = NoteUtils.getNoteByFnameV5({
              fname: "has-link",
              wsRoot,
              vault: vaults[0],
              notes: engine.notes,
            });
            expect(
              await AssertUtils.assertInString({
                body: afterRenameLink!.body,
                match: ["[[Foo Bar|has-header#foo-bar]]"],
                nomatch: [
                  "[[Lorem ipsum dolor amet|has-header#lorem-ipsum-dolor-amet]]",
                  "[[has-header#foo-bar]]",
                ],
              })
            ).toBeTruthy();
            done();
          } finally {
            prompt.restore();
          }
        },
      });
    });

    test("does not rename a wikilink to another header", (done) => {
      let note: NoteProps;
      runLegacyMultiWorkspaceTest({
        ctx,
        preSetupHook: async ({ wsRoot, vaults }) => {
          note = await NoteTestUtilsV4.createNote({
            fname: "has-header",
            wsRoot,
            vault: vaults[0],
            body: "## Lorem ipsum dolor amet\n\n## Maxime Distinctio Officia",
          });
          await NoteTestUtilsV4.createNote({
            fname: "has-link",
            wsRoot,
            vault: vaults[0],
            body: "[[has-header#maxime-distinctio-officia]]",
          });
        },
        onInit: async ({ engine, vaults, wsRoot }) => {
          const editor = await WSUtils.openNote(note);
          editor.selection = LocationTestUtils.getPresetWikiLinkSelection();

          const prompt = sinon
            .stub(vscode.window, "showInputBox")
            .returns(Promise.resolve("Foo Bar"));
          try {
            await new RenameHeaderCommand().run({});

            const afterRename = NoteUtils.getNoteByFnameV5({
              fname: "has-header",
              wsRoot,
              vault: vaults[0],
              notes: engine.notes,
            });
            expect(
              await AssertUtils.assertInString({
                body: afterRename!.body,
                match: ["## Foo Bar", "## Maxime Distinctio Officia"],
                nomatch: ["Lorem", "ipsum", "dolor", "amet"],
              })
            ).toBeTruthy();
            const afterRenameLink = NoteUtils.getNoteByFnameV5({
              fname: "has-link",
              wsRoot,
              vault: vaults[0],
              notes: engine.notes,
            });
            expect(
              await AssertUtils.assertInString({
                body: afterRenameLink!.body,
                match: ["[[has-header#maxime-distinctio-officia]]"],
                nomatch: ["[[has-header#foo-bar]]"],
              })
            ).toBeTruthy();
            done();
          } finally {
            prompt.restore();
          }
        },
      });
    });

    test("wikilink to same file", (done) => {
      let note: NoteProps;
      runLegacyMultiWorkspaceTest({
        ctx,
        preSetupHook: async ({ wsRoot, vaults }) => {
          note = await NoteTestUtilsV4.createNote({
            fname: "has-link",
            wsRoot,
            vault: vaults[0],
            body: [
              "## Lorem ipsum dolor amet",
              "",
              "[[#lorem-ipsum-dolor-amet]]",
            ].join("\n"),
          });
        },
        onInit: async ({ engine, vaults, wsRoot }) => {
          const editor = await WSUtils.openNote(note);
          editor.selection = LocationTestUtils.getPresetWikiLinkSelection();

          const prompt = sinon
            .stub(vscode.window, "showInputBox")
            .returns(Promise.resolve("Foo Bar"));
          try {
            await new RenameHeaderCommand().run({});

            const afterRename = NoteUtils.getNoteByFnameV5({
              fname: "has-link",
              wsRoot,
              vault: vaults[0],
              notes: engine.notes,
            });
            await checkFile({
              note: afterRename!,
              wsRoot,
              nomatch: [
                "Lorem",
                "ipsum",
                "dolor",
                "amet",
                "[[has-header#lorem-ipsum-dolor-amet]]",
              ],
              match: ["## Foo Bar", "[[#foo-bar]]"],
            });
            done();
          } finally {
            prompt.restore();
          }
        },
      });
    });

    test("with old header containing block anchor", (done) => {
      let note: NoteProps;
      runLegacyMultiWorkspaceTest({
        ctx,
        preSetupHook: async ({ wsRoot, vaults }) => {
          note = await NoteTestUtilsV4.createNote({
            fname: "has-header",
            wsRoot,
            vault: vaults[0],
            body: "## Lorem ipsum dolor amet ^anchor",
          });
          await NoteTestUtilsV4.createNote({
            fname: "has-link",
            wsRoot,
            vault: vaults[0],
            body: "[[has-header#lorem-ipsum-dolor-amet]]",
          });
        },
        onInit: async ({ engine, vaults, wsRoot }) => {
          const editor = await WSUtils.openNote(note);
          editor.selection = LocationTestUtils.getPresetWikiLinkSelection();

          const prompt = sinon
            .stub(vscode.window, "showInputBox")
            .returns(Promise.resolve("Foo Bar"));
          try {
            await new RenameHeaderCommand().run({});

            const afterRename = NoteUtils.getNoteByFnameV5({
              fname: "has-header",
              wsRoot,
              vault: vaults[0],
              notes: engine.notes,
            });
            expect(
              await AssertUtils.assertInString({
                body: afterRename!.body,
                match: ["## Foo Bar ^anchor"],
                nomatch: ["Lorem", "ipsum", "dolor", "amet"],
              })
            ).toBeTruthy();
            const afterRenameLink = NoteUtils.getNoteByFnameV5({
              fname: "has-link",
              wsRoot,
              vault: vaults[0],
              notes: engine.notes,
            });
            expect(
              await AssertUtils.assertInString({
                body: afterRenameLink!.body,
                match: ["[[has-header#foo-bar]]"],
                nomatch: ["[[has-header#lorem-ipsum-dolor-amet]]"],
              })
            ).toBeTruthy();
            done();
          } finally {
            prompt.restore();
          }
        },
      });
    });

    test("with old header containing a wikilink", (done) => {
      let note: NoteProps;
      runLegacyMultiWorkspaceTest({
        ctx,
        preSetupHook: async ({ wsRoot, vaults }) => {
          note = await NoteTestUtilsV4.createNote({
            fname: "has-header",
            wsRoot,
            vault: vaults[0],
            body: "## Lorem ipsum [[dolor|note.dolor]] amet ^anchor",
          });
          await NoteTestUtilsV4.createNote({
            fname: "has-link",
            wsRoot,
            vault: vaults[0],
            body: "[[has-header#lorem-ipsum-dolor-amet]]",
          });
        },
        onInit: async ({ engine, vaults, wsRoot }) => {
          const editor = await WSUtils.openNote(note);
          editor.selection = LocationTestUtils.getPresetWikiLinkSelection();

          const prompt = sinon
            .stub(vscode.window, "showInputBox")
            .returns(Promise.resolve("Foo Bar"));
          try {
            await new RenameHeaderCommand().run({});

            const afterRename = NoteUtils.getNoteByFnameV5({
              fname: "has-header",
              wsRoot,
              vault: vaults[0],
              notes: engine.notes,
            });
            expect(
              await AssertUtils.assertInString({
                body: afterRename!.body,
                match: ["## Foo Bar ^anchor"],
                nomatch: ["Lorem", "ipsum", "dolor", "amet"],
              })
            ).toBeTruthy();
            const afterRenameLink = NoteUtils.getNoteByFnameV5({
              fname: "has-link",
              wsRoot,
              vault: vaults[0],
              notes: engine.notes,
            });
            expect(
              await AssertUtils.assertInString({
                body: afterRenameLink!.body,
                match: ["[[has-header#foo-bar]]"],
                nomatch: ["[[has-header#lorem-ipsum-dolor-amet]]"],
              })
            ).toBeTruthy();
            done();
          } finally {
            prompt.restore();
          }
        },
      });
    });

    test("with new header containing a wikilink", (done) => {
      let note: NoteProps;
      runLegacyMultiWorkspaceTest({
        ctx,
        preSetupHook: async ({ wsRoot, vaults }) => {
          note = await NoteTestUtilsV4.createNote({
            fname: "has-header",
            wsRoot,
            vault: vaults[0],
            body: "## Lorem ipsum dolor amet ^anchor",
          });
          await NoteTestUtilsV4.createNote({
            fname: "has-link",
            wsRoot,
            vault: vaults[0],
            body: "[[has-header#lorem-ipsum-dolor-amet]]",
          });
        },
        onInit: async ({ engine, vaults, wsRoot }) => {
          const editor = await WSUtils.openNote(note);
          editor.selection = LocationTestUtils.getPresetWikiLinkSelection();

          const prompt = sinon
            .stub(vscode.window, "showInputBox")
            .returns(Promise.resolve("Foo [[Bar|note.bar]] Baz"));
          try {
            await new RenameHeaderCommand().run({});

            const afterRename = NoteUtils.getNoteByFnameV5({
              fname: "has-header",
              wsRoot,
              vault: vaults[0],
              notes: engine.notes,
            });
            expect(
              await AssertUtils.assertInString({
                body: afterRename!.body,
                match: ["## Foo [[Bar|note.bar]] Baz ^anchor"],
                nomatch: ["Lorem", "ipsum", "dolor", "amet"],
              })
            ).toBeTruthy();
            const afterRenameLink = NoteUtils.getNoteByFnameV5({
              fname: "has-link",
              wsRoot,
              vault: vaults[0],
              notes: engine.notes,
            });
            expect(
              await AssertUtils.assertInString({
                body: afterRenameLink!.body,
                match: ["[[has-header#foo-bar-baz]]"],
                nomatch: ["[[has-header#lorem-ipsum-dolor-amet]]"],
              })
            ).toBeTruthy();
            done();
          } finally {
            prompt.restore();
          }
        },
      });
    });

    test("with a reference", (done) => {
      let note: NoteProps;
      runLegacyMultiWorkspaceTest({
        ctx,
        preSetupHook: async ({ wsRoot, vaults }) => {
          note = await NoteTestUtilsV4.createNote({
            fname: "has-header",
            wsRoot,
            vault: vaults[0],
            body: "## Lorem ipsum dolor amet",
          });
          await NoteTestUtilsV4.createNote({
            fname: "has-link",
            wsRoot,
            vault: vaults[0],
            body: "![[has-header#lorem-ipsum-dolor-amet]]",
          });
        },
        onInit: async ({ engine, vaults, wsRoot }) => {
          const editor = await WSUtils.openNote(note);
          editor.selection = LocationTestUtils.getPresetWikiLinkSelection();

          const prompt = sinon
            .stub(vscode.window, "showInputBox")
            .returns(Promise.resolve("Foo Bar"));
          try {
            await new RenameHeaderCommand().run({});

            const afterRename = NoteUtils.getNoteByFnameV5({
              fname: "has-header",
              wsRoot,
              vault: vaults[0],
              notes: engine.notes,
            });
            expect(
              await AssertUtils.assertInString({
                body: afterRename!.body,
                match: ["## Foo Bar"],
                nomatch: ["Lorem", "ipsum", "dolor", "amet"],
              })
            ).toBeTruthy();
            const afterRenameLink = NoteUtils.getNoteByFnameV5({
              fname: "has-link",
              wsRoot,
              vault: vaults[0],
              notes: engine.notes,
            });
            expect(
              await AssertUtils.assertInString({
                body: afterRenameLink!.body,
                match: ["![[has-header#foo-bar]]"],
                nomatch: ["![[has-header#lorem-ipsum-dolor-amet]]"],
              })
            ).toBeTruthy();
            done();
          } finally {
            prompt.restore();
          }
        },
      });
    });

    test("with a reference range, header at the start", (done) => {
      let note: NoteProps;
      runLegacyMultiWorkspaceTest({
        ctx,
        preSetupHook: async ({ wsRoot, vaults }) => {
          note = await NoteTestUtilsV4.createNote({
            fname: "has-header",
            wsRoot,
            vault: vaults[0],
            body: [
              "## Lorem ipsum dolor amet",
              "",
              "middle",
              "",
              "## end",
            ].join("\n"),
          });
          await NoteTestUtilsV4.createNote({
            fname: "has-link",
            wsRoot,
            vault: vaults[0],
            body: "![[has-header#lorem-ipsum-dolor-amet:#end]]",
          });
        },
        onInit: async ({ engine, vaults, wsRoot }) => {
          const editor = await WSUtils.openNote(note);
          editor.selection = LocationTestUtils.getPresetWikiLinkSelection();

          const prompt = sinon
            .stub(vscode.window, "showInputBox")
            .returns(Promise.resolve("Foo Bar"));
          try {
            await new RenameHeaderCommand().run({});

            const afterRename = NoteUtils.getNoteByFnameV5({
              fname: "has-header",
              wsRoot,
              vault: vaults[0],
              notes: engine.notes,
            });
            expect(
              await AssertUtils.assertInString({
                body: afterRename!.body,
                match: ["## Foo Bar"],
                nomatch: ["Lorem", "ipsum", "dolor", "amet"],
              })
            ).toBeTruthy();
            const afterRenameLink = NoteUtils.getNoteByFnameV5({
              fname: "has-link",
              wsRoot,
              vault: vaults[0],
              notes: engine.notes,
            });
            expect(
              await AssertUtils.assertInString({
                body: afterRenameLink!.body,
                match: ["![[has-header#foo-bar:#end]]"],
                nomatch: [
                  "![[has-header#lorem-ipsum-dolor-amet:#end]]",
                  "![[has-header#foo-bar]]",
                ],
              })
            ).toBeTruthy();
            done();
          } finally {
            prompt.restore();
          }
        },
      });
    });

    test("with a reference range, header at the end", (done) => {
      let note: NoteProps;
      runLegacyMultiWorkspaceTest({
        ctx,
        preSetupHook: async ({ wsRoot, vaults }) => {
          note = await NoteTestUtilsV4.createNote({
            fname: "has-header",
            wsRoot,
            vault: vaults[0],
            body: [
              "## start",
              "",
              "middle",
              "",
              "## Lorem Ipsum Dolor Amet",
            ].join("\n"),
          });
          await NoteTestUtilsV4.createNote({
            fname: "has-link",
            wsRoot,
            vault: vaults[0],
            body: "![[has-header#start:#lorem-ipsum-dolor-amet]]",
          });
        },
        onInit: async ({ engine, vaults, wsRoot }) => {
          const editor = await WSUtils.openNote(note);
          editor.selection = LocationTestUtils.getPresetWikiLinkSelection({
            line: 11,
          });

          const prompt = sinon
            .stub(vscode.window, "showInputBox")
            .returns(Promise.resolve("Foo Bar"));
          try {
            await new RenameHeaderCommand().run({});

            const afterRename = NoteUtils.getNoteByFnameV5({
              fname: "has-header",
              wsRoot,
              vault: vaults[0],
              notes: engine.notes,
            });
            expect(
              await AssertUtils.assertInString({
                body: afterRename!.body,
                match: ["## Foo Bar"],
                nomatch: ["Lorem", "ipsum", "dolor", "amet"],
              })
            ).toBeTruthy();
            const afterRenameLink = NoteUtils.getNoteByFnameV5({
              fname: "has-link",
              wsRoot,
              vault: vaults[0],
              notes: engine.notes,
            });
            expect(
              await AssertUtils.assertInString({
                body: afterRenameLink!.body,
                match: ["![[has-header#start:#foo-bar]]"],
                nomatch: [
                  "![[has-header#start:#lorem-ipsum-dolor-amet]]",
                  "![[has-header#foo-bar]]",
                  "![[has-header#start]]",
                ],
              })
            ).toBeTruthy();
            done();
          } finally {
            prompt.restore();
          }
        },
      });
    });

    test("does not rename a reference range to another header", (done) => {
      let note: NoteProps;
      runLegacyMultiWorkspaceTest({
        ctx,
        preSetupHook: async ({ wsRoot, vaults }) => {
          note = await NoteTestUtilsV4.createNote({
            fname: "has-header",
            wsRoot,
            vault: vaults[0],
            body: [
              "## Lorem ipsum dolor amet",
              "",
              "## Maxime Distinctio Officia",
              "",
              "middle",
              "",
              "## end",
            ].join("\n"),
          });
          await NoteTestUtilsV4.createNote({
            fname: "has-link",
            wsRoot,
            vault: vaults[0],
            body: "![[has-header#maxime-distinctio-officia:#end]]",
          });
        },
        onInit: async ({ engine, vaults, wsRoot }) => {
          const editor = await WSUtils.openNote(note);
          editor.selection = LocationTestUtils.getPresetWikiLinkSelection();

          const prompt = sinon
            .stub(vscode.window, "showInputBox")
            .returns(Promise.resolve("Foo Bar"));
          try {
            await new RenameHeaderCommand().run({});

            const afterRename = NoteUtils.getNoteByFnameV5({
              fname: "has-header",
              wsRoot,
              vault: vaults[0],
              notes: engine.notes,
            });
            expect(
              await AssertUtils.assertInString({
                body: afterRename!.body,
                match: ["## Foo Bar"],
                nomatch: ["Lorem", "ipsum", "dolor", "amet"],
              })
            ).toBeTruthy();
            const afterRenameLink = NoteUtils.getNoteByFnameV5({
              fname: "has-link",
              wsRoot,
              vault: vaults[0],
              notes: engine.notes,
            });
            expect(
              await AssertUtils.assertInString({
                body: afterRenameLink!.body,
                match: ["![[has-header#maxime-distinctio-officia:#end]]"],
                nomatch: [
                  "![[has-header#foo-bar:#end]]",
                  "![[has-header#foo-bar]]",
                ],
              })
            ).toBeTruthy();
            done();
          } finally {
            prompt.restore();
          }
        },
      });
    });
  });
});
Example #16
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 #17
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 #18
Source File: TextDocumentService.test.ts    From dendron with GNU Affero General Public License v3.0 4 votes vote down vote up
suite("TextDocumentService", function testSuite() {
  let textDocumentService: TextDocumentService | undefined;
  let onDidChangeTextDocumentHandler: vscode.Disposable | undefined;
  this.timeout(5000);

  afterEach(() => {
    if (textDocumentService) {
      textDocumentService.dispose();
    }
    if (onDidChangeTextDocumentHandler) {
      onDidChangeTextDocumentHandler.dispose();
    }
  });

  describe("Given a TextDocumentChangeEvent", () => {
    describeSingleWS(
      "WHEN the contents have changed",
      {
        postSetupHook: async ({ wsRoot, vaults }) => {
          const vault = vaults[0];
          await NoteTestUtilsV4.createNote({
            wsRoot,
            vault,
            fname: "alpha",
            body: "First Line\n",
          });
        },
      },
      () => {
        test("THEN processTextDocumentChangeEvent should return note with updated text", (done) => {
          textDocumentService = new TextDocumentService(
            ExtensionProvider.getExtension(),
            vscode.workspace.onDidSaveTextDocument
          );
          const textToAppend = "new text here";
          const engine = ExtensionProvider.getEngine();
          const alphaNote = engine.notes["alpha"];

          onDidChangeTextDocumentHandler =
            vscode.workspace.onDidChangeTextDocument(async (event) => {
              if (event.document.isDirty) {
                const maybeNote =
                  await textDocumentService?.processTextDocumentChangeEvent(
                    event
                  );
                expect(maybeNote?.body).toEqual("First Line\n" + textToAppend);
                // Make sure updated has not changed
                expect(maybeNote?.updated).toEqual(alphaNote.updated);
                done();
              }
            });
          ExtensionProvider.getWSUtils()
            .openNote(alphaNote)
            .then((editor) => {
              editor.edit((editBuilder) => {
                const line = editor.document.getText().split("\n").length;
                editBuilder.insert(new vscode.Position(line, 0), textToAppend);
              });
            });
        });
      }
    );

    describeSingleWS(
      "WHEN the contents have changed tags",
      {
        postSetupHook: async ({ wsRoot, vaults }) => {
          const vault = vaults[0];
          await NoteTestUtilsV4.createNote({
            fname: "foo",
            wsRoot,
            vault,
            body: "foo body",
          });
          await NoteTestUtilsV4.createNote({
            fname: "tags.test",
            wsRoot,
            vault,
          });
        },
      },
      () => {
        test("THEN processTextDocumentChangeEvent should return note with updated links", (done) => {
          textDocumentService = new TextDocumentService(
            ExtensionProvider.getExtension(),
            vscode.workspace.onDidSaveTextDocument
          );
          const engine = ExtensionProvider.getEngine();
          const foo = engine.notes["foo"];

          onDidChangeTextDocumentHandler =
            vscode.workspace.onDidChangeTextDocument(async (event) => {
              if (event.document.isDirty) {
                const maybeNote =
                  await textDocumentService?.processTextDocumentChangeEvent(
                    event
                  );
                expect(maybeNote?.links.length).toEqual(1);
                expect(maybeNote?.links[0].type).toEqual("frontmatterTag");
                expect(maybeNote?.links[0].to?.fname).toEqual("tags.test");
                done();
              }
            });
          ExtensionProvider.getWSUtils()
            .openNote(foo)
            .then((editor) => {
              editor.edit((editBuilder) => {
                const pos = new vscode.Position(6, 0);
                editBuilder.insert(pos, `tags: test\n`);
              });
            });
        });
      }
    );

    describeSingleWS(
      "WHEN the editor has not changed contents",
      {
        postSetupHook: async ({ wsRoot, vaults }) => {
          const vault = vaults[0];
          await NoteTestUtilsV4.createNote({
            wsRoot,
            vault,
            fname: "beta",
            body: "First Line\n",
          });
        },
      },
      () => {
        test("THEN processTextDocumentChangeEvent should not be called", (done) => {
          textDocumentService = new TextDocumentService(
            ExtensionProvider.getExtension(),
            vscode.workspace.onDidSaveTextDocument
          );
          const engine = ExtensionProvider.getEngine();
          const currentNote = engine.notes["beta"];

          onDidChangeTextDocumentHandler =
            vscode.workspace.onDidChangeTextDocument(() => {
              assert(false, "Callback not expected");
            });
          ExtensionProvider.getWSUtils()
            .openNote(currentNote)
            .then((editor) => {
              editor.edit((editBuilder) => {
                const line = editor.document.getText().split("\n").length;
                editBuilder.insert(new vscode.Position(line, 0), "");
              });
            });
          // Small sleep to ensure callback doesn't fire.
          waitInMilliseconds(10).then(() => done());
        });
      }
    );

    describeSingleWS(
      "WHEN the contents of the event are the same as what's in the engine",
      {
        postSetupHook: async ({ wsRoot, vaults }) => {
          const vault = vaults[0];
          await NoteTestUtilsV4.createNote({
            wsRoot,
            vault,
            fname: "beta",
            body: "First Line\n",
          });
        },
      },
      () => {
        test("THEN processTextDocumentChangeEvent should return original note", (done) => {
          textDocumentService = new TextDocumentService(
            ExtensionProvider.getExtension(),
            vscode.workspace.onDidSaveTextDocument
          );

          const engine = ExtensionProvider.getEngine();
          const currentNote = engine.notes["beta"];

          onDidChangeTextDocumentHandler =
            vscode.workspace.onDidChangeTextDocument(async (event) => {
              if (event.document.isDirty) {
                // Set content hash to be same as event to enter content no change logic
                currentNote.contentHash = genHash(event.document.getText());

                const maybeNote =
                  await textDocumentService?.processTextDocumentChangeEvent(
                    event
                  );
                expect(maybeNote).toEqual(currentNote);
                done();
              }
            });
          ExtensionProvider.getWSUtils()
            .openNote(currentNote)
            .then((editor) => {
              editor.edit((editBuilder) => {
                const line = editor.document.getText().split("\n").length;
                editBuilder.insert(new vscode.Position(line, 0), "1");
              });
            });
        });
      }
    );

    describeSingleWS("WHEN the contents don't match any notes", {}, () => {
      test("THEN processTextDocumentChangeEvent should return undefined", (done) => {
        textDocumentService = new TextDocumentService(
          ExtensionProvider.getExtension(),
          vscode.workspace.onDidSaveTextDocument
        );
        const textToAppend = "new text here";
        const { engine, wsRoot, vaults } = ExtensionProvider.getDWorkspace();

        NoteTestUtilsV4.createNote({
          fname: "blahblah123",
          body: `[[beta]]`,
          vault: vaults[0],
          wsRoot,
        }).then((testNoteProps) => {
          ExtensionProvider.getWSUtils()
            .openNote(testNoteProps)
            .then((editor) => {
              editor.edit((editBuilder) => {
                const line = editor.document.getText().split("\n").length;
                editBuilder.insert(new vscode.Position(line, 0), textToAppend);
              });
            });
        });

        expect(engine.notes["blahblah123"]).toBeFalsy();

        onDidChangeTextDocumentHandler =
          vscode.workspace.onDidChangeTextDocument(async (event) => {
            if (event.document.isDirty) {
              const maybeNote =
                await textDocumentService?.processTextDocumentChangeEvent(
                  event
                );
              expect(maybeNote).toBeFalsy();
              done();
            }
          });
      });
    });
  });

  describe("GIVEN a vscode.workspace.onDidSaveTextDocument event is fired", () => {
    describeSingleWS(
      "WHEN the contents of the note has changed",
      {
        postSetupHook: ENGINE_HOOKS.setupBasic,
      },
      () => {
        test("THEN engine note contents should be updated", async () => {
          const fname = "foo";
          const { onDidSave } = setupTextDocumentService();
          const { engine, editor, note, textToAppend } = await openAndEdit(
            fname
          );
          const updatedNote = await onDidSave(editor.document);
          expect(updatedNote?.body).toEqual(note.body + textToAppend);
          expect(engine.notes[fname].body).toEqual(note.body + textToAppend);
        });
      }
    );

    describeSingleWS(
      "WHEN the contents of the note has changed",
      {
        postSetupHook: ENGINE_HOOKS.setupBasic,
      },
      () => {
        test("THEN update engine events should be fired", (done) => {
          textDocumentService = new TextDocumentService(
            ExtensionProvider.getExtension(),
            vscode.workspace.onDidSaveTextDocument
          );
          const engine = ExtensionProvider.getEngine();
          const testNoteProps = engine.notes["foo"];
          const textToAppend = "new text here";

          const disposable = subscribeToEngineStateChange(
            (noteChangeEntries: NoteChangeEntry[]) => {
              const createEntries = extractNoteChangeEntriesByType(
                noteChangeEntries,
                "create"
              );

              const deleteEntries = extractNoteChangeEntriesByType(
                noteChangeEntries,
                "delete"
              );

              const updateEntries = extractNoteChangeEntriesByType(
                noteChangeEntries,
                "update"
              ) as NoteChangeUpdateEntry[];

              testAssertsInsideCallback(() => {
                expect(createEntries.length).toEqual(0);
                expect(updateEntries.length).toEqual(1);
                expect(deleteEntries.length).toEqual(0);

                const updateEntry = updateEntries[0];

                expect(updateEntry.note.fname).toEqual("foo");
                expect(updateEntry.note.body).toEqual(
                  testNoteProps.body + textToAppend
                );
                disposable.dispose();
              }, done);
            }
          );

          ExtensionProvider.getWSUtils()
            .openNote(testNoteProps)
            .then((editor) => {
              editor.edit((editBuilder) => {
                const line = editor.document.getText().split("\n").length;
                editBuilder.insert(new vscode.Position(line, 0), textToAppend);
              });
              editor.document.save();
            });
        });
      }
    );

    describeSingleWS(
      "WHEN the original note contains wikilink and backlink",
      {
        postSetupHook: ENGINE_HOOKS.setupLinks,
      },
      () => {
        test("THEN the wikilink and backlink should remain unchanged", async () => {
          const fname = "alpha";
          const { onDidSave } = setupTextDocumentService();
          const { engine, editor, note } = await openAndEdit(fname);
          const updatedNote = await onDidSave(editor.document);

          expect(updatedNote?.links).toEqual(note.links);
          expect(engine.notes[fname].links).toEqual(note.links);
          expect(updatedNote?.links).toEqual([
            {
              alias: "beta",
              from: {
                fname: "alpha",
                id: "alpha",
                vaultName: "vault",
              },
              position: {
                end: {
                  column: 9,
                  line: 1,
                  offset: 8,
                },
                indent: [],
                start: {
                  column: 1,
                  line: 1,
                  offset: 0,
                },
              },
              sameFile: false,
              to: {
                fname: "beta",
              },
              type: "wiki",
              value: "beta",
              xvault: false,
            },
            {
              alias: "alpha",
              from: {
                fname: "beta",
                vaultName: "vault",
              },
              position: {
                end: {
                  column: 13,
                  line: 1,
                  offset: 12,
                },
                indent: [],
                start: {
                  column: 1,
                  line: 1,
                  offset: 0,
                },
              },
              type: "backlink",
              value: "alpha",
            },
          ]);
        });
      }
    );
    describeSingleWS(
      "WHEN the original note contains only backlink",
      {
        postSetupHook: ENGINE_HOOKS.setupRefs,
      },
      () => {
        test("THEN the backlink should remain unchanged", async () => {
          const fname = "simple-note-ref.one";
          const { onDidSave } = setupTextDocumentService();
          const { engine, editor, note } = await openAndEdit(fname);
          const updatedNote = await onDidSave(editor.document);

          expect(updatedNote?.links).toEqual(note.links);
          expect(engine.notes[fname].links).toEqual(note.links);
          expect(updatedNote?.links[0]).toEqual({
            from: {
              fname: "simple-note-ref",
              vaultName: "vault",
            },
            position: {
              end: {
                column: 25,
                line: 1,
                offset: 24,
              },
              indent: [],
              start: {
                column: 1,
                line: 1,
                offset: 0,
              },
            },
            type: "backlink",
            value: "simple-note-ref.one",
          });
        });
      }
    );

    describeSingleWS(
      "WHEN the original note contains frontmatter tag",
      {
        postSetupHook: async (opts) => {
          const vault = opts.vaults[0];
          await ENGINE_HOOKS.setupRefs(opts);
          await NOTE_PRESETS_V4.NOTE_WITH_FM_TAG.create({ ...opts, vault });
        },
      },
      () => {
        test("THEN the fm-tag should remain unchanged", async () => {
          const fname = "fm-tag";
          const { onDidSave } = setupTextDocumentService();
          const { engine, editor, note } = await openAndEdit(fname);
          const updatedNote = await onDidSave(editor.document);

          expect(updatedNote?.links).toEqual(note.links);
          expect(engine.notes[fname].links).toEqual(note.links);
          expect(updatedNote?.links).toEqual([
            {
              alias: "foo",
              from: {
                fname: "fm-tag",
                id: "fm-tag",
                vaultName: "vault",
              },
              to: {
                fname: "tags.foo",
              },
              type: "frontmatterTag",
              value: "tags.foo",
              xvault: false,
            },
          ]);
        });
      }
    );

    describeMultiWS(
      "WHEN the contents of the note has not changed",
      {
        preSetupHook: ENGINE_HOOKS.setupBasic,
      },
      () => {
        test("THEN onDidSave should return original note and engine note contents should be untouched", async () => {
          textDocumentService = new TextDocumentService(
            ExtensionProvider.getExtension(),
            vscode.workspace.onDidSaveTextDocument
          );
          const engine = ExtensionProvider.getEngine();
          const testNoteProps = engine.notes["foo"];
          const editor = await ExtensionProvider.getWSUtils().openNote(
            testNoteProps
          );

          const { onDidSave } =
            textDocumentService.__DO_NOT_USE_IN_PROD_exposePropsForTesting();
          const updatedNote = await onDidSave(editor.document);

          expect(updatedNote).toBeTruthy();
          expect(updatedNote).toEqual(testNoteProps);
          expect(engine.notes["foo"].body).toEqual(testNoteProps.body);
        });
      }
    );

    describeSingleWS(
      "WHEN the contents don't match any note",
      {
        postSetupHook: ENGINE_HOOKS.setupBasic,
      },
      () => {
        test("THEN onDidSave should return undefined and engine note contents should be untouched", async () => {
          textDocumentService = new TextDocumentService(
            ExtensionProvider.getExtension(),
            vscode.workspace.onDidSaveTextDocument
          );
          const { engine, wsRoot, vaults } = ExtensionProvider.getDWorkspace();
          const testNoteProps = await NoteTestUtilsV4.createNote({
            fname: "blahblah123",
            body: `[[beta]]`,
            vault: vaults[0],
            wsRoot,
          });
          const editor = await ExtensionProvider.getWSUtils().openNote(
            testNoteProps
          );

          expect(engine.notes["blahblah123"]).toBeFalsy();
          const { onDidSave } =
            textDocumentService.__DO_NOT_USE_IN_PROD_exposePropsForTesting();
          const updatedNote = await onDidSave(editor.document);

          expect(updatedNote).toBeFalsy();
        });
      }
    );
  });

  describeSingleWS(
    "Given a note with frontmatter",
    {
      postSetupHook: async ({ wsRoot, vaults }) => {
        const vault = vaults[0];
        await NoteTestUtilsV4.createNote({
          wsRoot,
          vault,
          fname: "alpha",
          body: "First Line\n",
        });
      },
    },
    () => {
      test("WHEN the note has frontmatter, THEN getFrontmatterPosition should return true", async () => {
        const engine = ExtensionProvider.getEngine();
        const alphaNote = engine.notes["alpha"];
        const editor = await ExtensionProvider.getWSUtils().openNote(alphaNote);
        const hasFrontmatter = TextDocumentService.containsFrontmatter(
          editor.document
        );
        expect(hasFrontmatter).toBeTruthy();
      });

      test("WHEN frontmatter is removed, THEN getFrontmatterPosition should return false", async () => {
        const engine = ExtensionProvider.getEngine();
        const alphaNote = engine.notes["alpha"];

        const editor = await ExtensionProvider.getWSUtils().openNote(alphaNote);
        editor.edit((editBuilder) => {
          editBuilder.delete(
            new vscode.Range(
              new vscode.Position(0, 0),
              new vscode.Position(1, 0)
            )
          );
        });
        await editor.document.save();
        const hasFrontmatter = TextDocumentService.containsFrontmatter(
          editor.document
        );
        expect(hasFrontmatter).toBeFalsy();
      });
    }
  );
});
Example #19
Source File: WorkspaceWatcher.test.ts    From dendron with GNU Affero General Public License v3.0 4 votes vote down vote up
suite("WorkspaceWatcher", function () {
  let watcher: WorkspaceWatcher;

  describeSingleWS(
    "GIVEN a basic setup on a single vault workspace",
    {
      postSetupHook: setupBasic,
    },
    () => {
      test("WHEN user renames a file outside of dendron rename command, THEN all of its references are also updated", (done) => {
        const { engine, wsRoot, vaults } = ExtensionProvider.getDWorkspace();
        const previewProxy = new MockPreviewProxy();
        const extension = ExtensionProvider.getExtension();

        const windowWatcher = new WindowWatcher({
          extension,
          previewProxy,
        });

        watcher = new WorkspaceWatcher({
          schemaSyncService: ExtensionProvider.getExtension().schemaSyncService,
          extension,
          windowWatcher,
        });
        const oldPath = path.join(wsRoot, vaults[0].fsPath, "oldfile.md");
        const oldUri = vscode.Uri.file(oldPath);
        const newPath = path.join(wsRoot, vaults[0].fsPath, "newfile.md");
        const newUri = vscode.Uri.file(newPath);
        const args: vscode.FileWillRenameEvent = {
          files: [
            {
              oldUri,
              newUri,
            },
          ],
          // eslint-disable-next-line no-undef
          waitUntil: (_args: Thenable<any>) => {
            _args.then(() => {
              const reference = NoteUtils.getNoteOrThrow({
                fname: "foo.one",
                vault: vaults[0],
                wsRoot,
                notes: engine.notes,
              });
              expect(reference.body).toEqual(`[[newfile]]\n`);
              done();
            });
          },
        };

        watcher.onWillRenameFiles(args);
      });
    }
  );

  describeSingleWS(
    "GIVEN a basic setup on a single vault workspace",
    {
      postSetupHook: setupBasic,
    },
    () => {
      test("WHEN user renames a file outside of dendron rename command, THEN the title of fileName is also updated", async () => {
        const { engine, wsRoot, vaults } = ExtensionProvider.getDWorkspace();
        const previewProxy = new MockPreviewProxy();
        const extension = ExtensionProvider.getExtension();

        const windowWatcher = new WindowWatcher({
          extension,
          previewProxy,
        });
        watcher = new WorkspaceWatcher({
          schemaSyncService: ExtensionProvider.getExtension().schemaSyncService,
          extension,
          windowWatcher,
        });
        const oldPath = path.join(wsRoot, vaults[0].fsPath, "oldfile.md");
        const oldUri = vscode.Uri.file(oldPath);
        const newPath = path.join(wsRoot, vaults[0].fsPath, "newfile.md");
        const newUri = vscode.Uri.file(newPath);
        const args: vscode.FileRenameEvent = {
          files: [
            {
              oldUri,
              newUri,
            },
          ],
        };
        const edit = new vscode.WorkspaceEdit();
        edit.renameFile(oldUri, newUri);
        const success = await vscode.workspace.applyEdit(edit);
        if (success) {
          await watcher.onDidRenameFiles(args);
          const newFile = NoteUtils.getNoteOrThrow({
            fname: "newfile",
            vault: vaults[0],
            wsRoot,
            notes: engine.notes,
          });
          expect(newFile.title).toEqual(`Newfile`);
        }
      });
    }
  );

  describeSingleWS(
    "GIVEN a basic setup on a single vault workspace",
    {
      postSetupHook: setupBasic,
    },
    () => {
      test("WHEN user saves a file and content has not changed, THEN updated timestamp in frontmatter is not updated", async () => {
        const engine = ExtensionProvider.getEngine();
        const previewProxy = new MockPreviewProxy();
        const extension = ExtensionProvider.getExtension();

        const windowWatcher = new WindowWatcher({
          extension,
          previewProxy,
        });
        watcher = new WorkspaceWatcher({
          schemaSyncService: ExtensionProvider.getExtension().schemaSyncService,
          extension,
          windowWatcher,
        });
        const fooNote = engine.notes["foo.one"];
        const updatedBefore = fooNote.updated;
        const editor = await ExtensionProvider.getWSUtils().openNote(fooNote);
        const vscodeEvent: vscode.TextDocumentWillSaveEvent = {
          document: editor.document,
        };
        const changes = watcher.onWillSaveTextDocument(vscodeEvent);
        expect(changes).toBeTruthy();
        expect(changes?.changes.length).toEqual(0);
        expect(fooNote.updated).toEqual(updatedBefore);
      });

      test("WHEN user saves a file and content has changed, THEN updated timestamp in frontmatter is updated", (done) => {
        const engine = ExtensionProvider.getEngine();
        const previewProxy = new MockPreviewProxy();
        const extension = ExtensionProvider.getExtension();

        const windowWatcher = new WindowWatcher({
          extension,
          previewProxy,
        });
        watcher = new WorkspaceWatcher({
          schemaSyncService: ExtensionProvider.getExtension().schemaSyncService,
          extension,
          windowWatcher,
        });
        const fooNote = engine.notes["foo.one"];
        const bodyBefore = fooNote.body;
        const updatedBefore = fooNote.updated;
        const textToAppend = "new text here";
        ExtensionProvider.getWSUtils()
          .openNote(fooNote)
          .then((editor) => {
            editor.edit((editBuilder) => {
              const line = editor.document.getText().split("\n").length;
              editBuilder.insert(new vscode.Position(line, 0), textToAppend);
            });
            editor.document.save().then(() => {
              const vscodeEvent: vscode.TextDocumentWillSaveEvent = {
                document: editor.document,
                // eslint-disable-next-line no-undef
                waitUntil: (_args: Thenable<any>) => {
                  _args.then(() => {
                    // Engine note body hasn't been updated yet
                    expect(engine.notes["foo.one"].body).toEqual(bodyBefore);
                    expect(engine.notes["foo.one"].updated).toNotEqual(
                      updatedBefore
                    );
                    done();
                  });
                },
              };
              const changes = watcher.onWillSaveTextDocument(vscodeEvent);
              expect(changes).toBeTruthy();
              expect(changes?.changes.length).toEqual(1);
            });
          });
      });
    }
  );

  describe("GIVEN the user opening a file", () => {
    let ext: IDendronExtension;
    let workspaceWatcher: WorkspaceWatcher;

    beforeEach(async () => {
      ext = ExtensionProvider.getExtension();
      const previewProxy = new MockPreviewProxy();

      const windowWatcher = new WindowWatcher({
        extension: ext,
        previewProxy,
      });

      workspaceWatcher = new WorkspaceWatcher({
        schemaSyncService: ext.schemaSyncService,
        extension: ext,
        windowWatcher,
      });
    });
    afterEach(async () => {
      // imporant since we activate workspace watchers
      await ext.deactivate();
    });
    describeSingleWS(
      "AND WHEN user opens non dendron file for the first time",
      {},
      () => {
        test("THEN do not affect frontmatter", async () => {
          const { wsRoot } = ExtensionProvider.getDWorkspace();
          await FileTestUtils.createFiles(wsRoot, [{ path: "sample" }]);
          const notePath = path.join(wsRoot, "sample");
          const editor = await VSCodeUtils.openFileInEditor(
            vscode.Uri.file(notePath)
          );
          const { onFirstOpen } =
            UNSAFE_getWorkspaceWatcherPropsForTesting(workspaceWatcher);
          expect(await onFirstOpen(editor)).toBeFalsy();
        });
      }
    );

    describeSingleWS(
      "AND WHEN user opens non dendron markdown file for the first time",
      {},
      () => {
        test("THEN do not affect frontmatter", async () => {
          const { wsRoot } = ExtensionProvider.getDWorkspace();
          await FileTestUtils.createFiles(wsRoot, [{ path: "sample.md" }]);
          const notePath = path.join(wsRoot, "sample.md");
          const editor = await VSCodeUtils.openFileInEditor(
            vscode.Uri.file(notePath)
          );
          const { onFirstOpen } =
            UNSAFE_getWorkspaceWatcherPropsForTesting(workspaceWatcher);
          expect(await onFirstOpen(editor)).toBeFalsy();
        });
      }
    );

    describeSingleWS(
      "WHEN user opens dendron note for the first time",
      {},
      () => {
        let note: NoteProps;
        before(async () => {
          const { engine, vaults, wsRoot } = ExtensionProvider.getDWorkspace();
          note = await NoteTestUtilsV4.createNoteWithEngine({
            engine,
            fname: "test",
            vault: vaults[0],
            wsRoot,
          });
        });
        test("THEN the cursor moves past the frontmatter", async () => {
          const ext = ExtensionProvider.getExtension();
          const wsutils = new WSUtilsV2(ext);
          const editor = await wsutils.openNote(note);
          const { onFirstOpen } =
            UNSAFE_getWorkspaceWatcherPropsForTesting(workspaceWatcher);

          const stubTimeout = sinon.stub(Wrap, "setTimeout");
          expect(await onFirstOpen(editor)).toBeTruthy();
          stubTimeout.callArg(0);
          // the selection should have been moved past the frontmatter
          const { line, character } = editor.selection.active;
          expect(line).toEqual(7);
          expect(character).toEqual(3);
          stubTimeout.restore();
        });
      }
    );

    describeSingleWS(
      "WHEN the user opens the file through the search",
      {},
      () => {
        let note: NoteProps;
        before(async () => {
          const { engine, vaults, wsRoot } = ExtensionProvider.getDWorkspace();
          note = await NoteTestUtilsV4.createNoteWithEngine({
            engine,
            fname: "test",
            vault: vaults[0],
            wsRoot,
          });
        });
        test("THEN the cursor moves past the frontmatter", async () => {
          const stubTimeout = sinon.stub(Wrap, "setTimeout");
          const editor = await WSUtils.openNote(note);
          // pre-move the selection, like what would happen when opening through the serach
          editor.selection = new vscode.Selection(5, 0, 5, 0);
          WorkspaceWatcher.moveCursorPastFrontmatter(editor);
          stubTimeout.callArg(0);
          // the selection didn't move from what it was before
          const { line, character } = editor.selection.active;
          expect(line).toEqual(5);
          expect(character).toEqual(0);
          stubTimeout.restore();
        });
      }
    );
  });
});
Example #20
Source File: BaseExportPodCommand.test.ts    From dendron with GNU Affero General Public License v3.0 4 votes vote down vote up
suite("BaseExportPodCommand", function () {
  describe("GIVEN a BaseExportPodCommand implementation", () => {
    describeSingleWS(
      "WHEN exporting a note scope",
      {
        postSetupHook: ENGINE_HOOKS.setupBasic,
      },
      () => {
        test("THEN note prop should be in the export payload", async () => {
          const cmd = new TestExportPodCommand(
            ExtensionProvider.getExtension()
          );
          const { wsRoot, vaults } = ExtensionProvider.getDWorkspace();

          const notePath = path.join(
            vault2Path({ vault: vaults[0], wsRoot }),
            "root.md"
          );
          await VSCodeUtils.openFileInEditor(vscode.Uri.file(notePath));

          const payload = await cmd.enrichInputs({
            exportScope: PodExportScope.Note,
          });
          expect((payload?.payload as NoteProps[])[0].fname).toEqual("root");
          expect((payload?.payload as NoteProps[]).length).toEqual(1);
        });

        test("AND note is dirty, THEN a onDidSaveTextDocument should be fired", (done) => {
          const cmd = new TestExportPodCommand(
            ExtensionProvider.getExtension()
          );
          const engine = ExtensionProvider.getEngine();

          const testNote = engine.notes["foo"];
          const textToAppend = "BaseExportPodCommand testing";
          // onEngineNoteStateChanged is not being triggered by save so test to make sure that save is being triggered instead
          const disposable = vscode.workspace.onDidSaveTextDocument(
            (textDocument) => {
              testAssertsInsideCallback(() => {
                expect(
                  textDocument.getText().includes(textToAppend)
                ).toBeTruthy();
                expect(textDocument.fileName.endsWith("foo.md")).toBeTruthy();
                disposable.dispose();
                cmd.dispose();
              }, done);
            }
          );

          ExtensionProvider.getWSUtils()
            .openNote(testNote)
            .then(async (editor) => {
              editor
                .edit(async (editBuilder) => {
                  const line = editor.document.getText().split("\n").length;
                  editBuilder.insert(
                    new vscode.Position(line, 0),
                    textToAppend
                  );
                })
                .then(async () => {
                  cmd.run();
                });
            });
        });

        test("AND note is clean, THEN a onDidSaveTextDocument should not be fired", (done) => {
          const cmd = new TestExportPodCommand(
            ExtensionProvider.getExtension()
          );
          const engine = ExtensionProvider.getEngine();

          const testNote = engine.notes["foo"];
          vscode.workspace.onDidSaveTextDocument(() => {
            assert(false, "Callback not expected");
          });

          ExtensionProvider.getWSUtils()
            .openNote(testNote)
            .then(async () => {
              cmd.run();
            });
          // Small sleep to ensure callback doesn't fire.
          waitInMilliseconds(10).then(() => done());
        });
      }
    );

    describeMultiWS(
      "WHEN exporting a hierarchy scope",
      {
        preSetupHook: async ({ wsRoot, vaults }) => {
          await ENGINE_HOOKS_MULTI.setupBasicMulti({ wsRoot, vaults });
          await NoteTestUtilsV4.createNote({
            wsRoot,
            vault: vaults[1],
            fname: "foo.test",
          });
        },
      },
      () => {
        test("THEN hierarchy note props should be in the export payload AND a note with a hierarchy match but in a different vault should not appear", async () => {
          const cmd = new TestExportPodCommand(
            ExtensionProvider.getExtension()
          );
          const payload = await cmd.enrichInputs({
            exportScope: PodExportScope.Hierarchy,
          });

          // 'foo' note and its child: foo.test should not appear
          expect(payload?.payload.length).toEqual(2);
        });

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

    describeMultiWS(
      "WHEN exporting a workspace scope",
      {
        preSetupHook: ENGINE_HOOKS.setupBasic,
      },
      () => {
        test("THEN workspace note props should be in the export payload", async () => {
          const cmd = new TestExportPodCommand(
            ExtensionProvider.getExtension()
          );
          const payload = await cmd.enrichInputs({
            exportScope: PodExportScope.Workspace,
          });

          expect(payload?.payload.length).toEqual(6);
        });
      }
    );

    describeMultiWS(
      "WHEN exporting a lookup based scope",
      {
        preSetupHook: async ({ wsRoot, vaults }) => {
          await ENGINE_HOOKS.setupBasic({ wsRoot, vaults });
          await NoteTestUtilsV4.createNote({
            wsRoot,
            vault: vaults[0],
            fname: "test-note-for-pod1",
          });
          await NoteTestUtilsV4.createNote({
            wsRoot,
            vault: vaults[0],
            fname: "test-note-for-pod2",
          });
        },
      },
      () => {
        let sandbox: sinon.SinonSandbox;

        beforeEach(() => {
          sandbox = sinon.createSandbox();
        });

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

        test("THEN lookup is prompted and lookup result should be the export payload", async () => {
          const cmd = new TestExportPodCommand(
            ExtensionProvider.getExtension()
          );
          const engine = ExtensionProvider.getEngine();
          const { wsRoot, vaults } = engine;
          const testNote1 = NoteUtils.getNoteByFnameV5({
            fname: "test-note-for-pod1",
            notes: engine.notes,
            wsRoot,
            vault: vaults[0],
          }) as NoteProps;
          const testNote2 = NoteUtils.getNoteByFnameV5({
            fname: "test-note-for-pod2",
            notes: engine.notes,
            wsRoot,
            vault: vaults[0],
          }) as NoteProps;
          const selectedItems = [
            { ...testNote1, label: "" },
            { ...testNote2, label: "" },
          ];
          const lookupStub = sandbox
            .stub(PodUIControls, "promptForScopeLookup")
            .resolves({
              selectedItems,
              onAcceptHookResp: [],
            });

          await cmd.enrichInputs({
            exportScope: PodExportScope.Lookup,
          });

          expect(lookupStub.calledOnce).toBeTruthy();

          await cmd.enrichInputs({
            exportScope: PodExportScope.LinksInSelection,
          });

          expect(lookupStub.calledTwice).toBeTruthy();
        });
      }
    );

    describeMultiWS(
      "WHEN exporting a vault scope",
      {
        preSetupHook: ENGINE_HOOKS.setupBasic,
      },
      () => {
        test("THEN quickpick is prompted and selected vault's notes shoul be export payload", async () => {
          const cmd = new TestExportPodCommand(
            ExtensionProvider.getExtension()
          );
          const engine = ExtensionProvider.getEngine();
          const { vaults } = engine;
          stubQuickPick(vaults[0]);
          const payload = await cmd.enrichInputs({
            exportScope: PodExportScope.Vault,
          });
          expect(payload?.payload.length).toEqual(4);
        });
      }
    );
  });
});
Example #21
Source File: ConvertLink.test.ts    From dendron with GNU Affero General Public License v3.0 4 votes vote down vote up
suite("ConvertLink", function () {
  let activeNote: NoteProps;
  let activeNoteCreateOpts: CreateNoteOptsV4;
  let anotherNote: NoteProps;

  const noAliasBrokenLinkPosition = new vscode.Position(7, 0);
  const aliasBrokenLinkPosition = new vscode.Position(8, 0);

  describeMultiWS(
    "GIVEN note with broken links",
    {
      preSetupHook: async (opts) => {
        const { vaults, wsRoot } = opts;
        activeNoteCreateOpts = {
          fname: "active",
          vault: vaults[0],
          wsRoot,
          body: [
            "[[foo.bar.broken.link]]", // link 7
            "[[broken link|foo.bar.broken.link]]", // line 8
          ].join("\n"),
          genRandomId: true,
        };
        activeNote = await NoteTestUtilsV4.createNote(activeNoteCreateOpts);
        anotherNote = await NoteTestUtilsV4.createNote({
          fname: "another",
          vault: vaults[0],
          wsRoot,
          genRandomId: true,
        });
      },
    },
    () => {
      let sandbox: sinon.SinonSandbox;
      beforeEach(async () => {
        sandbox = sinon.createSandbox();
        activeNote = await NoteTestUtilsV4.createNote(activeNoteCreateOpts);
        await WSUtils.openNote(activeNote);
      });

      afterEach(async () => {
        sandbox.restore();
        await VSCodeUtils.closeAllEditors();
      });

      test("WHEN broken link with no alias, THEN doesn't show option to use alias text.", async () => {
        const editor = vscode.window.activeTextEditor as vscode.TextEditor;
        const cmd = new ConvertLinkCommand();
        const reference = await getReference({
          editor,
          position: noAliasBrokenLinkPosition,
        });
        const { options, parsedLink } =
          cmd.prepareBrokenLinkConvertOptions(reference);
        expect(parsedLink.alias).toBeFalsy();
        expect(
          _.findIndex(options, (option) => {
            return option.label === "Alias";
          })
        ).toEqual(-1);
      });

      test("WHEN alias option selected, THEN converts broken link to plain alias text", async () => {
        const editor = vscode.window.activeTextEditor as vscode.TextEditor;
        editor.selection = new vscode.Selection(
          aliasBrokenLinkPosition,
          aliasBrokenLinkPosition
        );
        const cmd = new ConvertLinkCommand();
        const reference = await getReference({
          editor,
          position: aliasBrokenLinkPosition,
        });
        sandbox.stub(cmd, "promptBrokenLinkConvertOptions").resolves({
          option: {
            label: "Alias",
          },
          parsedLink: LinkUtils.parseLinkV2({
            linkString: reference.refText,
            explicitAlias: true,
          }) as ParseLinkV2Resp,
        });
        const gatherOut = await cmd.gatherInputs();
        expect(gatherOut.text).toEqual("broken link");
      });

      test("WHEN note name option selected, THEN converts broken link to note name text", async () => {
        const editor = vscode.window.activeTextEditor as vscode.TextEditor;
        editor.selection = new vscode.Selection(
          aliasBrokenLinkPosition,
          aliasBrokenLinkPosition
        );
        const cmd = new ConvertLinkCommand();
        const reference = await getReference({
          editor,
          position: aliasBrokenLinkPosition,
        });
        sandbox.stub(cmd, "promptBrokenLinkConvertOptions").resolves({
          option: {
            label: "Note name",
          },
          parsedLink: LinkUtils.parseLinkV2({
            linkString: reference.refText,
            explicitAlias: true,
          }) as ParseLinkV2Resp,
        });
        const gatherOut = await cmd.gatherInputs();
        expect(gatherOut.text).toEqual("link");
      });

      test("WHEN hierarchy option selected, THEN converts broken link to hierarchy text", async () => {
        const editor = vscode.window.activeTextEditor as vscode.TextEditor;
        editor.selection = new vscode.Selection(
          aliasBrokenLinkPosition,
          aliasBrokenLinkPosition
        );
        const cmd = new ConvertLinkCommand();
        const reference = await getReference({
          editor,
          position: aliasBrokenLinkPosition,
        });
        sandbox.stub(cmd, "promptBrokenLinkConvertOptions").resolves({
          option: {
            label: "Hierarchy",
          },
          parsedLink: LinkUtils.parseLinkV2({
            linkString: reference.refText,
            explicitAlias: true,
          }) as ParseLinkV2Resp,
        });
        const gatherOut = await cmd.gatherInputs();
        expect(gatherOut.text).toEqual("foo.bar.broken.link");
      });

      test("WHEN prompt option selected, THEN prompts for user input and converts broken link to user input", async () => {
        const editor = vscode.window.activeTextEditor as vscode.TextEditor;
        editor.selection = new vscode.Selection(
          aliasBrokenLinkPosition,
          aliasBrokenLinkPosition
        );
        const cmd = new ConvertLinkCommand();
        const reference = await getReference({
          editor,
          position: aliasBrokenLinkPosition,
        });
        sandbox.stub(cmd, "promptBrokenLinkConvertOptions").resolves({
          option: {
            label: "Prompt",
          },
          parsedLink: LinkUtils.parseLinkV2({
            linkString: reference.refText,
            explicitAlias: true,
          }) as ParseLinkV2Resp,
        });
        sandbox.stub(cmd, "promptBrokenLinkUserInput").resolves("user input");
        const gatherOut = await cmd.gatherInputs();
        expect(gatherOut.text).toEqual("user input");
      });

      test("WHEN change destination option selected, THEN prompts lookup for new destination and converts broken link to new link", async () => {
        const editor = vscode.window.activeTextEditor as vscode.TextEditor;
        editor.selection = new vscode.Selection(
          aliasBrokenLinkPosition,
          aliasBrokenLinkPosition
        );
        const cmd = new ConvertLinkCommand();
        const reference = await getReference({
          editor,
          position: aliasBrokenLinkPosition,
        });
        sandbox.stub(cmd, "promptBrokenLinkConvertOptions").resolves({
          option: {
            label: "Change destination",
          },
          parsedLink: LinkUtils.parseLinkV2({
            linkString: reference.refText,
            explicitAlias: true,
          }) as ParseLinkV2Resp,
        });
        sandbox.stub(cmd, "lookupNewDestination").resolves({
          selectedItems: [
            {
              ...anotherNote,
              label: "another",
            },
          ],
          onAcceptHookResp: [],
        });
        const gatherOut = await cmd.gatherInputs();
        expect(gatherOut.text).toEqual("[[Another|another]]");
      });
    }
  );

  const userTagPosition = new vscode.Position(7, 0);
  const userWikiLinkPosition = new vscode.Position(8, 0);
  const hashTagPosition = new vscode.Position(9, 0);
  const tagWikiLinkPosition = new vscode.Position(10, 0);
  const regularWikiLinkPosition = new vscode.Position(11, 0);
  const plainTextPosition = new vscode.Position(12, 0);

  describeMultiWS(
    "GIVEN note with valid links",
    {
      preSetupHook: async (opts) => {
        const { vaults, wsRoot } = opts;
        activeNoteCreateOpts = {
          fname: "active",
          vault: vaults[0],
          wsRoot,
          body: [
            "@timothy", // link 7
            "[[user.timothy]]", // line 8
            "#foo", // line 9
            "[[tags.foo]]", // line 10
            "[[root]]", // line 11
            "plaintext", // line 12
          ].join("\n"),
          genRandomId: true,
        };
        activeNote = await NoteTestUtilsV4.createNote(activeNoteCreateOpts);
        anotherNote = await NoteTestUtilsV4.createNote({
          fname: "another",
          vault: vaults[0],
          wsRoot,
          genRandomId: true,
        });
        await NoteTestUtilsV4.createNote({
          fname: "user.timothy",
          vault: vaults[0],
          wsRoot,
          genRandomId: true,
        });
        await NoteTestUtilsV4.createNote({
          fname: "tags.foo",
          vault: vaults[0],
          wsRoot,
          genRandomId: true,
        });
      },
    },
    () => {
      let sandbox: sinon.SinonSandbox;
      beforeEach(async () => {
        sandbox = sinon.createSandbox();
        activeNote = await NoteTestUtilsV4.createNote(activeNoteCreateOpts);
        await WSUtils.openNote(activeNote);
      });

      afterEach(async () => {
        sandbox.restore();
        await VSCodeUtils.closeAllEditors();
      });

      test("WHEN usertag, THEN convert to correct wikilink", async () => {
        const editor = vscode.window.activeTextEditor as vscode.TextEditor;
        editor.selection = new vscode.Selection(
          userTagPosition,
          userTagPosition
        );
        const cmd = new ConvertLinkCommand();
        sandbox.stub(cmd, "promptConfirmation").resolves(true);
        const gatherOut = await cmd.gatherInputs();
        expect(gatherOut.text).toEqual("[[user.timothy]]");
      });

      test("WHEN wikilink with user.* hierarchy, THEN convert to usertag", async () => {
        const editor = vscode.window.activeTextEditor as vscode.TextEditor;
        editor.selection = new vscode.Selection(
          userWikiLinkPosition,
          userWikiLinkPosition
        );
        const cmd = new ConvertLinkCommand();
        sandbox.stub(cmd, "promptConfirmation").resolves(true);
        const gatherOut = await cmd.gatherInputs();
        expect(gatherOut.text).toEqual("@timothy");
      });

      test("WHEN hashtag, THEN convert to correct wikilink", async () => {
        const editor = vscode.window.activeTextEditor as vscode.TextEditor;
        editor.selection = new vscode.Selection(
          hashTagPosition,
          hashTagPosition
        );
        const cmd = new ConvertLinkCommand();
        sandbox.stub(cmd, "promptConfirmation").resolves(true);
        const gatherOut = await cmd.gatherInputs();
        expect(gatherOut.text).toEqual("[[tags.foo]]");
      });

      test("WHEN wikilink with tags.* hierarchy, THEN convert to hashtag", async () => {
        const editor = vscode.window.activeTextEditor as vscode.TextEditor;
        editor.selection = new vscode.Selection(
          tagWikiLinkPosition,
          tagWikiLinkPosition
        );
        const cmd = new ConvertLinkCommand();
        sandbox.stub(cmd, "promptConfirmation").resolves(true);
        const gatherOut = await cmd.gatherInputs();
        expect(gatherOut.text).toEqual("#foo");
      });

      test("WHEN regular valid link, THEN raise error", async () => {
        const editor = vscode.window.activeTextEditor as vscode.TextEditor;
        editor.selection = new vscode.Selection(
          regularWikiLinkPosition,
          regularWikiLinkPosition
        );
        const cmd = new ConvertLinkCommand();
        try {
          await cmd.gatherInputs();
        } catch (error) {
          expect(error).toBeTruthy();
        }
      });

      test("WHEN plaintext, THEN raise error", async () => {
        const editor = vscode.window.activeTextEditor as vscode.TextEditor;
        editor.selection = new vscode.Selection(
          plainTextPosition,
          plainTextPosition
        );
        const cmd = new ConvertLinkCommand();
        try {
          await cmd.gatherInputs();
        } catch (error) {
          expect(error).toBeTruthy();
        }
      });
    }
  );
});
Example #22
Source File: BacklinksTreeDataProvider.test.ts    From dendron with GNU Affero General Public License v3.0 4 votes vote down vote up
suite("BacklinksTreeDataProvider", function () {
  // Set test timeout to 3 seconds
  this.timeout(3000);

  describeSingleWS(
    "GIVEN a single vault workspace with two notes (target, link)",
    {
      postSetupHook: async ({ wsRoot, vaults }) => {
        await NOTE_PRESETS_V4.NOTE_WITH_TARGET.create({
          wsRoot,
          vault: vaults[0],
        });
        await NOTE_PRESETS_V4.NOTE_WITH_LINK.create({
          wsRoot,
          vault: vaults[0],
        });
      },
    },
    () => {
      test("THEN BacklinksTreeDataProvider calculates correct number of backlinks", async () => {
        const { engine, wsRoot, vaults } = ExtensionProvider.getDWorkspace();
        await ExtensionProvider.getWSUtils().openNote(engine.notes["alpha"]);
        const { out } = await getRootChildrenBacklinksAsPlainObject();
        const expectedPath = vscode.Uri.file(
          path.join(wsRoot, vaults[0].fsPath, "beta.md")
        ).path;
        expect(
          out[0].command.arguments[0].path.toLowerCase() as string
        ).toEqual(expectedPath.toLowerCase());
        expect(out.length).toEqual(1);
      });

      test("THEN validate get parent works", async () => {
        const { out: backlinks, provider } = await getRootChildrenBacklinks();
        const parentBacklink = backlinks[0];

        // Our utility method will add the children into out backlink structure.
        // The provider will give just the backlink hence we will remove the
        // children from the structure that will be used to assert equality.
        const { children, ...parentBacklinkForAssert } = parentBacklink;

        // Validate children added by the test setup are able to getParent()
        expect(parentBacklink.children).toBeTruthy();
        parentBacklink.children?.forEach((child) => {
          const foundParent = provider.getParent(child);

          assertAreEqual(foundParent, parentBacklinkForAssert);
        });

        // Validate backlinks created out of refs can getParent()
        expect(parentBacklink.refs).toBeTruthy();
      });

      test("THEN calculating backlinks from cache returns same number of backlinks", async () => {
        // re-initialize engine from cache
        await new ReloadIndexCommand().run();
        const { engine, wsRoot, vaults } = ExtensionProvider.getDWorkspace();
        await ExtensionProvider.getWSUtils().openNote(engine.notes["alpha"]);

        const { out } = await getRootChildrenBacklinksAsPlainObject();
        const expectedPath = vscode.Uri.file(
          path.join(wsRoot, vaults[0].fsPath, "beta.md")
        ).path;
        expect(
          out[0].command.arguments[0].path.toLowerCase() as string
        ).toEqual(expectedPath.toLowerCase());
        expect(out.length).toEqual(1);
      });
    }
  );

  describeMultiWS(
    "WHEN there is one note with the candidate word",
    {
      // NOTE: this test often times out
      timeout: 10e3,
      preSetupHook: async ({ wsRoot, vaults }) => {
        await NOTE_PRESETS_V4.NOTE_WITH_TARGET.create({
          wsRoot,
          vault: vaults[0],
        });
        await NOTE_PRESETS_V4.NOTE_WITH_LINK_CANDIDATE_TARGET.create({
          wsRoot,
          vault: vaults[0],
        });
      },
      modConfigCb: (config) => {
        config.dev = {
          enableLinkCandidates: true,
        };
        return config;
      },
    },
    () => {
      test("THEN finds the backlink candidate for that note", async () => {
        const { wsRoot, vaults, engine } = ExtensionProvider.getDWorkspace();
        const isLinkCandidateEnabled = TestConfigUtils.getConfig({ wsRoot }).dev
          ?.enableLinkCandidates;
        expect(isLinkCandidateEnabled).toBeTruthy();

        const noteWithTarget = NoteUtils.getNoteByFnameFromEngine({
          fname: "alpha",
          engine,
          vault: vaults[0],
        });
        await checkNoteBacklinks({ wsRoot, vaults, noteWithTarget });
      });
    }
  );

  describeMultiWS(
    "WHEN there are multiple notes with the candidate word",
    {
      preSetupHook: async ({ wsRoot, vaults }) => {
        // Create 2 notes with the same name
        await NOTE_PRESETS_V4.NOTE_WITH_TARGET.create({
          wsRoot,
          vault: vaults[0],
          genRandomId: true,
        });
        await NOTE_PRESETS_V4.NOTE_WITH_TARGET.create({
          wsRoot,
          vault: vaults[1],
          genRandomId: true,
        });
        await NOTE_PRESETS_V4.NOTE_WITH_LINK_CANDIDATE_TARGET.create({
          wsRoot,
          vault: vaults[0],
        });
      },
      modConfigCb: (config) => {
        config.dev = {
          enableLinkCandidates: true,
        };
        return config;
      },
    },
    () => {
      test("THEN finds the backlink candidate for all notes", async () => {
        const { wsRoot, vaults, engine } = ExtensionProvider.getDWorkspace();
        const isLinkCandidateEnabled = TestConfigUtils.getConfig({ wsRoot }).dev
          ?.enableLinkCandidates;
        expect(isLinkCandidateEnabled).toBeTruthy();

        // Check the backlinks for both notes
        await checkNoteBacklinks({
          wsRoot,
          vaults,
          noteWithTarget: NoteUtils.getNoteByFnameFromEngine({
            fname: "alpha",
            engine,
            vault: vaults[0],
          }),
        });
        await checkNoteBacklinks({
          wsRoot,
          vaults,
          noteWithTarget: NoteUtils.getNoteByFnameFromEngine({
            fname: "alpha",
            engine,
            vault: vaults[1],
          }),
        });
      });
    }
  );

  describeMultiWS(
    "GIVEN a multi vault workspace with notes referencing each other across vaults",
    {
      preSetupHook: async ({ wsRoot, vaults }) => {
        await NoteTestUtilsV4.createNote({
          fname: "alpha",
          body: `[[beta]]`,
          vault: vaults[0],
          wsRoot,
        });

        await NoteTestUtilsV4.createNote({
          fname: "beta",
          body: `[[alpha]]`,
          vault: vaults[1],
          wsRoot,
          props: {
            updated: 2,
          },
        });

        await NoteTestUtilsV4.createNote({
          fname: "omega",
          body: `[[alpha]]`,
          vault: vaults[1],
          wsRoot,
          props: {
            updated: 3,
          },
        });
      },
      modConfigCb: (config) => {
        config.dev = {
          enableLinkCandidates: true,
        };
        return config;
      },
    },
    () => {
      test("THEN backlink sort order is correct", async () => {
        const { wsRoot, vaults } = ExtensionProvider.getDWorkspace();
        function buildVault1Path(fileName: string) {
          return vscode.Uri.file(
            path.join(wsRoot, vaults[1].fsPath, fileName)
          ).path.toLowerCase();
        }

        const notePath = path.join(wsRoot, vaults[0].fsPath, "alpha.md");
        await VSCodeUtils.openFileInEditor(Uri.file(notePath));

        // Test Last Updated sort order
        {
          const { out } = await getRootChildrenBacklinksAsPlainObject(
            BacklinkPanelSortOrder.LastUpdated
          );
          expect(
            out[0].command.arguments[0].path.toLowerCase() as string
          ).toEqual(buildVault1Path("omega.md"));
          expect(out.length).toEqual(2);
        }

        // Test PathNames sort order
        {
          const { out } = await getRootChildrenBacklinksAsPlainObject(
            BacklinkPanelSortOrder.PathNames
          );
          expect(
            out[0].command.arguments[0].path.toLowerCase() as string
          ).toEqual(buildVault1Path("beta.md"));
          expect(out.length).toEqual(2);
        }
      });

      test("THEN BacklinksTreeDataProvider calculates correct number of backlinks", async () => {
        const { engine, wsRoot, vaults } = ExtensionProvider.getDWorkspace();
        await ExtensionProvider.getWSUtils().openNote(engine.notes["alpha"]);
        const { out } = await getRootChildrenBacklinksAsPlainObject();
        const expectedPath = vscode.Uri.file(
          path.join(wsRoot, vaults[1].fsPath, "beta.md")
        ).path;
        expect(
          out[0].command.arguments[0].path.toLowerCase() as string
        ).toEqual(expectedPath.toLowerCase());
        expect(out.length).toEqual(2);
      });
    }
  );

  describeMultiWS(
    "GIVEN a multi vault workspace with two notes in different vaults",
    {
      preSetupHook: async ({ wsRoot, vaults }) => {
        await NoteTestUtilsV4.createNote({
          fname: "alpha",
          body: `gamma`,
          vault: vaults[0],
          wsRoot,
        });
        await NOTE_PRESETS_V4.NOTE_WITH_LINK_CANDIDATE_TARGET.create({
          wsRoot,
          vault: vaults[1],
        });
      },
      modConfigCb: (config) => {
        config.dev = {
          enableLinkCandidates: true,
        };
        return config;
      },
    },
    () => {
      test("THEN link candidates should only work within a vault", async () => {
        const engine = ExtensionProvider.getEngine();

        const alpha = engine.notes["alpha"];
        await ExtensionProvider.getWSUtils().openNote(alpha);
        const alphaOut = (await getRootChildrenBacklinksAsPlainObject()).out;
        expect(alphaOut).toEqual([]);
        expect(alpha.links).toEqual([]);

        const gamma = engine.notes["gamma"];
        await ExtensionProvider.getWSUtils().openNote(gamma);
        const gammaOut = (await getRootChildrenBacklinksAsPlainObject()).out;
        expect(gammaOut).toEqual([]);
        expect(gamma.links).toEqual([]);
      });
    }
  );

  describeSingleWS(
    "GIVEN a single vault workspace with links and feature flag was not enabled",
    {
      postSetupHook: async ({ wsRoot, vaults }) => {
        await NoteTestUtilsV4.createNote({
          fname: "alpha",
          body: "[[beta]] beta",
          vault: vaults[0],
          wsRoot,
        });
        await NoteTestUtilsV4.createNote({
          fname: "beta",
          body: "alpha",
          vault: vaults[0],
          wsRoot,
        });
      },
    },
    () => {
      test("THEN candidate links don't show up", async () => {
        const engine = ExtensionProvider.getEngine();

        await new ReloadIndexCommand().execute();
        const alpha = engine.notes["alpha"];
        await ExtensionProvider.getWSUtils().openNote(alpha);

        const { out: alphaOut } = await getRootChildrenBacklinks();
        const alphaOutObj = backlinksToPlainObject(alphaOut) as any;
        expect(_.isEmpty(alphaOutObj)).toBeTruthy();

        const beta = engine.notes["beta"];
        await ExtensionProvider.getWSUtils().openNote(beta);
        const { out: betaOut } = await getRootChildrenBacklinks();
        const betaOutObj = backlinksToPlainObject(betaOut) as any;
        expect(betaOutObj[0].children.length).toEqual(1);
      });
    }
  );

  describeSingleWS(
    "GIVEN a single vault workspace and a note with many links",
    {
      postSetupHook: async ({ wsRoot, vaults }) => {
        await NoteTestUtilsV4.createNote({
          fname: "alpha",
          body: "this note has many links and candidates to it.",
          vault: vaults[0],
          wsRoot,
        });
        await NoteTestUtilsV4.createNote({
          fname: "beta",
          body: "[[alpha]] alpha alpha [[alpha]] [[alpha]] alpha\nalpha\n\nalpha",
          vault: vaults[0],
          wsRoot,
        });
      },
      modConfigCb: (config) => {
        config.dev = {
          enableLinkCandidates: true,
        };
        return config;
      },
    },
    () => {
      test("THEN multi backlink items are displayed correctly", async () => {
        const engine = ExtensionProvider.getEngine();

        // need this until we move it out of the feature flag.
        await new ReloadIndexCommand().execute();
        const alpha = engine.notes["alpha"];
        await ExtensionProvider.getWSUtils().openNote(alpha);

        const { out } = await getRootChildrenBacklinks();
        const outObj = backlinksToPlainObject(out) as any;

        // source should be beta.md

        const sourceTreeItem = outObj[0];
        expect(sourceTreeItem.label).toEqual("beta");
        // it should have all links and references in a flat list
        expect(sourceTreeItem.children.length).toEqual(8);
      });
    }
  );

  describeMultiWS(
    "GIVEN a multi vault workspace with xvault links",
    {
      preSetupHook: async ({ wsRoot, vaults }) => {
        await NoteTestUtilsV4.createNote({
          fname: "alpha",
          body: `[[beta]]`,
          vault: vaults[0],
          wsRoot,
        });
        await NoteTestUtilsV4.createNote({
          fname: "beta",
          body: `[[dendron://${VaultUtils.getName(vaults[0])}/alpha]]`,
          vault: vaults[1],
          wsRoot,
        });
      },
      modConfigCb: (config) => {
        config.dev = {
          enableLinkCandidates: true,
        };
        return config;
      },
    },
    () => {
      test("THEN BacklinksTreeDataProvider calculates correct number of backlinks", async () => {
        const { engine, wsRoot, vaults } = ExtensionProvider.getDWorkspace();

        const alpha = engine.notes["alpha"];
        await ExtensionProvider.getWSUtils().openNote(alpha);
        const { out } = await getRootChildrenBacklinksAsPlainObject();
        const expectedPath = vscode.Uri.file(
          path.join(wsRoot, vaults[1].fsPath, "beta.md")
        ).path;
        expect(
          out[0].command.arguments[0].path.toLowerCase() as string
        ).toEqual(expectedPath.toLowerCase());
        expect(out.length).toEqual(1);
      });
    }
  );

  describeSingleWS(
    "GIVEN a single vault workspace and anchor notes",
    {
      postSetupHook: async ({ wsRoot, vaults }) => {
        await NOTE_PRESETS_V4.NOTE_WITH_ANCHOR_TARGET.create({
          wsRoot,
          vault: vaults[0],
        });
        await NOTE_PRESETS_V4.NOTE_WITH_ANCHOR_LINK.create({
          wsRoot,
          vault: vaults[0],
        });
      },
    },
    () => {
      test("THEN BacklinksTreeDataProvider calculates correct number of links", async () => {
        const { engine, wsRoot } = ExtensionProvider.getDWorkspace();
        const alpha = engine.notes["alpha"];
        await ExtensionProvider.getWSUtils().openNote(alpha);
        const { out } = await getRootChildrenBacklinksAsPlainObject();
        const expectedPath = vscode.Uri.file(
          NoteUtils.getFullPath({
            note: engine.notes["beta"],
            wsRoot,
          })
        ).path;

        expect(
          out[0].command.arguments[0].path.toLowerCase() as string
        ).toEqual(expectedPath.toLowerCase());
        expect(out.length).toEqual(1);
      });
    }
  );

  describeSingleWS(
    "GIVEN a single vault workspace and alias notes",
    {
      postSetupHook: async ({ wsRoot, vaults }) => {
        await NOTE_PRESETS_V4.NOTE_WITH_TARGET.create({
          wsRoot,
          vault: vaults[0],
        });
        await NOTE_PRESETS_V4.NOTE_WITH_ALIAS_LINK.create({
          wsRoot,
          vault: vaults[0],
        });
      },
    },
    () => {
      test("THEN BacklinksTreeDataProvider calculates correct number of links", async () => {
        const { engine, wsRoot } = ExtensionProvider.getDWorkspace();
        const alpha = engine.notes["alpha"];
        await ExtensionProvider.getWSUtils().openNote(alpha);
        const { out } = await getRootChildrenBacklinksAsPlainObject();
        // assert.strictEqual(
        //   out[0].command.arguments[0].path.toLowerCase() as string,
        //   NoteUtils.getPathV4({ note: noteWithLink, wsRoot })
        // );
        const expectedPath = vscode.Uri.file(
          NoteUtils.getFullPath({
            note: engine.notes["beta"],
            wsRoot,
          })
        ).path;
        expect(
          out[0].command.arguments[0].path.toLowerCase() as string
        ).toEqual(expectedPath.toLowerCase());
        expect(out.length).toEqual(1);
      });
    }
  );

  describeSingleWS(
    "GIVEN a single vault workspace and hashtags",
    {
      postSetupHook: async ({ wsRoot, vaults }) => {
        await NoteTestUtilsV4.createNote({
          wsRoot,
          vault: vaults[0],
          fname: "tags.my.test-0.tag",
        });
        await NoteTestUtilsV4.createNote({
          wsRoot,
          vault: vaults[0],
          fname: "test",
          body: "#my.test-0.tag",
        });
      },
    },
    () => {
      test("THEN BacklinksTreeDataProvider calculates correct number of links", async () => {
        const { engine, wsRoot } = ExtensionProvider.getDWorkspace();
        const alpha = engine.notes["tags.my.test-0.tag"];
        await ExtensionProvider.getWSUtils().openNote(alpha);
        const { out } = await getRootChildrenBacklinksAsPlainObject();
        const expectedPath = vscode.Uri.file(
          NoteUtils.getFullPath({
            note: engine.notes["test"],
            wsRoot,
          })
        ).path;
        expect(
          out[0].command.arguments[0].path.toLowerCase() as string
        ).toEqual(expectedPath.toLowerCase());
        expect(out.length).toEqual(1);
      });
    }
  );

  describeMultiWS(
    "WHEN a basic workspace exists",
    {
      preSetupHook: ENGINE_HOOKS.setupBasic,
    },
    () => {
      let updateSortOrder: sinon.SinonStub;
      let backlinksTreeDataProvider: BacklinksTreeDataProvider;
      let mockEvents: MockEngineEvents;

      beforeEach(() => {
        mockEvents = new MockEngineEvents();
        backlinksTreeDataProvider = new BacklinksTreeDataProvider(
          mockEvents,
          ExtensionProvider.getEngine().config.dev?.enableLinkCandidates
        );

        updateSortOrder = sinon
          .stub(BacklinksTreeDataProvider.prototype, "sortOrder")
          .returns(undefined);
      });
      afterEach(() => {
        updateSortOrder.restore();
        backlinksTreeDataProvider.dispose();
      });

      test("AND a note gets created, THEN the data provider refresh event gets invoked", (done) => {
        const engine = ExtensionProvider.getEngine();
        const testNoteProps = engine.notes["foo"];
        const entry: NoteChangeEntry = {
          note: testNoteProps,
          status: "create",
        };

        backlinksTreeDataProvider.onDidChangeTreeData(() => {
          done();
        });

        mockEvents.testFireOnNoteChanged([entry]);
      });

      test("AND a note gets updated, THEN the data provider refresh event gets invoked", (done) => {
        const engine = ExtensionProvider.getEngine();
        const testNoteProps = engine.notes["foo"];
        const entry: NoteChangeEntry = {
          prevNote: testNoteProps,
          note: testNoteProps,
          status: "update",
        };

        backlinksTreeDataProvider.onDidChangeTreeData(() => {
          done();
        });

        mockEvents.testFireOnNoteChanged([entry]);
      });

      test("AND a note gets deleted, THEN the data provider refresh event gets invoked", (done) => {
        const engine = ExtensionProvider.getEngine();
        const testNoteProps = engine.notes["foo"];
        const entry: NoteChangeEntry = {
          note: testNoteProps,
          status: "delete",
        };

        backlinksTreeDataProvider.onDidChangeTreeData(() => {
          done();
        });

        mockEvents.testFireOnNoteChanged([entry]);
      });
    }
  );
});