Repository: obsidian-tasks-group/obsidian-tasks
Pull request #2283 (2023-09-24 08:47:53)

refactor: Add getInstance() to GlobalFilter & make all else non-static
<!--- Provide a general summary of your changes in the Title above -->

# Description

- Outside of tests, all access to `GlobalFilter` is now via `GlobalFilter.getInstance()`
- All other data and methods are now non-static

This is a step on the way to making `GlobalFilter` behave like `GlobalQuery`.

/CC @ilandikov for info.

## Motivation and Context

This is part 1 of #2281

<!--- Why is this change required? What problem does it solve? -->
<!--- If it fixes an open issue, please link to the issue here. -->

## How has this been tested?

- Running existing tests
- Adding new tests
- Running the smoke tests

## Screenshots (if appropriate)

## Types of changes

<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->

Internal changes:

- [x] **Refactor** (prefix: `refactor` - non-breaking change which only improves the design or structure of existing code, and making no changes to its external behaviour)
- [x] **Tests** (prefix: `test` - additions and improvements to unit tests and the smoke tests)

## Checklist

<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->

- [x] My code follows the code style of this project and passes `yarn run lint`.
- [ ] My change requires a change to the documentation.
- [ ] I have [updated the documentation](https://publish.obsidian.md/tasks-contributing/Documentation/About+Documentation) accordingly.
- [x] My change has adequate [Unit Test coverage](https://publish.obsidian.md/tasks-contributing/Testing/About+Testing).

## Terms

<!--
By submitting this pull request, you must agree to follow our
[contributing guide](https://publish.obsidian.md/tasks-contributing) and
[Code of Conduct](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/CODE_OF_CONDUCT.md).
Put an x in the boxes to confirm you agree.
-->

- [x] My contribution follow this project's [contributing guide](https://publish.obsidian.md/tasks-contributing)
- [x] I agree to follow this project's [Code of Conduct](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/CODE_OF_CONDUCT.md)



Patch:
diff --git a/src/Commands/CreateOrEditTaskParser.ts b/src/Commands/CreateOrEditTaskParser.ts
index f21823dc..d56e513f 100644
--- a/src/Commands/CreateOrEditTaskParser.ts
+++ b/src/Commands/CreateOrEditTaskParser.ts
@@ -29,8 +29,8 @@ function shouldUpdateCreatedDateForTask(task: Task) {
 
     // If the global filter will be added when the task is saved, treat it as new and add a creation date.
     // See issue #2112.
-    const globalFilterEnabled = !GlobalFilter.isEmpty();
-    const taskDoesNotContainGlobalFilter = !GlobalFilter.includedIn(task.description);
+    const globalFilterEnabled = !GlobalFilter.getInstance().isEmpty();
+    const taskDoesNotContainGlobalFilter = !GlobalFilter.getInstance().includedIn(task.description);
     const needsGlobalFilterToBeAdded = globalFilterEnabled && taskDoesNotContainGlobalFilter;
 
     return descriptionIsEmpty || needsGlobalFilterToBeAdded;
diff --git a/src/Config/GlobalFilter.ts b/src/Config/GlobalFilter.ts
index e5c29332..a24b3ac3 100644
--- a/src/Config/GlobalFilter.ts
+++ b/src/Config/GlobalFilter.ts
@@ -11,43 +11,57 @@ import * as RegExpTools from '../lib/RegExpTools';
  *     - These static methods will be made non-static in a future change.
  */
 export class GlobalFilter {
+    private static instance: GlobalFilter;
+
     static empty = '';
-    static _globalFilter = '';
-    static _removeGlobalFilter = false;
+    private _globalFilter = '';
+    private _removeGlobalFilter = false;
+
+    /**
+     * Provides access to the single global instance of GlobalFilter.
+     * This should eventually only be used in the plugin code.
+     */
+    public static getInstance(): GlobalFilter {
+        if (!GlobalFilter.instance) {
+            GlobalFilter.instance = new GlobalFilter();
+        }
+
+        return GlobalFilter.instance;
+    }
 
-    static get(): string {
-        return GlobalFilter._globalFilter;
+    public get(): string {
+        return this._globalFilter;
     }
 
-    static set(value: string) {
-        GlobalFilter._globalFilter = value;
+    public set(value: string) {
+        this._globalFilter = value;
     }
 
-    static reset() {
-        GlobalFilter.set(GlobalFilter.empty);
+    public reset() {
+        this.set(GlobalFilter.empty);
     }
 
-    static isEmpty(): boolean {
-        return GlobalFilter.get() === GlobalFilter.empty;
+    public isEmpty(): boolean {
+        return this.get() === GlobalFilter.empty;
     }
 
-    static equals(tag: string): boolean {
-        return GlobalFilter.get() === tag;
+    public equals(tag: string): boolean {
+        return this.get() === tag;
     }
 
-    static includedIn(description: string): boolean {
-        const globalFilter = GlobalFilter.get();
+    public includedIn(description: string): boolean {
+        const globalFilter = this.get();
         return description.includes(globalFilter);
     }
 
-    static prependTo(description: string): string {
-        return GlobalFilter.get() + ' ' + description;
+    public prependTo(description: string): string {
+        return this.get() + ' ' + description;
     }
 
-    static removeAsWordFromDependingOnSettings(description: string): string {
-        const removeGlobalFilter = GlobalFilter.getRemoveGlobalFilter();
+    public removeAsWordFromDependingOnSettings(description: string): string {
+        const removeGlobalFilter = this.getRemoveGlobalFilter();
         if (removeGlobalFilter) {
-            return GlobalFilter.removeAsWordFrom(description);
+            return this.removeAsWordFrom(description);
         }
 
         return description;
@@ -56,15 +70,15 @@ export class GlobalFilter {
     /**
      * @see setRemoveGlobalFilter
      */
-    static getRemoveGlobalFilter() {
-        return GlobalFilter._removeGlobalFilter;
+    public getRemoveGlobalFilter() {
+        return this._removeGlobalFilter;
     }
 
     /**
      * @see getRemoveGlobalFilter
      */
-    static setRemoveGlobalFilter(removeGlobalFilter: boolean) {
-        GlobalFilter._removeGlobalFilter = removeGlobalFilter;
+    public setRemoveGlobalFilter(removeGlobalFilter: boolean) {
+        this._removeGlobalFilter = removeGlobalFilter;
     }
 
     /**
@@ -73,13 +87,13 @@ export class GlobalFilter {
      * or a space), because we don't want to cut-off nested tags like #task/subtag.
      * If the global filter exists as part of a nested tag, we keep it untouched.
      */
-    static removeAsWordFrom(description: string): string {
-        if (GlobalFilter.isEmpty()) {
+    public removeAsWordFrom(description: string): string {
+        if (this.isEmpty()) {
             return description;
         }
 
         // This matches the global filter (after escaping it) only when it's a complete word
-        const theRegExp = RegExp('(^|\\s)' + RegExpTools.escapeRegExp(GlobalFilter.get()) + '($|\\s)', 'ug');
+        const theRegExp = RegExp('(^|\\s)' + RegExpTools.escapeRegExp(this.get()) + '($|\\s)', 'ug');
 
         if (description.search(theRegExp) > -1) {
             description = description.replace(theRegExp, '$1$2').replace('  ', ' ').trim();
@@ -88,8 +102,8 @@ export class GlobalFilter {
         return description;
     }
 
-    static removeAsSubstringFrom(description: string): string {
-        const globalFilter = GlobalFilter.get();
+    public removeAsSubstringFrom(description: string): string {
+        const globalFilter = this.get();
         return description.replace(globalFilter, '').trim();
     }
 }
diff --git a/src/Config/SettingsTab.ts b/src/Config/SettingsTab.ts
index d12152bd..c9e91765 100644
--- a/src/Config/SettingsTab.ts
+++ b/src/Config/SettingsTab.ts
@@ -101,10 +101,10 @@ export class SettingsTab extends PluginSettingTab {
                 // but wasn't able to figure out how to make the text box
                 // wide enough for the whole string to be visible.
                 text.setPlaceholder('e.g. #task or TODO')
-                    .setValue(GlobalFilter.get())
+                    .setValue(GlobalFilter.getInstance().get())
                     .onChange(async (value) => {
                         updateSettings({ globalFilter: value });
-                        GlobalFilter.set(value);
+                        GlobalFilter.getInstance().set(value);
                         await this.plugin.saveSettings();
                     });
             });
@@ -119,7 +119,7 @@ export class SettingsTab extends PluginSettingTab {
 
                 toggle.setValue(settings.removeGlobalFilter).onChange(async (value) => {
                     updateSettings({ removeGlobalFilter: value });
-                    GlobalFilter.setRemoveGlobalFilter(value);
+                    GlobalFilter.getInstance().setRemoveGlobalFilter(value);
                     await this.plugin.saveSettings();
                 });
             });
diff --git a/src/File.ts b/src/File.ts
index 56c301bc..914cec04 100644
--- a/src/File.ts
+++ b/src/File.ts
@@ -345,7 +345,7 @@ function tryFindingLineNumberFromTaskSectionInfo(
         }
 
         const line = fileLines[listItemLineNumber];
-        if (GlobalFilter.includedIn(line)) {
+        if (GlobalFilter.getInstance().includedIn(line)) {
             if (sectionIndex === originalTask.taskLocation.sectionIndex) {
                 if (line === originalTask.originalMarkdown) {
                     taskLineNumber = listItemLineNumber;
diff --git a/src/InlineRenderer.ts b/src/InlineRenderer.ts
index 188c31e7..1e39fd39 100644
--- a/src/InlineRenderer.ts
+++ b/src/InlineRenderer.ts
@@ -46,7 +46,7 @@ export class InlineRenderer {
                 return false;
             }
 
-            return GlobalFilter.includedIn(firstLineText);
+            return GlobalFilter.getInstance().includedIn(firstLineText);
         });
         if (renderedElements.length === 0) {
             // No tasks means nothing to do.
diff --git a/src/Query/Filter/DescriptionField.ts b/src/Query/Filter/DescriptionField.ts
index 85f78655..1753cc63 100644
--- a/src/Query/Filter/DescriptionField.ts
+++ b/src/Query/Filter/DescriptionField.ts
@@ -25,7 +25,7 @@ export class DescriptionField extends TextField {
         // Remove global filter from description match if present.
         // This is necessary to match only on the content of the task, not
         // the global filter.
-        return GlobalFilter.removeAsSubstringFrom(task.description);
+        return GlobalFilter.getInstance().removeAsSubstringFrom(task.description);
     }
 
     public supportsSorting(): boolean {
@@ -55,7 +55,7 @@ export class DescriptionField extends TextField {
      * Properly reads links [[like this|one]] (note pipe).
      */
     public static cleanDescription(description: string): string {
-        description = GlobalFilter.removeAsSubstringFrom(description);
+        description = GlobalFilter.getInstance().removeAsSubstringFrom(description);
 
         const startsWithLinkRegex = /^\[\[?([^\]]*)]]?/;
         const linkRegexMatch = description.match(startsWithLinkRegex);
diff --git a/src/Suggestor/Suggestor.ts b/src/Suggestor/Suggestor.ts
index b24d7936..006ca84e 100644
--- a/src/Suggestor/Suggestor.ts
+++ b/src/Suggestor/Suggestor.ts
@@ -414,7 +414,7 @@ export function onlySuggestIfBracketOpen(fn: SuggestionBuilder, brackets: [strin
  *                          See 'ch' in https://docs.obsidian.md/Reference/TypeScript+API/EditorPosition/EditorPosition
  */
 export function canSuggestForLine(line: string, cursorPosition: number) {
-    return GlobalFilter.includedIn(line) && cursorIsInTaskLineDescription(line, cursorPosition);
+    return GlobalFilter.getInstance().includedIn(line) && cursorIsInTaskLineDescription(line, cursorPosition);
 }
 
 /**
diff --git a/src/Task.ts b/src/Task.ts
index bc59eadc..79b59e4a 100644
--- a/src/Task.ts
+++ b/src/Task.ts
@@ -239,7 +239,7 @@ export class Task {
 
         // return if the line does not have the global filter. Do this before
         // any other processing to improve performance.
-        if (!GlobalFilter.includedIn(taskComponents.body)) {
+        if (!GlobalFilter.getInstance().includedIn(taskComponents.body)) {
             return null;
         }
 
@@ -286,7 +286,7 @@ export class Task {
         taskInfo.tags = taskInfo.tags.map((tag) => tag.trim());
 
         // Remove the Global Filter if it is there
-        taskInfo.tags = taskInfo.tags.filter((tag) => !GlobalFilter.equals(tag));
+        taskInfo.tags = taskInfo.tags.filter((tag) => !GlobalFilter.getInstance().equals(tag));
 
         return new Task({
             ...taskComponents,
diff --git a/src/TaskLineRenderer.ts b/src/TaskLineRenderer.ts
index 42767c06..b7ea278c 100644
--- a/src/TaskLineRenderer.ts
+++ b/src/TaskLineRenderer.ts
@@ -139,7 +139,7 @@ async function taskToHtml(
         let componentString = emojiSerializer.componentToString(task, taskLayout, component);
         if (componentString) {
             if (component === 'description') {
-                componentString = GlobalFilter.removeAsWordFromDependingOnSettings(componentString);
+                componentString = GlobalFilter.getInstance().removeAsWordFromDependingOnSettings(componentString);
             }
             // Create the text span that will hold the rendered component
             const span = document.createElement('span');
diff --git a/src/lib/QueryRendererHelper.ts b/src/lib/QueryRendererHelper.ts
index 0f8b6c41..5ccc6546 100644
--- a/src/lib/QueryRendererHelper.ts
+++ b/src/lib/QueryRendererHelper.ts
@@ -26,8 +26,8 @@ import { Query } from '../Query/Query';
 export function explainResults(source: string, globalQuery: GlobalQuery, path: string | undefined = undefined): string {
     let result = '';
 
-    if (!GlobalFilter.isEmpty()) {
-        result += `Only tasks containing the global filter '${GlobalFilter.get()}'.\n\n`;
+    if (!GlobalFilter.getInstance().isEmpty()) {
+        result += `Only tasks containing the global filter '${GlobalFilter.getInstance().get()}'.\n\n`;
     }
 
     const tasksBlockQuery = new Query({ source }, path);
diff --git a/src/main.ts b/src/main.ts
index f5326f2e..abef4640 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -70,8 +70,8 @@ export default class TasksPlugin extends Plugin {
     async loadSettings() {
         const newSettings = await this.loadData();
         updateSettings(newSettings);
-        GlobalFilter.set(newSettings.globalFilter);
-        GlobalFilter.setRemoveGlobalFilter(newSettings.removeGlobalFilter);
+        GlobalFilter.getInstance().set(newSettings.globalFilter);
+        GlobalFilter.getInstance().setRemoveGlobalFilter(newSettings.removeGlobalFilter);
         GlobalQuery.getInstance().set(newSettings.globalQuery);
         await this.loadTaskStatuses();
     }
diff --git a/src/ui/EditTask.svelte b/src/ui/EditTask.svelte
index 63c22d4c..a72ef8f3 100644
--- a/src/ui/EditTask.svelte
+++ b/src/ui/EditTask.svelte
@@ -231,11 +231,11 @@
     onMount(() => {
         const { provideAccessKeys } = getSettings();
         withAccessKeys = provideAccessKeys;
-        const description = GlobalFilter.removeAsWordFrom(task.description);
+        const description = GlobalFilter.getInstance().removeAsWordFrom(task.description);
         // If we're displaying to the user the description without the global filter (i.e. it was removed in the method
         // above), or if the description did not include a global filter in the first place, we'll add the global filter
         // when saving the task.
-        if (description != task.description || !GlobalFilter.includedIn(task.description)) {
+        if (description != task.description || !GlobalFilter.getInstance().includedIn(task.description)) {
             addGlobalFilterOnSave = true;
         }
         let priority: typeof editableTask.priority = 'none';
@@ -299,7 +299,7 @@
     const _onSubmit = () => {
         let description = editableTask.description.trim();
         if (addGlobalFilterOnSave) {
-            description = GlobalFilter.prependTo(description);
+            description = GlobalFilter.getInstance().prependTo(description);
         }
 
         const startDate = parseTypedDateForSaving(editableTask.startDate);


Test patch:
diff --git a/tests/Commands/CreateOrEditTaskParser.test.ts b/tests/Commands/CreateOrEditTaskParser.test.ts
index a4a74881..332bdf57 100644
--- a/tests/Commands/CreateOrEditTaskParser.test.ts
+++ b/tests/Commands/CreateOrEditTaskParser.test.ts
@@ -11,7 +11,7 @@ window.moment = moment;
 
 describe('CreateOrEditTaskParser - testing edited task if line is saved unchanged', () => {
     afterEach(() => {
-        GlobalFilter.reset();
+        GlobalFilter.getInstance().reset();
     });
 
     it.each([
@@ -63,7 +63,7 @@ describe('CreateOrEditTaskParser - testing edited task if line is saved unchange
     ])(
         'line loaded into "Create or edit task" command: "%s"',
         (line: string, expectedResult: string, globalFilter: string) => {
-            GlobalFilter.set(globalFilter);
+            GlobalFilter.getInstance().set(globalFilter);
             const path = 'a/b/c.md';
             const task = taskFromLine({ line, path });
             expect(task.toFileLineString()).toStrictEqual(expectedResult);
@@ -74,11 +74,11 @@ describe('CreateOrEditTaskParser - testing edited task if line is saved unchange
 
 describe('CreateOrEditTaskParser - task recognition', () => {
     afterEach(() => {
-        GlobalFilter.reset();
+        GlobalFilter.getInstance().reset();
     });
 
     it('should recognize task details without global filter', () => {
-        GlobalFilter.set('#task');
+        GlobalFilter.getInstance().set('#task');
         const taskLine =
             '- [ ] without global filter but with all the info ⏬ 🔁 every 2 days ➕ 2022-03-10 🛫 2022-01-31 ⏳ 2023-06-13 📅 2024-12-10 ✅ 2023-06-22';
         const path = 'a/b/c.md';
@@ -107,7 +107,7 @@ describe('CreateOrEditTaskParser - created date', () => {
     afterEach(() => {
         jest.useRealTimers();
         resetSettings();
-        GlobalFilter.reset();
+        GlobalFilter.getInstance().reset();
     });
 
     it.each([
@@ -155,7 +155,7 @@ describe('CreateOrEditTaskParser - created date', () => {
 
     it('should add created date if adding the global filter', () => {
         updateSettings({ setCreatedDate: true });
-        GlobalFilter.set('#task');
+        GlobalFilter.getInstance().set('#task');
         const path = 'a/b/c.md';
         const line = '- [ ] did not have the global filter';
 
diff --git a/tests/Commands/ToggleDone.test.ts b/tests/Commands/ToggleDone.test.ts
index 1d762cf4..b9deff09 100644
--- a/tests/Commands/ToggleDone.test.ts
+++ b/tests/Commands/ToggleDone.test.ts
@@ -78,7 +78,7 @@ function testToggleLineForOutOfRangeCursorPositions(
 
 describe('ToggleDone', () => {
     afterEach(() => {
-        GlobalFilter.reset();
+        GlobalFilter.getInstance().reset();
     });
 
     const todaySpy = jest.spyOn(Date, 'now').mockReturnValue(moment('2022-09-04').valueOf());
@@ -93,7 +93,7 @@ describe('ToggleDone', () => {
         testToggleLine('|', '- |');
         testToggleLine('foo|bar', '- foobar|');
 
-        GlobalFilter.set('#task');
+        GlobalFilter.getInstance().set('#task');
 
         testToggleLine('|', '- |');
         testToggleLine('foo|bar', '- foobar|');
@@ -104,7 +104,7 @@ describe('ToggleDone', () => {
         testToggleLine('- |', '- [ ] |');
         testToggleLine('- |foobar', '- [ ] foobar|');
 
-        GlobalFilter.set('#task');
+        GlobalFilter.getInstance().set('#task');
 
         testToggleLine('|- ', '- [ ] |');
         testToggleLine('- |', '- [ ] |');
@@ -118,7 +118,7 @@ describe('ToggleDone', () => {
         // Issue #449 - cursor jumped 13 characters to the right on completion
         testToggleLine('- [ ] I have a |proper description', '- [x] I have a |proper description ✅ 2022-09-04');
 
-        GlobalFilter.set('#task');
+        GlobalFilter.getInstance().set('#task');
 
         testToggleLine('|- [ ] ', '|- [x] ');
         testToggleLine('- [ ] |', '- [x] |');
@@ -134,7 +134,7 @@ describe('ToggleDone', () => {
         // Issue #449 - cursor jumped 13 characters to the left on un-completion
         testToggleLine('- [x] I have a proper description| ✅ 2022-09-04', '- [ ] I have a proper description|');
 
-        GlobalFilter.set('#task');
+        GlobalFilter.getInstance().set('#task');
 
         // Done date is not removed if task does not match global filter
         testToggleLine('|- [x]  ✅ 2022-09-04', '|- [ ] ✅ 2022-09-04');
@@ -162,7 +162,7 @@ describe('ToggleDone', () => {
 - [x] I am a recurring task| 🔁 every day 📅 2022-09-04 ✅ 2022-09-04`,
         );
 
-        GlobalFilter.set('#task');
+        GlobalFilter.getInstance().set('#task');
 
         // Tasks do not recur, and no done-date added, if not matching global filter
         testToggleLine(
@@ -180,7 +180,7 @@ describe('ToggleDone', () => {
 
     describe('should honour next status character', () => {
         afterEach(() => {
-            GlobalFilter.reset();
+            GlobalFilter.getInstance().reset();
         });
 
         // Arrange
@@ -201,7 +201,7 @@ describe('ToggleDone', () => {
         });
 
         it('when there is a global filter and task with global filter is toggled', () => {
-            GlobalFilter.set('#task');
+            GlobalFilter.getInstance().set('#task');
 
             const line1 = '- [C] #task this is a task starting at Con';
 
@@ -214,7 +214,7 @@ describe('ToggleDone', () => {
         });
 
         it('when there is a global filter and task without global filter is toggled', () => {
-            GlobalFilter.set('#task');
+            GlobalFilter.getInstance().set('#task');
 
             const line1 = '- [P] this is a task starting at Pro, not matching the global filter';
 
diff --git a/tests/EditTask.test.ts b/tests/EditTask.test.ts
index 3ecbdaad..7f0f9b74 100644
--- a/tests/EditTask.test.ts
+++ b/tests/EditTask.test.ts
@@ -100,7 +100,7 @@ async function editTaskLine(line: string, newDescription: string | undefined) {
 
 describe('Task rendering', () => {
     afterEach(() => {
-        GlobalFilter.reset();
+        GlobalFilter.getInstance().reset();
     });
 
     function testDescriptionRender(taskDescription: string, expectedDescription: string) {
@@ -118,29 +118,29 @@ describe('Task rendering', () => {
     });
 
     it('should display task description without non-tag Global Filter)', () => {
-        GlobalFilter.set('filter');
+        GlobalFilter.getInstance().set('filter');
         testDescriptionRender('filter important thing', 'important thing');
     });
 
     it('should display task description with complex non-tag Global Filter)', () => {
-        GlobalFilter.set('filter');
+        GlobalFilter.getInstance().set('filter');
         // This behavior is inconsistent with Obsidian's tag definition which includes nested tags
         testDescriptionRender('filter/important thing', 'filter/important thing');
     });
 
     it('should display task description without tag-like Global Filter', () => {
-        GlobalFilter.set('#todo');
+        GlobalFilter.getInstance().set('#todo');
         testDescriptionRender('#todo another plan', 'another plan');
     });
 
     it('should display task description with complex tag-like Global Filter', () => {
-        GlobalFilter.set('#todo');
+        GlobalFilter.getInstance().set('#todo');
         // This behavior is inconsistent with Obsidian's tag definition which includes nested tags
         testDescriptionRender('#todo/important another plan', '#todo/important another plan');
     });
 
     it('should display task description with emoji removed, when global filter is in initial line', () => {
-        GlobalFilter.set('#todo');
+        GlobalFilter.getInstance().set('#todo');
         testDescriptionRender(
             '#todo with global filter and with scheduled date ⏳ 2023-06-13',
             'with global filter and with scheduled date',
@@ -148,7 +148,7 @@ describe('Task rendering', () => {
     });
 
     it('should display task description with emoji removed, even if the global filter is missing from initial line (bug 2037)', () => {
-        GlobalFilter.set('#todo');
+        GlobalFilter.getInstance().set('#todo');
         // When written, this was a demonstration of the behaviour logged in
         // https://github.com/obsidian-tasks-group/obsidian-tasks/issues/2037
         testDescriptionRender(
@@ -160,7 +160,7 @@ describe('Task rendering', () => {
 
 describe('Task editing', () => {
     afterEach(() => {
-        GlobalFilter.reset();
+        GlobalFilter.getInstance().reset();
     });
 
     async function testDescriptionEdit(taskDescription: string, newDescription: string, expectedDescription: string) {
@@ -181,7 +181,7 @@ describe('Task editing', () => {
     it('should not change the description if the task was not edited and keep Global Filter', async () => {
         const globalFilter = '#remember';
         const description = 'simple task';
-        GlobalFilter.set(globalFilter);
+        GlobalFilter.getInstance().set(globalFilter);
         await testDescriptionEdit(`${globalFilter} ${description}`, description, `${globalFilter} ${description}`);
     });
 
@@ -189,7 +189,7 @@ describe('Task editing', () => {
         const globalFilter = '#remember';
         const oldDescription = 'simple task';
         const newDescription = 'new plan';
-        GlobalFilter.set(globalFilter);
+        GlobalFilter.getInstance().set(globalFilter);
         await testDescriptionEdit(
             `${globalFilter} ${oldDescription}`,
             newDescription,
@@ -211,7 +211,7 @@ describe('Exhaustive editing', () => {
     });
 
     afterEach(() => {
-        GlobalFilter.reset();
+        GlobalFilter.getInstance().reset();
         resetSettings();
         jest.useRealTimers();
     });
@@ -247,7 +247,7 @@ describe('Exhaustive editing', () => {
             name,
             title,
             async (globalFilter, setCreatedDate, initialTaskLine) => {
-                GlobalFilter.set(globalFilter as string);
+                GlobalFilter.getInstance().set(globalFilter as string);
 
                 // @ts-expect-error: TS2322: Type 'T2' is not assignable to type 'boolean | undefined'.
                 updateSettings({ setCreatedDate });
diff --git a/tests/GlobalFilter.test.ts b/tests/GlobalFilter.test.ts
index 35546b67..af988e43 100644
--- a/tests/GlobalFilter.test.ts
+++ b/tests/GlobalFilter.test.ts
@@ -1,79 +1,109 @@
-import { resetSettings } from '../src/Config/Settings';
 import { GlobalFilter } from '../src/Config/GlobalFilter';
 
 describe('Global Filter tests', () => {
-    afterEach(() => {
-        GlobalFilter.reset();
+    it('getInstance() should return the same object', () => {
+        const globalFilter1 = GlobalFilter.getInstance();
+        const globalFilter2 = GlobalFilter.getInstance();
+
+        expect(Object.is(globalFilter1, globalFilter2)).toEqual(true);
     });
 
     it('Should provide Global Filter with the default value with get()', () => {
-        expect(GlobalFilter.get()).toEqual('');
+        const globalFilter = new GlobalFilter();
+        expect(globalFilter.get()).toEqual('');
     });
 
     it('Should set new Global Filter', () => {
         // Arrange
         const testValue = 'newGlobalFilter';
-        GlobalFilter.set(testValue);
+        const globalFilter = new GlobalFilter();
+
+        // Act
+        globalFilter.set(testValue);
 
         // Assert
-        expect(GlobalFilter.get()).toEqual(testValue);
+        expect(globalFilter.get()).toEqual(testValue);
+    });
+
+    it('Should allow independent values for independent instances', () => {
+        // Arrange
+        const globalFilter1 = new GlobalFilter();
+        const globalFilter2 = new GlobalFilter();
+
+        // Act
+        globalFilter1.set('1');
+        globalFilter2.set('2');
+
+        // Assert
+        expect(globalFilter1.get()).toEqual('1');
+        expect(globalFilter2.get()).toEqual('2');
     });
 
     it('Should reset the Global Filter', () => {
         // Arrange
         const testValue = '#important';
-        GlobalFilter.set(testValue);
-        GlobalFilter.reset();
+        const globalFilter = new GlobalFilter();
+        globalFilter.set(testValue);
+
+        // Act
+        globalFilter.reset();
 
         // Assert
-        expect(GlobalFilter.get()).toEqual('');
+        expect(globalFilter.get()).toEqual('');
     });
 
     it('Should indicate empty Global Filter by default', () => {
         // Assert
-        expect(GlobalFilter.isEmpty()).toEqual(true);
+        const globalFilter = new GlobalFilter();
+        expect(globalFilter.isEmpty()).toEqual(true);
     });
 
     it('Should indicate non-empty Global Filter after setting one', () => {
         // Arrange
-        GlobalFilter.set('newGlobalFilter');
+        const globalFilter = new GlobalFilter();
+        globalFilter.set('newGlobalFilter');
 
         // Assert
-        expect(GlobalFilter.isEmpty()).toEqual(false);
+        expect(globalFilter.isEmpty()).toEqual(false);
     });
 
     it('Should match a string with Global Filter', () => {
         // Arrange
-        GlobalFilter.set('#task');
+        const globalFilter = new GlobalFilter();
+        globalFilter.set('#task');
 
         // Assert
-        expect(GlobalFilter.includedIn('Important #task inside')).toEqual(true);
+        expect(globalFilter.includedIn('Important #task inside')).toEqual(true);
     });
 
     it('Should check equality correctly', () => {
         // Arrange
-        GlobalFilter.set('#task');
+        const globalFilter = new GlobalFilter();
+        globalFilter.set('#task');
 
         // Assert
-        expect(GlobalFilter.equals('#task')).toEqual(true);
-        expect(GlobalFilter.equals('#tasks')).toEqual(false);
-        expect(GlobalFilter.equals('#TODO')).toEqual(false);
+        expect(globalFilter.equals('#task')).toEqual(true);
+        expect(globalFilter.equals('#tasks')).toEqual(false);
+        expect(globalFilter.equals('#TODO')).toEqual(false);
     });
 
     it('Should not match a string without Global Filter', () => {
         // Arrange
-        GlobalFilter.set('testValue');
+        const globalFilter = new GlobalFilter();
+        globalFilter.set('testValue');
 
         // Assert
-        expect(GlobalFilter.includedIn('Without Global Filter')).toEqual(false);
+        expect(globalFilter.includedIn('Without Global Filter')).toEqual(false);
     });
 
     it('Should control whether to remove the global filter from displayed tasks', () => {
-        GlobalFilter.setRemoveGlobalFilter(false);
-        expect(GlobalFilter.getRemoveGlobalFilter()).toEqual(false);
+        const globalFilter = new GlobalFilter();
 
-        GlobalFilter.setRemoveGlobalFilter(true);
-        expect(GlobalFilter.getRemoveGlobalFilter()).toEqual(true);
+        globalFilter.setRemoveGlobalFilter(false);
+        expect(globalFilter.getRemoveGlobalFilter()).toEqual(false);
+
+        globalFilter.setRemoveGlobalFilter(true);
+        expect(globalFilter.getRemoveGlobalFilter()).toEqual(true);
     });
 
     it('Should remove Global Filter from the beginning of a string', () => {
@@ -81,10 +111,11 @@ describe('Global Filter tests', () => {
         const testValue = '#end';
         const testStringBefore = 'Important thing to do #end';
         const testStringAfter = 'Important thing to do';
-        GlobalFilter.set(testValue);
+        const globalFilter = new GlobalFilter();
+        globalFilter.set(testValue);
 
         // Assert
-        expect(GlobalFilter.removeAsSubstringFrom(testStringBefore)).toEqual(testStringAfter);
+        expect(globalFilter.removeAsSubstringFrom(testStringBefore)).toEqual(testStringAfter);
     });
 
     it('Should remove Global Filter from the end of a string', () => {
@@ -92,10 +123,11 @@ describe('Global Filter tests', () => {
         const testValue = '#beginning';
         const testStringBefore = '#beginning another important thing';
         const testStringAfter = 'another important thing';
-        GlobalFilter.set(testValue);
+        const globalFilter = new GlobalFilter();
+        globalFilter.set(testValue);
 
         // Assert
-        expect(GlobalFilter.removeAsSubstringFrom(testStringBefore)).toEqual(testStringAfter);
+        expect(globalFilter.removeAsSubstringFrom(testStringBefore)).toEqual(testStringAfter);
     });
 
     it('Should remove Global Filter in the middle of a string', () => {
@@ -104,37 +136,35 @@ describe('Global Filter tests', () => {
         const testStringBefore = 'With the GF in the #middle of the string';
         // Note the 2 spaces where the 'newGlobalFilter' was
         const testStringAfter = 'With the GF in the  of the string';
-        GlobalFilter.set(testValue);
+        const globalFilter = new GlobalFilter();
+        globalFilter.set(testValue);
 
         // Assert
-        expect(GlobalFilter.removeAsSubstringFrom(testStringBefore)).toEqual(testStringAfter);
+        expect(globalFilter.removeAsSubstringFrom(testStringBefore)).toEqual(testStringAfter);
     });
 });
 
 describe('Global Filter tests with Remove Global Filter Setting', () => {
-    afterEach(() => {
-        GlobalFilter.reset();
-        resetSettings();
-    });
-
     it('Should remove Global Filter from a string when Setting is set to false', () => {
         // Arrange
-        GlobalFilter.set('todo');
-        GlobalFilter.setRemoveGlobalFilter(false);
+        const globalFilter = new GlobalFilter();
+        globalFilter.set('todo');
+        globalFilter.setRemoveGlobalFilter(false);
 
         // Assert
-        expect(GlobalFilter.removeAsWordFromDependingOnSettings('This is absolutely todo')).toEqual(
+        expect(globalFilter.removeAsWordFromDependingOnSettings('This is absolutely todo')).toEqual(
             'This is absolutely todo',
         );
     });
 
     it('Should remove Global Filter from a string when Setting is set to true', () => {
         // Arrange
-        GlobalFilter.set('todo');
-        GlobalFilter.setRemoveGlobalFilter(true);
+        const globalFilter = new GlobalFilter();
+        globalFilter.set('todo');
+        globalFilter.setRemoveGlobalFilter(true);
 
         // Assert
-        expect(GlobalFilter.removeAsWordFromDependingOnSettings('This is absolutely todo')).toEqual(
+        expect(globalFilter.removeAsWordFromDependingOnSettings('This is absolutely todo')).toEqual(
             'This is absolutely',
         );
     });
@@ -151,153 +181,147 @@ describe('Global Filter tests with Remove Global Filter Setting', () => {
         ['TODO', 'at the end TODO/maybe'],
     ])(
         'should not remove Global Filter (%s) if it is in a substring for a task "- [ ] %s"',
-        async (globalFilter: string, description: string) => {
-            GlobalFilter.setRemoveGlobalFilter(true);
-            GlobalFilter.set(globalFilter);
+        async (globalFilterValue: string, description: string) => {
+            const globalFilter = new GlobalFilter();
+            globalFilter.setRemoveGlobalFilter(true);
+            globalFilter.set(globalFilterValue);
 
-            expect(GlobalFilter.removeAsWordFrom(description)).toEqual(description);
+            expect(globalFilter.removeAsWordFrom(description)).toEqual(description);
         },
     );
 });
 
 describe('check removal of the global filter', () => {
-    afterEach(() => {
-        GlobalFilter.reset();
-    });
-
     type GlobalFilterRemovalExpectation = {
-        globalFilter: string;
+        globalFilterValue: string;
         inputDescription: string;
         expectedDescription: string;
     };
 
     test.each<GlobalFilterRemovalExpectation>([
         {
-            globalFilter: '#task',
+            globalFilterValue: '#task',
             inputDescription: '#task this is a very simple task',
             expectedDescription: 'this is a very simple task',
         },
         {
-            globalFilter: '',
+            globalFilterValue: '',
             inputDescription: '#task this is a very simple task',
             expectedDescription: '#task this is a very simple task',
         },
         {
-            globalFilter: '🞋',
+            globalFilterValue: '🞋',
             inputDescription: 'task with emoji 🞋 global filter',
             expectedDescription: 'task with emoji global filter',
         },
         {
-            globalFilter: '#t',
+            globalFilterValue: '#t',
             inputDescription: 'task with #t global filter in the middle',
             expectedDescription: 'task with global filter in the middle',
         },
         {
-            globalFilter: '#t',
+            globalFilterValue: '#t',
             inputDescription: 'task with global filter in the end #t',
             expectedDescription: 'task with global filter in the end',
         },
         {
-            globalFilter: '#t',
+            globalFilterValue: '#t',
             inputDescription: 'task #t with #t several global #t filters #t',
             // Trailing spaces present after the first removal
             expectedDescription: 'task with  several global  filters',
         },
         {
-            globalFilter: '#t',
+            globalFilterValue: '#t',
             inputDescription: 'task with global filter in the end and some spaces  #t  ',
             expectedDescription: 'task with global filter in the end and some spaces',
         },
         {
-            globalFilter: '#complex/global/filter',
+            globalFilterValue: '#complex/global/filter',
             inputDescription: 'task with #complex/global/filter in the middle',
             expectedDescription: 'task with in the middle',
         },
         {
-            globalFilter: '#task',
+            globalFilterValue: '#task',
             inputDescription: 'task with an extension of the global filter #task/with/extension',
             expectedDescription: 'task with an extension of the global filter #task/with/extension',
         },
         {
-            globalFilter: '#t',
+            globalFilterValue: '#t',
             inputDescription: 'task with #t multiple global filters #t',
             expectedDescription: 'task with multiple global filters',
         },
         {
-            globalFilter: '#t',
+            globalFilterValue: '#t',
             inputDescription: '#t', // confirm behaviour when the description is empty
             expectedDescription: '',
         },
         {
-            globalFilter: '#t',
+            globalFilterValue: '#t',
             inputDescription: '#t #t',
             // Wrong behaviour - the expected should be empty
             expectedDescription: '#t',
         },
         {
-            globalFilter: '#t',
+            globalFilterValue: '#t',
             inputDescription: '#t #t #t',
             // Wrong behaviour - the expected should be empty
             expectedDescription: '#t',
         },
         {
-            globalFilter: '#t',
+            globalFilterValue: '#t',
             inputDescription: '#t #t #t #t',
             // Wrong behaviour - the expected should be empty
             expectedDescription: '#t #t',
         },
         {
-            globalFilter: '#t',
+            globalFilterValue: '#t',
             inputDescription: '#t #t #t #t #t',
             // Wrong behaviour - the expected should be empty
             expectedDescription: '#t #t',
         },
         {
-            globalFilter: 'some',
+            globalFilterValue: 'some',
             inputDescription: 'some', // confirm behaviour when the description is empty
             expectedDescription: '',
         },
         {
-            globalFilter: 'some',
+            globalFilterValue: 'some',
             inputDescription: 'some some',
             // Wrong behaviour - the expected should be empty
             expectedDescription: 'some',
         },
         {
-            globalFilter: 'some',
+            globalFilterValue: 'some',
             inputDescription: 'some some some',
             // Wrong behaviour - the expected should be empty
             expectedDescription: 'some',
         },
         {
-            globalFilter: 'some',
+            globalFilterValue: 'some',
             inputDescription: 'some some some some',
             // Wrong behaviour - the expected should be empty
             expectedDescription: 'some some',
         },
         {
-            globalFilter: 'some',
+            globalFilterValue: 'some',
             inputDescription: 'some some some some some',
             // Wrong behaviour - the expected should be empty
             expectedDescription: 'some some',
         },
     ])(
         'should parse "$inputDescription" and extract "$expectedDescription"',
-        ({ globalFilter, inputDescription, expectedDescription }) => {
+        ({ globalFilterValue, inputDescription, expectedDescription }) => {
             // Arrange
-            GlobalFilter.set(globalFilter);
+            const globalFilter = new GlobalFilter();
+            globalFilter.set(globalFilterValue);
 
             // Assert
-            expect(GlobalFilter.removeAsWordFrom(inputDescription)).toEqual(expectedDescription);
+            expect(globalFilter.removeAsWordFrom(inputDescription)).toEqual(expectedDescription);
         },
     );
 });
 
 describe('check removal of the global filter exhaustively', () => {
-    afterEach(() => {
-        GlobalFilter.reset();
-    });
-
     test.each<string>([
         '#t',
         // The characters listed below are the ones that are - or were - escaped by
@@ -332,44 +356,43 @@ describe('check removal of the global filter exhaustively', () => {
         // Failed attempt at creating a failing test for when / was not escaped
         '///',
         '\\',
-    ])('should parse global filter "%s" edge cases correctly', (globalFilter) => {
+    ])('should parse global filter "%s" edge cases correctly', (globalFilterValue) => {
         // Arrange
-        GlobalFilter.set(globalFilter);
+        const globalFilter = new GlobalFilter();
+        globalFilter.set(globalFilterValue);
 
         // global filter removed at beginning, middle and end
-        let inputDescription = `${globalFilter} 1 ${globalFilter} 2 ${globalFilter}`;
+        let inputDescription = `${globalFilterValue} 1 ${globalFilterValue} 2 ${globalFilterValue}`;
         let expectedDescription = '1 2';
-        expect(GlobalFilter.removeAsWordFrom(inputDescription)).toEqual(expectedDescription);
+        expect(globalFilter.removeAsWordFrom(inputDescription)).toEqual(expectedDescription);
 
         // global filter not removed if non-empty non-tag characters before or after it
-        inputDescription = `${globalFilter}x 1 x${globalFilter} ${globalFilter}x 2 x${globalFilter}`;
-        expectedDescription = `${globalFilter}x 1 x${globalFilter} ${globalFilter}x 2 x${globalFilter}`;
-        expect(GlobalFilter.removeAsWordFrom(inputDescription)).toEqual(expectedDescription);
+        inputDescription = `${globalFilterValue}x 1 x${globalFilterValue} ${globalFilterValue}x 2 x${globalFilterValue}`;
+        expectedDescription = `${globalFilterValue}x 1 x${globalFilterValue} ${globalFilterValue}x 2 x${globalFilterValue}`;
+        expect(globalFilter.removeAsWordFrom(inputDescription)).toEqual(expectedDescription);
 
         // global filter not removed if non-empty sub-tag characters after it.
         // Include at least one occurrence of global filter, so we don't pass by luck.
-        inputDescription = `${globalFilter}/x 1 x${globalFilter} ${globalFilter}/x 2 ${globalFilter} ${globalFilter}/x`;
-        expectedDescription = `${globalFilter}/x 1 x${globalFilter} ${globalFilter}/x 2 ${globalFilter}/x`;
-        expect(GlobalFilter.removeAsWordFrom(inputDescription)).toEqual(expectedDescription);
+        inputDescription = `${globalFilterValue}/x 1 x${globalFilterValue} ${globalFilterValue}/x 2 ${globalFilterValue} ${globalFilterValue}/x`;
+        expectedDescription = `${globalFilterValue}/x 1 x${globalFilterValue} ${globalFilterValue}/x 2 ${globalFilterValue}/x`;
+        expect(globalFilter.removeAsWordFrom(inputDescription)).toEqual(expectedDescription);
     });
 });
 
 describe('GlobalFilter.prepend() tests', () => {
-    afterEach(() => {
-        GlobalFilter.reset();
-    });
-
     it('Should prepend Global Filter', () => {
-        const globalFilter = 'awesome';
+        const globalFilterValue = 'awesome';
         const description = 'blossom';
 
-        GlobalFilter.set(globalFilter);
-        expect(GlobalFilter.prependTo(description)).toEqual(`${globalFilter} ${description}`);
+        const globalFilter = new GlobalFilter();
+        globalFilter.set(globalFilterValue);
+        expect(globalFilter.prependTo(description)).toEqual(`${globalFilterValue} ${description}`);
     });
 
     it('Should prepend not prepend empty Global Filter', () => {
         // Note that an empty space is currently prepended in case the Global Filter is empty
         // Not fixing this for now in a refactoring PR
-        expect(GlobalFilter.prependTo('description')).toEqual(' description');
+        const globalFilter = new GlobalFilter();
+        expect(globalFilter.prependTo('description')).toEqual(' description');
     });
 });
diff --git a/tests/Query.test.ts b/tests/Query.test.ts
index 59313373..2f1f3fcc 100644
--- a/tests/Query.test.ts
+++ b/tests/Query.test.ts
@@ -1055,7 +1055,7 @@ describe('Query', () => {
 
     describe('explanations', () => {
         afterEach(() => {
-            GlobalFilter.reset();
+            GlobalFilter.getInstance().reset();
         });
 
         it('should explain 0 filters', () => {
diff --git a/tests/Query/Filter/DescriptionField.test.ts b/tests/Query/Filter/DescriptionField.test.ts
index e03faa90..271d9aad 100644
--- a/tests/Query/Filter/DescriptionField.test.ts
+++ b/tests/Query/Filter/DescriptionField.test.ts
@@ -43,7 +43,7 @@ describe('description should strip signifiers, some duplicate spaces and trailin
 
     it('with tag as global filter - all tags included', () => {
         // Arrange
-        GlobalFilter.set('#task');
+        GlobalFilter.getInstance().set('#task');
 
         const task = fromLine({
             line: '- [ ] #task Initial  description  ⏫  #tag1 ✅ 2022-08-12 #tag2/sub-tag ',
@@ -54,12 +54,12 @@ describe('description should strip signifiers, some duplicate spaces and trailin
         expect(field.value(task)).toStrictEqual('Initial  description #tag1 #tag2/sub-tag');
 
         // Cleanup
-        GlobalFilter.reset();
+        GlobalFilter.getInstance().reset();
     });
 
     it('with non-tag as global filter - all tags included', () => {
         // Arrange
-        GlobalFilter.set('global-filter');
+        GlobalFilter.getInstance().set('global-filter');
 
         const task = fromLine({
             line: '- [ ] global-filter Initial  description  ⏫  #tag1 ✅ 2022-08-12 #tag2/sub-tag ',
@@ -70,14 +70,14 @@ describe('description should strip signifiers, some duplicate spaces and trailin
         expect(field.value(task)).toStrictEqual('Initial  description #tag1 #tag2/sub-tag');
 
         // Cleanup
-        GlobalFilter.reset();
+        GlobalFilter.getInstance().reset();
     });
 });
 
 describe('description', () => {
     it('ignores the global filter when filtering', () => {
         // Arrange
-        GlobalFilter.set('#task');
+        GlobalFilter.getInstance().set('#task');
         const filter = new DescriptionField().createFilterOrErrorMessage('description includes task');
 
         // Act, Assert
@@ -90,12 +90,12 @@ describe('description', () => {
         testDescriptionFilter(filter, '- [ ] #task this does: task', true);
 
         // Cleanup
-        GlobalFilter.reset();
+        GlobalFilter.getInstance().reset();
     });
 
     it('works without a global filter', () => {
         // Arrange
-        GlobalFilter.set('');
+        GlobalFilter.getInstance().set('');
         const filter = new DescriptionField().createFilterOrErrorMessage('description includes task');
 
         // Act, Assert
@@ -106,7 +106,7 @@ describe('description', () => {
         testDescriptionFilter(filter, '- [ ] #task this does: task', true);
 
         // Cleanup
-        GlobalFilter.reset();
+        GlobalFilter.getInstance().reset();
     });
 
     it('works with regex', () => {
diff --git a/tests/Query/Filter/TagsField.test.ts b/tests/Query/Filter/TagsField.test.ts
index 94454f2e..ccc48f3d 100644
--- a/tests/Query/Filter/TagsField.test.ts
+++ b/tests/Query/Filter/TagsField.test.ts
@@ -38,7 +38,7 @@ describe('tag presence & absence', () => {
     });
 
     it('should filter together with the global filter ("has tags")', () => {
-        GlobalFilter.set('#task');
+        GlobalFilter.getInstance().set('#task');
 
         // Arrange
         const filter = new TagsField().createFilterOrErrorMessage('has tags');
@@ -51,11 +51,11 @@ describe('tag presence & absence', () => {
         expect(filter).toMatchTaskFromLine('- [ ] #task stuff #one #two ');
         expect(filter).not.toMatchTaskFromLine('- [ ] #task global filter is not a tag');
 
-        GlobalFilter.reset();
+        GlobalFilter.getInstance().reset();
     });
 
     it('should filter together with the global filter ("no tags")', () => {
-        GlobalFilter.set('#task');
+        GlobalFilter.getInstance().set('#task');
 
         // Arrange
         const filter = new TagsField().createFilterOrErrorMessage('no tags');
@@ -68,7 +68,7 @@ describe('tag presence & absence', () => {
         expect(filter).not.toMatchTaskFromLine('- [ ] #task stuff #one #two ');
         expect(filter).toMatchTaskFromLine('- [ ] #task global filter is not a tag');
 
-        GlobalFilter.reset();
+        GlobalFilter.getInstance().reset();
     });
 });
 
@@ -221,7 +221,7 @@ describe('tag/tags', () => {
             'should filter tag with globalFilter %s',
             (_, { tasks: allTaskLines, filters, expectedResult }) => {
                 // Arrange
-                GlobalFilter.set('#task');
+                GlobalFilter.getInstance().set('#task');
 
                 // Run on the plural version of the filter first.
                 shouldSupportFiltering(filters, allTaskLines, expectedResult);
@@ -241,7 +241,7 @@ describe('tag/tags', () => {
                 shouldSupportFiltering(filters, allTaskLines, expectedResult);
 
                 // Cleanup
-                GlobalFilter.reset();
+                GlobalFilter.getInstance().reset();
             },
         );
 
@@ -276,14 +276,14 @@ describe('tag/tags', () => {
 
         it('should ignore the tag which is the global filter', () => {
             // Arrange
-            GlobalFilter.set('#task');
+            GlobalFilter.getInstance().set('#task');
             const filters: Array<string> = ['tags include task'];
 
             // Act, Assert
             shouldSupportFiltering(filters, defaultTasksWithTags, []);
 
             // Cleanup
-            GlobalFilter.reset();
+            GlobalFilter.getInstance().reset();
         });
     });
 
@@ -472,7 +472,7 @@ describe('Sort by tags', () => {
 
     it('should sort correctly by tag defaulting to first with global filter', () => {
         // Arrange
-        GlobalFilter.set('#task');
+        GlobalFilter.getInstance().set('#task');
 
         const t1 = fromLine({ line: '- [ ] #task a #aaa #jjj' });
         const t2 = fromLine({ line: '- [ ] #task a #aaaa #aaaa' });
@@ -499,12 +499,12 @@ describe('Sort by tags', () => {
         ).toEqual(expectedOrder);
 
         // Cleanup
-        GlobalFilter.reset();
+        GlobalFilter.getInstance().reset();
     });
 
     it('should sort correctly reversed by tag defaulting to first with global filter', () => {
         // Arrange
-        GlobalFilter.set('#task');
+        GlobalFilter.getInstance().set('#task');
 
         const t1 = fromLine({ line: '- [ ] #task a #aaa #jjj' });
         const t2 = fromLine({ line: '- [ ] #task a #aaaa #aaaa' });
@@ -531,12 +531,12 @@ describe('Sort by tags', () => {
         ).toEqual(expectedOrder);
 
         // Cleanup
-        GlobalFilter.reset();
+        GlobalFilter.getInstance().reset();
     });
 
     it('should sort correctly by second tag with global filter', () => {
         // Arrange
-        GlobalFilter.set('#task');
+        GlobalFilter.getInstance().set('#task');
 
         const t1 = fromLine({ line: '- [ ] #task a #fff #aaa' });
         const t2 = fromLine({ line: '- [ ] #task a #aaaa #aaaa' });
@@ -558,12 +558,12 @@ describe('Sort by tags', () => {
         expect(result).toEqual(expectedOrder);
 
         // Cleanup
-        GlobalFilter.reset();
+        GlobalFilter.getInstance().reset();
     });
 
     it('should sort correctly reversed by second tag with global filter', () => {
         // Arrange
-        GlobalFilter.set('#task');
+        GlobalFilter.getInstance().set('#task');
 
         const t1 = fromLine({ line: '- [ ] #task a #fff #aaa' });
         const t2 = fromLine({ line: '- [ ] #task a #aaaa #aaaa' });
@@ -585,7 +585,7 @@ describe('Sort by tags', () => {
         expect(result).toEqual(expectedOrder);
 
         // Cleanup
-        GlobalFilter.reset();
+        GlobalFilter.getInstance().reset();
     });
 
     // Issue #1407 - Multiple 'sort by tag' lines ignored all but last one
diff --git a/tests/Suggestor/Suggestor.test.ts b/tests/Suggestor/Suggestor.test.ts
index bb806c2c..02656660 100644
--- a/tests/Suggestor/Suggestor.test.ts
+++ b/tests/Suggestor/Suggestor.test.ts
@@ -230,7 +230,7 @@ describe('onlySuggestIfBracketOpen', () => {
 
 describe('canSuggestForLine', () => {
     afterEach(() => {
-        GlobalFilter.reset();
+        GlobalFilter.getInstance().reset();
     });
 
     function canSuggestForLineWithCursor(line: string) {
@@ -238,22 +238,22 @@ describe('canSuggestForLine', () => {
     }
 
     it('should not suggest if there is no checkbox', () => {
-        GlobalFilter.reset();
+        GlobalFilter.getInstance().reset();
         expect(canSuggestForLineWithCursor('- not a task line|')).toEqual(false);
     });
 
     it('should suggest if there is no global filter and cursor is in the description', () => {
-        GlobalFilter.reset();
+        GlobalFilter.getInstance().reset();
         expect(canSuggestForLineWithCursor('- [ ] global filter is not set|')).toEqual(true);
     });
 
     it('should suggest if global filter missing from line', () => {
-        GlobalFilter.set('#todo');
+        GlobalFilter.getInstance().set('#todo');
         expect(canSuggestForLineWithCursor('- [ ] #todo has global filter|')).toEqual(true);
     });
 
     it('should not suggest if global filter missing from line', () => {
-        GlobalFilter.set('#todo');
+        GlobalFilter.getInstance().set('#todo');
         expect(canSuggestForLineWithCursor('- [ ] no global filter|')).toEqual(false);
     });
 
diff --git a/tests/Task.test.ts b/tests/Task.test.ts
index 348816c8..04f45e89 100644
--- a/tests/Task.test.ts
+++ b/tests/Task.test.ts
@@ -406,7 +406,7 @@ describe('parsing tags', () => {
         ({ markdownTask, expectedDescription, extractedTags, globalFilter }) => {
             // Arrange
             if (globalFilter != '') {
-                GlobalFilter.set(globalFilter);
+                GlobalFilter.getInstance().set(globalFilter);
             }
 
             // Act
@@ -423,7 +423,7 @@ describe('parsing tags', () => {
 
             // Cleanup
             if (globalFilter != '') {
-                GlobalFilter.reset();
+                GlobalFilter.getInstance().reset();
             }
         },
     );
@@ -431,12 +431,12 @@ describe('parsing tags', () => {
 
 describe('task parsing VS global filter', () => {
     afterEach(() => {
-        GlobalFilter.reset();
+        GlobalFilter.getInstance().reset();
     });
 
     it('returns null when task does not have global filter', () => {
         // Arrange
-        GlobalFilter.set('#task');
+        GlobalFilter.getInstance().set('#task');
         const line = '- [x] this is a done task 🗓 2021-09-12 ✅ 2021-06-20';
 
         // Act
@@ -455,7 +455,7 @@ describe('task parsing VS global filter', () => {
         '- [ #task ] this is a task 🗓 2021-09-12',
     ])('should not parse task with global filter outside of the description: "%s"', (line: string) => {
         // Arrange
-        GlobalFilter.set('#task');
+        GlobalFilter.getInstance().set('#task');
 
         // Act
         const task = fromLine({
@@ -468,7 +468,7 @@ describe('task parsing VS global filter', () => {
 
     it('should not consider task status when searching for global filter', () => {
         // Arrange
-        GlobalFilter.set('@');
+        GlobalFilter.getInstance().set('@');
 
         // Act
         const task = fromLine({
@@ -673,7 +673,7 @@ describe('to string', () => {
     it('retains the global filter', () => {
         // Arrange
         const line = '- [ ] This is a task with #t as a global filter and also #t/some tags';
-        GlobalFilter.set('#t');
+        GlobalFilter.getInstance().set('#t');
 
         // Act
         const task: Task = fromLine({
@@ -684,7 +684,7 @@ describe('to string', () => {
         const expectedLine = 'This is a task with #t as a global filter and also #t/some tags';
         expect(task.toString()).toStrictEqual(expectedLine);
         resetSettings();
-        GlobalFilter.reset();
+        GlobalFilter.getInstance().reset();
     });
 });
 
@@ -1159,7 +1159,7 @@ describe('created dates on recurring task', () => {
     afterEach(() => {
         jest.useRealTimers();
         resetSettings();
-        GlobalFilter.reset();
+        GlobalFilter.getInstance().reset();
     });
 
     it('should not set created date with disabled setting', () => {
@@ -1216,13 +1216,13 @@ describe('order of recurring tasks', () => {
         jest.useFakeTimers();
         jest.setSystemTime(new Date(2023, 5 - 1, 16));
         resetSettings();
-        GlobalFilter.reset();
+        GlobalFilter.getInstance().reset();
     });
 
     afterAll(() => {
         jest.useRealTimers();
         resetSettings();
-        GlobalFilter.reset();
+        GlobalFilter.getInstance().reset();
     });
 
     it('should put new task before old, by default', () => {
diff --git a/tests/TaskLineRenderer.test.ts b/tests/TaskLineRenderer.test.ts
index 650d20e8..5d2bba1d 100644
--- a/tests/TaskLineRenderer.test.ts
+++ b/tests/TaskLineRenderer.test.ts
@@ -67,8 +67,8 @@ function getOtherLayoutComponents(parentElement: HTMLElement): string[] {
 describe('task line rendering', () => {
     afterEach(() => {
         resetSettings();
-        GlobalFilter.reset();
-        GlobalFilter.setRemoveGlobalFilter(false);
+        GlobalFilter.getInstance().reset();
+        GlobalFilter.getInstance().setRemoveGlobalFilter(false);
     });
 
     it('creates the correct span structure for a basic task', async () => {
@@ -120,8 +120,8 @@ describe('task line rendering', () => {
     };
 
     it('should render Global Filter when the Remove Global Filter is off', async () => {
-        GlobalFilter.setRemoveGlobalFilter(false);
-        GlobalFilter.set('#global');
+        GlobalFilter.getInstance().setRemoveGlobalFilter(false);
+        GlobalFilter.getInstance().set('#global');
 
         const taskLine = '- [ ] This is a simple task with a #global filter';
         const descriptionWithFilter = await getDescriptionTest(taskLine);
@@ -130,8 +130,8 @@ describe('task line rendering', () => {
     });
 
     it('should not render Global Filter when the Remove Global Filter is on', async () => {
-        GlobalFilter.setRemoveGlobalFilter(true);
-        GlobalFilter.set('#global');
+        GlobalFilter.getInstance().setRemoveGlobalFilter(true);
+        GlobalFilter.getInstance().set('#global');
 
         const taskLine = '- [ ] #global/subtag-shall-stay This is a simple task with a #global filter';
         const descriptionWithoutFilter = await getDescriptionTest(taskLine);
diff --git a/tests/lib/QueryRendererHelper.test.ts b/tests/lib/QueryRendererHelper.test.ts
index 5b97ccf3..b62c59e1 100644
--- a/tests/lib/QueryRendererHelper.test.ts
+++ b/tests/lib/QueryRendererHelper.test.ts
@@ -11,7 +11,7 @@ window.moment = moment;
 
 describe('explain', () => {
     afterEach(() => {
-        GlobalFilter.reset();
+        GlobalFilter.getInstance().reset();
     });
 
     it('should explain a task', () => {
@@ -26,7 +26,7 @@ No filters supplied. All tasks will match the query.`;
     });
 
     it('should explain a task with global filter active', () => {
-        GlobalFilter.set('#task');
+        GlobalFilter.getInstance().set('#task');
 
         const source = '';
         const query = new Query({ source });
@@ -58,7 +58,7 @@ No filters supplied. All tasks will match the query.`;
 
     it('should explain a task with global query and global filter active', () => {
         const globalQuery = new GlobalQuery('description includes hello');
-        GlobalFilter.set('#task');
+        GlobalFilter.getInstance().set('#task');
 
         const source = '';
         const query = new Query({ source });