Skip to content

Commit

Permalink
refactor(lib): changes to custom rule library and final unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
FlorianWoelki committed Oct 14, 2023
1 parent 811c6a5 commit 421ca13
Show file tree
Hide file tree
Showing 6 changed files with 211 additions and 107 deletions.
129 changes: 129 additions & 0 deletions src/lib/customRule.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,3 +204,132 @@ describe('getSortedRules', () => {
expect(sortedRules[2].order).toBe(2);
});
});

describe('add', () => {
let createIconNode: SpyInstance;
let plugin: any;
let rule: CustomRule;
let file: any;
beforeEach(() => {
document.body.innerHTML = '';
vi.restoreAllMocks();
createIconNode = vi.spyOn(dom, 'createIconNode');
// eslint-disable-next-line @typescript-eslint/no-empty-function
createIconNode.mockImplementationOnce(() => {});
plugin = {
app: {
vault: {
adapter: {
stat: () => ({ type: 'file' }),
},
},
},
getIconNameFromPath: () => false,
};
rule = {
for: 'everything',
rule: 'test',
icon: 'test',
order: 0,
};
file = {
path: 'test',
name: 'test',
};
});

it('should add the icon to the node', async () => {
const node = document.createElement('div');
node.setAttribute('data-path', 'test');
document.body.appendChild(node);

const result = await customRule.add(plugin, rule, file);
expect(createIconNode).toBeCalledTimes(1);
expect(result).toBe(true);
});

it('should not add the icon to the node if the node already has an icon', async () => {
plugin.getIconNameFromPath = () => 'IbTest';
const result = await customRule.add(plugin, rule, file);
expect(createIconNode).toBeCalledTimes(0);
expect(result).toBe(false);
});

it('should not add the icon to the node if the node does not match the rule', async () => {
rule.rule = 'test1';
const result = await customRule.add(plugin, rule, file);
expect(createIconNode).toBeCalledTimes(0);
expect(result).toBe(false);
});
});

describe('doesMatchPath', () => {
let rule: CustomRule;
beforeEach(() => {
rule = {
for: 'everything',
rule: 'test',
icon: 'test',
order: 0,
};
});

it('should return `true` if the rule matches the path', () => {
expect(customRule.doesMatchPath(rule, 'test')).toBe(true);
rule.rule = 'test.*';
expect(customRule.doesMatchPath(rule, 'test')).toBe(true);
});

it('should return `false` if the rule does not match the path', () => {
rule.rule = 'test1';
expect(customRule.doesMatchPath(rule, 'test')).toBe(false);
rule.rule = '.*-test';
expect(customRule.doesMatchPath(rule, 'test')).toBe(false);
});
});

describe('getFileItems', () => {
it('should return the file items which are applicable to the custom rule', async () => {
const plugin = {
app: {
vault: {
adapter: {
stat: () => ({ type: 'file' }),
},
},
},
getRegisteredFileExplorers: () => [
{
fileItems: [
{
file: {
path: 'test',
name: 'test',
},
},
{
file: {
path: 'foo',
name: 'foo',
},
},
{
file: {
path: 'test1',
name: 'test1',
},
},
],
},
],
} as any;
const rule: CustomRule = {
for: 'everything',
rule: 'test',
icon: 'test',
order: 0,
};
const result = await customRule.getFileItems(plugin, rule);
expect(result.length).toBe(2);
});
});
136 changes: 40 additions & 96 deletions src/lib/customRule.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Plugin, TAbstractFile } from 'obsidian';
import emoji from '../emoji';
import IconFolderPlugin from '../main';
import { CustomRule } from '../settings/data';
import dom from './util/dom';
import { getFileItemTitleEl } from '../util';
import config from '../config';
import { FileItem } from '../@types/obsidian';

export type CustomRuleFileType = 'file' | 'folder';

Expand Down Expand Up @@ -43,22 +43,14 @@ const isApplicable = async (
}

const fileType = metadata.type;
const toMatch = rule.useFilePath ? file.path : file.name;

const doesMatch = doesMatchFileType(rule, fileType);

try {
// Rule is in some sort of regex.
const regex = new RegExp(rule.rule);
if (!toMatch.match(regex)) {
return false;
}

return doesMatch;
} catch {
// Rule is not in some sort of regex, check for basic string match.
return toMatch.includes(rule.rule) && doesMatch;
if (!doesMatch) {
return false;
}

return doesMatchPath(rule, file.path);
};

/**
Expand Down Expand Up @@ -88,7 +80,7 @@ const removeFromAllFiles = async (
}

const fileType = (await plugin.app.vault.adapter.stat(dataPath)).type;
if (doesExistInPath(rule, dataPath) && doesMatchFileType(rule, fileType)) {
if (doesMatchPath(rule, dataPath) && doesMatchFileType(rule, fileType)) {
dom.removeIconInNode(parent);
}
}
Expand All @@ -104,44 +96,31 @@ const getSortedRules = (plugin: IconFolderPlugin): CustomRule[] => {
};

/**
* Tries to apply all custom rules to all files. This function iterates over all the saved
* custom rules and calls {@link addToAllFiles}.
* @param plugin Instance of the IconFolderPlugin.
*/
const addAll = async (plugin: IconFolderPlugin): Promise<void> => {
for (const rule of getSortedRules(plugin)) {
await addToAllFiles(plugin, rule);
}
};

/**
* Tries to add all specific custom rule icon to all registered files. It does that by
* calling the {@link add} function. Furthermore, it also checks whether the file or folder
* already has an icon. Custom rules should have the lowest priority and will get ignored
* if an icon already exists in the file or folder.
* @param plugin Instance of the IconFolderPlugin.
* @param rule Custom rule that will be applied, if applicable, to all files.
* Tries to add all specific custom rule icons to all registered files and directories.
* It does that by calling the {@link add} function. Custom rules should have the lowest
* priority and will get ignored if an icon already exists in the file or directory.
* @param plugin IconFolderPlugin instance.
* @param rule CustomRule that will be applied, if applicable, to all files and folders.
*/
const addToAllFiles = async (
plugin: IconFolderPlugin,
rule: CustomRule,
): Promise<void> => {
for (const fileExplorer of plugin.getRegisteredFileExplorers()) {
const files = Object.values(fileExplorer.fileItems);
for (const fileItem of files) {
await add(plugin, rule, fileItem.file, getFileItemTitleEl(fileItem));
}
const fileItems = await getFileItems(plugin, rule);
for (const fileItem of fileItems) {
await add(plugin, rule, fileItem.file, getFileItemTitleEl(fileItem));
}
};

/**
* Tries to add the icon of the custom rule to a file or folder. This function also checks
* if the file type matches the `for` property of the custom rule.
* @param plugin Instance of the IconFolderPlugin.
* @param rule Custom rule that will be used to check if the rule is applicable to the file.
* @param file File or folder that will be used to possibly create the icon for.
* @param container Optional element where the icon will be added if the custom rules matches.
* @returns A promise that resolves to true if the icon was added, false otherwise.
* @param plugin IconFolderPlugin instance.
* @param rule CustomRule that will be used to check if the rule is applicable to the file
* or directory.
* @param file TAbstractFile that will be used to possibly create the icon for.
* @param container HTMLElement where the icon will be added if the custom rules matches.
* @returns A promise that resolves to `true` if the icon was added, `false` otherwise.
*/
const add = async (
plugin: IconFolderPlugin,
Expand All @@ -153,33 +132,19 @@ const add = async (
return false;
}

// Gets the type of the file.
const fileType = (await plugin.app.vault.adapter.stat(file.path)).type;

// Checks if the file or directory already has an icon.
const hasIcon = plugin.getIconNameFromPath(file.path);
if (!doesMatchFileType(rule, fileType) || hasIcon) {
if (hasIcon) {
return false;
}
const toMatch = rule.useFilePath ? file.path : file.name;
try {
// Rule is in some sort of regex.
const regex = new RegExp(rule.rule);
if (toMatch.match(regex)) {
dom.createIconNode(plugin, file.path, rule.icon, {
color: rule.color,
container,
});
return true;
}
} catch {
// Rule is not applicable to a regex format.
if (toMatch.includes(rule.rule)) {
dom.createIconNode(plugin, file.path, rule.icon, {
color: rule.color,
container,
});
return true;
}

const doesMatch = await isApplicable(plugin, rule, file);
if (doesMatch) {
dom.createIconNode(plugin, file.path, rule.icon, {
color: rule.color,
container,
});
return true;
}

return false;
Expand All @@ -191,7 +156,7 @@ const add = async (
* @param path Path to check in.
* @returns True if the rule exists in the path, false otherwise.
*/
const doesExistInPath = (rule: CustomRule, path: string): boolean => {
const doesMatchPath = (rule: CustomRule, path: string): boolean => {
const toMatch = rule.useFilePath ? path : path.split('/').pop();
try {
// Rule is in some sort of regex.
Expand All @@ -208,55 +173,34 @@ const doesExistInPath = (rule: CustomRule, path: string): boolean => {
};

/**
* Gets a custom rule by its path.
* @param plugin Instance of the plugin.
* @param path Path to check for.
* @returns The custom rule if it exists, undefined otherwise.
*/
const getByPath = (
plugin: IconFolderPlugin,
path: string,
): CustomRule | undefined => {
if (path === 'settings' || path === 'migrated') {
return undefined;
}

return getSortedRules(plugin).find(
(rule) => !emoji.isEmoji(rule.icon) && doesExistInPath(rule, path),
);
};

/**
* Gets all the files and directories that can be applied to the specific custom rule.
* Gets all the file items that can be applied to the specific custom rule.
* @param plugin Instance of IconFolderPlugin.
* @param rule Custom rule that will be checked for.
* @returns An array of files and directories that match the custom rule.
* @returns A promise that resolves to an array of file items that match the custom rule.
*/
const getFiles = (
const getFileItems = async (
plugin: IconFolderPlugin,
rule: CustomRule,
): TAbstractFile[] => {
const result: TAbstractFile[] = [];
): Promise<FileItem[]> => {
const result: FileItem[] = [];
for (const fileExplorer of plugin.getRegisteredFileExplorers()) {
const files = Object.values(fileExplorer.fileItems);
for (const fileItem of files) {
if (doesExistInPath(rule, fileItem.file.path)) {
result.push(fileItem.file);
if (await isApplicable(plugin, rule, fileItem.file)) {
result.push(fileItem);
}
}
}
return result;
};

export default {
getFiles,
doesExistInPath,
getFileItems,
doesMatchPath,
doesMatchFileType,
getSortedRules,
getByPath,
removeFromAllFiles,
add,
addAll,
addToAllFiles,
isApplicable,
};
8 changes: 6 additions & 2 deletions src/lib/icon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,9 @@ const addAll = (
}

// Handles the custom rules.
customRule.addAll(plugin);
for (const rule of customRule.getSortedRules(plugin)) {
customRule.addToAllFiles(plugin, rule);
}
};

/**
Expand Down Expand Up @@ -285,7 +287,9 @@ const getByPath = (
}

// Tries to get the custom rule for the path and returns its icon if it exists.
const rule = customRule.getByPath(plugin, path);
const rule = customRule.getSortedRules(plugin).find((rule) => {
return customRule.doesMatchPath(rule, path);
});
if (rule) {
return rule.icon;
}
Expand Down
Loading

0 comments on commit 421ca13

Please sign in to comment.