mocha#beforeEach TypeScript Examples

The following examples show how to use mocha#beforeEach. 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: 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 #2
Source File: Extension-PostUpgrade.test.ts    From dendron with GNU Affero General Public License v3.0 6 votes vote down vote up
suite(
  "temporary testing of Dendron version compatibility downgrade sequence",
  () => {
    describe(`GIVEN the activation sequence of Dendron`, () => {
      describe(`WHEN VS Code Version is up to date`, () => {
        let invokedWorkspaceTrustFn: boolean = false;

        beforeEach(() => {
          invokedWorkspaceTrustFn = semver.gte(vscode.version, "1.57.0");
        });

        it(`THEN onDidGrantWorkspaceTrust will get invoked.`, () => {
          expect(invokedWorkspaceTrustFn).toEqual(true);
        });

        it(`AND onDidGrantWorkspaceTrust can be found in the API.`, () => {
          vscode.workspace.onDidGrantWorkspaceTrust(() => {
            //no-op for testing
          });
        });
      });

      describe(`WHEN VS Code Version is on a version less than 1.57.0`, () => {
        let invokedWorkspaceTrustFn: boolean = false;
        const userVersion = "1.56.1";
        beforeEach(() => {
          invokedWorkspaceTrustFn = semver.gte(userVersion, "1.57.0");
        });

        it(`THEN onDidGrantWorkspaceTrust will not get invoked.`, () => {
          expect(invokedWorkspaceTrustFn).toEqual(false);
        });
      });
    });
  }
);
Example #3
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 #4
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 #5
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 #6
Source File: utils.test.ts    From dendron with GNU Affero General Public License v3.0 5 votes vote down vote up
describe(`filterPickerResults`, () => {
  const transformedQuery = ({
    vaultName,
    wasMadeFromWikiLink,
    queryString,
  }: {
    queryString?: string;
    vaultName?: string;
    wasMadeFromWikiLink?: boolean;
  }): TransformedQueryString => {
    return {
      originalQuery: queryString || "f",
      wasMadeFromWikiLink: wasMadeFromWikiLink || false,
      queryString: queryString || "f",
      vaultName,
    };
  };

  const inputItem = async ({
    fname,
    vaultName,
    isStub,
  }: {
    fname: string;
    vaultName?: string;
    isStub?: boolean;
  }): Promise<DNodePropsQuickInputV2> => {
    const promise = NoteTestUtilsV4.createNotePropsInput({
      noWrite: true,
      vault: {
        fsPath: "/tmp/vault1",
        name: vaultName || "vault1",
      },
      wsRoot: "/tmp/ws-root",
      fname,
    });
    const val = await promise;
    val.stub = isStub;
    return val;
  };

  describe(`WHEN simplest query possible`, () => {
    it(`THEN keep all results`, async () => {
      const inputs = [
        await inputItem({ fname: "f1", vaultName: "v1" }),
        await inputItem({ fname: "f2", vaultName: "v1" }),
        await inputItem({ fname: "f3", vaultName: "v2" }),
      ];

      const results = filterPickerResults({
        itemsToFilter: inputs,
        transformedQuery: transformedQuery({
          vaultName: undefined,
          wasMadeFromWikiLink: false,
          queryString: "f",
        }),
      });

      expect(results.length).toEqual(3);
    });
  });

  describe(`WHEN using dot at the end of the query. ['data.']`, () => {
    it(`THEN filter to items with children AND sort accordingly`, async () => {
      const inputs = [
        await inputItem({ fname: "data.stub", isStub: true }),
        // Pass in inputs in completely wrong order.
        await inputItem({ fname: "i.completely.do-not.belong" }),
        await inputItem({
          fname: "i.have.no-data-children.hence-filter-me-out.data.",
        }),
        await inputItem({ fname: "level1.level2.data.integer.has-grandchild" }),
        await inputItem({ fname: "l1.l2.with-data.and-child.has-grandchild" }),
        await inputItem({ fname: "l1.l2.with-data.and-child" }),
        await inputItem({ fname: "l1.with-data.and-child" }),
        await inputItem({ fname: "l1.l2.l3.data.bool" }),
        await inputItem({ fname: "level1.level2.data.integer" }),
        await inputItem({ fname: "data.driven" }),
      ];

      const results = filterPickerResults({
        itemsToFilter: inputs,
        transformedQuery: transformedQuery({
          queryString: "data.",
        }),
      });
      const actual = results.map((item) => item.fname);

      const expected = [
        // 'data.' is at the highest level of hierarchy hence it comes up on top
        "data.driven",

        "level1.level2.data.integer",
        // idx of match is closer to beginning then the previous element. But hierarchy
        // of this element is more dug in hence sort order goes down.
        "l1.l2.l3.data.bool",

        // Non clean match of data we still keep it but lower it below all the clean
        // matches regardless of hierarchy depth.
        "l1.with-data.and-child",
        "l1.l2.with-data.and-child",

        // Now lastly come the nodes with grandchildren first with clean match, then
        // with partial match.
        "level1.level2.data.integer.has-grandchild",
        "l1.l2.with-data.and-child.has-grandchild",

        // And further down the stub note.
        "data.stub",
      ];

      expect(actual).toEqual(expected);
    });
  });

  describe(`WHEN query is made from wiki link`, () => {
    it(`THEN only keep results that match the transformed query exactly`, async () => {
      const inputs = [
        await inputItem({ fname: "f1" }),
        await inputItem({ fname: "f2" }),
      ];

      const results = filterPickerResults({
        itemsToFilter: inputs,
        transformedQuery: transformedQuery({
          wasMadeFromWikiLink: true,
          queryString: "f1",
        }),
      });

      expect(results.length).toEqual(1);
      expect(results[0].fname).toEqual("f1");
    });
  });

  describe(`WHEN vault name is specified in the query`, () => {
    it(`THEN filter results to matching vault only.`, async () => {
      const inputs = [
        await inputItem({ fname: "f1", vaultName: "v1" }),
        await inputItem({ fname: "f2", vaultName: "v1" }),
        await inputItem({ fname: "f3", vaultName: "v2" }),
      ];

      const results = filterPickerResults({
        itemsToFilter: inputs,
        transformedQuery: transformedQuery({ vaultName: "v1" }),
      });

      expect(results.length).toEqual(2);
      results.forEach((r) => {
        expect(r.vault.name).toEqual("v1");
      });
    });
  });

  pickerValue = "h1.v1";
  describe(`WHEN dot splitting is used by the query. pickerValue: '${pickerValue}'`, () => {
    let results: NoteProps[];

    beforeEach(async () => {
      const inputs = [
        // Expected to be matched:
        await inputItem({ fname: "h1.h2.h3.v1" }),
        await inputItem({ fname: "h1.h3.v1" }),
        await inputItem({ fname: "h1.v1" }),

        // Out of order
        await inputItem({ fname: "v1.h2.h3.h1" }),
        await inputItem({ fname: "v1.h1" }),
      ];

      results = filterPickerResults({
        itemsToFilter: inputs,
        // Note: using the actual method that generates transform string here.
        transformedQuery: transformQueryString({ pickerValue }),
      });
    });

    ["h1.h2.h3.v1", "h1.h3.v1", "h1.v1"].forEach((fname) => {
      it(`THEN '${fname}' is to be kept`, () => {
        const actual = results
          .filter((item) => item.fname === fname)
          .map((item) => item.fname);

        expect(actual).toEqual([fname]);
      });
    });

    ["v1.h2.h3.h1", "v1.h1"].forEach((fname) => {
      it(`THEN '${fname}' is to be filtered out`, () => {
        expect(results.filter((item) => item.fname === fname).length).toEqual(
          0
        );
      });
    });
  });

  pickerValue = "h1.v1 GG";
  describe(`WHEN dot splitting with additional tokens are used by the query. pickerValue: '${pickerValue}'`, () => {
    let results: NoteProps[];

    beforeEach(async () => {
      const inputs = [
        // Expected to be matched:
        await inputItem({ fname: "h1.h2.GG.h3.v1" }),
        await inputItem({ fname: "h1.h2.h3.v1.GG" }),
        await inputItem({ fname: "h1.h3.v1" }),
        await inputItem({ fname: "h1.v1 GG" }),

        // Out of order
        await inputItem({ fname: "v1.h2.h3.h1GG" }),
        await inputItem({ fname: "v1.h1GG" }),
      ];

      results = filterPickerResults({
        itemsToFilter: inputs,
        // Note: using the actual method that generates transform string here.
        transformedQuery: transformQueryString({ pickerValue }),
      });
    });

    ["h1.h2.GG.h3.v1", "h1.h2.h3.v1.GG", "h1.h3.v1", "h1.v1 GG"].forEach(
      (fname) => {
        it(`THEN '${fname}' is to be kept.`, () => {
          expect(results.filter((item) => item.fname === fname).length).toEqual(
            1
          );
        });
      }
    );

    ["v1.h2.h3.h1GG", "v1.h1GG"].forEach((fname) => {
      it(`THEN '${fname}' is to be filtered out.`, () => {
        expect(results.filter((item) => item.fname === fname).length).toEqual(
          0
        );
      });
    });
  });
});
Example #7
Source File: buttons.test.ts    From dendron with GNU Affero General Public License v3.0 5 votes vote down vote up
suite("buttons tests", () => {
  describe(`canToggle tests:`, () => {
    describe(`GIVEN pressed button that is NOT allowed to be toggled`, () => {
      let dendronBtn: DendronBtn;

      beforeEach(() => {
        dendronBtn = new DendronBtn({
          pressed: true,
          canToggle: false,
          iconOff: "icon-off-val",
          iconOn: "icon-on-val",
          type: "horizontal",
          description: "test description",
          title: "title-val",
        });
      });

      describe(`WHEN toggle invoked.`, () => {
        beforeEach(() => {
          dendronBtn.toggle();
        });

        it(`THEN pressed val stays the same.`, () => {
          expect(dendronBtn.pressed).toEqual(true);
        });

        it(`THEN icon is set to on icon.`, () => {
          expect(dendronBtn.iconPath.id).toEqual("icon-on-val");
        });
      });
    });

    describe(`GIVEN pressed button that is allowed to be toggled`, () => {
      let dendronBtn: DendronBtn;

      beforeEach(() => {
        dendronBtn = new DendronBtn({
          pressed: true,
          canToggle: true,
          iconOff: "icon-off-val",
          iconOn: "icon-on-val",
          type: "horizontal",
          description: "test description",
          title: "title-val",
        });
      });

      describe(`WHEN toggle invoked.`, () => {
        beforeEach(() => {
          dendronBtn.toggle();
        });

        it(`THEN pressed val is flipped.`, () => {
          expect(dendronBtn.pressed).toEqual(false);
        });

        it(`THEN icon is set to off icon.`, () => {
          expect(dendronBtn.iconPath.id).toEqual("icon-off-val");
        });
      });
    });
  });
});
Example #8
Source File: LookupProviderV3.test.ts    From dendron with GNU Affero General Public License v3.0 5 votes vote down vote up
suite("LookupProviderV3 utility methods:", () => {
  describe(`shouldBubbleUpCreateNew`, () => {
    describe(`WHEN no special characters and no exact matches`, () => {
      let querystring: string;
      let numberOfExactMatches: number;

      beforeEach(() => {
        querystring = "simple-query-no-special-chars";
        numberOfExactMatches = 0;
      });

      it(`AND bubble up is omitted THEN bubble up`, () => {
        expect(
          shouldBubbleUpCreateNew({ querystring, numberOfExactMatches })
        ).toBeTruthy();

        expect(
          shouldBubbleUpCreateNew({
            querystring,
            numberOfExactMatches,
            bubbleUpCreateNew: undefined,
          })
        ).toBeTruthy();
      });

      it("AND bubble up is set to false THEN do NOT bubble up", () => {
        const actual = shouldBubbleUpCreateNew({
          querystring,
          numberOfExactMatches,
          bubbleUpCreateNew: false,
        });
        expect(actual).toBeFalsy();
      });

      it("AND bubble up is set to true THEN bubble up", () => {
        const actual = shouldBubbleUpCreateNew({
          querystring,
          numberOfExactMatches,
          bubbleUpCreateNew: true,
        });
        expect(actual).toBeTruthy();
      });
    });

    it(`WHEN special char is used THEN do NOT bubble up`, () => {
      const actual = shouldBubbleUpCreateNew({
        querystring: "query with space",
        numberOfExactMatches: 0,
      });
      expect(actual).toBeFalsy();
    });

    it(`WHEN number of exact matches is more than 0 THEN do NOT bubble up`, () => {
      const actual = shouldBubbleUpCreateNew({
        querystring: "query-val",
        numberOfExactMatches: 1,
      });
      expect(actual).toBeFalsy();
    });
  });

  describe(`sortBySimilarity`, () => {
    it("WHEN notes out of order THEN sort by similarity", async () => {
      const noteFactory = TestNoteFactory.defaultUnitTestFactory();

      const notes = [
        await noteFactory.createForFName("pkg.hi.components"),
        await noteFactory.createForFName("pkg.hi.arch"),
        await noteFactory.createForFName("pkg.hi.quickstart"),
      ];

      const sorted = sortBySimilarity(notes, "pkg.hi.arc");
      expect(sorted.map((sorted) => sorted.fname)).toEqual([
        "pkg.hi.arch",
        "pkg.hi.quickstart",
        "pkg.hi.components",
      ]);
    });
  });
});
Example #9
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 #10
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 #11
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 #12
Source File: RenameProvider.test.ts    From dendron with GNU Affero General Public License v3.0 4 votes vote down vote up
suite("RenameProvider", function () {
  let activeNote: NoteProps;
  let targetNote: NoteProps;
  let editor: vscode.TextEditor;
  let provider: RenameProvider;

  describeMultiWS(
    "GIVEN wikilink",
    {
      preSetupHook: async (opts) => {
        const { wsRoot, vaults } = opts;
        activeNote = await NoteTestUtilsV4.createNote({
          fname: "active",
          vault: vaults[0],
          wsRoot,
          body: [
            "[[target]]", // line 7, char 2 ~ 8
            "[[Target|target]]", // line 8, char 9 ~ 15
            "[[Target|dendron://vault1/target]]", // line 9, char 26 ~ 32
            "[[Target|dendron://vault1/target#foo]]", // line 10, 26 ~ 32
          ].join("\n"),
        });
        targetNote = await NoteTestUtilsV4.createNote({
          fname: "target",
          vault: vaults[0],
          wsRoot,
          body: ["# Foo"].join("\n"),
        });
        await NoteTestUtilsV4.createNote({
          fname: "note-with-link",
          vault: vaults[0],
          wsRoot,
          body: ["[[target]]"].join("\n"),
        });
        await NoteTestUtilsV4.createNote({
          fname: "note-with-link-in-another-vault",
          vault: vaults[1],
          wsRoot,
          body: ["[[dendron://vault1/target]]"].join("\n"),
        });
      },
    },
    () => {
      beforeEach(async () => {
        editor = await WSUtils.openNote(activeNote);
        provider = new RenameProvider();
      });
      test("THEN range is properly provided", async () => {
        const positions = [
          new vscode.Position(7, 0),
          new vscode.Position(8, 0),
          new vscode.Position(9, 0),
          new vscode.Position(10, 0),
        ];
        const actualRanges = await Promise.all(
          positions.map(async (position) => {
            const range = await provider.prepareRename(
              editor.document,
              position
            );
            return range;
          })
        );
        const expectRanges = [
          new vscode.Range(
            new vscode.Position(7, 2),
            new vscode.Position(7, 8)
          ),
          new vscode.Range(
            new vscode.Position(8, 9),
            new vscode.Position(8, 15)
          ),
          new vscode.Range(
            new vscode.Position(9, 26),
            new vscode.Position(9, 32)
          ),
          new vscode.Range(
            new vscode.Position(10, 26),
            new vscode.Position(10, 32)
          ),
        ];
        expect(actualRanges).toEqual(expectRanges);
      });

      describe("WHEN rename is executed", () => {
        let executeOut: { changed: NoteChangeEntry[] } | undefined;
        before(async () => {
          provider.targetNote = targetNote;
          executeOut = await provider.executeRename({ newName: "new-target" });
        });
        test("THEN correctly renamed at symbol position", async () => {
          const { vaults, engine, wsRoot } = getDWorkspace();
          const { notes } = engine;
          const active = NoteUtils.getNoteByFnameV5({
            fname: "active",
            vault: vaults[0],
            notes,
            wsRoot,
          });
          const expectedBody = [
            "[[new-target]]",
            "[[New Target|new-target]]",
            "[[New Target|dendron://vault1/new-target]]",
            "[[New Target|dendron://vault1/new-target#foo]]",
            "",
          ].join("\n");
          expect(active?.body).toEqual(expectedBody);
        });

        test("AND target note is correctly renamed", (done) => {
          const { vaults, engine, wsRoot } = getDWorkspace();
          const { notes } = engine;
          const newTarget = NoteUtils.getNoteByFnameV5({
            fname: "new-target",
            vault: vaults[0],
            notes,
            wsRoot,
          });
          expect(newTarget).toBeTruthy();
          done();
        });
        test("THEN references to target note is correctly updated", (done) => {
          expect(executeOut?.changed.length).toEqual(7);
          const { vaults, engine, wsRoot } = getDWorkspace();
          const { notes } = engine;
          const noteWithLink = NoteUtils.getNoteByFnameV5({
            fname: "note-with-link",
            vault: vaults[0],
            notes,
            wsRoot,
          });
          const noteWithLinkInAnotherVault = NoteUtils.getNoteByFnameV5({
            fname: "note-with-link-in-another-vault",
            vault: vaults[1],
            notes,
            wsRoot,
          });

          expect(noteWithLink?.body).toEqual("[[new-target]]\n");
          expect(noteWithLinkInAnotherVault?.body).toEqual(
            "[[dendron://vault1/new-target]]\n"
          );
          done();
        });
      });
    }
  );

  describeMultiWS(
    "GIVEN note references",
    {
      preSetupHook: async (opts) => {
        const { wsRoot, vaults } = opts;
        activeNote = await NoteTestUtilsV4.createNote({
          fname: "active",
          vault: vaults[0],
          wsRoot,
          body: [
            "![[target]]", // line 7, char 2 ~ 8
            "![[dendron://vault1/target]]", // line 9, char 26 ~ 32
            "![[dendron://vault1/target#foo]]", // line 10, 26 ~ 32
          ].join("\n"),
        });
        targetNote = await NoteTestUtilsV4.createNote({
          fname: "target",
          vault: vaults[0],
          wsRoot,
          body: ["# Foo"].join("\n"),
        });
        await NoteTestUtilsV4.createNote({
          fname: "note-with-link",
          vault: vaults[0],
          wsRoot,
          body: ["![[target]]"].join("\n"),
        });
        await NoteTestUtilsV4.createNote({
          fname: "note-with-link-in-another-vault",
          vault: vaults[1],
          wsRoot,
          body: ["![[dendron://vault1/target]]"].join("\n"),
        });
      },
    },
    () => {
      beforeEach(async () => {
        editor = await WSUtils.openNote(activeNote);
        provider = new RenameProvider();
      });
      test("THEN range is properly provided", async () => {
        const positions = [
          new vscode.Position(7, 1),
          new vscode.Position(8, 1),
          new vscode.Position(9, 1),
        ];
        const actualRanges = await Promise.all(
          positions.map(async (position) => {
            const range = await provider.prepareRename(
              editor.document,
              position
            );
            return range;
          })
        );
        const expectRanges = [
          new vscode.Range(
            new vscode.Position(7, 3),
            new vscode.Position(7, 9)
          ),
          new vscode.Range(
            new vscode.Position(8, 26),
            new vscode.Position(8, 20)
          ),
          new vscode.Range(
            new vscode.Position(9, 26),
            new vscode.Position(9, 20)
          ),
        ];
        expect(actualRanges).toEqual(expectRanges);
      });

      describe("WHEN rename is executed", () => {
        let executeOut: { changed: NoteChangeEntry[] } | undefined;
        before(async () => {
          provider.targetNote = targetNote;
          executeOut = await provider.executeRename({ newName: "new-target" });
        });
        test("THEN correctly renamed at symbol position", async () => {
          const { vaults, engine, wsRoot } = getDWorkspace();
          const { notes } = engine;
          const active = NoteUtils.getNoteByFnameV5({
            fname: "active",
            vault: vaults[0],
            notes,
            wsRoot,
          });
          const expectedBody = [
            "![[new-target]]",
            "![[dendron://vault1/new-target]]",
            "![[dendron://vault1/new-target#foo]]",
            "",
          ].join("\n");
          expect(active?.body).toEqual(expectedBody);
        });

        test("AND target note is correctly renamed", (done) => {
          const { vaults, engine, wsRoot } = getDWorkspace();
          const { notes } = engine;
          const newTarget = NoteUtils.getNoteByFnameV5({
            fname: "new-target",
            vault: vaults[0],
            notes,
            wsRoot,
          });
          expect(newTarget).toBeTruthy();
          done();
        });
        test("THEN references to target note is correctly updated", (done) => {
          expect(executeOut?.changed.length).toEqual(7);
          const { vaults, engine, wsRoot } = getDWorkspace();
          const { notes } = engine;
          const noteWithLink = NoteUtils.getNoteByFnameV5({
            fname: "note-with-link",
            vault: vaults[0],
            notes,
            wsRoot,
          });
          const noteWithLinkInAnotherVault = NoteUtils.getNoteByFnameV5({
            fname: "note-with-link-in-another-vault",
            vault: vaults[1],
            notes,
            wsRoot,
          });

          expect(noteWithLink?.body).toEqual("![[new-target]]\n");
          expect(noteWithLinkInAnotherVault?.body).toEqual(
            "![[dendron://vault1/new-target]]\n"
          );
          done();
        });
      });
    }
  );

  describeMultiWS(
    "GIVEN hashtag",
    {
      preSetupHook: async (opts) => {
        const { wsRoot, vaults } = opts;
        activeNote = await NoteTestUtilsV4.createNote({
          fname: "active",
          vault: vaults[0],
          wsRoot,
          body: [
            "#target", // line 7, char 2 ~ 8
          ].join("\n"),
        });
        targetNote = await NoteTestUtilsV4.createNote({
          fname: "tags.target",
          vault: vaults[0],
          wsRoot,
          body: ["# Foo"].join("\n"),
        });
        await NoteTestUtilsV4.createNote({
          fname: "note-with-link",
          vault: vaults[0],
          wsRoot,
          body: ["#target"].join("\n"),
        });
      },
    },
    () => {
      beforeEach(async () => {
        editor = await WSUtils.openNote(activeNote);
        provider = new RenameProvider();
      });
      test("THEN range is properly provided", async () => {
        const position = new vscode.Position(7, 0);
        const actualRange = await provider.prepareRename(
          editor.document,
          position
        );

        const expectRange = new vscode.Range(
          new vscode.Position(7, 1),
          new vscode.Position(7, 7)
        );
        expect(actualRange).toEqual(expectRange);
      });

      describe("WHEN rename is executed", () => {
        let executeOut: { changed: NoteChangeEntry[] } | undefined;
        before(async () => {
          provider.targetNote = targetNote;
          executeOut = await provider.executeRename({ newName: "new-target" });
        });
        test("THEN correctly renamed at symbol position", async () => {
          const { vaults, engine, wsRoot } = getDWorkspace();
          const { notes } = engine;
          const active = NoteUtils.getNoteByFnameV5({
            fname: "active",
            vault: vaults[0],
            notes,
            wsRoot,
          });
          const expectedBody = "#new-target\n";
          expect(active?.body).toEqual(expectedBody);
        });

        test("AND target note is correctly renamed", (done) => {
          const { vaults, engine, wsRoot } = getDWorkspace();
          const { notes } = engine;
          const newTarget = NoteUtils.getNoteByFnameV5({
            fname: "tags.new-target",
            vault: vaults[0],
            notes,
            wsRoot,
          });
          expect(newTarget).toBeTruthy();
          done();
        });
        test("THEN references to target note is correctly updated", (done) => {
          expect(executeOut?.changed.length).toEqual(8);
          const { vaults, engine, wsRoot } = getDWorkspace();
          const { notes } = engine;
          const noteWithLink = NoteUtils.getNoteByFnameV5({
            fname: "note-with-link",
            vault: vaults[0],
            notes,
            wsRoot,
          });

          expect(noteWithLink?.body).toEqual("#new-target\n");
          done();
        });
      });
    }
  );

  describeMultiWS(
    "GIVEN frontmatter tag",
    {
      preSetupHook: async (opts) => {
        const { wsRoot, vaults } = opts;
        activeNote = await NoteTestUtilsV4.createNote({
          fname: "active",
          vault: vaults[0],
          wsRoot,
          props: { tags: "target" },
        });
        targetNote = await NoteTestUtilsV4.createNote({
          fname: "tags.target",
          vault: vaults[0],
          wsRoot,
          body: ["# Foo"].join("\n"),
        });
        await NoteTestUtilsV4.createNote({
          fname: "note-with-link",
          vault: vaults[0],
          wsRoot,
          props: { tags: "target" },
        });
      },
    },
    () => {
      beforeEach(async () => {
        editor = await WSUtils.openNote(activeNote);
        provider = new RenameProvider();
      });
      test("THEN range is properly provided", async () => {
        const position = new vscode.Position(6, 7);
        const actualRange = await provider.prepareRename(
          editor.document,
          position
        );
        const expectRange = new vscode.Range(
          new vscode.Position(6, 6),
          new vscode.Position(6, 12)
        );
        expect(actualRange).toEqual(expectRange);
      });

      describe("WHEN rename is executed", () => {
        let executeOut: { changed: NoteChangeEntry[] } | undefined;
        before(async () => {
          provider.targetNote = targetNote;
          executeOut = await provider.executeRename({ newName: "new-target" });
        });
        test("THEN correctly renamed at symbol position", async () => {
          const { vaults, engine, wsRoot } = getDWorkspace();
          const { notes } = engine;
          const active = NoteUtils.getNoteByFnameV5({
            fname: "active",
            vault: vaults[0],
            notes,
            wsRoot,
          });
          expect(active?.tags).toEqual("new-target");
        });

        test("AND target note is correctly renamed", (done) => {
          const { vaults, engine, wsRoot } = getDWorkspace();
          const { notes } = engine;
          const newTarget = NoteUtils.getNoteByFnameV5({
            fname: "tags.new-target",
            vault: vaults[0],
            notes,
            wsRoot,
          });
          expect(newTarget).toBeTruthy();
          done();
        });
        test("THEN references to target note is correctly updated", (done) => {
          expect(executeOut?.changed.length).toEqual(8);
          const { vaults, engine, wsRoot } = getDWorkspace();
          const { notes } = engine;
          const noteWithLink = NoteUtils.getNoteByFnameV5({
            fname: "note-with-link",
            vault: vaults[0],
            notes,
            wsRoot,
          });

          expect(noteWithLink?.tags).toEqual("new-target");

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

  describeMultiWS(
    "GIVEN usertag",
    {
      preSetupHook: async (opts) => {
        const { wsRoot, vaults } = opts;
        activeNote = await NoteTestUtilsV4.createNote({
          fname: "active",
          vault: vaults[0],
          wsRoot,
          body: [
            "@target", // line 7, char 2 ~ 8
          ].join("\n"),
        });
        targetNote = await NoteTestUtilsV4.createNote({
          fname: "user.target",
          vault: vaults[0],
          wsRoot,
          body: ["# Foo"].join("\n"),
        });
        await NoteTestUtilsV4.createNote({
          fname: "note-with-link",
          vault: vaults[0],
          wsRoot,
          body: ["@target"].join("\n"),
        });
      },
    },
    () => {
      beforeEach(async () => {
        editor = await WSUtils.openNote(activeNote);
        provider = new RenameProvider();
      });
      test("THEN range is properly provided", async () => {
        const position = new vscode.Position(7, 0);
        const actualRange = await provider.prepareRename(
          editor.document,
          position
        );

        const expectRange = new vscode.Range(
          new vscode.Position(7, 1),
          new vscode.Position(7, 7)
        );
        expect(actualRange).toEqual(expectRange);
      });

      describe("WHEN rename is executed", () => {
        let executeOut: { changed: NoteChangeEntry[] } | undefined;
        before(async () => {
          provider.targetNote = targetNote;
          executeOut = await provider.executeRename({ newName: "new-target" });
        });
        test("THEN correctly renamed at symbol position", async () => {
          const { vaults, engine, wsRoot } = getDWorkspace();
          const { notes } = engine;
          const active = NoteUtils.getNoteByFnameV5({
            fname: "active",
            vault: vaults[0],
            notes,
            wsRoot,
          });
          const expectedBody = "@new-target\n";
          expect(active?.body).toEqual(expectedBody);
        });

        test("AND target note is correctly renamed", (done) => {
          const { vaults, engine, wsRoot } = getDWorkspace();
          const { notes } = engine;
          const newTarget = NoteUtils.getNoteByFnameV5({
            fname: "user.new-target",
            vault: vaults[0],
            notes,
            wsRoot,
          });
          expect(newTarget).toBeTruthy();
          done();
        });
        test("THEN references to target note is correctly updated", (done) => {
          expect(executeOut?.changed.length).toEqual(8);
          const { vaults, engine, wsRoot } = getDWorkspace();
          const { notes } = engine;
          const noteWithLink = NoteUtils.getNoteByFnameV5({
            fname: "note-with-link",
            vault: vaults[0],
            notes,
            wsRoot,
          });

          expect(noteWithLink?.body).toEqual("@new-target\n");
          done();
        });
      });
    }
  );
});
Example #13
Source File: queryStringTransformer.test.ts    From dendron with GNU Affero General Public License v3.0 4 votes vote down vote up
suite("transformQueryString tests:", () => {
  describe(`WHEN given simple string with slashes`, () => {
    let transformed: TransformedQueryString;

    beforeEach(() => {
      transformed = transformQueryString({
        pickerValue: "some/string/value",
      });
    });

    it(`THEN convert to query string to spaces`, () => {
      expect(transformed.queryString).toEqual("some string value");
    });

    it(`THEN split by dots is populates with values`, () => {
      expect(transformed.splitByDots).toEqual(["some", "string", "value"]);
    });

    it(`THEN wasMadeFromWikiLink is false`, () => {
      expect(transformed.wasMadeFromWikiLink).toEqual(false);
    });
  });

  describe(`WHEN given simple string with dots`, () => {
    let transformed: TransformedQueryString;

    beforeEach(() => {
      transformed = transformQueryString({
        pickerValue: "some.string.value",
      });
    });

    it(`THEN dots are transformed to spaces`, () => {
      expect(transformed.queryString).toEqual("some string value");
    });

    it(`THEN split by dots is populates with values`, () => {
      expect(transformed.splitByDots).toEqual(["some", "string", "value"]);
    });

    it(`THEN wasMadeFromWikiLink is false`, () => {
      expect(transformed.wasMadeFromWikiLink).toEqual(false);
    });
  });

  describe(`WHEN onlyDirectChildren is set`, () => {
    [
      ["dev.vs.", "^dev.vs."],
      ["^dev.vs.", "^dev.vs."],
    ].forEach((arr) => {
      it(`WHEN input='${arr[0]}' THEN output is '${arr[1]}'`, () => {
        const transformed = transformQueryString({
          pickerValue: arr[0],
          onlyDirectChildren: true,
        });

        expect(transformed.queryString).toEqual(arr[1]);
      });
    });
  });

  describe(`WHEN given string with dot that ends with a dot`, () => {
    let transformed: TransformedQueryString;

    beforeEach(() => {
      transformed = transformQueryString({
        pickerValue: "some.string.value.",
      });
    });

    it(`THEN do NOT split by dots`, () => {
      expect(transformed.queryString).toEqual("some.string.value.");
    });

    it(`THEN split by dots is not populated`, () => {
      expect(transformed.splitByDots).toBeFalsy();
    });

    it(`THEN wasMadeFromWikiLink is false`, () => {
      expect(transformed.wasMadeFromWikiLink).toEqual(false);
    });
  });

  describe(`WHEN given string with dots and separate search tokens`, () => {
    let transformed: TransformedQueryString;

    beforeEach(() => {
      transformed = transformQueryString({
        pickerValue: "some.string.value t1 t2 c1.c2",
      });
    });

    it(`THEN dots of initial string are transformed to spaces`, () => {
      expect(transformed.queryString).toEqual("some string value t1 t2 c1.c2");
    });

    it(`THEN split by dots is populates with values from dotted string`, () => {
      expect(transformed.splitByDots).toEqual(["some", "string", "value"]);
    });

    it(`THEN wasMadeFromWikiLink is false`, () => {
      expect(transformed.wasMadeFromWikiLink).toEqual(false);
    });
  });

  describe(`WHEN given simple string with space inside and on the side`, () => {
    let transformed: TransformedQueryString;

    beforeEach(() => {
      transformed = transformQueryString({
        pickerValue: " some string.value  ",
      });
    });

    it(`THEN trim the side spaces keep the inside space`, () => {
      expect(transformed.queryString).toEqual("some string.value");
    });

    it(`THEN wasMadeFromWikiLink is false`, () => {
      expect(transformed.wasMadeFromWikiLink).toEqual(false);
    });
  });

  describe(`WHEN given string with OR operator`, () => {
    let transformed: TransformedQueryString;

    beforeEach(() => {
      transformed = transformQueryString({ pickerValue: "v1 | v2" });
    });

    it(`THEN value stays as is`, () => {
      expect(transformed.queryString).toEqual("v1 | v2");
    });

    it(`THEN wasMadeFromWikiLink is false`, () => {
      expect(transformed.wasMadeFromWikiLink).toEqual(false);
    });
  });

  describe(`WHEN given string with wiki link without description`, () => {
    let transformed: TransformedQueryString;

    beforeEach(() => {
      transformed = transformQueryString({
        pickerValue: "[[some.string.value]]",
      });
    });

    it(`THEN strip out wiki link decoration`, () => {
      expect(transformed.queryString).toEqual("some.string.value");
    });

    it(`THEN wasMadeFromWikiLink is true`, () => {
      expect(transformed.wasMadeFromWikiLink).toEqual(true);
    });
  });

  describe(`WHEN given string with wiki link with side spaces`, () => {
    let transformed: TransformedQueryString;

    beforeEach(() => {
      transformed = transformQueryString({
        pickerValue: "  [[some.string.value]]   ",
      });
    });

    it(`THEN strip out spaces and wiki link decoration`, () => {
      expect(transformed.queryString).toEqual("some.string.value");
    });

    it(`THEN wasMadeFromWikiLink is true`, () => {
      expect(transformed.wasMadeFromWikiLink).toEqual(true);
    });
  });

  describe(`WHEN given string with wiki link with description`, () => {
    let transformed: TransformedQueryString;

    beforeEach(() => {
      transformed = transformQueryString({
        pickerValue: "[[some description|some.string.value]]",
      });
    });

    it(`THEN strip out wiki link decoration and description`, () => {
      expect(transformed.queryString).toEqual("some.string.value");
    });

    it(`THEN wasMadeFromWikiLink is true`, () => {
      expect(transformed.wasMadeFromWikiLink).toEqual(true);
    });
  });

  describe(`WHEN given string fully qualified with wiki link with description`, () => {
    let transformed: TransformedQueryString;

    beforeEach(() => {
      transformed = transformQueryString({
        pickerValue: "[[some description|dendron://private/some.string.value]]",
      });
    });

    it(`THEN strip out wiki link decoration and description`, () => {
      expect(transformed.queryString).toEqual("some.string.value");
    });

    it(`THEN wasMadeFromWikiLink is true`, () => {
      expect(transformed.wasMadeFromWikiLink).toEqual(true);
    });
  });

  describe(`WHEN given string fully qualified with wiki link with description and header`, () => {
    let transformed: TransformedQueryString;

    beforeEach(() => {
      transformed = transformQueryString({
        pickerValue:
          "[[some description|dendron://private.vault/some.string.value#header-val]]",
      });
    });

    // For now since we don't index the headers they need to be stripped out to be able
    // to query for the note.
    it(`THEN strip out wiki link decoration and description and header`, () => {
      expect(transformed.queryString).toEqual("some.string.value");
    });

    it(`THEN wasMadeFromWikiLink is true`, () => {
      expect(transformed.wasMadeFromWikiLink).toEqual(true);
    });

    it(`THEN vaultName is extracted`, () => {
      expect(transformed.vaultName).toEqual("private.vault");
    });
  });
});
Example #14
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]);
      });
    }
  );
});
Example #15
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 #16
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 #17
Source File: CopyNoteLink.test.ts    From dendron with GNU Affero General Public License v3.0 4 votes vote down vote up
suite("CopyNoteLink", function () {
  // these tests can run long, set timeout to 5s
  this.timeout(5e5);
  let copyNoteLinkCommand: CopyNoteLinkCommand;
  beforeEach(() => {
    copyNoteLinkCommand = new CopyNoteLinkCommand(
      ExtensionProvider.getExtension()
    );
  });

  describeSingleWS(
    "GIVEN a basic setup on a single vault workspace",
    {
      postSetupHook: ENGINE_HOOKS.setupBasic,
    },
    () => {
      test("WHEN the editor is on a saved file, THEN CopyNoteLink should return link with title and fname of engine note", async () => {
        const { wsRoot, vaults } = ExtensionProvider.getDWorkspace();
        const notePath = path.join(
          vault2Path({ vault: vaults[0], wsRoot }),
          "foo.md"
        );
        await VSCodeUtils.openFileInEditor(vscode.Uri.file(notePath));
        const link = (await copyNoteLinkCommand.run())?.link;
        expect(link).toEqual("[[Foo|foo]]");
      });

      test("WHEN the editor is on a dirty file, THEN CopyNoteLink should return undefined and cause an onDidSaveTextDocument to be fired", (done) => {
        const engine = ExtensionProvider.getEngine();
        const testNote = engine.notes["foo"];
        // 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("id: barbar")
              ).toBeTruthy();
              disposable.dispose();
            }, done);
          }
        );

        ExtensionProvider.getWSUtils()
          .openNote(testNote)
          .then(async (editor) => {
            editor
              .edit(async (editBuilder) => {
                // Replace id of frontmatter
                const startPos = new vscode.Position(1, 4);
                const endPos = new vscode.Position(1, 7);

                editBuilder.replace(
                  new vscode.Range(startPos, endPos),
                  "barbar"
                );
              })
              .then(async () => {
                copyNoteLinkCommand.run();
              });
          });
      });

      test("WHEN the editor is selecting a header, THEN CopyNoteLink should return a link with that header", async () => {
        const { engine, wsRoot, vaults } = ExtensionProvider.getDWorkspace();
        const noteWithLink = await NoteTestUtilsV4.createNoteWithEngine({
          fname: "testHeader",
          vault: vaults[0],
          wsRoot,
          body: "## [[Foo Bar|foo.bar]]",
          engine,
        });
        // Open and select the header
        const editor = await openNote(noteWithLink);
        const start = LocationTestUtils.getPresetWikiLinkPosition();
        const end = LocationTestUtils.getPresetWikiLinkPosition({ char: 10 });
        editor.selection = new vscode.Selection(start, end);
        // generate a wikilink for it
        const link = (await copyNoteLinkCommand.run())?.link;
        expect(link).toEqual(`[[Foo Bar|${noteWithLink.fname}#foo-bar]]`);
      });

      test("WHEN the editor is selecting a header with unicode characters, THEN CopyNoteLink should return a link with that header", async () => {
        const { engine, wsRoot, vaults } = ExtensionProvider.getDWorkspace();
        const noteWithLink = await NoteTestUtilsV4.createNoteWithEngine({
          fname: "testUnicode",
          vault: vaults[0],
          wsRoot,
          body: "## Lörem [[Foo:Bar?Baz|foo:bar?baz]] Ipsum",
          engine,
        });
        // Open and select the header
        const editor = await openNote(noteWithLink);
        const start = LocationTestUtils.getPresetWikiLinkPosition();
        const end = LocationTestUtils.getPresetWikiLinkPosition({ char: 10 });
        editor.selection = new vscode.Selection(start, end);
        // generate a wikilink for it
        const link = (await copyNoteLinkCommand.run())?.link;
        expect(link).toEqual(
          `[[Lörem Foo:Bar?Baz Ipsum|testUnicode#lörem-foobarbaz-ipsum]]`
        );
      });

      test("WHEN the editor is selecting an anchor, THEN CopyNoteLink should return a link with that anchor", async () => {
        const { engine, wsRoot, vaults } = ExtensionProvider.getDWorkspace();
        const noteWithTarget =
          await NOTE_PRESETS_V4.NOTE_WITH_ANCHOR_TARGET.createWithEngine({
            wsRoot,
            vault: vaults[0],
            engine,
          });
        await NOTE_PRESETS_V4.NOTE_WITH_ANCHOR_LINK.createWithEngine({
          wsRoot,
          vault: vaults[0],
          engine,
        });

        const editor = await openNote(noteWithTarget);
        const pos = LocationTestUtils.getPresetWikiLinkPosition();
        const pos2 = LocationTestUtils.getPresetWikiLinkPosition({
          char: 12,
        });
        editor.selection = new vscode.Selection(pos, pos2);
        const link = (await copyNoteLinkCommand.run())?.link;
        expect(link).toEqual(`[[H1|${noteWithTarget.fname}#h1]]`);
        editor.selection = new vscode.Selection(
          LocationTestUtils.getPresetWikiLinkPosition({ line: 8 }),
          LocationTestUtils.getPresetWikiLinkPosition({ line: 8, char: 12 })
        );
        const link2 = (await copyNoteLinkCommand.run())?.link;
        expect(link2).toEqual(`[[H2|${noteWithTarget.fname}#h2]]`);
      });

      test("WHEN the editor is selecting a block anchor, THEN CopyNoteLink should return a link with that block anchor", async () => {
        const { engine, wsRoot, vaults } = ExtensionProvider.getDWorkspace();
        const note =
          await NOTE_PRESETS_V4.NOTE_WITH_BLOCK_ANCHOR_TARGET.createWithEngine({
            wsRoot,
            vault: vaults[0],
            engine,
          });

        const editor = await openNote(note);
        editor.selection = new vscode.Selection(
          LocationTestUtils.getPresetWikiLinkPosition({ line: 10 }),
          LocationTestUtils.getPresetWikiLinkPosition({ line: 10, char: 10 })
        );
        const link = (await copyNoteLinkCommand.execute({}))?.link;
        const body = editor.document.getText();

        // check that the link looks like what we expect
        expect(link).toEqual("[[Anchor Target|anchor-target#^block-id]]");

        // should not have inserted any more anchors into the note
        AssertUtils.assertTimesInString({
          body,
          match: [[1, "^"]],
        });
      });

      test("WHEN the editor is selecting a footnote, THEN CopyNoteLink should not confuse it for a block anchor", async () => {
        const { engine, wsRoot, vaults } = ExtensionProvider.getDWorkspace();
        const note = await NoteTestUtilsV4.createNoteWithEngine({
          fname: "testFootnote",
          vault: vaults[0],
          wsRoot,
          body: "Sapiente accusantium omnis quia. [^est]\n\n[^est]: Quia iure tempore eum.",
          engine,
        });

        const editor = await openNote(note);
        editor.selection = new vscode.Selection(
          LocationTestUtils.getPresetWikiLinkPosition(),
          LocationTestUtils.getPresetWikiLinkPosition({ char: 10 })
        );
        const link = (await copyNoteLinkCommand.execute({}))?.link;
        const body = editor.document.getText();

        // check that the link looks like what we expect
        expect(link).toNotEqual("[[testFootnote|testFootnote#^est]]");
        const anchor = getAnchorFromLink(link!);

        // check that the anchor has been inserted into the note
        await AssertUtils.assertTimesInString({
          body,
          match: [[1, anchor]],
        });
      });

      test("WHEN the note has a block anchor target, THEN CopyNoteLink should generate block anchor", async () => {
        const { engine, wsRoot, vaults } = ExtensionProvider.getDWorkspace();
        const note =
          await NOTE_PRESETS_V4.NOTE_WITH_BLOCK_ANCHOR_TARGET.createWithEngine({
            wsRoot,
            vault: vaults[0],
            engine,
            fname: "generateBlockAnchorSingle",
          });

        const editor = await openNote(note);
        editor.selection = new vscode.Selection(
          LocationTestUtils.getPresetWikiLinkPosition({ line: 8 }),
          LocationTestUtils.getPresetWikiLinkPosition({ line: 12, char: 12 })
        );
        const link = (await copyNoteLinkCommand.execute({}))?.link;
        const body = editor.document.getText();

        // check that the link looks like what we expect
        const anchor = getAnchorFromLink(link!);

        // check that the anchor has been inserted into the note
        await AssertUtils.assertTimesInString({
          body,
          match: [[1, anchor]],
        });
      });

      test("WHEN the editor is selecting a header of a tag note, THEN CopyNoteLink should return a link with that header", async () => {
        const { engine, wsRoot, vaults } = ExtensionProvider.getDWorkspace();
        const noteWithLink = await NoteTestUtilsV4.createNoteWithEngine({
          fname: "tags.foo.bar",
          vault: vaults[0],
          wsRoot,
          body: "## [[Foo Bar|foo.bar]]",
          engine,
        });

        const editor = await openNote(noteWithLink);
        const start = LocationTestUtils.getPresetWikiLinkPosition();
        const end = LocationTestUtils.getPresetWikiLinkPosition({ char: 10 });
        editor.selection = new vscode.Selection(start, end);
        // generate a wikilink for it
        const link = (await copyNoteLinkCommand.run())?.link;
        expect(link).toEqual(`#foo.bar`);
      });
    }
  );

  describeMultiWS(
    "GIVEN a basic setup on a multivault workspace with enableXVaultWikiLink enabled",
    {
      modConfigCb: (config) => {
        ConfigUtils.setWorkspaceProp(config, "enableXVaultWikiLink", true);
        return config;
      },
      postSetupHook: ENGINE_HOOKS.setupBasic,
    },
    () => {
      test("WHEN the editor is on a saved file, THEN CopyNoteLink should return link with title and fname of engine note", async () => {
        const { wsRoot, vaults } = ExtensionProvider.getDWorkspace();
        const notePath = path.join(
          vault2Path({ vault: vaults[0], wsRoot }),
          "foo.md"
        );
        await VSCodeUtils.openFileInEditor(vscode.Uri.file(notePath));
        const link = (await copyNoteLinkCommand.run())?.link;
        expect(link).toEqual("[[Foo|dendron://vault1/foo]]");
      });

      test("WHEN the editor is on a dirty file, THEN CopyNoteLink should return undefined and cause an onDidSaveTextDocument to be fired", (done) => {
        const engine = ExtensionProvider.getEngine();
        const testNote = engine.notes["foo"];
        // 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("id: barbar")
              ).toBeTruthy();
              disposable.dispose();
            }, done);
          }
        );

        ExtensionProvider.getWSUtils()
          .openNote(testNote)
          .then(async (editor) => {
            editor
              .edit(async (editBuilder) => {
                // Replace id of frontmatter
                const startPos = new vscode.Position(1, 4);
                const endPos = new vscode.Position(1, 7);

                editBuilder.replace(
                  new vscode.Range(startPos, endPos),
                  "barbar"
                );
              })
              .then(async () => {
                copyNoteLinkCommand.run();
              });
          });
      });

      test("WHEN the editor is selecting an anchor, THEN CopyNoteLink should return a link with that anchor", async () => {
        const { engine, wsRoot, vaults } = ExtensionProvider.getDWorkspace();
        const noteWithTarget =
          await NOTE_PRESETS_V4.NOTE_WITH_ANCHOR_TARGET.createWithEngine({
            wsRoot,
            vault: vaults[0],
            engine,
          });
        const noteWithAnchor =
          await NOTE_PRESETS_V4.NOTE_WITH_ANCHOR_LINK.createWithEngine({
            wsRoot,
            vault: vaults[1],
            engine,
          });

        const editor = await openNote(noteWithTarget);
        const pos = LocationTestUtils.getPresetWikiLinkPosition();
        const pos2 = LocationTestUtils.getPresetWikiLinkPosition({
          char: 12,
        });
        editor.selection = new vscode.Selection(pos, pos2);
        const link = (await copyNoteLinkCommand.run())?.link;
        expect(link).toEqual(
          `[[H1|dendron://vault1/${noteWithTarget.fname}#h1]]`
        );
        editor.selection = new vscode.Selection(
          LocationTestUtils.getPresetWikiLinkPosition({ line: 8 }),
          LocationTestUtils.getPresetWikiLinkPosition({ line: 8, char: 12 })
        );
        const link2 = (await copyNoteLinkCommand.run())?.link;
        expect(link2).toEqual(
          `[[H2|dendron://vault1/${noteWithTarget.fname}#h2]]`
        );

        await openNote(noteWithAnchor);
        const link3 = (await copyNoteLinkCommand.run())?.link;
        expect(link3).toEqual(
          `[[Beta|dendron://vault2/${noteWithAnchor.fname}]]`
        );
      });

      test("WHEN the editor is selecting a block anchor, THEN CopyNoteLink should return a link with that block anchor", async () => {
        const { engine, wsRoot, vaults } = ExtensionProvider.getDWorkspace();
        const note =
          await NOTE_PRESETS_V4.NOTE_WITH_BLOCK_ANCHOR_TARGET.createWithEngine({
            wsRoot,
            vault: vaults[0],
            engine,
          });

        const editor = await openNote(note);
        editor.selection = new vscode.Selection(
          LocationTestUtils.getPresetWikiLinkPosition({ line: 10 }),
          LocationTestUtils.getPresetWikiLinkPosition({ line: 10, char: 10 })
        );
        const link = (await copyNoteLinkCommand.execute({}))?.link;
        const body = editor.document.getText();

        // check that the link looks like what we expect
        expect(link).toEqual(
          "[[Anchor Target|dendron://vault1/anchor-target#^block-id]]"
        );

        // should not have inserted any more anchors into the note
        AssertUtils.assertTimesInString({
          body,
          match: [[1, "^"]],
        });
      });

      test("WHEN the note has a block anchor target, THEN CopyNoteLink should generate block anchor", async () => {
        const { engine, wsRoot, vaults } = ExtensionProvider.getDWorkspace();
        const note =
          await NOTE_PRESETS_V4.NOTE_WITH_BLOCK_ANCHOR_TARGET.createWithEngine({
            wsRoot,
            vault: vaults[0],
            engine,
            fname: "generateBlockAnchorMulti",
          });

        const editor = await openNote(note);
        editor.selection = new vscode.Selection(
          LocationTestUtils.getPresetWikiLinkPosition({ line: 10 }),
          LocationTestUtils.getPresetWikiLinkPosition({ line: 10, char: 12 })
        );
        const link = (await copyNoteLinkCommand.execute({}))?.link;
        const body = editor.document.getText();

        // check that the link looks like what we expect
        const anchor = getAnchorFromLink(link!);
        expect(
          link!.startsWith(`[[${note.fname}|dendron://vault1/${note.fname}#^`)
        ).toBeTruthy();

        // check that the anchor has been inserted into the note
        AssertUtils.assertTimesInString({
          body,
          match: [[1, anchor]],
        });
      });
    }
  );

  describeSingleWS("WHEN in a non-note file", {}, () => {
    test("THEN creates a link to that file", async () => {
      const { wsRoot } = ExtensionProvider.getDWorkspace();
      const fsPath = path.join(wsRoot, "test.js");
      await fs.writeFile(
        fsPath,
        "const x = 'Pariatur officiis voluptatem molestiae.'"
      );
      await VSCodeUtils.openFileInEditor(vscode.Uri.file(fsPath));
      const link = (await copyNoteLinkCommand.run())?.link;
      expect(link).toEqual("[[test.js]]");
    });

    describe("AND the file name starts with a dot", async () => {
      test("THEN creates a link to that file", async () => {
        const { wsRoot } = ExtensionProvider.getDWorkspace();
        const fsPath = path.join(wsRoot, ".config.yaml");
        await fs.writeFile(fsPath, "x: 1");
        await VSCodeUtils.openFileInEditor(vscode.Uri.file(fsPath));
        const link = (await copyNoteLinkCommand.run())?.link;
        expect(link).toEqual("[[.config.yaml]]");
      });
    });

    describe("AND the file is in assets", () => {
      test("THEN creates a link using assets", async () => {
        const { wsRoot, vaults } = ExtensionProvider.getDWorkspace();
        const dirPath = path.join(
          wsRoot,
          VaultUtils.getRelPath(vaults[0]),
          "assets"
        );
        await fs.ensureDir(dirPath);
        const fsPath = path.join(dirPath, "test.py");
        await fs.writeFile(
          fsPath,
          "x = 'Pariatur officiis voluptatem molestiae.'"
        );
        await VSCodeUtils.openFileInEditor(vscode.Uri.file(fsPath));
        const link = (await copyNoteLinkCommand.run())?.link;
        expect(link).toEqual(path.join("[[assets", "test.py]]"));
      });
    });

    describe("AND the file is in a vault, but not in assets", () => {
      test("THEN creates a link from root", async () => {
        const { wsRoot, vaults } = ExtensionProvider.getDWorkspace();
        const vaultPath = VaultUtils.getRelPath(vaults[0]);
        const fsPath = path.join(path.join(wsRoot, vaultPath), "test.rs");
        await fs.writeFile(fsPath, "let x = 123;");
        await VSCodeUtils.openFileInEditor(vscode.Uri.file(fsPath));
        const link = (await copyNoteLinkCommand.run())?.link;
        expect(link).toEqual(path.join(`[[${vaultPath}`, "test.rs]]"));
      });
    });

    describe("AND the file is in a nested folder", () => {
      test("THEN creates a link to that file", async () => {
        const { wsRoot } = ExtensionProvider.getDWorkspace();
        const dirPath = path.join(wsRoot, "src", "clj");
        await fs.ensureDir(dirPath);
        const fsPath = path.join(dirPath, "test.clj");
        await fs.writeFile(fsPath, "(set! x 1)");
        await VSCodeUtils.openFileInEditor(vscode.Uri.file(fsPath));
        const link = (await copyNoteLinkCommand.run())?.link;
        expect(link).toEqual(path.join("[[src", "clj", "test.clj]]"));
      });
    });
  });

  describe("WHEN using selections in non-note files", () => {
    describeSingleWS(
      "AND there's an existing block anchor",
      {
        modConfigCb: (config) => {
          ConfigUtils.setNonNoteLinkAnchorType(config, "block");
          return config;
        },
      },
      () => {
        test("THEN creates a link to that file with a block anchor", async () => {
          await prepFileAndSelection(" ^my-block-anchor");
          const link = (await copyNoteLinkCommand.run())?.link;
          expect(
            await linkHasAnchor(
              "block",
              ["src", "test.hs"],
              link,
              "^my-block-anchor"
            )
          ).toBeTruthy();
        });
      }
    );

    describeSingleWS(
      "AND config is set to line anchor",
      {
        modConfigCb: (config) => {
          ConfigUtils.setNonNoteLinkAnchorType(config, "line");
          return config;
        },
      },
      () => {
        test("THEN creates a link to that file with a line anchor", async () => {
          await prepFileAndSelection();
          const link = (await copyNoteLinkCommand.run())?.link;
          // Link should contain an anchor
          expect(
            await linkHasAnchor("line", ["src", "test.hs"], link)
          ).toBeTruthy();
        });
      }
    );

    describeSingleWS(
      "AND config is set to block anchor",
      {
        modConfigCb: (config) => {
          ConfigUtils.setNonNoteLinkAnchorType(config, "block");
          return config;
        },
      },
      () => {
        test("THEN creates a link to that file with a block anchor", async () => {
          await prepFileAndSelection();
          const link = (await copyNoteLinkCommand.run())?.link;
          expect(
            await linkHasAnchor("block", ["src", "test.hs"], link)
          ).toBeTruthy();
        });
      }
    );

    describeSingleWS("AND config is set unset", {}, () => {
      test("THEN creates a link to that file with a block anchor", async () => {
        await prepFileAndSelection();
        const link = (await copyNoteLinkCommand.run())?.link;
        expect(
          await linkHasAnchor("block", ["src", "test.hs"], link)
        ).toBeTruthy();
      });
    });

    describeSingleWS(
      "GIVEN a workspace where config is set to prompt",
      {
        modConfigCb: (config) => {
          ConfigUtils.setNonNoteLinkAnchorType(config, "prompt");
          return config;
        },
      },
      () => {
        test("WHEN user picks line in the prompt, THEN CopyNoteLinkCommand generates a link anchor ", async () => {
          await prepFileAndSelection();
          const pick = sinon
            .stub(vscode.window, "showQuickPick")
            .resolves({ label: "line" });
          const link = (await copyNoteLinkCommand.run())?.link;
          expect(pick.calledOnce).toBeTruthy();
          expect(
            await linkHasAnchor("line", ["src", "test.hs"], link)
          ).toBeTruthy();
        });
      }
    );

    describeSingleWS(
      "GIVEN a workspace where config is set to prompt",
      {
        modConfigCb: (config) => {
          ConfigUtils.setNonNoteLinkAnchorType(config, "prompt");
          return config;
        },
      },
      () => {
        test("WHEN user picks block in the prompt, THEN CopyNoteLinkCommand generates a block anchor ", async () => {
          await prepFileAndSelection();
          const pick = sinon
            .stub(vscode.window, "showQuickPick")
            .resolves({ label: "block" });
          const link = (await copyNoteLinkCommand.run())?.link;
          expect(pick.calledOnce).toBeTruthy();
          expect(
            await linkHasAnchor("block", ["src", "test.hs"], link)
          ).toBeTruthy();
        });
      }
    );

    describeSingleWS(
      "GIVEN a workspace where config is set to prompt",
      {
        modConfigCb: (config) => {
          ConfigUtils.setNonNoteLinkAnchorType(config, "prompt");
          return config;
        },
      },
      () => {
        test("WHEN user cancels the prompt, THEN CopyNoteLinkCommand generates a line anchor ", async () => {
          await prepFileAndSelection();
          const pick = sinon
            .stub(vscode.window, "showQuickPick")
            .resolves(undefined);
          const link = (await copyNoteLinkCommand.run())?.link;
          expect(pick.calledOnce).toBeTruthy();
          expect(
            await linkHasAnchor("line", ["src", "test.hs"], link)
          ).toBeTruthy();
        });
      }
    );
  });
});
Example #18
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 #19
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 #20
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 #21
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 #22
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 #23
Source File: PreviewLinkHandler.test.ts    From dendron with GNU Affero General Public License v3.0 4 votes vote down vote up
suite("PreviewLinkHandler", () => {
  const ctx: vscode.ExtensionContext = setupBeforeAfter(this, {
    noSetTimeout: true,
    beforeHook: () => {},
  });

  let testNoteAmbiguous: NoteProps;
  describeMultiWS(
    "GIVEN onLinkClicked",
    {
      ctx,
      preSetupHook: async ({ vaults, wsRoot }) => {
        await NoteTestUtilsV4.createNote({
          fname: "target",
          vault: vaults[0],
          wsRoot,
          body: [
            "Qui dicta nulla at atque qui voluptatem.",
            "Harum qui quasi sint.",
            "",
            "## Nostrum",
            "",
            "Ut recusandae fuga recusandae nihil.",
            "Illum nostrum id animi. ^nihil",
          ].join("\n"),
          props: {
            id: "test-id",
          },
        });
        await NoteTestUtilsV4.createNote({
          fname: "lorem",
          vault: vaults[0],
          wsRoot,
          body: "Est saepe ut et accusamus soluta id",
          props: {
            id: "est",
          },
        });
        testNoteAmbiguous = await NoteTestUtilsV4.createNote({
          fname: "lorem",
          vault: vaults[1],
          wsRoot,
          body: "Reprehenderit dolores pariatur",
          props: {
            id: "reprehenderit",
          },
        });
      },
    },
    () => {
      let note: NoteProps;
      beforeEach(async () => {
        const { engine, vaults } = ExtensionProvider.getDWorkspace();
        note = NoteUtils.getNoteByFnameFromEngine({
          fname: "root",
          engine,
          vault: vaults[0],
        })!;
        expect(note).toBeTruthy();
        await ExtensionProvider.getWSUtils().openNote(note);
      });

      describe("WHEN clicking on an wikilink", () => {
        test("THEN the clicked note is opened", async () => {
          const handler = new PreviewLinkHandler(
            ExtensionProvider.getExtension()
          );
          const out = await handler.onLinkClicked({
            data: {
              href: "vscode-webview://76b3da02-f902-4652-b6a8-746551d032ce/test-id#nostrum",
              id: note.id,
            },
          });
          expect(out).toEqual(LinkType.WIKI);
          expect(
            VSCodeUtils.getActiveTextEditor()?.document.fileName.endsWith(
              "target.md"
            )
          ).toBeTruthy();
        });

        describe("AND the link is to a header", () => {
          test("THEN the note is opened at that header", async () => {
            const handler = new PreviewLinkHandler(
              ExtensionProvider.getExtension()
            );
            const out = await handler.onLinkClicked({
              data: {
                href: "vscode-webview://76b3da02-f902-4652-b6a8-746551d032ce/test-id#nostrum",
                id: note.id,
              },
            });
            expect(out).toEqual(LinkType.WIKI);
            expect(
              VSCodeUtils.getActiveTextEditor()?.document.fileName.endsWith(
                "target.md"
              )
            ).toBeTruthy();
            expect(
              VSCodeUtils.getActiveTextEditor()?.selection.active.line
            ).toEqual(10);
          });
        });

        describe("AND the link is to a block anchor", () => {
          test("THEN the note is opened at that block", async () => {
            const handler = new PreviewLinkHandler(
              ExtensionProvider.getExtension()
            );
            const out = await handler.onLinkClicked({
              data: {
                href: "vscode-webview://76b3da02-f902-4652-b6a8-746551d032ce/test-id#^nihil",
                id: note.id,
              },
            });
            expect(out).toEqual(LinkType.WIKI);
            expect(
              VSCodeUtils.getActiveTextEditor()?.document.fileName.endsWith(
                "target.md"
              )
            ).toBeTruthy();
            expect(
              VSCodeUtils.getActiveTextEditor()?.selection.active.line
            ).toEqual(13);
          });
        });

        describe("AND if the link is to a missing note", () => {
          test("THEN nothing happens", async () => {
            const handler = new PreviewLinkHandler(
              ExtensionProvider.getExtension()
            );
            const openWithDefaultApp = sinon.stub(
              ShowPreviewAssetOpener,
              "openWithDefaultApp"
            );
            const out = await handler.onLinkClicked({
              data: {
                href: "vscode-webview://76b3da02-f902-4652-b6a8-746551d032ce/does-not-exist",
                id: note.id,
              },
            });
            expect(out).toEqual(LinkType.UNKNOWN);
            expect(openWithDefaultApp.called).toBeFalsy();
          });
        });

        describe("AND the link is ambiguous", () => {
          test("THEN it prompts for a note", async () => {
            const showChooseNote = sinon
              .stub(QuickPickUtil, "showChooseNote")
              .returns(Promise.resolve(testNoteAmbiguous));
            const handler = new PreviewLinkHandler(
              ExtensionProvider.getExtension()
            );
            const out = await handler.onLinkClicked({
              data: {
                href: "vscode-webview://76b3da02-f902-4652-b6a8-746551d032ce/lorem",
                id: note.id,
              },
            });
            expect(out).toEqual(LinkType.WIKI);
            expect(
              VSCodeUtils.getActiveTextEditor()
                ?.document.getText()
                .includes("Reprehenderit dolores pariatur")
            ).toBeTruthy();
            expect(showChooseNote.called).toBeTruthy();
          });
        });
      });

      describe("WHEN clicking on a web URL", () => {
        test("THEN opening is left to VSCode", async () => {
          const openWithDefaultApp = sinon.stub(
            ShowPreviewAssetOpener,
            "openWithDefaultApp"
          );
          const handler = new PreviewLinkHandler(
            ExtensionProvider.getExtension()
          );
          const out = await handler.onLinkClicked({
            data: {
              href: "https://wiki.dendron.so/#getting-started",
              id: note.id,
            },
          });
          expect(out).toEqual(LinkType.WEBSITE);
          expect(openWithDefaultApp.called).toBeFalsy();
        });
      });

      describe("WHEN clicking on an asset inside a vault", () => {
        before(async () => {
          const { wsRoot, vaults } = ExtensionProvider.getDWorkspace();
          const assetsPath = path.join(
            wsRoot,
            VaultUtils.getRelPath(vaults[0]),
            "assets"
          );
          await fs.mkdir(assetsPath);
          await fs.writeFile(path.join(assetsPath, "test.pdf"), "");
        });

        test("THEN it is opened with the default app", async () => {
          const { wsRoot, vaults } = ExtensionProvider.getDWorkspace();
          const openWithDefaultApp = sinon.stub(
            ShowPreviewAssetOpener,
            "openWithDefaultApp"
          );
          const handler = new PreviewLinkHandler(
            ExtensionProvider.getExtension()
          );
          const out = await handler.onLinkClicked({
            data: {
              href: "vscode-webview://76b3da02-f902-4652-b6a8-746551d032ce/assets/test.pdf",
              id: note.id,
            },
          });
          expect(out).toEqual(LinkType.ASSET);
          expect(openWithDefaultApp.called).toBeTruthy();
          expect(
            openWithDefaultApp.calledWith(
              path.join(
                wsRoot,
                VaultUtils.getRelPath(vaults[0]),
                "assets",
                "test.pdf"
              )
            )
          ).toBeTruthy();
        });
      });

      describe("WHEN clicking on an asset with path relative to wsRoot", () => {
        before(async () => {
          const { wsRoot } = ExtensionProvider.getDWorkspace();
          await fs.writeFile(path.join(wsRoot, "test.pdf"), "");
        });

        test("THEN it is opened with the default app", async () => {
          const { wsRoot } = ExtensionProvider.getDWorkspace();
          const openWithDefaultApp = sinon.stub(
            ShowPreviewAssetOpener,
            "openWithDefaultApp"
          );
          const handler = new PreviewLinkHandler(
            ExtensionProvider.getExtension()
          );
          const out = await handler.onLinkClicked({
            data: {
              href: "vscode-webview://76b3da02-f902-4652-b6a8-746551d032ce/test.pdf",
              id: note.id,
            },
          });
          expect(out).toEqual(LinkType.ASSET);
          expect(openWithDefaultApp.called).toBeTruthy();
          expect(
            openWithDefaultApp.calledWith(path.join(wsRoot, "test.pdf"))
          ).toBeTruthy();
        });
      });

      describe("WHEN clicking on an asset with an absolute path", () => {
        let testDir: string;
        before(async () => {
          testDir = tmpDir().name;
          await fs.writeFile(path.join(testDir, "test.pdf"), "");
        });

        test("THEN it is opened with the default app", async () => {
          const openWithDefaultApp = sinon.stub(
            ShowPreviewAssetOpener,
            "openWithDefaultApp"
          );
          const handler = new PreviewLinkHandler(
            ExtensionProvider.getExtension()
          );
          const out = await handler.onLinkClicked({
            data: {
              href: `vscode-webview://76b3da02-f902-4652-b6a8-746551d032ce/${path.join(
                testDir,
                "test.pdf"
              )}`,
              id: note.id,
            },
          });
          expect(out).toEqual(LinkType.ASSET);
          expect(openWithDefaultApp.called).toBeTruthy();
          // Added the "toLowerCase"s here because on Windows link handler
          // gets called with C:\ while testDir is c:\
          expect(openWithDefaultApp.args[0][0].toLowerCase()).toEqual(
            path.join(testDir, "test.pdf").toLowerCase()
          );
        });
      });

      describe("WHEN opening a non-note text file", () => {
        before(async () => {
          const { wsRoot } = ExtensionProvider.getDWorkspace();
          await fs.writeFile(
            path.join(wsRoot, "test.py"),
            [
              "print('hello world!')",
              "print('hello from a test')",
              "print('hi!') # ^target",
              "print('hey!!!')",
            ].join("\n")
          );
        });

        test("THEN it is opened in the editor", async () => {
          const openWithDefaultApp = sinon.stub(
            ShowPreviewAssetOpener,
            "openWithDefaultApp"
          );
          const handler = new PreviewLinkHandler(
            ExtensionProvider.getExtension()
          );
          const out = await handler.onLinkClicked({
            data: {
              href: "vscode-webview://76b3da02-f902-4652-b6a8-746551d032ce/test.py",
              id: note.id,
            },
          });
          expect(out).toEqual(LinkType.TEXT);
          expect(openWithDefaultApp.called).toBeFalsy();
          expect(
            VSCodeUtils.getActiveTextEditor()?.document.fileName.endsWith(
              "test.py"
            )
          ).toBeTruthy();
        });

        describe("AND the file link is to a line", () => {
          test("THEN it is opened at that line", async () => {
            const openWithDefaultApp = sinon.stub(
              ShowPreviewAssetOpener,
              "openWithDefaultApp"
            );
            const handler = new PreviewLinkHandler(
              ExtensionProvider.getExtension()
            );
            const out = await handler.onLinkClicked({
              data: {
                href: "vscode-webview://76b3da02-f902-4652-b6a8-746551d032ce/test.py#L2",
                id: note.id,
              },
            });
            expect(out).toEqual(LinkType.TEXT);
            expect(openWithDefaultApp.called).toBeFalsy();
            expect(
              VSCodeUtils.getActiveTextEditor()?.document.fileName.endsWith(
                "test.py"
              )
            ).toBeTruthy();
            expect(
              VSCodeUtils.getActiveTextEditor()?.selection.start.line
            ).toEqual(1);
          });
        });

        describe("AND the file link is to an anchor", () => {
          test("THEN it is opened at that anchor", async () => {
            const openWithDefaultApp = sinon.stub(
              ShowPreviewAssetOpener,
              "openWithDefaultApp"
            );
            const handler = new PreviewLinkHandler(
              ExtensionProvider.getExtension()
            );
            const out = await handler.onLinkClicked({
              data: {
                href: "vscode-webview://76b3da02-f902-4652-b6a8-746551d032ce/test.py#^target",
                id: note.id,
              },
            });
            expect(out).toEqual(LinkType.TEXT);
            expect(openWithDefaultApp.called).toBeFalsy();
            expect(
              VSCodeUtils.getActiveTextEditor()?.document.fileName.endsWith(
                "test.py"
              )
            ).toBeTruthy();
            expect(
              VSCodeUtils.getActiveTextEditor()?.selection.start.line
            ).toEqual(2);
          });
        });
      });
    }
  );

  describe(`extractNoteIdFromHref`, () => {
    describe(`WHEN id is present`, () => {
      it("AND with header anchor THEN extract id", () => {
        const linkHandler = new PreviewLinkHandler(
          new MockDendronExtension({})
        );
        const actual = linkHandler.extractNoteIdFromHref({
          id: "id1",
          href: "vscode-webview://4e98b9cf-41d8-49eb-b458-fcfda32c6c01/FSi3bKWQeQXYTjE1PoTB0#heading-2",
        });

        expect(actual).toEqual("FSi3bKWQeQXYTjE1PoTB0");
      });

      it("AND without the header anchor THEN extract id", () => {
        const linkHandler = new PreviewLinkHandler(
          new MockDendronExtension({})
        );

        const actual = linkHandler.extractNoteIdFromHref({
          id: "id1",
          href: "vscode-webview://4e98b9cf-41d8-49eb-b458-fcfda32c6c01/FSi3bKWQeQXYTjE1PoTB0",
        });

        expect(actual).toEqual("FSi3bKWQeQXYTjE1PoTB0");
      });

      it("AND is guid like", () => {
        // This shouldnt typically happen with the way we currently generate ids but we do
        // have some guid like ids in our test workspace right now so to make those
        // notes happy, and in case some older id generation used guid looking identifers.

        const linkHandler = new PreviewLinkHandler(
          new MockDendronExtension({})
        );

        const actual = linkHandler.extractNoteIdFromHref({
          id: "id1",
          href: "vscode-webview://4e98b9cf-41d8-49eb-b458-fcfda32c6c01/56497553-c195-4ec8-bc74-6a76462d9333",
        });

        expect(actual).toEqual("56497553-c195-4ec8-bc74-6a76462d9333");
      });
    });

    it(`WHEN id not present in href THEN default onto passed in id`, () => {
      const linkHandler = new PreviewLinkHandler(new MockDendronExtension({}));

      const actual = linkHandler.extractNoteIdFromHref({
        id: "id1",
        href: "http://localhost:3005/vscode/note-preview.html?ws=WS-VALUE&port=3005#head2",
      });
      expect(actual).toEqual("id1");
    });
  });
});
Example #24
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 #25
Source File: NativeTreeView.test.ts    From dendron with GNU Affero General Public License v3.0 4 votes vote down vote up
suite("NativeTreeView tests", function () {
  this.timeout(2000);

  describe("Rename Note Command interactions", function () {
    describeMultiWS(
      "WHEN renaming note with top level hierarchy",
      {
        preSetupHook: async (opts) => {
          const { wsRoot, vaults } = opts;
          await NoteTestUtilsV4.createNote({
            wsRoot,
            vault: vaults[0],
            fname: "foo",
            genRandomId: true,
          });
        },
      },
      () => {
        test("THEN tree view correctly displays renamed note", async () => {
          const mockEvents = new MockEngineEvents();
          const provider = new EngineNoteProvider(mockEvents);

          const propsBefore = await (provider.getChildren() as Promise<
            NoteProps[]
          >);

          const vaultOneRootPropsBefore = propsBefore[0];

          const childrenBefore = await (provider.getChildren(
            vaultOneRootPropsBefore
          ) as Promise<NoteProps[]>);
          expect(childrenBefore.map((child) => child.fname)).toEqual(["foo"]);

          await runRenameNote({
            noteId: childrenBefore[0].id,
            newName: "fooz",
          });

          const childrenAfter = await (provider.getChildren(
            vaultOneRootPropsBefore
          ) as Promise<NoteProps[]>);
          expect(childrenAfter.map((child) => child.fname)).toEqual(["fooz"]);
        });
      }
    );

    describeMultiWS(
      "WHEN renaming note with stub parent",
      {
        preSetupHook: async (opts) => {
          const { wsRoot, vaults } = opts;
          await NoteTestUtilsV4.createNote({
            wsRoot,
            vault: vaults[0],
            fname: "foo.bar",
            genRandomId: true,
          });
        },
      },
      () => {
        test("THEN tree view correctly displays renamed note", async () => {
          const mockEvents = new MockEngineEvents();
          const provider = new EngineNoteProvider(mockEvents);

          const propsBefore = await (provider.getChildren() as Promise<
            NoteProps[]
          >);

          const vaultOneRootPropsBefore = propsBefore[0];
          const vaultOneRootId = propsBefore[0].id;

          const childrenBefore = await (provider.getChildren(
            vaultOneRootPropsBefore
          ) as Promise<NoteProps[]>);
          expect(childrenBefore.map((child) => child.fname)).toEqual(["foo"]);

          const grandChildrenBefore = await (provider.getChildren(
            childrenBefore[0]
          ) as Promise<NoteProps[]>);
          expect(grandChildrenBefore.map((gchild) => gchild.fname)).toEqual([
            "foo.bar",
          ]);

          await runRenameNote({
            noteId: grandChildrenBefore[0].id,
            newName: "foo.baz",
          });

          const engine = ExtensionProvider.getEngine();
          const vault1RootPropsAfter = engine.notes[vaultOneRootId];
          const childrenAfter = await (provider.getChildren(
            vault1RootPropsAfter
          ) as Promise<NoteProps[]>);
          expect(childrenAfter.map((child) => child.fname)).toEqual(["foo"]);

          const grandChildrenAfter = await (provider.getChildren(
            childrenAfter[0]
          ) as Promise<NoteProps[]>);
          expect(grandChildrenAfter.map((gchild) => gchild.fname)).toEqual([
            "foo.baz",
          ]);
        });
      }
    );

    describeMultiWS(
      "WHEN renaming a note to an existing stub that has a stub parent and any children",
      {
        preSetupHook: async (opts) => {
          const { wsRoot, vaults } = opts;
          await NoteTestUtilsV4.createNote({
            wsRoot,
            vault: vaults[0],
            fname: "foo.bar.baz",
            genRandomId: true,
          });
          await NoteTestUtilsV4.createNote({
            wsRoot,
            vault: vaults[0],
            fname: "dummy",
            body: "this is some dummy content",
          });
        },
      },
      () => {
        test("THEN tree view correctly displays renamed note", async () => {
          const mockEvents = new MockEngineEvents();
          const provider = new EngineNoteProvider(mockEvents);

          const propsBefore = await (provider.getChildren() as Promise<
            NoteProps[]
          >);

          const vaultOneRootPropsBefore = propsBefore[0];

          const fullTreeBefore = await getFullTree({
            root: vaultOneRootPropsBefore,
            provider,
            extra: { stub: true },
          });

          expect(fullTreeBefore).toEqual({
            fname: "root",
            childNodes: [
              {
                fname: "dummy",
                childNodes: [],
              },
              {
                fname: "foo",
                stub: true,
                childNodes: [
                  {
                    fname: "foo.bar",
                    stub: true,
                    childNodes: [
                      {
                        fname: "foo.bar.baz",
                        childNodes: [],
                      },
                    ],
                  },
                ],
              },
            ],
          });

          await runRenameNote({
            noteId: "dummy",
            newName: "foo.bar",
          });

          const propsAfter = await (provider.getChildren() as Promise<
            NoteProps[]
          >);
          const vaultOneRootPropsAfter = propsAfter[0];
          const fullTreeAfter = await getFullTree({
            root: vaultOneRootPropsAfter,
            provider,
            extra: { stub: true },
          });

          expect(fullTreeAfter).toEqual({
            fname: "root",
            childNodes: [
              {
                fname: "foo",
                stub: true,
                childNodes: [
                  {
                    fname: "foo.bar",
                    childNodes: [
                      {
                        fname: "foo.bar.baz",
                        childNodes: [],
                      },
                    ],
                  },
                ],
              },
            ],
          });
        });
      }
    );

    describeMultiWS(
      "WHEN renaming note with stub parent and any children",
      {
        preSetupHook: async (opts) => {
          const { wsRoot, vaults } = opts;
          await NoteTestUtilsV4.createNote({
            wsRoot,
            vault: vaults[0],
            fname: "foo.bar.baz",
            genRandomId: true,
          });
          await NoteTestUtilsV4.createNote({
            wsRoot,
            vault: vaults[0],
            fname: "foo.bar",
            body: "this is some dummy content",
          });
        },
      },
      () => {
        test("THEN tree view correctly displays renamed note", async () => {
          const mockEvents = new MockEngineEvents();
          const provider = new EngineNoteProvider(mockEvents);

          const propsBefore = await (provider.getChildren() as Promise<
            NoteProps[]
          >);

          const vaultOneRootPropsBefore = propsBefore[0];

          const fullTreeBefore = await getFullTree({
            root: vaultOneRootPropsBefore,
            provider,
            extra: { stub: true },
          });

          expect(fullTreeBefore).toEqual({
            fname: "root",
            childNodes: [
              {
                fname: "foo",
                stub: true,
                childNodes: [
                  {
                    fname: "foo.bar",
                    childNodes: [
                      {
                        fname: "foo.bar.baz",
                        childNodes: [],
                      },
                    ],
                  },
                ],
              },
            ],
          });

          await runRenameNote({
            noteId: "foo.bar",
            newName: "dummy",
          });

          const propsAfter = await (provider.getChildren() as Promise<
            NoteProps[]
          >);
          const vaultOneRootPropsAfter = propsAfter[0];
          const fullTreeAfter = await getFullTree({
            root: vaultOneRootPropsAfter,
            provider,
            extra: { stub: true },
          });

          expect(fullTreeAfter).toEqual({
            fname: "root",
            childNodes: [
              {
                fname: "dummy",
                childNodes: [],
              },
              {
                fname: "foo",
                stub: true,
                childNodes: [
                  {
                    fname: "foo.bar",
                    stub: true,
                    childNodes: [
                      {
                        fname: "foo.bar.baz",
                        childNodes: [],
                      },
                    ],
                  },
                ],
              },
            ],
          });
        });
      }
    );

    describeMultiWS(
      "WHEN renaming note with non-stub parent",
      {
        preSetupHook: async (opts) => {
          const { wsRoot, vaults } = opts;
          await NoteTestUtilsV4.createNote({
            wsRoot,
            vault: vaults[0],
            fname: "foo",
            genRandomId: true,
          });
          await NoteTestUtilsV4.createNote({
            wsRoot,
            vault: vaults[0],
            fname: "foo.bar",
            genRandomId: true,
          });
        },
      },
      () => {
        test("THEN tree view correctly displays renamed note", async () => {
          const mockEvents = new MockEngineEvents();
          const provider = new EngineNoteProvider(mockEvents);

          const propsBefore = await (provider.getChildren() as Promise<
            NoteProps[]
          >);

          const vaultOneRootPropsBefore = propsBefore[0];
          const vaultOneRootId = propsBefore[0].id;

          const childrenBefore = await (provider.getChildren(
            vaultOneRootPropsBefore
          ) as Promise<NoteProps[]>);
          expect(childrenBefore.map((child) => child.fname)).toEqual(["foo"]);

          const grandChildrenBefore = await (provider.getChildren(
            childrenBefore[0]
          ) as Promise<NoteProps[]>);
          expect(grandChildrenBefore.map((gchild) => gchild.fname)).toEqual([
            "foo.bar",
          ]);

          await runRenameNote({
            noteId: grandChildrenBefore[0].id,
            newName: "foo.baz",
          });

          const engine = ExtensionProvider.getEngine();
          const vault1RootPropsAfter = engine.notes[vaultOneRootId];
          const childrenAfter = await (provider.getChildren(
            vault1RootPropsAfter
          ) as Promise<NoteProps[]>);
          expect(childrenAfter.map((child) => child.fname)).toEqual(["foo"]);

          const grandChildrenAfter = await (provider.getChildren(
            childrenAfter[0]
          ) as Promise<NoteProps[]>);
          expect(grandChildrenAfter.map((gchild) => gchild.fname)).toEqual([
            "foo.baz",
          ]);
        });
      }
    );

    describeMultiWS(
      "WHEN renaming note with stub children",
      {
        preSetupHook: async (opts) => {
          const { wsRoot, vaults } = opts;
          await NoteTestUtilsV4.createNote({
            wsRoot,
            vault: vaults[0],
            fname: "foo",
            genRandomId: true,
          });
          await NoteTestUtilsV4.createNote({
            wsRoot,
            vault: vaults[0],
            fname: "foo.bar.egg",
            genRandomId: true,
          });
        },
      },
      () => {
        test("THEN tree view correctly displays renamed note", async () => {
          const mockEvents = new MockEngineEvents();
          const provider = new EngineNoteProvider(mockEvents);

          const propsBefore = await (provider.getChildren() as Promise<
            NoteProps[]
          >);

          const vaultOneRootPropsBefore = propsBefore[0];
          const vaultOneRootId = propsBefore[0].id;

          const childrenBefore = await (provider.getChildren(
            vaultOneRootPropsBefore
          ) as Promise<NoteProps[]>);
          expect(childrenBefore.map((child) => child.fname)).toEqual(["foo"]);

          const grandChildrenBefore = await (provider.getChildren(
            childrenBefore[0]
          ) as Promise<NoteProps[]>);
          expect(grandChildrenBefore.map((gchild) => gchild.fname)).toEqual([
            "foo.bar",
          ]);

          const greatGrandChildrenBefore = await (provider.getChildren(
            grandChildrenBefore[0]
          ) as Promise<NoteProps[]>);
          expect(
            greatGrandChildrenBefore.map((ggchild) => ggchild.fname)
          ).toEqual(["foo.bar.egg"]);

          const engine = ExtensionProvider.getEngine();
          await runRenameNote({
            noteId: childrenBefore[0].id,
            newName: "fooz",
          });

          const vault1RootPropsAfter = engine.notes[vaultOneRootId];
          const childrenAfter = await (provider.getChildren(
            vault1RootPropsAfter
          ) as Promise<NoteProps[]>);
          expect(
            childrenAfter.map((child) => {
              return { fname: child.fname, stub: child.stub };
            })
          ).toEqual([
            { fname: "foo", stub: true },
            { fname: "fooz", stub: undefined },
          ]);

          const grandChildrenAfter = await (provider.getChildren(
            childrenAfter[0]
          ) as Promise<NoteProps[]>);
          expect(grandChildrenAfter.map((gchild) => gchild.fname)).toEqual([
            "foo.bar",
          ]);

          const greatGrandChildrenAfter = await (provider.getChildren(
            grandChildrenAfter[0]
          ) as Promise<NoteProps[]>);
          expect(
            greatGrandChildrenAfter.map((ggchild) => ggchild.fname)
          ).toEqual(["foo.bar.egg"]);
        });
      }
    );

    describeMultiWS(
      "WHEN renaming note with non-stub children",
      {
        preSetupHook: async (opts) => {
          const { wsRoot, vaults } = opts;
          await NoteTestUtilsV4.createNote({
            wsRoot,
            vault: vaults[0],
            fname: "foo",
            genRandomId: true,
          });
          await NoteTestUtilsV4.createNote({
            wsRoot,
            vault: vaults[0],
            fname: "foo.bar",
            genRandomId: true,
          });
          await NoteTestUtilsV4.createNote({
            wsRoot,
            vault: vaults[0],
            fname: "foo.baz",
            genRandomId: true,
          });
        },
      },
      () => {
        test("THEN tree view correctly displays renamed note", async () => {
          const mockEvents = new MockEngineEvents();
          const provider = new EngineNoteProvider(mockEvents);

          const propsBefore = await (provider.getChildren() as Promise<
            NoteProps[]
          >);

          const vaultOneRootPropsBefore = propsBefore[0];
          const vaultOneRootId = propsBefore[0].id;

          const childrenBefore = await (provider.getChildren(
            vaultOneRootPropsBefore
          ) as Promise<NoteProps[]>);
          expect(childrenBefore.map((child) => child.fname)).toEqual(["foo"]);

          const grandChildrenBefore = await (provider.getChildren(
            childrenBefore[0]
          ) as Promise<NoteProps[]>);
          expect(grandChildrenBefore.map((gchild) => gchild.fname)).toEqual([
            "foo.bar",
            "foo.baz",
          ]);

          const engine = ExtensionProvider.getEngine();
          await runRenameNote({
            noteId: childrenBefore[0].id,
            newName: "fooz",
          });

          const vault1RootPropsAfter = engine.notes[vaultOneRootId];
          const childrenAfter = await (provider.getChildren(
            vault1RootPropsAfter
          ) as Promise<NoteProps[]>);
          expect(
            childrenAfter.map((child) => {
              return { fname: child.fname, stub: child.stub };
            })
          ).toEqual([
            { fname: "foo", stub: true },
            { fname: "fooz", stub: undefined },
          ]);

          const grandChildrenAfter = await (provider.getChildren(
            childrenAfter[0]
          ) as Promise<NoteProps[]>);
          expect(grandChildrenAfter.map((gchild) => gchild.fname)).toEqual([
            "foo.bar",
            "foo.baz",
          ]);
        });
      }
    );

    describeMultiWS(
      "WHEN renaming note with a chain of ancestors that are only stubs",
      {
        preSetupHook: async (opts) => {
          const { wsRoot, vaults } = opts;
          await NoteTestUtilsV4.createNote({
            wsRoot,
            vault: vaults[0],
            fname: "one.two.three.foo",
          });
        },
      },
      () => {
        test("THEN tree view correctly displays renamed note", async () => {
          const mockEvents = new MockEngineEvents();
          const provider = new EngineNoteProvider(mockEvents);

          const engine = ExtensionProvider.getEngine();

          const propsBefore = await (provider.getChildren() as Promise<
            NoteProps[]
          >);

          const vaultOneRootPropsBefore = propsBefore[0];
          const vaultOneRootId = propsBefore[0].id;
          const fullTreeBefore = await getFullTree({
            root: vaultOneRootPropsBefore,
            provider,
          });
          expect(fullTreeBefore).toEqual({
            fname: "root",
            childNodes: [
              {
                fname: "one",
                childNodes: [
                  {
                    fname: "one.two",
                    childNodes: [
                      {
                        fname: "one.two.three",
                        childNodes: [
                          {
                            fname: "one.two.three.foo",
                            childNodes: [],
                          },
                        ],
                      },
                    ],
                  },
                ],
              },
            ],
          });

          await runRenameNote({
            noteId: "one.two.three.foo",
            newName: "zero",
          });

          const vaultOneRootPropsAfter = engine.notes[vaultOneRootId];
          const fullTreeAfter = await getFullTree({
            root: vaultOneRootPropsAfter,
            provider,
          });
          expect(fullTreeAfter).toEqual({
            fname: "root",
            childNodes: [
              {
                fname: "zero",
                childNodes: [],
              },
            ],
          });
        });
      }
    );

    describeMultiWS(
      "WHEN renaming note results in a chain of stub ancestors",
      {
        preSetupHook: async (opts) => {
          const { wsRoot, vaults } = opts;
          await NoteTestUtilsV4.createNote({
            wsRoot,
            vault: vaults[0],
            fname: "foo",
          });
        },
      },
      () => {
        test("THEN tree view correctly displays renamed note", async () => {
          const mockEvents = new MockEngineEvents();
          const provider = new EngineNoteProvider(mockEvents);

          const engine = ExtensionProvider.getEngine();

          const propsBefore = await (provider.getChildren() as Promise<
            NoteProps[]
          >);

          const vaultOneRootPropsBefore = propsBefore[0];
          const vaultOneRootId = propsBefore[0].id;
          const fullTreeBefore = await getFullTree({
            root: vaultOneRootPropsBefore,
            provider,
          });
          expect(fullTreeBefore).toEqual({
            fname: "root",
            childNodes: [
              {
                fname: "foo",
                childNodes: [],
              },
            ],
          });

          await runRenameNote({
            noteId: "foo",
            newName: "one.two.three.foo",
          });

          const vaultOneRootPropsAfter = engine.notes[vaultOneRootId];
          const fullTreeAfter = await getFullTree({
            root: vaultOneRootPropsAfter,
            provider,
          });
          expect(fullTreeAfter).toEqual({
            fname: "root",
            childNodes: [
              {
                fname: "one",
                childNodes: [
                  {
                    fname: "one.two",
                    childNodes: [
                      {
                        fname: "one.two.three",
                        childNodes: [
                          {
                            fname: "one.two.three.foo",
                            childNodes: [],
                          },
                        ],
                      },
                    ],
                  },
                ],
              },
            ],
          });
        });
      }
    );

    describeMultiWS(
      "WHEN renaming note to replace an existing stub note",
      {
        preSetupHook: async (opts) => {
          const { wsRoot, vaults } = opts;
          await NoteTestUtilsV4.createNote({
            wsRoot,
            vault: vaults[0],
            fname: "foo",
          });
          await NoteTestUtilsV4.createNote({
            wsRoot,
            vault: vaults[0],
            fname: "one.two.three",
          });
        },
      },
      () => {
        test("THEN tree view correctly displays renamed note", async () => {
          const mockEvents = new MockEngineEvents();
          const provider = new EngineNoteProvider(mockEvents);

          const engine = ExtensionProvider.getEngine();

          const propsBefore = await (provider.getChildren() as Promise<
            NoteProps[]
          >);

          const vaultOneRootPropsBefore = propsBefore[0];
          const vaultOneRootId = propsBefore[0].id;
          const fullTreeBefore = await getFullTree({
            root: vaultOneRootPropsBefore,
            provider,
          });
          expect(fullTreeBefore).toEqual({
            fname: "root",
            childNodes: [
              {
                fname: "foo",
                childNodes: [],
              },
              {
                fname: "one",
                childNodes: [
                  {
                    fname: "one.two",
                    childNodes: [
                      {
                        fname: "one.two.three",
                        childNodes: [],
                      },
                    ],
                  },
                ],
              },
            ],
          });

          await runRenameNote({
            noteId: "foo",
            newName: "one",
          });

          const vaultOneRootPropsAfter = engine.notes[vaultOneRootId];
          const fullTreeAfter = await getFullTree({
            root: vaultOneRootPropsAfter,
            provider,
          });
          expect(fullTreeAfter).toEqual({
            fname: "root",
            childNodes: [
              {
                fname: "one",
                childNodes: [
                  {
                    fname: "one.two",
                    childNodes: [
                      {
                        fname: "one.two.three",
                        childNodes: [],
                      },
                    ],
                  },
                ],
              },
            ],
          });

          expect(engine.notes["foo"].stub).toBeFalsy();
        });
      }
    );
  });

  describe("Delete Note Command interactions", function () {
    describeMultiWS(
      "WHEN deleting note with top level hierarchy",
      {
        preSetupHook: async (opts) => {
          const { wsRoot, vaults } = opts;
          await NoteTestUtilsV4.createNote({
            wsRoot,
            vault: vaults[0],
            fname: "foo",
          });
        },
      },
      () => {
        test("THEN tree view correctly removes deleted note", async () => {
          const mockEvents = new MockEngineEvents();
          const provider = new EngineNoteProvider(mockEvents);

          const propsBefore = await (provider.getChildren() as Promise<
            NoteProps[]
          >);
          const vaultOneRootPropsBefore = propsBefore[0];
          const fullTreeBefore = await getFullTree({
            root: vaultOneRootPropsBefore,
            provider,
          });

          expect(fullTreeBefore).toEqual({
            fname: "root",
            childNodes: [
              {
                fname: "foo",
                childNodes: [],
              },
            ],
          });

          await runDeleteNote({ noteId: "foo" });

          const propsAfter = await (provider.getChildren() as Promise<
            NoteProps[]
          >);
          const vaultOneRootPropsAfter = propsAfter[0];
          const fullTreeAfter = await getFullTree({
            root: vaultOneRootPropsAfter,
            provider,
          });

          expect(fullTreeAfter).toEqual({
            fname: "root",
            childNodes: [],
          });
        });
      }
    );

    describeMultiWS(
      "WHEN deleting note with stub parent",
      {
        preSetupHook: async (opts) => {
          const { wsRoot, vaults } = opts;
          await NoteTestUtilsV4.createNote({
            wsRoot,
            vault: vaults[0],
            fname: "foo.bar",
          });
        },
      },
      () => {
        test("THEN tree view correctly removes deleted note and stub parent", async () => {
          const mockEvents = new MockEngineEvents();
          const provider = new EngineNoteProvider(mockEvents);

          const propsBefore = await (provider.getChildren() as Promise<
            NoteProps[]
          >);
          const vaultOneRootPropsBefore = propsBefore[0];
          const fullTreeBefore = await getFullTree({
            root: vaultOneRootPropsBefore,
            provider,
            extra: { stub: true },
          });

          expect(fullTreeBefore).toEqual({
            fname: "root",
            childNodes: [
              {
                fname: "foo",
                childNodes: [
                  {
                    fname: "foo.bar",
                    childNodes: [],
                  },
                ],
                stub: true,
              },
            ],
          });

          await runDeleteNote({ noteId: "foo.bar" });

          const propsAfter = await (provider.getChildren() as Promise<
            NoteProps[]
          >);
          const vaultOneRootPropsAfter = propsAfter[0];
          const fullTreeAfter = await getFullTree({
            root: vaultOneRootPropsAfter,
            provider,
            extra: { stub: true },
          });

          expect(fullTreeAfter).toEqual({
            fname: "root",
            childNodes: [],
          });
        });
      }
    );

    describeMultiWS(
      "WHEN deleting note with non-stub parent",
      {
        preSetupHook: async (opts) => {
          const { wsRoot, vaults } = opts;
          await NoteTestUtilsV4.createNote({
            wsRoot,
            vault: vaults[0],
            fname: "foo",
          });
          await NoteTestUtilsV4.createNote({
            wsRoot,
            vault: vaults[0],
            fname: "foo.bar",
          });
        },
      },
      () => {
        test("THEN tree view correctly removes deleted note", async () => {
          const mockEvents = new MockEngineEvents();
          const provider = new EngineNoteProvider(mockEvents);

          const propsBefore = await (provider.getChildren() as Promise<
            NoteProps[]
          >);
          const vaultOneRootPropsBefore = propsBefore[0];
          const fullTreeBefore = await getFullTree({
            root: vaultOneRootPropsBefore,
            provider,
            extra: { stub: true },
          });

          expect(fullTreeBefore).toEqual({
            fname: "root",
            childNodes: [
              {
                fname: "foo",
                childNodes: [
                  {
                    fname: "foo.bar",
                    childNodes: [],
                  },
                ],
              },
            ],
          });

          await runDeleteNote({ noteId: "foo.bar" });

          const propsAfter = await (provider.getChildren() as Promise<
            NoteProps[]
          >);
          const vaultOneRootPropsAfter = propsAfter[0];
          const fullTreeAfter = await getFullTree({
            root: vaultOneRootPropsAfter,
            provider,
            extra: { stub: true },
          });

          expect(fullTreeAfter).toEqual({
            fname: "root",
            childNodes: [
              {
                fname: "foo",
                childNodes: [],
              },
            ],
          });
        });
      }
    );

    describeMultiWS(
      "WHEN deleting note with stub children",
      {
        preSetupHook: async (opts) => {
          const { wsRoot, vaults } = opts;
          await NoteTestUtilsV4.createNote({
            wsRoot,
            vault: vaults[0],
            fname: "foo",
          });
          await NoteTestUtilsV4.createNote({
            wsRoot,
            vault: vaults[0],
            fname: "foo.bar.egg",
          });
        },
      },
      () => {
        test("THEN tree view correctly removes deleted note and replaces it with a stub", async () => {
          const mockEvents = new MockEngineEvents();
          const provider = new EngineNoteProvider(mockEvents);

          const propsBefore = await (provider.getChildren() as Promise<
            NoteProps[]
          >);
          const vaultOneRootPropsBefore = propsBefore[0];
          const fullTreeBefore = await getFullTree({
            root: vaultOneRootPropsBefore,
            provider,
            extra: { stub: true },
          });

          expect(fullTreeBefore).toEqual({
            fname: "root",
            childNodes: [
              {
                fname: "foo",
                childNodes: [
                  {
                    fname: "foo.bar",
                    childNodes: [
                      {
                        fname: "foo.bar.egg",
                        childNodes: [],
                      },
                    ],
                    stub: true,
                  },
                ],
              },
            ],
          });

          await runDeleteNote({ noteId: "foo" });

          const propsAfter = await (provider.getChildren() as Promise<
            NoteProps[]
          >);
          const vaultOneRootPropsAfter = propsAfter[0];
          const fullTreeAfter = await getFullTree({
            root: vaultOneRootPropsAfter,
            provider,
            extra: { stub: true },
          });

          expect(fullTreeAfter).toEqual({
            fname: "root",
            childNodes: [
              {
                fname: "foo",
                childNodes: [
                  {
                    fname: "foo.bar",
                    childNodes: [
                      {
                        fname: "foo.bar.egg",
                        childNodes: [],
                      },
                    ],
                    stub: true,
                  },
                ],
                stub: true,
              },
            ],
          });
        });
      }
    );

    describeMultiWS(
      "WHEN deleting note with non-stub children",
      {
        preSetupHook: async (opts) => {
          const { wsRoot, vaults } = opts;
          await NoteTestUtilsV4.createNote({
            wsRoot,
            vault: vaults[0],
            fname: "foo",
          });
          await NoteTestUtilsV4.createNote({
            wsRoot,
            vault: vaults[0],
            fname: "foo.bar",
          });
          await NoteTestUtilsV4.createNote({
            wsRoot,
            vault: vaults[0],
            fname: "foo.baz",
          });
        },
      },
      () => {
        test("THEN tree view correctly removes deleted note and replaces it with a stub", async () => {
          const mockEvents = new MockEngineEvents();
          const provider = new EngineNoteProvider(mockEvents);

          const propsBefore = await (provider.getChildren() as Promise<
            NoteProps[]
          >);
          const vaultOneRootPropsBefore = propsBefore[0];
          const fullTreeBefore = await getFullTree({
            root: vaultOneRootPropsBefore,
            provider,
            extra: { stub: true },
          });

          expect(fullTreeBefore).toEqual({
            fname: "root",
            childNodes: [
              {
                fname: "foo",
                childNodes: [
                  {
                    fname: "foo.bar",
                    childNodes: [],
                  },
                  {
                    fname: "foo.baz",
                    childNodes: [],
                  },
                ],
              },
            ],
          });

          await runDeleteNote({ noteId: "foo" });

          const propsAfter = await (provider.getChildren() as Promise<
            NoteProps[]
          >);
          const vaultOneRootPropsAfter = propsAfter[0];
          const fullTreeAfter = await getFullTree({
            root: vaultOneRootPropsAfter,
            provider,
            extra: { stub: true },
          });

          expect(fullTreeAfter).toEqual({
            fname: "root",
            childNodes: [
              {
                fname: "foo",
                childNodes: [
                  {
                    fname: "foo.bar",
                    childNodes: [],
                  },
                  {
                    fname: "foo.baz",
                    childNodes: [],
                  },
                ],
                stub: true,
              },
            ],
          });
        });
      }
    );
    describeMultiWS(
      "WHEN deleting note with chain of ancestors that are only stubs",
      {
        preSetupHook: async (opts) => {
          const { wsRoot, vaults } = opts;
          await NoteTestUtilsV4.createNote({
            wsRoot,
            vault: vaults[0],
            fname: "one.two.three.foo",
          });
        },
      },
      () => {
        test("THEN tree view correctly removes deleted note and all its ancestor stubs", async () => {
          const mockEvents = new MockEngineEvents();
          const provider = new EngineNoteProvider(mockEvents);

          const propsBefore = await (provider.getChildren() as Promise<
            NoteProps[]
          >);
          const vaultOneRootPropsBefore = propsBefore[0];
          const fullTreeBefore = await getFullTree({
            root: vaultOneRootPropsBefore,
            provider,
            extra: { stub: true },
          });

          expect(fullTreeBefore).toEqual({
            fname: "root",
            childNodes: [
              {
                fname: "one",
                childNodes: [
                  {
                    fname: "one.two",
                    childNodes: [
                      {
                        fname: "one.two.three",
                        childNodes: [
                          {
                            fname: "one.two.three.foo",
                            childNodes: [],
                          },
                        ],
                        stub: true,
                      },
                    ],
                    stub: true,
                  },
                ],
                stub: true,
              },
            ],
          });

          await runDeleteNote({ noteId: "one.two.three.foo" });

          const propsAfter = await (provider.getChildren() as Promise<
            NoteProps[]
          >);
          const vaultOneRootPropsAfter = propsAfter[0];
          const fullTreeAfter = await getFullTree({
            root: vaultOneRootPropsAfter,
            provider,
            extra: { stub: true },
          });

          expect(fullTreeAfter).toEqual({
            fname: "root",
            childNodes: [],
          });
        });
      }
    );

    describeMultiWS(
      "WHEN deleting note that was just created",
      {
        preSetupHook: async (opts) => {
          const { wsRoot, vaults } = opts;
          await NoteTestUtilsV4.createNote({
            wsRoot,
            vault: vaults[0],
            fname: "foo",
          });
        },
      },
      () => {
        describe("AND created note is top hierarchy", () => {
          test("THEN tree view correctly removes deleted note", async () => {
            const mockEvents = new MockEngineEvents();
            const provider = new EngineNoteProvider(mockEvents);
            const { wsRoot, vaults, engine } =
              ExtensionProvider.getDWorkspace();
            await NoteTestUtilsV4.createNoteWithEngine({
              wsRoot,
              vault: vaults[0],
              fname: "bar",
              engine,
            });
            const propsBefore = await (provider.getChildren() as Promise<
              NoteProps[]
            >);
            const vaultOneRootPropsBefore = propsBefore[0];
            const fullTreeBefore = await getFullTree({
              root: vaultOneRootPropsBefore,
              provider,
              extra: { stub: true },
            });

            expect(fullTreeBefore).toEqual({
              fname: "root",
              childNodes: [
                {
                  fname: "bar",
                  childNodes: [],
                },
                {
                  fname: "foo",
                  childNodes: [],
                },
              ],
            });

            await runDeleteNote({ noteId: "bar" });

            const propsAfter = await (provider.getChildren() as Promise<
              NoteProps[]
            >);
            const vaultOneRootPropsAfter = propsAfter[0];
            const fullTreeAfter = await getFullTree({
              root: vaultOneRootPropsAfter,
              provider,
            });
            expect(fullTreeAfter).toEqual({
              fname: "root",
              childNodes: [
                {
                  fname: "foo",
                  childNodes: [],
                },
              ],
            });
          });
        });

        describe("AND created note also creates stub parent", () => {
          test("THEN tree view correctly removes deleted note and stub parent", async () => {
            const mockEvents = new MockEngineEvents();
            const provider = new EngineNoteProvider(mockEvents);
            const { wsRoot, vaults, engine } =
              ExtensionProvider.getDWorkspace();
            await NoteTestUtilsV4.createNoteWithEngine({
              wsRoot,
              vault: vaults[0],
              fname: "bar.egg",
              engine,
            });
            const propsBefore = await (provider.getChildren() as Promise<
              NoteProps[]
            >);
            const vaultOneRootPropsBefore = propsBefore[0];
            const fullTreeBefore = await getFullTree({
              root: vaultOneRootPropsBefore,
              provider,
              extra: { stub: true },
            });

            expect(fullTreeBefore).toEqual({
              fname: "root",
              childNodes: [
                {
                  fname: "bar",
                  childNodes: [
                    {
                      fname: "bar.egg",
                      childNodes: [],
                    },
                  ],
                  stub: true,
                },
                {
                  fname: "foo",
                  childNodes: [],
                },
              ],
            });

            await runDeleteNote({ noteId: "bar.egg" });

            const propsAfter = await (provider.getChildren() as Promise<
              NoteProps[]
            >);
            const vaultOneRootPropsAfter = propsAfter[0];
            const fullTreeAfter = await getFullTree({
              root: vaultOneRootPropsAfter,
              provider,
              extra: { stub: true },
            });
            expect(fullTreeAfter).toEqual({
              fname: "root",
              childNodes: [
                {
                  fname: "foo",
                  childNodes: [],
                },
              ],
            });
          });
        });

        describe("AND created note has stub children", () => {
          test("THEN tree view correctly removes deleted note and replaces it with a stub", async () => {
            const mockEvents = new MockEngineEvents();
            const provider = new EngineNoteProvider(mockEvents);
            const { wsRoot, vaults, engine } =
              ExtensionProvider.getDWorkspace();
            await NoteTestUtilsV4.createNoteWithEngine({
              wsRoot,
              vault: vaults[0],
              fname: "one.two.three",
              engine,
            });

            await NoteTestUtilsV4.createNoteWithEngine({
              wsRoot,
              vault: vaults[0],
              fname: "one.two",
              engine,
            });
            const propsBefore = await (provider.getChildren() as Promise<
              NoteProps[]
            >);
            const vaultOneRootPropsBefore = propsBefore[0];
            const fullTreeBefore = await getFullTree({
              root: vaultOneRootPropsBefore,
              provider,
              extra: { stub: true },
            });

            expect(fullTreeBefore).toEqual({
              fname: "root",
              childNodes: [
                {
                  fname: "foo",
                  childNodes: [],
                },
                {
                  fname: "one",
                  childNodes: [
                    {
                      fname: "one.two",
                      childNodes: [
                        {
                          fname: "one.two.three",
                          childNodes: [],
                        },
                      ],
                    },
                  ],
                  stub: true,
                },
              ],
            });

            await runDeleteNote({ noteId: "one.two" });

            const propsAfter = await (provider.getChildren() as Promise<
              NoteProps[]
            >);
            const vaultOneRootPropsAfter = propsAfter[0];
            const fullTreeAfter = await getFullTree({
              root: vaultOneRootPropsAfter,
              provider,
              extra: { stub: true },
            });
            expect(fullTreeAfter).toEqual({
              fname: "root",
              childNodes: [
                {
                  fname: "foo",
                  childNodes: [],
                },
                {
                  fname: "one",
                  childNodes: [
                    {
                      fname: "one.two",
                      childNodes: [
                        {
                          fname: "one.two.three",
                          childNodes: [],
                        },
                      ],
                      stub: true,
                    },
                  ],
                  stub: true,
                },
              ],
            });
          });
        });
      }
    );
    describeMultiWS("WHEN deleting note that was just renamed", {}, () => {
      beforeEach(async () => {
        const { vaults, wsRoot, engine } = ExtensionProvider.getDWorkspace();
        await NoteTestUtilsV4.createNoteWithEngine({
          wsRoot,
          vault: vaults[0],
          fname: "foo",
          engine,
        });
      });
      describe("AND renamed note is top hierarchy", () => {
        test("THEN tree view correctly removes note that was just renamed and deleted", async () => {
          const mockEvents = new MockEngineEvents();
          const provider = new EngineNoteProvider(mockEvents);

          await runRenameNote({
            noteId: "foo",
            newName: "bar",
          });

          const propsBefore = await (provider.getChildren() as Promise<
            NoteProps[]
          >);
          const vaultOneRootPropsBefore = propsBefore[0];
          const fullTreeBefore = await getFullTree({
            root: vaultOneRootPropsBefore,
            provider,
            extra: { stub: true },
          });

          expect(fullTreeBefore).toEqual({
            fname: "root",
            childNodes: [
              {
                fname: "bar",
                childNodes: [],
              },
            ],
          });

          await runDeleteNote({
            noteId: "foo",
          });

          const propsAfter = await (provider.getChildren() as Promise<
            NoteProps[]
          >);
          const vaultOneRootPropsAfter = propsAfter[0];
          const fullTreeAfter = await getFullTree({
            root: vaultOneRootPropsAfter,
            provider,
            extra: { stub: true },
          });

          expect(fullTreeAfter).toEqual({
            fname: "root",
            childNodes: [],
          });
        });
      });

      describe("AND renamed note created a stub parent", () => {
        test("THEN tree view correctly removes note that was just renamed and deleted as well as the stub parent", async () => {
          const mockEvents = new MockEngineEvents();
          const provider = new EngineNoteProvider(mockEvents);

          await runRenameNote({
            noteId: "foo",
            newName: "one.two.three.foo",
          });

          const propsBefore = await (provider.getChildren() as Promise<
            NoteProps[]
          >);
          const vaultOneRootPropsBefore = propsBefore[0];
          const fullTreeBefore = await getFullTree({
            root: vaultOneRootPropsBefore,
            provider,
            extra: { stub: true },
          });

          expect(fullTreeBefore).toEqual({
            fname: "root",
            childNodes: [
              {
                fname: "one",
                childNodes: [
                  {
                    fname: "one.two",
                    childNodes: [
                      {
                        fname: "one.two.three",
                        childNodes: [
                          {
                            fname: "one.two.three.foo",
                            childNodes: [],
                          },
                        ],
                        stub: true,
                      },
                    ],
                    stub: true,
                  },
                ],
                stub: true,
              },
            ],
          });

          await runDeleteNote({
            noteId: "foo",
          });

          const propsAfter = await (provider.getChildren() as Promise<
            NoteProps[]
          >);
          const vaultOneRootPropsAfter = propsAfter[0];
          const fullTreeAfter = await getFullTree({
            root: vaultOneRootPropsAfter,
            provider,
            extra: { stub: true },
          });

          expect(fullTreeAfter).toEqual({
            fname: "root",
            childNodes: [],
          });
        });
      });

      describe("AND renamed note has any children", () => {
        test("THEN THEN tree view correctly removes note that was just renamed and deleted and replaces it with a stub", async () => {
          const mockEvents = new MockEngineEvents();
          const provider = new EngineNoteProvider(mockEvents);

          const { vaults, wsRoot, engine } = ExtensionProvider.getDWorkspace();
          await NoteTestUtilsV4.createNoteWithEngine({
            wsRoot,
            vault: vaults[0],
            fname: "one.two.three.foo",
            engine,
          });

          await runRenameNote({
            noteId: "foo",
            newName: "one.two.three",
          });

          const propsBefore = await (provider.getChildren() as Promise<
            NoteProps[]
          >);
          const vaultOneRootPropsBefore = propsBefore[0];
          const fullTreeBefore = await getFullTree({
            root: vaultOneRootPropsBefore,
            provider,
            extra: { stub: true },
          });

          expect(fullTreeBefore).toEqual({
            fname: "root",
            childNodes: [
              {
                fname: "one",
                childNodes: [
                  {
                    fname: "one.two",
                    childNodes: [
                      {
                        fname: "one.two.three",
                        childNodes: [
                          {
                            fname: "one.two.three.foo",
                            childNodes: [],
                          },
                        ],
                      },
                    ],
                    stub: true,
                  },
                ],
                stub: true,
              },
            ],
          });

          await runDeleteNote({ noteId: "foo" });

          const propsAfter = await (provider.getChildren() as Promise<
            NoteProps[]
          >);
          const vaultOneRootPropsAfter = propsAfter[0];
          const fullTreeAfter = await getFullTree({
            root: vaultOneRootPropsAfter,
            provider,
            extra: { stub: true },
          });

          expect(fullTreeAfter).toEqual({
            fname: "root",
            childNodes: [
              {
                fname: "one",
                childNodes: [
                  {
                    fname: "one.two",
                    childNodes: [
                      {
                        fname: "one.two.three",
                        childNodes: [
                          {
                            fname: "one.two.three.foo",
                            childNodes: [],
                          },
                        ],
                        stub: true,
                      },
                    ],
                    stub: true,
                  },
                ],
                stub: true,
              },
            ],
          });
        });
      });
    });
  });
  describe("filesystem change interactions", function () {});
});
Example #26
Source File: MoveHeader.test.ts    From dendron with GNU Affero General Public License v3.0 4 votes vote down vote up
suite("MoveHeader", function () {
  const ctx = setupBeforeAfter(this);

  describe("GIVEN a note with a simple header", () => {
    let originNote: NoteProps;
    let preSetupHook: PreSetupHookFunction;

    beforeEach(() => {
      preSetupHook = async ({
        wsRoot,
        vaults,
      }: {
        wsRoot: string;
        vaults: DVault[];
      }) => {
        originNote = await NoteTestUtilsV4.createNote({
          fname: "origin",
          wsRoot,
          vault: vaults[0],
          body: "## Foo header\n\n some text with anchor ^123",
        });
        await NoteTestUtilsV4.createNote({
          fname: "dest",
          wsRoot,
          vault: vaults[0],
          body: "# Some header",
        });
        await NoteTestUtilsV4.createNote({
          fname: "ref-note",
          wsRoot,
          vault: vaults[0],
          body: "[[Origin|origin]]\n\n[[Foo|origin#foo-header]]\n\n",
        });
        await NoteTestUtilsV4.createNote({
          fname: "ref-note2",
          wsRoot,
          vault: vaults[0],
          body: "[[Foo|dendron://vault1/origin#foo-header]]\n\n",
        });
      };
    });

    describe("WHEN header is selected", () => {
      let onInitFunc: Function;
      beforeEach(() => {
        onInitFunc = (nextFunc: Function) => {
          return async () => {
            const editor = await WSUtils.openNote(originNote);
            editor.selection = new vscode.Selection(7, 0, 7, 0);
            nextFunc();
          };
        };
      });

      describe("AND WHEN existing item is selected for destination", () => {
        test("THEN selected item is used for destination", (done) => {
          runLegacyMultiWorkspaceTest({
            ctx,
            preSetupHook,
            onInit: onInitFunc(async () => {
              const cmd = new MoveHeaderCommand();
              const gatherOut = await cmd.gatherInputs({
                initialValue: "dest",
                nonInteractive: true,
              });

              expect(gatherOut?.dest?.fname).toEqual("dest");
              done();
            }),
          });
        });
      });

      describe("AND WHEN move destination note does not exist", () => {
        test("THEN new note is created and header is appended to new note", (done) => {
          runLegacyMultiWorkspaceTest({
            ctx,
            preSetupHook,
            onInit: onInitFunc(async () => {
              const cmd = new MoveHeaderCommand();
              const out = await cmd.run({
                useSameVault: true,
                initialValue: "new-note",
                nonInteractive: true,
              });
              const { engine, vaults } = ExtensionProvider.getDWorkspace();
              const newNote = NoteUtils.getNoteByFnameFromEngine({
                fname: "new-note",
                engine,
                vault: vaults[0],
              });

              expect(!_.isUndefined(newNote)).toBeTruthy();
              expect(out!.origin.body.includes("## Foo header")).toBeFalsy();
              expect(out!.dest!.body.includes("## Foo header")).toBeTruthy();
              expect(out!.dest!.body.includes("^123")).toBeTruthy();

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

      describe("AND WHEN note reference exists in destination", () => {
        test("THEN selected header is moved from origin to dest", (done) => {
          runLegacyMultiWorkspaceTest({
            ctx,
            preSetupHook: async ({
              wsRoot,
              vaults,
            }: {
              wsRoot: string;
              vaults: DVault[];
            }) => {
              originNote = await NoteTestUtilsV4.createNote({
                fname: "origin",
                wsRoot,
                vault: vaults[0],
                body: "## Foo header\n\n",
              });
              await NoteTestUtilsV4.createNote({
                fname: "dest",
                wsRoot,
                vault: vaults[0],
                body: "![[ref-note]] ",
              });
              await NoteTestUtilsV4.createNote({
                fname: "ref-note",
                wsRoot,
                vault: vaults[0],
                body: "[[Foo|origin#foo-header]]",
              });
            },
            onInit: onInitFunc(async () => {
              const cmd = new MoveHeaderCommand();
              const out = await cmd.run({
                initialValue: "dest",
                useSameVault: true,
                nonInteractive: true,
              });
              expect(out!.origin.body.includes("## Foo header")).toBeFalsy();
              expect(out!.dest!.body.includes("## Foo header")).toBeTruthy();
              done();
            }),
          });
        });
      });

      test("THEN selected header is moved from origin to dest", (done) => {
        runLegacyMultiWorkspaceTest({
          ctx,
          preSetupHook,
          onInit: onInitFunc(async () => {
            const cmd = new MoveHeaderCommand();
            const out = await cmd.run({
              initialValue: "dest",
              useSameVault: true,
              nonInteractive: true,
            });
            expect(out!.origin.body.includes("## Foo header")).toBeFalsy();
            expect(out!.dest!.body.includes("## Foo header")).toBeTruthy();
            done();
          }),
        });
      });

      test("THEN only reference to moved header is updated", (done) => {
        runLegacyMultiWorkspaceTest({
          ctx,
          preSetupHook,
          onInit: onInitFunc(async () => {
            const cmd = new MoveHeaderCommand();
            const out = await cmd.run({
              initialValue: "dest",
              useSameVault: true,
              nonInteractive: true,
            });
            await new Promise<void>((resolve) => {
              setTimeout(() => {
                resolve();
              }, 100);
            });
            const refNote = out!.updated.find(
              (n) => n.id === "ref-note"
            ) as NoteProps;
            const refNote2 = out!.updated.find(
              (n) => n.id === "ref-note2"
            ) as NoteProps;

            expect(
              refNote.body.includes("[[Foo|dest#foo-header]]")
            ).toBeTruthy();
            expect(
              refNote2.body.includes("[[Foo|dendron://vault1/dest#foo-header]]")
            );
            expect(refNote.body.includes("[[Origin|dest]]")).toBeFalsy();
            done();
          }),
        });
      });

      test("THEN vault prefix is added to bare links if there are notes with same name as destination in different vaults", (done) => {
        runLegacyMultiWorkspaceTest({
          ctx,
          preSetupHook: async ({ wsRoot, vaults }) => {
            await preSetupHook({ wsRoot, vaults });
            await NoteTestUtilsV4.createNote({
              fname: "dest",
              wsRoot,
              vault: vaults[2],
              genRandomId: true,
            });
          },
          onInit: onInitFunc(async () => {
            const cmd = new MoveHeaderCommand();
            const out = await cmd.run({
              initialValue: "dest",
              useSameVault: true,
              nonInteractive: true,
            });
            await new Promise<void>((resolve) => {
              setTimeout(() => {
                resolve();
              }, 100);
            });
            const refNote = out!.updated.find(
              (n) => n.id === "ref-note"
            ) as NoteProps;
            expect(
              refNote.body.includes("[[Foo|dest#foo-header]]")
            ).toBeFalsy();
            expect(
              refNote.body.includes("[[Foo|dendron://vault1/dest#foo-header]]")
            );
            expect(refNote.body.includes("[[Origin|dest]]")).toBeFalsy();
            done();
          }),
        });
      });
    });

    describe("WHEN header is not select", () => {
      const onInitFunc = (nextFunc: Function) => {
        return async () => {
          const editor = await WSUtils.openNote(originNote);
          editor.selection = new vscode.Selection(8, 0, 8, 0);
          nextFunc();
        };
      };
      test("THEN MoveHeaderCommand throws an error", (done) => {
        runLegacyMultiWorkspaceTest({
          ctx,
          preSetupHook,
          onInit: onInitFunc(async () => {
            const cmd = new MoveHeaderCommand();
            let out;
            let wasThrown = false;
            try {
              out = await cmd.gatherInputs({
                initialValue: "dest",
                useSameVault: true,
                nonInteractive: true,
              });
            } catch (error) {
              wasThrown = true;
              expect(error instanceof DendronError).toBeTruthy();
              // Commented out since `.toContain` used to not do anything, now that `.toContain`
              // is fixed this assertion does not pass:
              //
              // expect(error).toContain(
              //   "You must first select the header you want to move."
              // );
            }

            expect(wasThrown).toBeTruthy();
            expect(_.isUndefined(out)).toBeTruthy();
            done();
          }),
        });
      });
    });

    describe("WHEN no note is open", () => {
      const onInitFunc = (nextFunc: Function) => {
        return async () => {
          await VSCodeUtils.closeAllEditors();
          nextFunc();
        };
      };
      test("THEN MoveHeaderCommand throws an error", (done) => {
        runLegacyMultiWorkspaceTest({
          ctx,
          preSetupHook,
          onInit: onInitFunc(async () => {
            const cmd = new MoveHeaderCommand();
            let out;
            let wasThrown = false;
            try {
              out = await cmd.gatherInputs({});
            } catch (error) {
              wasThrown = true;
              expect(error instanceof DendronError).toBeTruthy();
              // Commented out since `.toContain` used to not do anything, now that `.toContain`
              // is fixed this assertion does not pass:
              //
              // expect(error).toContain("no note open.");
            }
            expect(wasThrown).toBeTruthy();
            expect(_.isUndefined(out)).toBeTruthy();
            done();
          }),
        });
      });
    });
  });
});
Example #27
Source File: CreateDailyJournalNote.test.ts    From dendron with GNU Affero General Public License v3.0 4 votes vote down vote up
suite("Create Daily Journal Suite", function () {
  const TEMPLATE_BODY = "test daily template";

  beforeEach(() => {
    MetadataService.instance().deleteMeta("firstDailyJournalTime");
    MetadataService.instance().setInitialInstall(
      Time.DateTime.fromISO("2022-06-30").toSeconds()
    );
    sinon
      .stub(_2022_05_DAILY_JOURNAL_TEMPLATE_TEST, "getUserGroup")
      .returns(DailyJournalTestGroups.withTemplate!);
  });

  describeMultiWS(
    "GIVEN a basic workspace with a daily journal template note",
    {
      preSetupHook: ENGINE_HOOKS.setupBasic,
      preActivateHook: async ({ wsRoot, vaults }) => {
        await NoteTestUtilsV4.createNote({
          fname: CreateDailyJournalCommand.DENDRON_TEMPLATES_FNAME + ".daily",
          wsRoot,
          vault: vaults[0],
          body: TEMPLATE_BODY,
        });
      },
    },
    () => {
      test("WHEN CreateDailyJournalCommand is executed, then daily journal with template applied.", async () => {
        const ext = ExtensionProvider.getExtension();
        const cmd = new CreateDailyJournalCommand(ext);
        const metadataService = MetadataService.instance();
        expect(metadataService.getMeta().firstDailyJournalTime).toBeFalsy();

        await cmd.run();
        expect(metadataService.getMeta().firstDailyJournalTime).toBeTruthy();
        const activeNote = getNoteFromTextEditor();
        // Verify template body is applied
        expect(activeNote.fname.startsWith("daily.journal")).toBeTruthy();
        expect(activeNote.body.includes(TEMPLATE_BODY)).toBeTruthy();

        // Verify trait is applied
        const traits = (activeNote as any).traitIds;
        expect(traits.length === 1 && traits[0] === "journalNote").toBeTruthy();

        // Verify schema is created
        const engine = ExtensionProvider.getEngine();
        const dailySchema = engine.schemas["daily"];
        expect(dailySchema.fname === "dendron.daily").toBeTruthy();
        expect(_.size(dailySchema.schemas) === 5).toBeTruthy();
      });
    }
  );

  describeMultiWS(
    "GIVEN a basic workspace with a daily journal template note and DAILY JOURNAL has already been run before",
    {
      preSetupHook: ENGINE_HOOKS.setupBasic,
      preActivateHook: async ({ wsRoot, vaults }) => {
        await NoteTestUtilsV4.createNote({
          fname: CreateDailyJournalCommand.DENDRON_TEMPLATES_FNAME + ".daily",
          wsRoot,
          vault: vaults[0],
          body: TEMPLATE_BODY,
        });
      },
    },
    () => {
      test("WHEN CreateDailyJournalCommand is executed, then default template and schema is not created", async () => {
        const ext = ExtensionProvider.getExtension();
        const cmd = new CreateDailyJournalCommand(ext);
        const metadataService = MetadataService.instance();
        metadataService.setFirstDailyJournalTime();
        expect(metadataService.getMeta().firstDailyJournalTime).toBeTruthy();

        await cmd.run();
        expect(metadataService.getMeta().firstDailyJournalTime).toBeTruthy();
        const activeNote = getNoteFromTextEditor();
        // Verify template body is NOT applied
        expect(activeNote.fname.startsWith("daily.journal")).toBeTruthy();
        expect(activeNote.body.includes(TEMPLATE_BODY)).toBeFalsy();

        // Verify trait is applied
        const traits = (activeNote as any).traitIds;
        expect(traits.length === 1 && traits[0] === "journalNote").toBeTruthy();

        // Verify schema is NOT created
        const engine = ExtensionProvider.getEngine();
        const dailySchema = engine.schemas["daily"];
        expect(dailySchema).toBeFalsy();
      });
    }
  );

  describeMultiWS(
    "GIVEN a basic workspace with a daily journal template note and first install is before 5/31/22",
    {
      preSetupHook: ENGINE_HOOKS.setupBasic,
      preActivateHook: async ({ wsRoot, vaults }) => {
        await NoteTestUtilsV4.createNote({
          fname: CreateDailyJournalCommand.DENDRON_TEMPLATES_FNAME + ".daily",
          wsRoot,
          vault: vaults[0],
          body: TEMPLATE_BODY,
        });
      },
    },
    () => {
      test("WHEN CreateDailyJournalCommand is executed, then default template and schema is not created", async () => {
        const ext = ExtensionProvider.getExtension();
        const cmd = new CreateDailyJournalCommand(ext);
        const metadataService = MetadataService.instance();
        metadataService.setInitialInstall(
          Time.DateTime.fromISO("2022-04-30").toSeconds()
        );
        expect(metadataService.getMeta().firstDailyJournalTime).toBeFalsy();

        await cmd.run();
        expect(metadataService.getMeta().firstDailyJournalTime).toBeTruthy();
        const activeNote = getNoteFromTextEditor();
        // Verify template body is NOT applied
        expect(activeNote.fname.startsWith("daily.journal")).toBeTruthy();
        expect(activeNote.body.includes(TEMPLATE_BODY)).toBeFalsy();

        // Verify trait is applied
        const traits = (activeNote as any).traitIds;
        expect(traits.length === 1 && traits[0] === "journalNote").toBeTruthy();

        // Verify schema is NOT created
        const engine = ExtensionProvider.getEngine();
        const dailySchema = engine.schemas["daily"];
        expect(dailySchema).toBeFalsy();
      });
    }
  );

  describeMultiWS(
    "GIVEN a basic workspace with a daily journal template note and dailyVault set",
    {
      preSetupHook: ENGINE_HOOKS.setupBasic,
      preActivateHook: async ({ wsRoot, vaults }) => {
        await NoteTestUtilsV4.createNote({
          fname: CreateDailyJournalCommand.DENDRON_TEMPLATES_FNAME + ".daily",
          wsRoot,
          vault: vaults[0],
          body: TEMPLATE_BODY,
        });
      },
      modConfigCb: (config) => {
        ConfigUtils.setNoteLookupProps(config, "confirmVaultOnCreate", false);
        ConfigUtils.setJournalProps(config, "dailyVault", "vault2");
        return config;
      },
    },
    () => {
      test("WHEN CreateDailyJournalCommand is executed, then daily journal is created in daily vault with template applied.", async () => {
        const ext = ExtensionProvider.getExtension();
        const cmd = new CreateDailyJournalCommand(ext);

        await cmd.run();
        const activeNote = getNoteFromTextEditor();
        // Verify template body is applied
        expect(activeNote.fname.startsWith("daily.journal")).toBeTruthy();
        expect(activeNote.body.includes(TEMPLATE_BODY)).toBeTruthy();

        // Verify trait is applied
        const traits = (activeNote as any).traitIds;
        expect(traits.length === 1 && traits[0] === "journalNote").toBeTruthy();

        // Verify schema is created
        const engine = ExtensionProvider.getEngine();
        const dailySchema = engine.schemas["daily"];
        expect(dailySchema.fname === "dendron.daily").toBeTruthy();
        expect(_.size(dailySchema.schemas) === 5).toBeTruthy();

        expect(
          (await EditorUtils.getURIForActiveEditor()).fsPath.includes(
            engine.vaults[1].fsPath
          )
        ).toBeTruthy();
      });
    }
  );

  describeMultiWS(
    "GIVEN a basic workspace with a daily journal template note and dailyVault set with lookup Confirm",
    {
      preSetupHook: ENGINE_HOOKS.setupBasic,
      preActivateHook: async ({ wsRoot, vaults }) => {
        await NoteTestUtilsV4.createNote({
          fname: CreateDailyJournalCommand.DENDRON_TEMPLATES_FNAME + ".daily",
          wsRoot,
          vault: vaults[0],
          body: TEMPLATE_BODY,
        });
      },
      modConfigCb: (config) => {
        ConfigUtils.setNoteLookupProps(config, "confirmVaultOnCreate", true);
        ConfigUtils.setJournalProps(config, "dailyVault", "vault1");
        return config;
      },
    },
    () => {
      test("WHEN CreateDailyJournalCommand is executed, then daily journal is created in daily vault with template is applied.", async () => {
        const ext = ExtensionProvider.getExtension();
        const cmd = new CreateDailyJournalCommand(ext);

        await cmd.run();
        const activeNote = getNoteFromTextEditor();
        // Verify template body is applied
        expect(activeNote.fname.startsWith("daily.journal")).toBeTruthy();
        expect(activeNote.body.includes(TEMPLATE_BODY)).toBeTruthy();

        // Verify trait is applied
        const traits = (activeNote as any).traitIds;
        expect(traits.length === 1 && traits[0] === "journalNote").toBeTruthy();

        // Verify schema is created
        const engine = ExtensionProvider.getEngine();
        const dailySchema = engine.schemas["daily"];
        expect(dailySchema.fname === "dendron.daily").toBeTruthy();
        expect(_.size(dailySchema.schemas) === 5).toBeTruthy();

        expect(
          (await EditorUtils.getURIForActiveEditor()).fsPath.includes(
            engine.vaults[0].fsPath
          )
        ).toBeTruthy();
      });
    }
  );

  describeMultiWS(
    "GIVEN a basic workspace with a daily journal template note and dailyVault not set with lookup Confirm",
    {
      preSetupHook: ENGINE_HOOKS.setupBasic,
      preActivateHook: async ({ wsRoot, vaults }) => {
        await NoteTestUtilsV4.createNote({
          fname: CreateDailyJournalCommand.DENDRON_TEMPLATES_FNAME + ".daily",
          wsRoot,
          vault: vaults[0],
          body: TEMPLATE_BODY,
        });
      },
      modConfigCb: (config) => {
        ConfigUtils.setNoteLookupProps(config, "confirmVaultOnCreate", true);
        return config;
      },
    },
    () => {
      test("WHEN CreateDailyJournalCommand is executed, then daily journal is created in daily vault with template applied.", async () => {
        const ext = ExtensionProvider.getExtension();
        const { vaults, engine } = ExtensionProvider.getDWorkspace();
        stubVaultPick(vaults);
        const cmd = new CreateDailyJournalCommand(ext);

        await cmd.run();
        const activeNote = getNoteFromTextEditor();
        // Verify template body is applied
        expect(activeNote.fname.startsWith("daily.journal")).toBeTruthy();
        expect(activeNote.body.includes(TEMPLATE_BODY)).toBeTruthy();

        // Verify trait is applied
        const traits = (activeNote as any).traitIds;
        expect(traits.length === 1 && traits[0] === "journalNote").toBeTruthy();

        // Verify schema is created
        const dailySchema = engine.schemas["daily"];
        expect(dailySchema.fname === "dendron.daily").toBeTruthy();
        expect(_.size(dailySchema.schemas) === 5).toBeTruthy();

        expect(
          (await EditorUtils.getURIForActiveEditor()).fsPath.includes(
            engine.vaults[2].fsPath
          )
        ).toBeTruthy();
      });
    }
  );

  describeMultiWS(
    "GIVEN a basic workspace with a daily journal template note and dailyDomain set",
    {
      preSetupHook: ENGINE_HOOKS.setupBasic,
      preActivateHook: async ({ wsRoot, vaults }) => {
        await NoteTestUtilsV4.createNote({
          fname: CreateDailyJournalCommand.DENDRON_TEMPLATES_FNAME + ".bar",
          wsRoot,
          vault: vaults[0],
          body: TEMPLATE_BODY,
        });
      },
      modConfigCb: (config) => {
        ConfigUtils.setJournalProps(config, "dailyDomain", "bar");
        return config;
      },
    },
    () => {
      test("WHEN CreateDailyJournalCommand is executed, then daily journal is created with right domain and with template applied.", async () => {
        const ext = ExtensionProvider.getExtension();
        const cmd = new CreateDailyJournalCommand(ext);

        await cmd.run();
        const activeNote = getNoteFromTextEditor();
        // Verify template body is applied
        expect(activeNote.fname.startsWith("bar.journal")).toBeTruthy();
        expect(activeNote.body.includes(TEMPLATE_BODY)).toBeTruthy();

        // Verify trait is applied
        const traits = (activeNote as any).traitIds;
        expect(traits.length === 1 && traits[0] === "journalNote").toBeTruthy();

        // Verify schema is created
        const engine = ExtensionProvider.getEngine();
        const dailySchema = engine.schemas["bar"];
        expect(dailySchema.fname === "dendron.bar").toBeTruthy();
        expect(_.size(dailySchema.schemas) === 5).toBeTruthy();
      });
    }
  );

  describeMultiWS(
    "GIVEN a basic workspace with a daily journal template note and deprecated config",
    {
      preSetupHook: ENGINE_HOOKS.setupBasic,
      preActivateHook: async ({ wsRoot, vaults }) => {
        await NoteTestUtilsV4.createNote({
          fname: CreateDailyJournalCommand.DENDRON_TEMPLATES_FNAME + ".daisy",
          wsRoot,
          vault: vaults[0],
          body: TEMPLATE_BODY,
        });
      },
      wsSettingsOverride: {
        settings: {
          [CONFIG.DEFAULT_JOURNAL_DATE_FORMAT.key]: "'q'q",
          [CONFIG.DEFAULT_JOURNAL_ADD_BEHAVIOR.key]: "childOfCurrent",
          [CONFIG.DAILY_JOURNAL_DOMAIN.key]: "daisy",
          [CONFIG.DEFAULT_JOURNAL_NAME.key]: "journey",
        },
      },
      modConfigCb: (config) => {
        ConfigUtils.setJournalProps(config, "dateFormat", "dd");
        ConfigUtils.setJournalProps(config, "dailyDomain", "daisy");
        ConfigUtils.setJournalProps(config, "name", "journey");
        return config;
      },
    },
    () => {
      test("WHEN CreateDailyJournalCommand is executed, then deprecated config is ignored.", async () => {
        const ext = ExtensionProvider.getExtension();
        const cmd = new CreateDailyJournalCommand(ext);

        await cmd.run();
        const activeNote = getNoteFromTextEditor();
        // Verify template body is applied
        const today = new Date();
        const dd = String(today.getDate()).padStart(2, "0");
        expect(activeNote.fname).toEqual(`daisy.journey.${dd}`);
        // TODO: Enable when/if we support applying templates to journals with configured dateFormat
        //expect(activeNote.body.includes(TEMPLATE_BODY)).toBeTruthy();

        // Verify trait is applied
        const traits = (activeNote as any).traitIds;
        expect(traits.length === 1 && traits[0] === "journalNote").toBeTruthy();

        // Verify schema is created
        const engine = ExtensionProvider.getEngine();
        const dailySchema = engine.schemas["daisy"];
        expect(dailySchema.fname === "dendron.daisy").toBeTruthy();
        expect(_.size(dailySchema.schemas) === 5).toBeTruthy();
      });
    }
  );

  describeMultiWS(
    "GIVEN a basic workspace with a daily journal template note",
    {
      preSetupHook: ENGINE_HOOKS.setupBasic,
      preActivateHook: async ({ wsRoot, vaults }) => {
        await NoteTestUtilsV4.createNote({
          fname: CreateDailyJournalCommand.DENDRON_TEMPLATES_FNAME + ".daily",
          wsRoot,
          vault: vaults[0],
          body: TEMPLATE_BODY,
        });
      },
    },
    () => {
      test("WHEN CreateDailyJournalCommand is executed multiple times, then template and schema are not generated again", async () => {
        const ext = ExtensionProvider.getExtension();
        const cmd = new CreateDailyJournalCommand(ext);

        await cmd.run();
        const activeNote = getNoteFromTextEditor();
        // Verify template body is applied
        expect(activeNote.fname.startsWith("daily.journal")).toBeTruthy();
        expect(activeNote.body.includes(TEMPLATE_BODY)).toBeTruthy();

        // Verify trait is applied
        const traits = (activeNote as any).traitIds;
        expect(traits.length === 1 && traits[0] === "journalNote").toBeTruthy();

        // Verify schema is created
        const engine = ExtensionProvider.getEngine();
        const dailySchema = engine.schemas["daily"];
        expect(dailySchema.fname === "dendron.daily").toBeTruthy();
        expect(_.size(dailySchema.schemas) === 5).toBeTruthy();
        const numNotesBefore = _.size(engine.notes);
        const numSchemasBefore = _.size(engine.schemas);
        await cmd.run();
        expect(numNotesBefore).toEqual(_.size(engine.notes));
        expect(numSchemasBefore).toEqual(_.size(engine.schemas));
      });
    }
  );
});
Example #28
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 #29
Source File: CreateSchemaFromHierarchyCommand.test.ts    From dendron with GNU Affero General Public License v3.0 4 votes vote down vote up
suite("CreateSchemaFromHierarchyCommand tests", () => {
  const ctx: vscode.ExtensionContext = setupBeforeAfter(this, {
    noSetTimeout: true,
  });

  function runTestWithInlineSchemaSetup(
    func: ({ vaults }: { vaults: any }) => Promise<void>
  ) {
    runLegacyMultiWorkspaceTest({
      ctx,
      preSetupHook: ENGINE_HOOKS.setupInlineSchema,
      onInit: func,
    });
  }

  describe(`HierarchyLevel tests:`, () => {
    describe(`GIVEN 'languages.python.*' hierarchy level`, () => {
      let level: HierarchyLevel;
      beforeEach(() => {
        level = new HierarchyLevel(2, ["languages", "python", "data"]);
      });

      describe(`isCandidateNote tests`, () => {
        [
          { in: "languages", out: false },
          { in: "languages.python", out: false },
          { in: "languages.python.data", out: true },
          { in: "languages.python.data.integer", out: true },
          { in: "languages.python.machine-learning", out: true },
        ].forEach((input) =>
          it(`WHEN testing '${input.in}' note THEN ${
            input.out ? "do match" : "do NOT match"
          }.`, () => {
            expect(level.isCandidateNote(input.in)).toEqual(input.out);
          })
        );
      });
    });
    describe(`GIVEN 'languages.*.data' hierarchy level`, () => {
      let level: HierarchyLevel;
      beforeEach(() => {
        level = new HierarchyLevel(1, ["languages", "python", "data"]);
      });

      it(`THEN have expected label`, () => {
        expect(level.label).toEqual("languages.*.data (python)");
      });

      it(`THEN have expected top id`, () => {
        expect(level.topId()).toEqual("languages");
      });

      it(`THEN produce expected default schema name`, () => {
        expect(level.getDefaultSchemaName()).toEqual("languages");
      });

      describe(`tokenize tests`, () => {
        [
          {
            in: "languages.python.data",
            out: ["languages", "*", "data"],
          },
          {
            in: "languages.python.data.integer",
            out: ["languages", "*", "data", "integer"],
          },
          {
            in: "languages.python.machine-learning",
            out: ["languages", "*", "machine-learning"],
          },
        ].forEach((input) =>
          it(`WHEN fname is '${input.in}' THEN tokenize to '${JSON.stringify(
            input.out
          )}'.`, () => {
            expect(level.tokenize(input.in)).toEqual(input.out);
          })
        );
      });

      describe(`isCandidateNote tests`, () => {
        [
          { in: "languages", out: false },
          { in: "languages.python", out: true },
          { in: "languages.python.data", out: true },
          { in: "languages.python.data.integer", out: true },
          { in: "languages.python.machine-learning", out: true },
        ].forEach((input) =>
          it(`WHEN testing '${input.in}' note THEN ${
            input.out ? "do match" : "do NOT match"
          }.`, () => {
            expect(level.isCandidateNote(input.in)).toEqual(input.out);
          })
        );
      });
    });
  });

  describe(`SchemaCreator tests: `, () => {
    let schemaCandidates: SchemaCandidate[];

    beforeEach(async () => {
      schemaCandidates = await createTestSchemaCandidatesDefault();
    });

    it(`WHEN valid schema candidates THEN generate schema`, () => {
      const actual = SchemaCreator.makeSchemaBody({
        candidates: schemaCandidates,
        hierarchyLevel: new HierarchyLevel(1, ["languages", "python", "data"]),
      });

      const expected = `version: 1
imports: []
schemas:
  - id: languages
    title: languages
    parent: root
    children:
      - pattern: '*'
        children:
          - pattern: data
            children:
              - pattern: integer
              - pattern: string
          - pattern: machine-learning
            children:
              - pattern: pandas\n`;

      expect(expected).toEqual(actual);
    });
  });

  describe(`CreateSchemaFromHierarchyCommand`, () => {
    describe(`sanityCheck tests:`, () => {
      it(`WHEN no active text editor THEN give error`, async () => {
        sinon.stub(VSCodeUtils, "getActiveTextEditor").returns(undefined);

        const cmd = new CreateSchemaFromHierarchyCommand();
        const actual = await cmd.sanityCheck();
        expect(actual?.includes("No note document open")).toBeTruthy();
      });
    });

    describe(`formatSchemaCandidates tests:`, () => {
      describe(`WHEN formatting valid input idx=1 ['h1.h2.h3a', 'h1.h2.h3b']`, () => {
        let schemaCandidates: SchemaCandidate[];

        beforeEach(async () => {
          const cmd = new CreateSchemaFromHierarchyCommand();

          const notes = await noteFactory.createForFNames([
            "h1.h2.h3a",
            "h1.h2.h3b",
          ]);

          schemaCandidates = cmd.formatSchemaCandidates(
            notes,
            new HierarchyLevel(1, ["h1", "h2", "h3a"])
          );
        });

        it(`THEN include 'h1.*.h3a' label`, () => {
          expect(
            schemaCandidates.some((cand) => cand.label === "h1.*.h3a")
          ).toBeTruthy();
        });
      });
    });

    describe(`addAnalyticsPayload tests`, () => {
      it(`WHEN successfully created THEN format success`, () => {
        const actual = buildCmd().addAnalyticsPayload(
          { isHappy: true },
          { successfullyCreated: true }
        );
        expect(actual).toEqual({ successfullyCreated: true });
      });

      it(`WHEN input was not happy THEN format the stop reason`, () => {
        const actual = buildCmd().addAnalyticsPayload({
          isHappy: false,
          stopReason: StopReason.DID_NOT_PICK_SCHEMA_FILE_NAME,
        });
        expect(actual).toEqual({
          successfullyCreated: false,
          stopReason: StopReason.DID_NOT_PICK_SCHEMA_FILE_NAME,
        });
      });

      it("WHEN stop reason is not present but create is not happy THEN format create not happy", () => {
        const actual = buildCmd().addAnalyticsPayload({ isHappy: false });
        expect(actual).toEqual({
          successfullyCreated: false,
        });
      });
    });
  });

  describe(`UserQueries tests`, () => {
    let ONE_SCHEMA_CAND: SchemaCandidate[];
    let TWO_SCHEMA_CAND: SchemaCandidate[];
    beforeEach(async () => {
      ONE_SCHEMA_CAND = await createSchemaCandidates(["1"]);
      TWO_SCHEMA_CAND = await createSchemaCandidates(["1", "2"]);
    });

    describe(`haveUserPickSchemaFileName tests:`, () => {
      it(`WHEN user picked non existent name THEN prompt use the name`, (done) => {
        runTestWithInlineSchemaSetup(async ({ vaults }) => {
          const vault: DVault = vaults[0];
          sinon
            .stub(VSCodeUtils, "showInputBox")
            .onFirstCall()
            .returns(Promise.resolve("happy"));

          const actual = await UserQueries.promptUserForSchemaFileName(
            new HierarchyLevel(1, ["languages", "python", "data"]),
            vault
          );

          expect(actual).toEqual("happy");
          done();
        });
      });

      it(`WHEN user picked pre existing name THEN prompt again`, (done) => {
        runTestWithInlineSchemaSetup(async ({ vaults }) => {
          const vault: DVault = vaults[0];
          sinon
            .stub(VSCodeUtils, "showInputBox")
            .onFirstCall()
            // 'inlined' already exists.
            .returns(Promise.resolve("inlined"))
            .returns(Promise.resolve("happy"));

          const actual = await UserQueries.promptUserForSchemaFileName(
            TEST_HIERARCHY_LVL,
            vault
          );

          expect(actual).toEqual("happy");
          done();
        });
      });
    });

    describe(`haveUserSelectHierarchyLevel tests:`, () => {
      beforeEach(() => {
        sinon
          .stub(VSCodeUtils, "showQuickPick")
          .returns(Promise.resolve(TEST_HIERARCHY_LVL));
      });

      it(`WHEN happy input THEN return user picked hierarchy level`, (done) => {
        runTestWithInlineSchemaSetup(async () => {
          const actual = await UserQueries.promptUserToSelectHierarchyLevel(
            "/tmp/languages.python.data.md"
          );

          expect(actual.hierarchyLevel).toEqual(TEST_HIERARCHY_LVL);
          done();
        });
      });

      // TODO: This test needs to be fixed
      it.skip(`WHEN hierarchy depth of current file is too small THEN undefined`, (done) => {
        runTestWithInlineSchemaSetup(async () => {
          const actual = await UserQueries.promptUserToSelectHierarchyLevel(
            "/tmp/languages.data.md"
          );

          expect(actual.hierarchyLevel).toEqual(undefined);
          done();
        });
      });

      // TODO: This test needs to be fixed
      it.skip(`WHEN top id is already used by existing schema THEN undefined`, (done) => {
        runTestWithInlineSchemaSetup(async () => {
          const actual = await UserQueries.promptUserToSelectHierarchyLevel(
            "/tmp/daily.python.data.md"
          );

          expect(actual.hierarchyLevel).toEqual(undefined);
          done();
        });
      });
    });

    describe(`hasSelected tests:`, () => {
      it(`WHEN curr is greater than prev THEN true`, () => {
        expect(
          UserQueries.hasSelected(ONE_SCHEMA_CAND, TWO_SCHEMA_CAND)
        ).toEqual(true);
      });

      it(`WHEN curr is equal to prev THEN false`, () => {
        expect(
          UserQueries.hasSelected(ONE_SCHEMA_CAND, ONE_SCHEMA_CAND)
        ).toEqual(false);
      });

      it(`WHEN curr is less than prev THEN false`, () => {
        expect(
          UserQueries.hasSelected(TWO_SCHEMA_CAND, ONE_SCHEMA_CAND)
        ).toEqual(false);
      });
    });

    describe(`hasUnselected tests:`, () => {
      it("WHEN curr is greater thhan prev THEN false", () => {
        expect(
          UserQueries.hasUnselected(ONE_SCHEMA_CAND, TWO_SCHEMA_CAND)
        ).toEqual(false);
      });

      it(`WHEN curr is equal to prev THEN false`, () => {
        expect(
          UserQueries.hasUnselected(ONE_SCHEMA_CAND, ONE_SCHEMA_CAND)
        ).toEqual(false);
      });

      it(`WHEN curr is less than prev THEN true`, () => {
        expect(
          UserQueries.hasUnselected(TWO_SCHEMA_CAND, ONE_SCHEMA_CAND)
        ).toEqual(true);
      });
    });

    describe(`findUncheckedItem tests:`, () => {
      it(`WHEN unchecked THEN get the candidate`, () => {
        const actual = UserQueries.findUncheckedItem(
          TWO_SCHEMA_CAND,
          ONE_SCHEMA_CAND
        );
        expect(actual.note.fname).toEqual("2");
      });
    });

    describe(`findCheckedItems tests:`, () => {
      it(`WHEN checked THEN get the candidate`, () => {
        const actual = UserQueries.findCheckedItem(
          ONE_SCHEMA_CAND,
          TWO_SCHEMA_CAND
        );
        expect(actual.note.fname).toEqual("2");
      });
    });

    describe(`determineAfterSelect tests:`, () => {
      describe(`WHEN a child of non selected hierarchy is checked`, () => {
        let actual: SchemaCandidate[];

        beforeEach(async () => {
          const all = await createSchemaCandidates([
            "h1.h2",
            "h1.h2.h3a",
            "h1.h2.h3b",
            "h1.h2.h3c",
            "h1.h2.h3c.h4",
            "otherHierarchy.h2",
          ]);

          const prev = await createSchemaCandidates(["otherHierarchy.h2"]);

          const curr = await createSchemaCandidates([
            "otherHierarchy.h2",
            "h1.h2.h3c.h4",
          ]);

          actual = UserQueries.determineAfterSelect(prev, curr, all);
        });

        it(`THEN make sure length is as expected`, () => {
          expect(actual.length).toEqual(4);
        });

        it("THEN retain the checked items", () => {
          expect(
            actual.some((c) => c.note.fname === "otherHierarchy.h2")
          ).toBeTruthy();
          expect(actual.some((c) => c.note.fname === "h1.h2.h3c")).toBeTruthy();
        });

        it(`THEN select parent of item`, () => {
          expect(actual.some((c) => c.note.fname === "h1.h2.h3c")).toBeTruthy();
        });

        it(`THEN select grand-parent (ancestor) of item`, () => {
          expect(actual.some((c) => c.note.fname === "h1.h2")).toBeTruthy();
        });
      });
    });

    describe(`determineAfterUnselect tests:`, () => {
      describe(`WHEN ancestor is unchecked`, () => {
        let actual: SchemaCandidate[];

        beforeEach(async () => {
          const prev = await createSchemaCandidates([
            "h1.h2",
            "h1.h2.h3a",
            "h1.h2.h3c.h4",
            "otherHierarchy.h2",
          ]);

          const curr = await createSchemaCandidates([
            "h1.h2.h3a",
            "h1.h2.h3c.h4",
            "otherHierarchy.h2",
          ]);

          actual = UserQueries.determineAfterUnselect(prev, curr);
        });

        it(`THEN keep another hierarchy as is`, () => {
          expect(
            actual.some((c) => c.note.fname === "otherHierarchy.h2")
          ).toBeTruthy();
        });

        it(`THEN unselect all the descendents`, () => {
          expect(actual.length).toEqual(1);
        });
      });
    });
  });
});