obsidian#InternalPlugins TypeScript Examples

The following examples show how to use obsidian#InternalPlugins. 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: commandHandler.test.ts    From obsidian-switcher-plus with GNU General Public License v3.0 6 votes vote down vote up
function makeInternalPluginList(
  commandPalettePlugin: MockProxy<InstalledPlugin>,
): MockProxy<InternalPlugins> {
  const mockPlugins = mock<Record<string, InstalledPlugin>>({
    [COMMAND_PALETTE_PLUGIN_ID]: commandPalettePlugin,
  });

  const mockInternalPlugins = mock<InternalPlugins>({ plugins: mockPlugins });

  mockInternalPlugins.getPluginById.mockImplementation((id) => mockPlugins[id]);

  return mockInternalPlugins;
}
Example #2
Source File: starredHandler.test.ts    From obsidian-switcher-plus with GNU General Public License v3.0 6 votes vote down vote up
function makeInternalPluginList(
  starredPlugin: MockProxy<InstalledPlugin>,
): MockProxy<InternalPlugins> {
  const mockPlugins = mock<Record<string, InstalledPlugin>>({
    starred: starredPlugin,
  });

  const mockInternalPlugins = mock<InternalPlugins>({ plugins: mockPlugins });

  mockInternalPlugins.getPluginById
    .calledWith(STARRED_PLUGIN_ID)
    .mockReturnValue(mockPlugins[STARRED_PLUGIN_ID]);

  return mockInternalPlugins;
}
Example #3
Source File: workspaceHandler.test.ts    From obsidian-switcher-plus with GNU General Public License v3.0 6 votes vote down vote up
function makeInternalPluginList(
  workspacePlugin: MockProxy<InstalledPlugin>,
): MockProxy<InternalPlugins> {
  const mockPlugins = mock<Record<string, InstalledPlugin>>({
    workspaces: workspacePlugin,
  });

  const mockInternalPlugins = mock<InternalPlugins>({ plugins: mockPlugins });

  mockInternalPlugins.getPluginById.mockImplementation((id) => mockPlugins[id]);

  return mockInternalPlugins;
}
Example #4
Source File: commandHandler.test.ts    From obsidian-switcher-plus with GNU General Public License v3.0 4 votes vote down vote up
describe('commandHandler', () => {
  let settings: SwitcherPlusSettings;
  let mockApp: MockProxy<App>;
  let mockInternalPlugins: MockProxy<InternalPlugins>;
  let mockCommandPalettePluginInstance: MockProxy<CommandPalettePluginInstance>;
  let mockCommands: Command[];
  let sut: CommandHandler;

  beforeAll(() => {
    const commandPalettePluginInstall = makeCommandPalettePluginInstall();
    mockCommandPalettePluginInstance =
      commandPalettePluginInstall.instance as MockProxy<CommandPalettePluginInstance>;

    mockCommands = [
      makeCommandItem(),
      makeCommandItem(),
      makeCommandItem(),
      makeCommandItem(),
      makeCommandItem({ name: expectedCommandName }),
    ];

    mockInternalPlugins = makeInternalPluginList(commandPalettePluginInstall);
    mockApp = mock<App>({
      internalPlugins: mockInternalPlugins,
      commands: {
        listCommands: jest.fn(() => mockCommands),
        executeCommandById: jest.fn(),
      },
    });

    settings = new SwitcherPlusSettings(null);
    jest.spyOn(settings, 'commandListCommand', 'get').mockReturnValue(commandTrigger);

    sut = new CommandHandler(mockApp, settings);
  });

  describe('commandString', () => {
    it('should return commandListCommand trigger', () => {
      expect(sut.commandString).toBe(commandTrigger);
    });
  });

  describe('validateCommand', () => {
    let inputText: string;
    let startIndex: number;
    const filterText = 'foo';

    beforeAll(() => {
      inputText = `${commandTrigger}${filterText}`;
      startIndex = commandTrigger.length;
    });

    it('should validate parsed input', () => {
      const inputInfo = new InputInfo(inputText);

      sut.validateCommand(inputInfo, startIndex, filterText, null, null);
      expect(inputInfo.mode).toBe(Mode.CommandList);

      const commandListCmd = inputInfo.parsedCommand();
      expect(commandListCmd.parsedInput).toBe(filterText);
      expect(commandListCmd.isValidated).toBe(true);
    });
  });

  describe('getSuggestions', () => {
    test('with falsy input, it should return an empty array', () => {
      const results = sut.getSuggestions(null);

      expect(results).not.toBeNull();
      expect(results).toBeInstanceOf(Array);
      expect(results).toHaveLength(0);
    });

    test('with default settings, it should return suggestions for command list mode', () => {
      const inputInfo = new InputInfo(commandTrigger);
      const results = sut.getSuggestions(inputInfo);

      expect(results).not.toBeNull();
      expect(results).toBeInstanceOf(Array);

      const resultCommandIds = new Set(results.map((sugg) => sugg.item.id));

      expect(results).toHaveLength(mockCommands.length);
      expect(mockCommands.every((command) => resultCommandIds.has(command.id))).toBe(
        true,
      );
      expect(results.every((sugg) => sugg.type === 'command')).toBe(true);
      expect(mockInternalPlugins.getPluginById).toHaveBeenCalledWith(
        COMMAND_PALETTE_PLUGIN_ID,
      );
    });

    test('with filter search term, it should return only matching suggestions for command list mode', () => {
      const filterText = expectedCommandName;

      const expectedItem = mockCommands.find(
        (command) => command.name === expectedCommandName,
      );

      const mockPrepareQuery = jest.mocked<typeof prepareQuery>(prepareQuery);
      mockPrepareQuery.mockReturnValueOnce(makePreparedQuery(filterText));

      const mockFuzzySearch = jest.mocked<typeof fuzzySearch>(fuzzySearch);

      mockFuzzySearch.mockImplementation((_q, text: string) => {
        const match = makeFuzzyMatch();
        return text.startsWith(filterText) ? match : null;
      });

      const inputInfo = new InputInfo(`${commandTrigger}${filterText}`);
      const results = sut.getSuggestions(inputInfo);

      expect(results).toBeInstanceOf(Array);
      expect(results).toHaveLength(1);

      const onlyResult = results[0];
      expect(onlyResult).toHaveProperty('type', 'command');
      expect(onlyResult.item.id).toBe(expectedItem.id);
      expect(onlyResult.item.name).toBe(expectedItem.name);

      expect(mockFuzzySearch).toHaveBeenCalled();
      expect(mockPrepareQuery).toHaveBeenCalled();
      expect(mockInternalPlugins.getPluginById).toHaveBeenCalled();

      mockFuzzySearch.mockReset();
    });
  });

  describe('renderSuggestion', () => {
    it('should not throw an error with a null suggestion', () => {
      expect(() => sut.renderSuggestion(null, null)).not.toThrow();
    });

    it('should render a suggestion with match offsets', () => {
      const mockParentEl = mock<HTMLElement>();
      const mockRenderResults = jest.mocked<typeof renderResults>(renderResults);

      const match = makeFuzzyMatch();
      const item = mockCommands[0];

      const sugg = mock<CommandSuggestion>({ item, match });
      sut.renderSuggestion(sugg, mockParentEl);

      expect(mockRenderResults).toHaveBeenCalledWith(mockParentEl, item.name, match);
    });
  });

  describe('onChooseSuggestion', () => {
    it('should not throw an error with a null suggestion', () => {
      expect(() => sut.onChooseSuggestion(null)).not.toThrow();
    });

    it('should tell the app to execute the command with the chosen ID', () => {
      const match = makeFuzzyMatch();
      const item = mockCommands[0];

      const sugg = mock<CommandSuggestion>({ item, match });

      sut.onChooseSuggestion(sugg);

      expect(mockInternalPlugins.getPluginById).toHaveBeenCalled();
      expect(mockApp.commands.executeCommandById).toHaveBeenCalledWith(item.id);
    });
  });

  describe('getItems', () => {
    let oldCommands: Command[];
    let oldPinnedCommandIds: string[] | null;

    beforeAll(() => {
      oldCommands = mockCommands;
      oldPinnedCommandIds = mockCommandPalettePluginInstance.options.pinned;
    });

    afterAll(() => {
      mockCommands = oldCommands;
      mockCommandPalettePluginInstance.options.pinned = oldPinnedCommandIds;
    });

    it('should order commands by name', () => {
      mockCommands = [
        makeCommandItem({ name: 'Command C' }),
        makeCommandItem({ name: 'Command B' }),
        makeCommandItem({ name: 'Command A' }),
        makeCommandItem({ name: 'Command D' }),
        makeCommandItem({ name: 'Command C' }),
      ];

      const results = sut.getItems();
      expect(results).toHaveLength(5);
      expect(results[0].name).toBe('Command A');
      expect(results[1].name).toBe('Command B');
      expect(results[2].name).toBe('Command C');
      expect(results[3].name).toBe('Command C');
      expect(results[4].name).toBe('Command D');
    });

    it('should order pinned commands first', () => {
      mockCommands = [
        makeCommandItem({ name: 'Command B' }),
        makeCommandItem({ name: 'Command A' }),
        makeCommandItem({ name: 'Command Pinned 1', id: 'pinned:command1' }),
        makeCommandItem({ name: 'Command Pinned 2', id: 'pinned:command2' }),
      ];
      mockCommandPalettePluginInstance.options.pinned = [
        'pinned:command1',
        'pinned:command2',
      ];

      const results = sut.getItems();
      expect(results).toHaveLength(4);
      expect(results[0].name).toBe('Command Pinned 1');
      expect(results[1].name).toBe('Command Pinned 2');
      expect(results[2].name).toBe('Command A');
      expect(results[3].name).toBe('Command B');
    });
  });
});
Example #5
Source File: starredHandler.test.ts    From obsidian-switcher-plus with GNU General Public License v3.0 4 votes vote down vote up
describe('starredHandler', () => {
  let settings: SwitcherPlusSettings;
  let mockWorkspace: MockProxy<Workspace>;
  let mockVault: MockProxy<Vault>;
  let mockApp: MockProxy<App>;
  let mockInternalPlugins: MockProxy<InternalPlugins>;
  let mockPluginInstance: MockProxy<StarredPluginInstance>;
  let sut: StarredHandler;

  beforeAll(() => {
    const pluginInstall = makeStarredPluginInstall();
    mockPluginInstance = pluginInstall.instance as StarredPluginInstance;
    mockInternalPlugins = makeInternalPluginList(pluginInstall);

    mockWorkspace = mock<Workspace>();
    mockVault = mock<Vault>();
    mockApp = mock<App>({
      workspace: mockWorkspace,
      vault: mockVault,
      internalPlugins: mockInternalPlugins,
    });

    settings = new SwitcherPlusSettings(null);
    jest.spyOn(settings, 'starredListCommand', 'get').mockReturnValue(starredTrigger);

    sut = new StarredHandler(mockApp, settings);
  });

  describe('commandString', () => {
    it('should return starredListCommand trigger', () => {
      expect(sut.commandString).toBe(starredTrigger);
    });
  });

  describe('validateCommand', () => {
    const filterText = 'foo';
    const inputText = `${starredTrigger}${filterText}`;
    const startIndex = starredTrigger.length;

    it('should validate parsed input with starred plugin enabled', () => {
      const inputInfo = new InputInfo(inputText);

      sut.validateCommand(inputInfo, startIndex, filterText, null, null);

      expect(inputInfo.mode).toBe(Mode.StarredList);

      const starredCmd = inputInfo.parsedCommand();
      expect(starredCmd.parsedInput).toBe(filterText);
      expect(starredCmd.isValidated).toBe(true);
      expect(mockApp.internalPlugins.getPluginById).toHaveBeenCalledWith(
        STARRED_PLUGIN_ID,
      );
    });

    it('should not validate parsed input with starred plugin disabled', () => {
      mockInternalPlugins.getPluginById.mockReturnValueOnce({
        enabled: false,
        instance: null,
      });

      const inputInfo = new InputInfo(inputText);

      sut.validateCommand(inputInfo, startIndex, filterText, null, null);
      expect(inputInfo.mode).toBe(Mode.Standard);

      const starredCmd = inputInfo.parsedCommand();
      expect(starredCmd.parsedInput).toBe(null);
      expect(starredCmd.isValidated).toBe(false);
      expect(mockInternalPlugins.getPluginById).toHaveBeenCalledWith(STARRED_PLUGIN_ID);
    });
  });

  describe('getSuggestions', () => {
    let expectedStarredPaths: string[];

    beforeAll(() => {
      expectedStarredPaths = mockPluginInstance.items
        .filter((v): v is FileStarredItem => isFileStarredItem(v))
        .map((v) => v.path);

      mockVault.getAbstractFileByPath.mockImplementation((path) => {
        let file: TFile = null;

        if (expectedStarredPaths.includes(path)) {
          file = new TFile();
          file.extension = 'md';
          file.path = path;
          file.basename = filenameFromPath(stripMDExtensionFromPath(file));
        }

        return file;
      });
    });

    afterAll(() => {
      mockReset(mockVault);
    });

    test('with falsy input, it should return an empty array', () => {
      const results = sut.getSuggestions(null);

      expect(results).toBeInstanceOf(Array);
      expect(results).toHaveLength(0);
    });

    test('that StarredSuggestion have a file property to enable interop with other plugins (like HoverEditor)', () => {
      const inputInfo = new InputInfo(starredTrigger);
      const results = sut.getSuggestions(inputInfo);

      const fileSuggs = results.filter((v) => isFileStarredItem(v.item));

      expect(fileSuggs.every((v) => v.file !== null)).toBe(true);
    });

    test('with default settings, it should return suggestions for files that have been starred', () => {
      const inputInfo = new InputInfo(starredTrigger);
      const results = sut.getSuggestions(inputInfo);

      const resultStarredPaths = new Set(
        results.map((sugg) => (sugg.item as FileStarredItem).path),
      );

      expect(results).toBeInstanceOf(Array);
      expect(results).toHaveLength(expectedStarredPaths.length);

      expect(expectedStarredPaths.every((item) => resultStarredPaths.has(item))).toBe(
        true,
      );

      expect(results.every((sugg) => sugg.type === 'starred')).toBe(true);
      expect(mockInternalPlugins.getPluginById).toHaveBeenCalledWith(STARRED_PLUGIN_ID);
      expect(results.every((sugg) => sugg.item.type === 'file')).toBe(true);
    });

    test('with filter search term, it should return only matching suggestions for starred mode', () => {
      const filterText = expectedStarredFileTitle;

      const expectedItem = mockPluginInstance.items.find((v): v is FileStarredItem => {
        return isFileStarredItem(v) && v.title === filterText;
      });

      const mockPrepareQuery = jest.mocked<typeof prepareQuery>(prepareQuery);
      mockPrepareQuery.mockReturnValueOnce(makePreparedQuery(filterText));

      const mockFuzzySearch = jest.mocked<typeof fuzzySearch>(fuzzySearch);

      mockFuzzySearch.mockImplementation((_q, text: string) => {
        const match = makeFuzzyMatch();
        return text.startsWith(filterText) ? match : null;
      });

      const inputInfo = new InputInfo(`${starredTrigger}${filterText}`);
      const results = sut.getSuggestions(inputInfo);

      expect(results).toBeInstanceOf(Array);
      expect(results).toHaveLength(1);

      const onlyResult = results[0];
      expect(onlyResult).toHaveProperty('type', 'starred');
      expect((onlyResult.item as FileStarredItem).path).toBe(expectedItem.path);

      expect(mockFuzzySearch).toHaveBeenCalled();
      expect(mockPrepareQuery).toHaveBeenCalled();
      expect(mockInternalPlugins.getPluginById).toHaveBeenCalled();

      mockFuzzySearch.mockReset();
    });
  });

  describe('renderSuggestion', () => {
    it('should not throw an error with a null suggestion', () => {
      expect(() => sut.renderSuggestion(null, null)).not.toThrow();
    });

    it('should render a suggestion with match offsets', () => {
      const mockParentEl = mock<HTMLElement>();
      const mockRenderResults = jest.mocked<typeof renderResults>(renderResults);

      const match = makeFuzzyMatch();
      const item = mockPluginInstance.items.find((v): v is FileStarredItem =>
        isFileStarredItem(v),
      );

      const sugg = mock<StarredSuggestion>({ item, match });
      sut.renderSuggestion(sugg, mockParentEl);

      expect(mockRenderResults).toHaveBeenCalledWith(mockParentEl, item.title, match);
    });
  });

  describe('onChooseSuggestion', () => {
    let sugg: MockProxy<StarredSuggestion>;
    const evt = mock<MouseEvent>();
    const mockKeymap = jest.mocked<typeof Keymap>(Keymap);

    beforeAll(() => {
      const item = mockPluginInstance.items.find((v): v is FileStarredItem =>
        isFileStarredItem(v),
      );

      const fileContainerLeaf = makeLeaf();
      fileContainerLeaf.openFile.mockResolvedValueOnce();
      mockWorkspace.getLeaf.mockReturnValue(fileContainerLeaf);

      sugg = mock<StarredSuggestion>({ item, file: new TFile() });
    });

    it('should not throw an error with a null suggestion', () => {
      expect(() => sut.onChooseSuggestion(null, null)).not.toThrow();
    });

    it('should open a new leaf for the chosen suggestion', () => {
      const isModDown = true;
      const navigateToLeafOrOpenFileSpy = jest.spyOn(
        Handler.prototype,
        'navigateToLeafOrOpenFile',
      );
      mockKeymap.isModEvent.mockReturnValueOnce(isModDown);

      sut.onChooseSuggestion(sugg, evt);

      expect(mockKeymap.isModEvent).toHaveBeenCalledWith(evt);
      expect(navigateToLeafOrOpenFileSpy).toHaveBeenCalledWith(
        isModDown,
        sugg.file,
        expect.any(String),
      );

      navigateToLeafOrOpenFileSpy.mockRestore();
    });
  });

  describe('getTFileByPath', () => {
    it('should return TFile object for path that exists', () => {
      const file = new TFile();

      mockVault.getAbstractFileByPath.calledWith(file.path).mockReturnValueOnce(file);

      const result = sut.getTFileByPath(file.path);

      expect(result).toBe(file);
      expect(mockVault.getAbstractFileByPath).toHaveBeenCalledWith(file.path);
    });

    it('should return return null for a path that does not exist', () => {
      const file = new TFile();

      mockVault.getAbstractFileByPath.calledWith(file.path).mockReturnValueOnce(null);

      const result = sut.getTFileByPath(file.path);

      expect(result).toBe(null);
      expect(mockVault.getAbstractFileByPath).toHaveBeenCalledWith(file.path);
    });
  });

  describe('getItems', () => {
    let oldItems: StarredPluginItem[];

    beforeAll(() => {
      oldItems = mockPluginInstance.items;
    });

    afterAll(() => {
      mockPluginInstance.items = oldItems;
    });

    it('should always use the file basename instead of the starred item title property', () => {
      const file = new TFile();

      const starredItem = makeFileStarredItem('starredItemTitle', file.path);
      mockPluginInstance.items = [starredItem];

      mockVault.getAbstractFileByPath.calledWith(file.path).mockReturnValueOnce(file);

      const results = sut.getItems();
      const resultItem = results[0].item;

      expect(results).toHaveLength(1);
      expect(resultItem.title).toBe(file.basename);
      expect(mockVault.getAbstractFileByPath).toBeCalledWith(file.path);
    });

    it('should not return items for starred items where the source file does not exist', () => {
      const file = new TFile();

      mockVault.getAbstractFileByPath.calledWith(file.path).mockReturnValueOnce(null);

      const starredItem = makeFileStarredItem('starredItemTitle', file.path);
      mockPluginInstance.items = [starredItem];

      const results = sut.getItems();

      expect(results).toHaveLength(0);
      expect(mockVault.getAbstractFileByPath).toBeCalledWith(file.path);
    });
  });
});
Example #6
Source File: workspaceHandler.test.ts    From obsidian-switcher-plus with GNU General Public License v3.0 4 votes vote down vote up
describe('workspaceHandler', () => {
  let settings: SwitcherPlusSettings;
  let mockApp: MockProxy<App>;
  let mockInternalPlugins: MockProxy<InternalPlugins>;
  let mockWsPluginInstance: MockProxy<WorkspacesPluginInstance>;
  let sut: WorkspaceHandler;
  let expectedWorkspaceIds: string[];
  let suggestionInstance: WorkspaceSuggestion;

  beforeAll(() => {
    const workspacePluginInstall = makeWorkspacesPluginInstall();
    mockWsPluginInstance =
      workspacePluginInstall.instance as MockProxy<WorkspacesPluginInstance>;

    mockInternalPlugins = makeInternalPluginList(workspacePluginInstall);
    mockApp = mock<App>({
      internalPlugins: mockInternalPlugins,
    });

    settings = new SwitcherPlusSettings(null);
    jest.spyOn(settings, 'workspaceListCommand', 'get').mockReturnValue(workspaceTrigger);

    expectedWorkspaceIds = Object.keys(mockWsPluginInstance.workspaces);
    suggestionInstance = {
      type: 'workspace',
      item: { type: 'workspaceInfo', id: expectedWorkspaceIds[0] },
      match: makeFuzzyMatch(),
    };

    sut = new WorkspaceHandler(mockApp, settings);
  });

  describe('commandString', () => {
    it('should return workspaceListCommand trigger', () => {
      expect(sut.commandString).toBe(workspaceTrigger);
    });
  });

  describe('validateCommand', () => {
    let inputText: string;
    let startIndex: number;
    const filterText = 'foo';

    beforeAll(() => {
      inputText = `${workspaceTrigger}${filterText}`;
      startIndex = workspaceTrigger.length;
    });

    it('should validate parsed input with workspace plugin enabled', () => {
      const inputInfo = new InputInfo(inputText);

      sut.validateCommand(inputInfo, startIndex, filterText, null, null);
      expect(inputInfo.mode).toBe(Mode.WorkspaceList);

      const workspaceCmd = inputInfo.parsedCommand();
      expect(workspaceCmd.parsedInput).toBe(filterText);
      expect(workspaceCmd.isValidated).toBe(true);
      expect(mockApp.internalPlugins.getPluginById).toHaveBeenCalledWith(
        WORKSPACE_PLUGIN_ID,
      );
    });

    it('should not validate parsed input with workspace plugin disabled', () => {
      mockInternalPlugins.getPluginById.mockReturnValueOnce({
        enabled: false,
        instance: null,
      });

      const inputInfo = new InputInfo(inputText);

      sut.validateCommand(inputInfo, startIndex, filterText, null, null);
      expect(inputInfo.mode).toBe(Mode.Standard);

      const workspaceCmd = inputInfo.parsedCommand();
      expect(workspaceCmd.parsedInput).toBe(null);
      expect(workspaceCmd.isValidated).toBe(false);
      expect(mockInternalPlugins.getPluginById).toHaveBeenCalledWith(WORKSPACE_PLUGIN_ID);
    });
  });

  describe('getSuggestions', () => {
    test('with falsy input, it should return an empty array', () => {
      const results = sut.getSuggestions(null);

      expect(results).not.toBeNull();
      expect(results).toBeInstanceOf(Array);
      expect(results).toHaveLength(0);
    });

    test('with default settings, it should return suggestions for workspace mode', () => {
      const inputInfo = new InputInfo(workspaceTrigger);
      const results = sut.getSuggestions(inputInfo);

      expect(results).not.toBeNull();
      expect(results).toBeInstanceOf(Array);

      const resultWorkspaceIds = new Set(results.map((sugg) => sugg.item.id));

      expect(results).toHaveLength(expectedWorkspaceIds.length);
      expect(expectedWorkspaceIds.every((id) => resultWorkspaceIds.has(id))).toBe(true);
      expect(results.every((sugg) => sugg.type === 'workspace')).toBe(true);
      expect(mockInternalPlugins.getPluginById).toHaveBeenCalledWith(WORKSPACE_PLUGIN_ID);
    });

    test('with filter search term, it should return only matching suggestions for workspace mode', () => {
      const filterText = 'first';
      const mockPrepareQuery = jest.mocked<typeof prepareQuery>(prepareQuery);
      mockPrepareQuery.mockReturnValueOnce(makePreparedQuery(filterText));

      const mockFuzzySearch = jest.mocked<typeof fuzzySearch>(fuzzySearch);
      mockFuzzySearch.mockImplementation((_q: PreparedQuery, text: string) => {
        const match = makeFuzzyMatch();
        return text.startsWith(filterText) ? match : null;
      });

      const inputInfo = new InputInfo(`${workspaceTrigger}${filterText}`);
      const results = sut.getSuggestions(inputInfo);

      expect(results).not.toBeNull();
      expect(results).toBeInstanceOf(Array);
      expect(results).toHaveLength(1);

      const onlyResult = results[0];
      expect(onlyResult).toHaveProperty('type', 'workspace');
      expect(onlyResult.item.id).toBe(expectedWorkspaceIds[0]);

      expect(mockFuzzySearch).toHaveBeenCalled();
      expect(mockPrepareQuery).toHaveBeenCalled();
      expect(mockInternalPlugins.getPluginById).toHaveBeenCalled();

      mockFuzzySearch.mockReset();
    });
  });

  describe('renderSuggestion', () => {
    it('should not throw an error with a null suggestion', () => {
      expect(() => sut.renderSuggestion(null, null)).not.toThrow();
    });

    it('should render a suggestion with match offsets', () => {
      const mockParentEl = mock<HTMLElement>();
      const mockRenderResults = jest.mocked<typeof renderResults>(renderResults);

      sut.renderSuggestion(suggestionInstance, mockParentEl);

      const {
        item: { id },
        match,
      } = suggestionInstance;
      expect(mockRenderResults).toHaveBeenCalledWith(mockParentEl, id, match);
    });
  });

  describe('onChooseSuggestion', () => {
    it('should not throw an error with a null suggestion', () => {
      expect(() => sut.onChooseSuggestion(null, null)).not.toThrow();
    });

    it('should tell the workspaces plugin to load the workspace with the chosen ID', () => {
      sut.onChooseSuggestion(suggestionInstance, null);

      expect(mockInternalPlugins.getPluginById).toHaveBeenCalled();
      expect(mockWsPluginInstance.loadWorkspace).toHaveBeenCalledWith(
        suggestionInstance.item.id,
      );
    });
  });
});
Example #7
Source File: switcherPlusSettings.test.ts    From obsidian-switcher-plus with GNU General Public License v3.0 4 votes vote down vote up
describe('SwitcherPlusSettings', () => {
  let mockApp: MockProxy<App>;
  let mockPlugin: MockProxy<SwitcherPlusPlugin>;
  let sut: SwitcherPlusSettings;

  beforeAll(() => {
    mockApp = mock<App>({ internalPlugins: mock<InternalPlugins>() });
    mockPlugin = mock<SwitcherPlusPlugin>({ app: mockApp });
  });

  beforeEach(() => {
    sut = new SwitcherPlusSettings(mockPlugin);
  });

  it('should return default settings', () => {
    // extract enabledSymbolTypes to handle separately, because it's not exposed
    // on SwitcherPlusSettings directly
    const { enabledSymbolTypes, ...defaults } = transientSettingsData(true);

    expect(sut).toEqual(expect.objectContaining(defaults));
    expect(sut.editorListPlaceholderText).toBe(defaults.editorListCommand);
    expect(sut.symbolListPlaceholderText).toBe(defaults.symbolListCommand);
    expect(sut.workspaceListPlaceholderText).toBe(defaults.workspaceListCommand);
    expect(sut.headingsListPlaceholderText).toBe(defaults.headingsListCommand);
    expect(sut.starredListPlaceholderText).toBe(defaults.starredListCommand);
    expect(sut.commandListPlaceholderText).toBe(defaults.commandListCommand);
    expect(sut.relatedItemsListPlaceholderText).toBe(defaults.relatedItemsListCommand);
    expect(sut.includeSidePanelViewTypesPlaceholder).toBe(
      defaults.includeSidePanelViewTypes.join('\n'),
    );

    expect(sut.isSymbolTypeEnabled(SymbolType.Embed)).toBe(
      enabledSymbolTypes[SymbolType.Embed],
    );
    expect(sut.isSymbolTypeEnabled(SymbolType.Heading)).toBe(
      enabledSymbolTypes[SymbolType.Heading],
    );
    expect(sut.isSymbolTypeEnabled(SymbolType.Link)).toBe(
      enabledSymbolTypes[SymbolType.Link],
    );
    expect(sut.isSymbolTypeEnabled(SymbolType.Tag)).toBe(
      enabledSymbolTypes[SymbolType.Tag],
    );
  });

  it('should save modified settings', async () => {
    const settings = transientSettingsData(false);

    sut.onOpenPreferNewPane = settings.onOpenPreferNewPane;
    sut.alwaysNewPaneForSymbols = settings.alwaysNewPaneForSymbols;
    sut.useActivePaneForSymbolsOnMobile = settings.useActivePaneForSymbolsOnMobile;
    sut.symbolsInLineOrder = settings.symbolsInLineOrder;
    sut.editorListCommand = settings.editorListCommand;
    sut.symbolListCommand = settings.symbolListCommand;
    sut.workspaceListCommand = settings.workspaceListCommand;
    sut.headingsListCommand = settings.headingsListCommand;
    sut.starredListCommand = settings.starredListCommand;
    sut.commandListCommand = settings.commandListCommand;
    sut.relatedItemsListCommand = settings.relatedItemsListCommand;
    sut.strictHeadingsOnly = settings.strictHeadingsOnly;
    sut.searchAllHeadings = settings.searchAllHeadings;
    sut.includeSidePanelViewTypes = settings.includeSidePanelViewTypes;
    sut.limit = settings.limit;
    sut.selectNearestHeading = settings.selectNearestHeading;
    sut.excludeFolders = settings.excludeFolders;
    sut.excludeLinkSubTypes = settings.excludeLinkSubTypes;
    sut.excludeRelatedFolders = settings.excludeRelatedFolders;
    sut.excludeOpenRelatedFiles = settings.excludeOpenRelatedFiles;

    sut.setSymbolTypeEnabled(
      SymbolType.Heading,
      settings.enabledSymbolTypes[SymbolType.Heading],
    );

    sut.setSymbolTypeEnabled(
      SymbolType.Link,
      settings.enabledSymbolTypes[SymbolType.Link],
    );

    sut.setSymbolTypeEnabled(SymbolType.Tag, settings.enabledSymbolTypes[SymbolType.Tag]);

    sut.setSymbolTypeEnabled(
      SymbolType.Embed,
      settings.enabledSymbolTypes[SymbolType.Embed],
    );

    let savedSettings: SettingsData;
    mockPlugin.saveData.mockImplementationOnce((data: SettingsData) => {
      savedSettings = data;
      return Promise.resolve();
    });

    await sut.saveSettings();

    expect(savedSettings).toEqual(expect.objectContaining(settings));
    expect(mockPlugin.saveData).toHaveBeenCalled();

    mockPlugin.saveData.mockReset();
  });

  it('should load saved settings', async () => {
    const settings = transientSettingsData(false);
    const { enabledSymbolTypes, ...prunedSettings } = settings;
    mockPlugin.loadData.mockResolvedValueOnce(settings);

    await sut.loadSettings();

    expect(sut).toEqual(expect.objectContaining(prunedSettings));
    expect(sut.isSymbolTypeEnabled(SymbolType.Heading)).toBe(
      enabledSymbolTypes[SymbolType.Heading],
    );
    expect(sut.isSymbolTypeEnabled(SymbolType.Link)).toBe(
      enabledSymbolTypes[SymbolType.Link],
    );
    expect(sut.isSymbolTypeEnabled(SymbolType.Tag)).toBe(
      enabledSymbolTypes[SymbolType.Tag],
    );
    expect(sut.isSymbolTypeEnabled(SymbolType.Embed)).toBe(
      enabledSymbolTypes[SymbolType.Embed],
    );

    expect(mockPlugin.loadData).toHaveBeenCalled();

    mockPlugin.loadData.mockReset();
  });

  it('should load saved settings, even with missing data keys', async () => {
    const defaults = transientSettingsData(true);
    const settings = transientSettingsData(false);

    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    const { enabledSymbolTypes, ...prunedSettings } = settings;
    mockPlugin.loadData.mockResolvedValueOnce(prunedSettings);

    await sut.loadSettings();

    expect(sut).toEqual(expect.objectContaining(prunedSettings));
    expect(sut.isSymbolTypeEnabled(SymbolType.Heading)).toBe(
      defaults.enabledSymbolTypes[SymbolType.Heading],
    );
    expect(sut.isSymbolTypeEnabled(SymbolType.Link)).toBe(
      defaults.enabledSymbolTypes[SymbolType.Link],
    );
    expect(sut.isSymbolTypeEnabled(SymbolType.Tag)).toBe(
      defaults.enabledSymbolTypes[SymbolType.Tag],
    );
    expect(sut.isSymbolTypeEnabled(SymbolType.Embed)).toBe(
      defaults.enabledSymbolTypes[SymbolType.Embed],
    );

    expect(mockPlugin.loadData).toHaveBeenCalled();

    mockPlugin.loadData.mockReset();
  });

  it('should use default data if settings cannot be loaded', async () => {
    const { enabledSymbolTypes, ...defaults } = transientSettingsData(true);
    mockPlugin.loadData.mockResolvedValueOnce(null);

    await sut.loadSettings();

    expect(sut).toEqual(expect.objectContaining(defaults));
    expect(sut.editorListPlaceholderText).toBe(defaults.editorListCommand);
    expect(sut.symbolListPlaceholderText).toBe(defaults.symbolListCommand);
    expect(sut.workspaceListPlaceholderText).toBe(defaults.workspaceListCommand);
    expect(sut.headingsListPlaceholderText).toBe(defaults.headingsListCommand);
    expect(sut.includeSidePanelViewTypesPlaceholder).toBe(
      defaults.includeSidePanelViewTypes.join('\n'),
    );

    expect(sut.isSymbolTypeEnabled(SymbolType.Embed)).toBe(
      enabledSymbolTypes[SymbolType.Embed],
    );
    expect(sut.isSymbolTypeEnabled(SymbolType.Heading)).toBe(
      enabledSymbolTypes[SymbolType.Heading],
    );
    expect(sut.isSymbolTypeEnabled(SymbolType.Link)).toBe(
      enabledSymbolTypes[SymbolType.Link],
    );
    expect(sut.isSymbolTypeEnabled(SymbolType.Tag)).toBe(
      enabledSymbolTypes[SymbolType.Tag],
    );

    expect(mockPlugin.loadData).toHaveBeenCalled();
  });

  it('should load built-in system switcher settings', () => {
    const builtInOptions = mock<QuickSwitcherOptions>({
      showAllFileTypes: chance.bool(),
      showAttachments: chance.bool(),
      showExistingOnly: chance.bool(),
    });

    const pluginInstance = mock<QuickSwitcherPluginInstance>({
      id: 'switcher',
      options: builtInOptions,
      QuickSwitcherModal: null,
    });

    const builtInSwitcherPlugin = mock<InstalledPlugin>({
      enabled: true,
      instance: pluginInstance,
    });

    const mockInternalPlugins = mockApp.internalPlugins as MockProxy<InternalPlugins>;
    mockInternalPlugins.getPluginById.mockReturnValue(builtInSwitcherPlugin);

    expect(sut.builtInSystemOptions).toMatchObject(builtInOptions);
    expect(sut.showAllFileTypes).toBe(builtInOptions.showAllFileTypes);
    expect(sut.showAttachments).toBe(builtInOptions.showAttachments);
    expect(sut.showExistingOnly).toBe(builtInOptions.showExistingOnly);
    expect(mockInternalPlugins.getPluginById).toHaveBeenCalled();

    mockInternalPlugins.getPluginById.mockReset();
  });

  it('should log errors to console on fire and forget save operation', () => {
    // Promise used to trigger the error condition
    const saveDataPromise = Promise.resolve();

    mockPlugin.saveData.mockImplementationOnce((_data: SettingsData) => {
      // throw to simulate saveData() failing. This happens first
      return saveDataPromise.then(() => {
        throw new Error('saveData() unit test mock error');
      });
    });

    // Promise used to track the call to console.log
    let consoleLogPromiseResolveFn: (value: void | PromiseLike<void>) => void;
    const consoleLogPromise = new Promise<void>((resolve, _reject) => {
      consoleLogPromiseResolveFn = resolve;
    });

    const consoleLogSpy = jest
      .spyOn(console, 'log')
      .mockImplementation((message: string) => {
        if (message.startsWith('Switcher++: error saving changes to settings')) {
          // resolve the consoleLogPromise. This happens second and will allow
          // allPromises to resolve itself
          consoleLogPromiseResolveFn();
        }
      });

    // wait for the other promises to resolve before this promise can resolve
    const allPromises = Promise.all([saveDataPromise, consoleLogPromise]);

    sut.save();

    // when all the promises are resolved check expectations and clean up
    return allPromises.finally(() => {
      expect(mockPlugin.saveData).toHaveBeenCalled();
      expect(consoleLogSpy).toHaveBeenCalled();

      consoleLogSpy.mockRestore();
    });
  });
});
Example #8
Source File: modeHandler.test.ts    From obsidian-switcher-plus with GNU General Public License v3.0 4 votes vote down vote up
describe('modeHandler', () => {
  let mockApp: MockProxy<App>;
  let settings: SwitcherPlusSettings;
  let sut: ModeHandler;

  beforeAll(() => {
    const mockInternalPlugins = mock<InternalPlugins>();
    mockInternalPlugins.getPluginById.mockImplementation((_id) => {
      return {
        enabled: true,
        instance: null,
      };
    });

    const mockWorkspace = mock<Workspace>({ activeLeaf: null });
    mockWorkspace.iterateAllLeaves.mockImplementation((_callback) => {
      //noop
    });

    mockApp = mock<App>({
      internalPlugins: mockInternalPlugins,
      workspace: mockWorkspace,
    });

    settings = new SwitcherPlusSettings(null);

    jest.spyOn(settings, 'editorListCommand', 'get').mockReturnValue(editorTrigger);
    jest.spyOn(settings, 'symbolListCommand', 'get').mockReturnValue(symbolTrigger);
    jest.spyOn(settings, 'workspaceListCommand', 'get').mockReturnValue(workspaceTrigger);
    jest.spyOn(settings, 'headingsListCommand', 'get').mockReturnValue(headingsTrigger);
    jest.spyOn(settings, 'starredListCommand', 'get').mockReturnValue(starredTrigger);
    jest.spyOn(settings, 'commandListCommand', 'get').mockReturnValue(commandTrigger);
    jest
      .spyOn(settings, 'relatedItemsListCommand', 'get')
      .mockReturnValue(relatedItemsTrigger);
  });

  describe('opening and closing the modal', () => {
    const mockKeymap = mock<Keymap>();

    beforeAll(() => {
      sut = new ModeHandler(mockApp, settings, mockKeymap);
    });

    test('onOpen() should open the keymap', () => {
      mockKeymap.isOpen = false;

      sut.onOpen();

      expect(mockKeymap.isOpen).toBe(true);
    });

    test('onClose() should close the keymap', () => {
      mockKeymap.isOpen = true;

      sut.onClose();

      expect(mockKeymap.isOpen).toBe(false);
    });
  });

  describe('Starting sessions with explicit command string', () => {
    let commandStringSpy: jest.SpyInstance;

    beforeAll(() => {
      sut = new ModeHandler(mockApp, settings, null);
    });

    describe('setSessionOpenMode', () => {
      it('should save the command string for any Ex modes', () => {
        commandStringSpy = jest
          .spyOn(EditorHandler.prototype, 'commandString', 'get')
          .mockReturnValueOnce(editorTrigger);

        sut.setSessionOpenMode(Mode.EditorList, null);

        expect(commandStringSpy).toHaveBeenCalled();

        commandStringSpy.mockRestore();
      });

      it('should not save the command string for any Ex modes', () => {
        const sSpy = jest.spyOn(SymbolHandler.prototype, 'commandString', 'get');
        const eSpy = jest.spyOn(EditorHandler.prototype, 'commandString', 'get');
        const wSpy = jest.spyOn(WorkspaceHandler.prototype, 'commandString', 'get');
        const hSpy = jest.spyOn(HeadingsHandler.prototype, 'commandString', 'get');
        const starredSpy = jest.spyOn(StarredHandler.prototype, 'commandString', 'get');
        const commandsSpy = jest.spyOn(CommandHandler.prototype, 'commandString', 'get');
        const relatedItemsSpy = jest.spyOn(
          RelatedItemsHandler.prototype,
          'commandString',
          'get',
        );

        sut.setSessionOpenMode(Mode.Standard, null);

        expect(sSpy).not.toHaveBeenCalled();
        expect(eSpy).not.toHaveBeenCalled();
        expect(wSpy).not.toHaveBeenCalled();
        expect(hSpy).not.toHaveBeenCalled();
        expect(starredSpy).not.toHaveBeenCalled();
        expect(commandsSpy).not.toHaveBeenCalled();
        expect(relatedItemsSpy).not.toHaveBeenCalled();

        sSpy.mockRestore();
        eSpy.mockRestore();
        wSpy.mockRestore();
        hSpy.mockRestore();
        starredSpy.mockRestore();
        commandsSpy.mockRestore();
        relatedItemsSpy.mockRestore();
      });
    });

    describe('insertSessionOpenModeCommandString', () => {
      const mockInputEl = mock<HTMLInputElement>();

      it('should insert the command string into the input element', () => {
        mockInputEl.value = '';
        sut.setSessionOpenMode(Mode.EditorList, null);

        sut.insertSessionOpenModeCommandString(mockInputEl);

        expect(mockInputEl).toHaveProperty('value', editorTrigger);
      });

      it('should do nothing when sessionOpenModeString is falsy', () => {
        mockInputEl.value = '';
        sut.setSessionOpenMode(Mode.Standard, null);

        sut.insertSessionOpenModeCommandString(mockInputEl);

        expect(mockInputEl).toHaveProperty('value', '');
      });
    });
  });

  describe('determineRunMode', () => {
    beforeAll(() => {
      sut = new ModeHandler(mockApp, settings, null);
    });

    it('should reset on falsy input', () => {
      const input: string = null;
      const inputInfo = sut.determineRunMode(input, null, null);

      expect(inputInfo.mode).toBe(Mode.Standard);
      expect(inputInfo.searchQuery).toBeFalsy();
      expect(inputInfo.inputText).toBe('');
    });

    describe('should identify unicode triggers', () => {
      test.each(unicodeInputFixture)(
        'for input: "$input" (array data index: $#)',
        ({ editorTrigger, symbolTrigger, input, expected: { mode, parsedInput } }) => {
          const s = new SwitcherPlusSettings(null);
          const mh = new ModeHandler(mockApp, s, null);
          let cmdSpy: jest.SpyInstance;

          if (editorTrigger) {
            cmdSpy = jest
              .spyOn(s, 'editorListCommand', 'get')
              .mockReturnValue(editorTrigger);
          }

          if (symbolTrigger) {
            cmdSpy = jest
              .spyOn(s, 'symbolListCommand', 'get')
              .mockReturnValue(symbolTrigger);
          }

          const leaf = makeLeaf();
          const es: EditorSuggestion = {
            item: leaf,
            file: leaf.view.file,
            type: 'editor',
            match: {
              score: 0,
              matches: [[0, 0]],
            },
          };

          const inputInfo = mh.determineRunMode(input, es, makeLeaf());
          const parsed = inputInfo.parsedCommand().parsedInput;

          expect(cmdSpy).toHaveBeenCalled();
          expect(inputInfo.mode).toBe(mode);
          expect(parsed).toBe(parsedInput);
        },
      );
    });

    describe('should parse as standard mode', () => {
      test(`with excluded active view for input: "${symbolTrigger} test"`, () => {
        const mockLeaf = makeLeaf();
        const mockView = mockLeaf.view as MockProxy<View>;
        const excludedType = 'foo';
        const input = `${symbolTrigger} test`;

        const excludeViewTypesSpy = jest
          .spyOn(settings, 'excludeViewTypes', 'get')
          .mockReturnValue([excludedType]);

        mockView.getViewType.mockReturnValue(excludedType);

        const inputInfo = sut.determineRunMode(input, null, mockLeaf);

        expect(inputInfo.mode).toBe(Mode.Standard);
        expect(inputInfo.inputText).toBe(input);
        expect(excludeViewTypesSpy).toHaveBeenCalled();
        expect(mockView.getViewType).toHaveBeenCalled();

        excludeViewTypesSpy.mockRestore();
      });

      test.each(standardModeInputFixture)(
        'for input: "$input" (array data index: $#)',
        ({ input, expected: { mode } }) => {
          const inputInfo = sut.determineRunMode(input, null, null);

          expect(inputInfo.mode).toBe(mode);
          expect(inputInfo.inputText).toBe(input);
        },
      );
    });

    describe('should parse as editor mode', () => {
      test.each(editorPrefixOnlyInputFixture)(
        'for input: "$input" (array data index: $#)',
        ({ input, expected: { mode, isValidated, parsedInput } }) => {
          const inputInfo = sut.determineRunMode(input, null, null);

          expect(inputInfo.mode).toBe(mode);
          expect(inputInfo.inputText).toBe(input);

          const editorCmd = inputInfo.parsedCommand();
          expect(editorCmd.isValidated).toBe(isValidated);
          expect(editorCmd.parsedInput).toBe(parsedInput);
        },
      );
    });

    describe('should parse as symbol mode', () => {
      test.each(symbolPrefixOnlyInputFixture)(
        'with ACTIVE LEAF for input: "$input" (array data index: $#)',
        ({ input, expected: { mode, isValidated, parsedInput } }) => {
          const mockLeaf = makeLeaf();
          const inputInfo = sut.determineRunMode(input, null, mockLeaf);

          expect(inputInfo.mode).toBe(mode);
          expect(inputInfo.inputText).toBe(input);

          const symbolCmd = inputInfo.parsedCommand() as SourcedParsedCommand;
          expect(symbolCmd.isValidated).toBe(isValidated);
          expect(symbolCmd.parsedInput).toBe(parsedInput);

          const { source } = symbolCmd;
          expect(source.isValidSource).toBe(true);
          expect(source.file).toBe(mockLeaf.view.file);
          expect(source.leaf).toBe(mockLeaf);
          expect(source.suggestion).toBe(null);
        },
      );

      test.each(symbolModeInputFixture)(
        'with FILE SUGGESTION for input: "$input" (array data index: $#)',
        ({ input, expected: { mode, isValidated, parsedInput } }) => {
          const fileSuggestion: FileSuggestion = {
            file: new TFile(),
            type: 'file',
            match: {
              score: 0,
              matches: [[0, 0]],
            },
          };

          const inputInfo = sut.determineRunMode(input, fileSuggestion, null);

          expect(inputInfo.mode).toBe(mode);
          expect(inputInfo.inputText).toBe(input);

          const symbolCmd = inputInfo.parsedCommand() as SourcedParsedCommand;
          expect(symbolCmd.isValidated).toBe(isValidated);
          expect(symbolCmd.parsedInput).toBe(parsedInput);

          const { source } = symbolCmd;
          expect(source.isValidSource).toBe(true);
          expect(source.file).toBe(fileSuggestion.file);
          expect(source.leaf).toBe(null);
          expect(source.suggestion).toBe(fileSuggestion);
        },
      );

      test.each(symbolModeInputFixture)(
        'with EDITOR SUGGESTION for input: "$input" (array data index: $#)',
        ({ input, expected: { mode, isValidated, parsedInput } }) => {
          const leaf = makeLeaf();
          const editorSuggestion: EditorSuggestion = {
            item: leaf,
            file: leaf.view.file,
            type: 'editor',
            match: {
              score: 0,
              matches: [[0, 0]],
            },
          };

          mockApp.workspace.activeLeaf = leaf;

          const inputInfo = sut.determineRunMode(input, editorSuggestion, null);

          expect(inputInfo.mode).toBe(mode);
          expect(inputInfo.inputText).toBe(input);

          const symbolCmd = inputInfo.parsedCommand() as SourcedParsedCommand;
          expect(symbolCmd.isValidated).toBe(isValidated);
          expect(symbolCmd.parsedInput).toBe(parsedInput);

          const { source } = symbolCmd;
          expect(source.isValidSource).toBe(true);
          expect(source.file).toBe(leaf.view.file);
          expect(source.leaf).toBe(leaf);
          expect(source.suggestion).toBe(editorSuggestion);

          mockApp.workspace.activeLeaf = null;
        },
      );
    });

    describe('should parse as workspace mode', () => {
      test.each(workspacePrefixOnlyInputFixture)(
        'for input: "$input" (array data index: $#)',
        ({ input, expected: { mode, isValidated, parsedInput } }) => {
          const inputInfo = sut.determineRunMode(input, null, null);

          expect(inputInfo.mode).toBe(mode);
          expect(inputInfo.inputText).toBe(input);

          const workspaceCmd = inputInfo.parsedCommand();
          expect(workspaceCmd.isValidated).toBe(isValidated);
          expect(workspaceCmd.parsedInput).toBe(parsedInput);
        },
      );
    });

    describe('should parse as starred mode', () => {
      test.each(starredPrefixOnlyInputFixture)(
        'for input: "$input" (array data index: $#)',
        ({ input, expected: { mode, isValidated, parsedInput } }) => {
          const inputInfo = sut.determineRunMode(input, null, null);

          expect(inputInfo.mode).toBe(mode);
          expect(inputInfo.inputText).toBe(input);

          const starredCmd = inputInfo.parsedCommand();
          expect(starredCmd.isValidated).toBe(isValidated);
          expect(starredCmd.parsedInput).toBe(parsedInput);
        },
      );
    });

    describe('should parse as headings mode', () => {
      test.each(headingsPrefixOnlyInputFixture)(
        'for input: "$input" (array data index: $#)',
        ({ input, expected: { mode, isValidated, parsedInput } }) => {
          const inputInfo = sut.determineRunMode(input, null, null);

          expect(inputInfo.mode).toBe(mode);
          expect(inputInfo.inputText).toBe(input);

          const headingsCmd = inputInfo.parsedCommand();
          expect(headingsCmd.isValidated).toBe(isValidated);
          expect(headingsCmd.parsedInput).toBe(parsedInput);
        },
      );
    });

    describe('should parse as command mode', () => {
      test.each(commandPrefixOnlyInputFixture)(
        'for input: "$input" (array data index: $#)',
        ({ input, expected: { mode, isValidated, parsedInput } }) => {
          const inputInfo = sut.determineRunMode(input, null, null);

          expect(inputInfo.mode).toBe(mode);
          expect(inputInfo.inputText).toBe(input);

          const commandCmd = inputInfo.parsedCommand();
          expect(commandCmd.isValidated).toBe(isValidated);
          expect(commandCmd.parsedInput).toBe(parsedInput);
        },
      );
    });

    describe('should parse as related mode', () => {
      test.each(relatedItemsPrefixOnlyInputFixture)(
        'with ACTIVE LEAF for input: "$input" (array data index: $#)',
        ({ input, expected: { mode, isValidated, parsedInput } }) => {
          const mockLeaf = makeLeaf();
          const inputInfo = sut.determineRunMode(input, null, mockLeaf);

          expect(inputInfo.mode).toBe(mode);
          expect(inputInfo.inputText).toBe(input);

          const cmd = inputInfo.parsedCommand() as SourcedParsedCommand;
          expect(cmd.isValidated).toBe(isValidated);
          expect(cmd.parsedInput).toBe(parsedInput);

          const { source } = cmd;
          expect(source.isValidSource).toBe(true);
          expect(source.file).toBe(mockLeaf.view.file);
          expect(source.leaf).toBe(mockLeaf);
          expect(source.suggestion).toBe(null);
        },
      );

      test.each(relatedItemsModeInputFixture)(
        'with FILE SUGGESTION for input: "$input" (array data index: $#)',
        ({ input, expected: { mode, isValidated, parsedInput } }) => {
          const fileSuggestion: FileSuggestion = {
            file: new TFile(),
            type: 'file',
            match: {
              score: 0,
              matches: [[0, 0]],
            },
          };

          const inputInfo = sut.determineRunMode(input, fileSuggestion, null);

          expect(inputInfo.mode).toBe(mode);
          expect(inputInfo.inputText).toBe(input);

          const cmd = inputInfo.parsedCommand() as SourcedParsedCommand;
          expect(cmd.isValidated).toBe(isValidated);
          expect(cmd.parsedInput).toBe(parsedInput);

          const { source } = cmd;
          expect(source.isValidSource).toBe(true);
          expect(source.file).toBe(fileSuggestion.file);
          expect(source.leaf).toBe(null);
          expect(source.suggestion).toBe(fileSuggestion);
        },
      );

      test.each(relatedItemsModeInputFixture)(
        'with EDITOR SUGGESTION for input: "$input" (array data index: $#)',
        ({ input, expected: { mode, isValidated, parsedInput } }) => {
          const leaf = makeLeaf();
          const editorSuggestion: EditorSuggestion = {
            item: leaf,
            file: leaf.view.file,
            type: 'editor',
            match: {
              score: 0,
              matches: [[0, 0]],
            },
          };

          mockApp.workspace.activeLeaf = leaf;

          const inputInfo = sut.determineRunMode(input, editorSuggestion, null);

          expect(inputInfo.mode).toBe(mode);
          expect(inputInfo.inputText).toBe(input);

          const cmd = inputInfo.parsedCommand() as SourcedParsedCommand;
          expect(cmd.isValidated).toBe(isValidated);
          expect(cmd.parsedInput).toBe(parsedInput);

          const { source } = cmd;
          expect(source.isValidSource).toBe(true);
          expect(source.file).toBe(leaf.view.file);
          expect(source.leaf).toBe(leaf);
          expect(source.suggestion).toBe(editorSuggestion);

          mockApp.workspace.activeLeaf = null;
        },
      );
    });
  });

  describe('managing suggestions', () => {
    const editorSugg: EditorSuggestion = {
      type: 'editor',
      file: null,
      item: makeLeaf(),
      match: null,
    };

    const symbolSugg: SymbolSuggestion = {
      type: 'symbol',
      file: null,
      item: {
        type: 'symbolInfo',
        symbol: getHeadings()[0],
        symbolType: SymbolType.Heading,
        isSelected: false,
      },
      match: null,
    };

    const workspaceSugg: WorkspaceSuggestion = {
      type: 'workspace',
      item: {
        type: 'workspaceInfo',
        id: 'foo',
      },
      match: null,
    };

    const headingsSugg: HeadingSuggestion = {
      type: 'heading',
      item: makeHeading('foo', 1),
      file: null,
      match: null,
    };

    const starredSugg: StarredSuggestion = {
      type: 'starred',
      file: new TFile(),
      item: makeFileStarredItem(),
      match: null,
    };

    const commandSugg: CommandSuggestion = {
      type: 'command',
      item: makeCommandItem(),
      match: null,
    };

    const relatedItemSugg: RelatedItemsSuggestion = {
      type: 'relatedItems',
      relationType: 'diskLocation',
      file: new TFile(),
      match: null,
    };

    beforeAll(() => {
      sut = new ModeHandler(mockApp, settings, mock<Keymap>());
    });

    describe('updateSuggestions', () => {
      const mockChooser = mock<Chooser<AnySuggestion>>();
      const mockSetSuggestion = mockChooser.setSuggestions.mockImplementation();
      let getSuggestionSpy: jest.SpyInstance;

      test('with falsy input (Standard mode), it should return not handled', () => {
        const results = sut.updateSuggestions(null, null);
        expect(results).toBe(false);
      });

      it('should debounce in Headings mode with filter text', () => {
        const validateCommandSpy = jest
          .spyOn(HeadingsHandler.prototype, 'validateCommand')
          .mockImplementation((inputInfo) => {
            inputInfo.mode = Mode.HeadingsList;
            const cmd = inputInfo.parsedCommand(Mode.HeadingsList);
            cmd.parsedInput = 'foo';
          });

        const mockDebouncedFn = jest.fn();
        const mockDebounce = debounce as jest.Mock;
        mockDebounce.mockImplementation(() => mockDebouncedFn);
        sut = new ModeHandler(mockApp, settings, mock<Keymap>());

        const results = sut.updateSuggestions(headingsTrigger, mockChooser);

        expect(results).toBe(true);
        expect(mockDebounce).toHaveBeenCalled();
        expect(mockDebouncedFn).toHaveBeenCalled();
        expect(validateCommandSpy).toHaveBeenCalled();

        validateCommandSpy.mockRestore();
        mockDebounce.mockReset();
      });

      it('should get suggestions for Editor Mode', () => {
        const expectedSuggestions = [editorSugg];
        getSuggestionSpy = jest
          .spyOn(EditorHandler.prototype, 'getSuggestions')
          .mockReturnValue(expectedSuggestions);

        const results = sut.updateSuggestions(editorTrigger, mockChooser);

        expect(results).toBe(true);
        expect(getSuggestionSpy).toHaveBeenCalled();
        expect(mockSetSuggestion).toHaveBeenLastCalledWith(expectedSuggestions);

        getSuggestionSpy.mockRestore();
        mockSetSuggestion.mockReset();
      });

      it('should get suggestions for Symbol Mode', () => {
        const expectedSuggestions = [symbolSugg];
        getSuggestionSpy = jest
          .spyOn(SymbolHandler.prototype, 'getSuggestions')
          .mockReturnValue(expectedSuggestions);

        const validateCommandSpy = jest
          .spyOn(SymbolHandler.prototype, 'validateCommand')
          .mockImplementation((inputInfo) => {
            inputInfo.mode = Mode.SymbolList;
          });

        const mockSetSelectedItem = mockChooser.setSelectedItem.mockImplementation();
        mockChooser.values = expectedSuggestions;

        const results = sut.updateSuggestions(symbolTrigger, mockChooser);

        expect(results).toBe(true);
        expect(getSuggestionSpy).toHaveBeenCalled();
        expect(validateCommandSpy).toHaveBeenCalled();
        expect(mockSetSelectedItem).not.toHaveBeenCalled();
        expect(mockSetSuggestion).toHaveBeenLastCalledWith(expectedSuggestions);

        getSuggestionSpy.mockRestore();
        validateCommandSpy.mockRestore();
        mockSetSelectedItem.mockRestore();
        mockSetSuggestion.mockReset();
      });

      it('should set the active suggestion in Symbol Mode', () => {
        const symbolSugg2: SymbolSuggestion = {
          type: 'symbol',
          file: null,
          item: {
            type: 'symbolInfo',
            symbol: getHeadings()[0],
            symbolType: SymbolType.Heading,
            isSelected: true, // <-- here
          },
          match: null,
        };

        const expectedSuggestions = [symbolSugg2];
        getSuggestionSpy = jest
          .spyOn(SymbolHandler.prototype, 'getSuggestions')
          .mockReturnValue(expectedSuggestions);

        const validateCommandSpy = jest
          .spyOn(SymbolHandler.prototype, 'validateCommand')
          .mockImplementation((inputInfo) => {
            inputInfo.mode = Mode.SymbolList;
          });

        const mockSetSelectedItem = mockChooser.setSelectedItem.mockImplementation();
        mockChooser.values = expectedSuggestions;

        const results = sut.updateSuggestions(symbolTrigger, mockChooser);

        expect(results).toBe(true);
        expect(getSuggestionSpy).toHaveBeenCalled();
        expect(validateCommandSpy).toHaveBeenCalled();
        expect(mockSetSelectedItem).toHaveBeenCalledWith(0, true); // <-- here
        expect(mockSetSuggestion).toHaveBeenLastCalledWith(expectedSuggestions);

        getSuggestionSpy.mockRestore();
        validateCommandSpy.mockRestore();
        mockSetSelectedItem.mockRestore();
        mockSetSuggestion.mockReset();
      });

      it('should get suggestions for Workspace Mode', () => {
        const expectedSuggestions = [workspaceSugg];
        getSuggestionSpy = jest
          .spyOn(WorkspaceHandler.prototype, 'getSuggestions')
          .mockReturnValue(expectedSuggestions);

        const results = sut.updateSuggestions(workspaceTrigger, mockChooser);

        expect(results).toBe(true);
        expect(getSuggestionSpy).toHaveBeenCalled();
        expect(mockSetSuggestion).toHaveBeenLastCalledWith(expectedSuggestions);

        getSuggestionSpy.mockRestore();
        mockSetSuggestion.mockReset();
      });

      it('should get suggestions for Starred Mode', () => {
        const expectedSuggestions = [starredSugg];
        getSuggestionSpy = jest
          .spyOn(StarredHandler.prototype, 'getSuggestions')
          .mockReturnValue(expectedSuggestions);

        const results = sut.updateSuggestions(starredTrigger, mockChooser);

        expect(results).toBe(true);
        expect(getSuggestionSpy).toHaveBeenCalled();
        expect(mockSetSuggestion).toHaveBeenLastCalledWith(expectedSuggestions);

        getSuggestionSpy.mockRestore();
        mockSetSuggestion.mockReset();
      });

      it('should get suggestions for Headings Mode', () => {
        const expectedSuggestions = [headingsSugg];
        getSuggestionSpy = jest
          .spyOn(HeadingsHandler.prototype, 'getSuggestions')
          .mockReturnValue(expectedSuggestions);

        const results = sut.updateSuggestions(headingsTrigger, mockChooser);

        expect(results).toBe(true);
        expect(getSuggestionSpy).toHaveBeenCalled();
        expect(mockSetSuggestion).toHaveBeenLastCalledWith(expectedSuggestions);

        getSuggestionSpy.mockRestore();
        mockSetSuggestion.mockReset();
      });

      it('should get suggestions for Command Mode', () => {
        const expectedSuggestions = [commandSugg];
        getSuggestionSpy = jest
          .spyOn(CommandHandler.prototype, 'getSuggestions')
          .mockReturnValue(expectedSuggestions);

        const results = sut.updateSuggestions(commandTrigger, mockChooser);

        expect(results).toBe(true);
        expect(getSuggestionSpy).toHaveBeenCalled();
        expect(mockSetSuggestion).toHaveBeenLastCalledWith(expectedSuggestions);

        getSuggestionSpy.mockRestore();
        mockSetSuggestion.mockReset();
      });

      it('should get suggestions for RelatedItems Mode', () => {
        const expectedSuggestions = [relatedItemSugg];
        getSuggestionSpy = jest
          .spyOn(RelatedItemsHandler.prototype, 'getSuggestions')
          .mockReturnValue(expectedSuggestions);

        const validateCommandSpy = jest
          .spyOn(RelatedItemsHandler.prototype, 'validateCommand')
          .mockImplementation((inputInfo) => {
            inputInfo.mode = Mode.RelatedItemsList;
          });

        const results = sut.updateSuggestions(relatedItemsTrigger, mockChooser);

        expect(results).toBe(true);
        expect(getSuggestionSpy).toHaveBeenCalled();
        expect(mockSetSuggestion).toHaveBeenLastCalledWith(expectedSuggestions);

        getSuggestionSpy.mockRestore();
        validateCommandSpy.mockRestore();
        mockSetSuggestion.mockReset();
      });
    });

    describe('renderSuggestions', () => {
      const mockParentEl = mock<HTMLElement>();
      let renderSuggestionSpy: jest.SpyInstance;

      it('should return false with falsy input', () => {
        const result = sut.renderSuggestion(null, null);
        expect(result).toBe(false);
      });

      it('should render suggestions for Editor Mode', () => {
        renderSuggestionSpy = jest
          .spyOn(EditorHandler.prototype, 'renderSuggestion')
          .mockImplementation();

        const result = sut.renderSuggestion(editorSugg, mockParentEl);

        expect(result).toBe(true);
        expect(renderSuggestionSpy).toHaveBeenCalledWith(editorSugg, mockParentEl);

        renderSuggestionSpy.mockRestore();
      });

      it('should render suggestions for Symbol Mode', () => {
        renderSuggestionSpy = jest
          .spyOn(SymbolHandler.prototype, 'renderSuggestion')
          .mockImplementation();

        const result = sut.renderSuggestion(symbolSugg, mockParentEl);

        expect(result).toBe(true);
        expect(renderSuggestionSpy).toHaveBeenCalledWith(symbolSugg, mockParentEl);

        renderSuggestionSpy.mockRestore();
      });

      it('should render suggestions for Headings Mode', () => {
        renderSuggestionSpy = jest
          .spyOn(HeadingsHandler.prototype, 'renderSuggestion')
          .mockImplementation();

        const result = sut.renderSuggestion(headingsSugg, mockParentEl);

        expect(result).toBe(true);
        expect(renderSuggestionSpy).toHaveBeenCalledWith(headingsSugg, mockParentEl);

        renderSuggestionSpy.mockRestore();
      });

      it('should render suggestions for Workspace Mode', () => {
        renderSuggestionSpy = jest
          .spyOn(WorkspaceHandler.prototype, 'renderSuggestion')
          .mockImplementation();

        const result = sut.renderSuggestion(workspaceSugg, mockParentEl);

        expect(result).toBe(true);
        expect(renderSuggestionSpy).toHaveBeenCalledWith(workspaceSugg, mockParentEl);

        renderSuggestionSpy.mockRestore();
      });

      it('should render suggestions for Starred Mode', () => {
        renderSuggestionSpy = jest
          .spyOn(StarredHandler.prototype, 'renderSuggestion')
          .mockImplementation();

        const result = sut.renderSuggestion(starredSugg, mockParentEl);

        expect(result).toBe(true);
        expect(renderSuggestionSpy).toHaveBeenCalledWith(starredSugg, mockParentEl);

        renderSuggestionSpy.mockRestore();
      });

      it('should render suggestions for Command Mode', () => {
        renderSuggestionSpy = jest
          .spyOn(CommandHandler.prototype, 'renderSuggestion')
          .mockImplementation();

        const result = sut.renderSuggestion(commandSugg, mockParentEl);

        expect(result).toBe(true);
        expect(renderSuggestionSpy).toHaveBeenCalledWith(commandSugg, mockParentEl);

        renderSuggestionSpy.mockRestore();
      });

      it('should render suggestions for RelatedItems Mode', () => {
        renderSuggestionSpy = jest
          .spyOn(RelatedItemsHandler.prototype, 'renderSuggestion')
          .mockImplementation();

        const result = sut.renderSuggestion(relatedItemSugg, mockParentEl);

        expect(result).toBe(true);
        expect(renderSuggestionSpy).toHaveBeenCalledWith(relatedItemSugg, mockParentEl);

        renderSuggestionSpy.mockRestore();
      });
    });

    describe('onchooseSuggestions', () => {
      const mockEvt = mock<MouseEvent>();
      let onChooseSuggestionSpy: jest.SpyInstance;

      it('should return false with falsy input', () => {
        const result = sut.onChooseSuggestion(null, null);
        expect(result).toBe(false);
      });

      it('should action suggestions for Editor Mode', () => {
        onChooseSuggestionSpy = jest
          .spyOn(EditorHandler.prototype, 'onChooseSuggestion')
          .mockImplementation();

        const result = sut.onChooseSuggestion(editorSugg, mockEvt);

        expect(result).toBe(true);
        expect(onChooseSuggestionSpy).toHaveBeenCalledWith(editorSugg, mockEvt);

        onChooseSuggestionSpy.mockRestore();
      });

      it('should action suggestions for Symbol Mode', () => {
        onChooseSuggestionSpy = jest
          .spyOn(SymbolHandler.prototype, 'onChooseSuggestion')
          .mockImplementation();

        const result = sut.onChooseSuggestion(symbolSugg, mockEvt);

        expect(result).toBe(true);
        expect(onChooseSuggestionSpy).toHaveBeenCalledWith(symbolSugg, mockEvt);

        onChooseSuggestionSpy.mockRestore();
      });

      it('should action suggestions for Headings Mode', () => {
        onChooseSuggestionSpy = jest
          .spyOn(HeadingsHandler.prototype, 'onChooseSuggestion')
          .mockImplementation();

        const result = sut.onChooseSuggestion(headingsSugg, mockEvt);

        expect(result).toBe(true);
        expect(onChooseSuggestionSpy).toHaveBeenCalledWith(headingsSugg, mockEvt);

        onChooseSuggestionSpy.mockRestore();
      });

      it('should action suggestions for Workspace Mode', () => {
        onChooseSuggestionSpy = jest
          .spyOn(WorkspaceHandler.prototype, 'onChooseSuggestion')
          .mockImplementation();

        const result = sut.onChooseSuggestion(workspaceSugg, mockEvt);

        expect(result).toBe(true);
        expect(onChooseSuggestionSpy).toHaveBeenCalledWith(workspaceSugg, mockEvt);

        onChooseSuggestionSpy.mockRestore();
      });

      it('should action suggestions for Starred Mode', () => {
        onChooseSuggestionSpy = jest
          .spyOn(StarredHandler.prototype, 'onChooseSuggestion')
          .mockImplementation();

        const result = sut.onChooseSuggestion(starredSugg, mockEvt);

        expect(result).toBe(true);
        expect(onChooseSuggestionSpy).toHaveBeenCalledWith(starredSugg, mockEvt);

        onChooseSuggestionSpy.mockRestore();
      });

      it('should action suggestions for Command Mode', () => {
        onChooseSuggestionSpy = jest
          .spyOn(CommandHandler.prototype, 'onChooseSuggestion')
          .mockImplementation();

        const result = sut.onChooseSuggestion(commandSugg, mockEvt);

        expect(result).toBe(true);
        expect(onChooseSuggestionSpy).toHaveBeenCalledWith(commandSugg, mockEvt);

        onChooseSuggestionSpy.mockRestore();
      });

      it('should action suggestions for RelatedItems Mode', () => {
        onChooseSuggestionSpy = jest
          .spyOn(RelatedItemsHandler.prototype, 'onChooseSuggestion')
          .mockImplementation();

        const result = sut.onChooseSuggestion(relatedItemSugg, mockEvt);

        expect(result).toBe(true);
        expect(onChooseSuggestionSpy).toHaveBeenCalledWith(relatedItemSugg, mockEvt);

        onChooseSuggestionSpy.mockRestore();
      });
    });
  });
});