Repository: obsidian-tasks-group/obsidian-tasks
Pull request #2215 (2023-08-15 08:51:55)

refactor: Introduce GlobalQuery class: the plugin uses a singleton.
# Description

~~Move `GlobalQuery` from `Settings` to a singleton abstraction.~~

Provide a `GlobalQuery` class to encapsulate a lot of the behaviour of global query setting.

It is based on ideas in the existing `StatusRegistry` class, where tests create instances of the class and use them in isolation. And code in `src/` only uses the static `GlobalQuery.getInstance()` to obtain a `GlobalQuery` object.

This PR is a refactored PR https://github.com/obsidian-tasks-group/obsidian-tasks/pull/2209

## Motivation and Context

We discussed several times that this is needed so I found some time =)

## How has this been tested?

Unit tests.

## Types of changes


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)
- [ ] **Infrastructure** (prefix: `chore` - examples include GitHub Actions, issue templates)

## Checklist

- [x] My code follows the code style of this project and passes `yarn run lint`.
- [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)

claremacrae: Hi @ilandikov, thanks very much!

I'll look at this as soon as I can...
ilandikov: @claremacrae I went through the comments several times, played around with the code and found the piece missing in my head to implement this.

So we don't want to remove the `globalQuery` from `Settings` because the save/load mechanism is working there and extracting the **stored** value somewhere else would be cumbersome - there would be a need to modify the `main.saveSettings()` etc. So we keep the `Settings` as they are and I agree with that 100% now =)

In my understanding the purpose of singleton pattern is to have a unique object that stores the actual data **just once.** But in our case it is the `Settings.settings` that does just that. So `GlobalQuery` is not synthetic sugar but data duplication. So what is the point?

In the previous PR you mentioned that `StatusRegistry` is also using the singleton pattern and if I check that I see that the `statusTypes` are actually stored twice - in the `settings` and in the `StatusRegistry` (with a `static` method that the singleton patter shall remove...):

```ts
async function updateAndSaveStatusSettings(statusTypes: StatusSettings, settings: SettingsTab) {
    updateSettings({
        statusSettings: statusTypes,
    });

    // Update the active statuses.
    // This saves the user from having to restart Obsidian in order to apply the changed status(es).
    StatusSettings.applyToStatusRegistry(statusTypes, StatusRegistry.getInstance());

    await settings.saveSettings(true);
}
```

PS. I'm quite sure that the solution you are proposing is better that what I have in my head, please help me understand it better.
ilandikov: @claremacrae as discussed yesterday

- added tests for GlobalQuery
- fixed the missing `trim()` with TDD ;)
- test are calling `new GlobalQuery()` and src calls `getInstance()`
- fixed storing & restoring the GlobalQuery from the settings. Smoke test:

````
```tasks
description include task block
explain
```
````

set GlobalQuery in settings to `tag includes #globalquery`

restart Obsidan -> explain shows GlobalQuery taken into account.

set GlobalQuery in settings to `tag includes #new_globalquery`

restart Obsidan -> explain shows GlobalQuery taken into account.

Patch:
diff --git a/manifest.json b/manifest.json
index e9e05936..8569d755 100644
--- a/manifest.json
+++ b/manifest.json
@@ -1,7 +1,7 @@
 {
   "id": "obsidian-tasks-plugin",
   "name": "Tasks",
-  "version": "4.7.0",
+  "version": "4.7.1",
   "minAppVersion": "1.1.1",
   "description": "Task management for Obsidian",
   "author": "Martin Schenck and Clare Macrae",
diff --git a/package.json b/package.json
index 8dcbfa29..344b676b 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "obsidian-tasks",
-  "version": "4.7.0",
+  "version": "4.7.1",
   "description": "Task management for the Obsidian knowledge base",
   "main": "main.js",
   "scripts": {
diff --git a/src/Config/GlobalQuery.ts b/src/Config/GlobalQuery.ts
new file mode 100644
index 00000000..9b6dc3f9
--- /dev/null
+++ b/src/Config/GlobalQuery.ts
@@ -0,0 +1,62 @@
+import { Query } from '../Query/Query';
+
+/**
+ * Global Query set in the {@link SettingsTab} and associated services.
+ *
+ * There are two ways of using this class.
+ * - In 'production' code, that is in the actual plugin code that is released,
+ *   call {@link GlobalQuery.getInstance()} to obtain the single global instance.
+ * - Tests of Global Query capabilities do not need to modify the global instance:
+ *   They should use `new GlobalQuery()`, which makes simpler, more readable
+ *   tests that can be run in parallel.
+ *
+ * @export
+ * @class GlobalQuery
+ */
+export class GlobalQuery {
+    private static instance: GlobalQuery;
+
+    static empty = '';
+    private _source;
+
+    /**
+     * Creates an instance of Global Query object.
+     *
+     * Code in the plugin should use {@link getInstance} to access the Global Query services.
+     */
+    constructor(source: string = GlobalQuery.empty) {
+        this._source = source;
+    }
+
+    /**
+     * Provides access to the single global instance of the Global Query.
+     * This should be used in the plugin code.
+     */
+    public static getInstance(): GlobalQuery {
+        if (!GlobalQuery.instance) {
+            GlobalQuery.instance = new GlobalQuery();
+        }
+
+        return GlobalQuery.instance;
+    }
+
+    public set(source: string) {
+        this._source = source;
+    }
+
+    /**
+     * Returns {@link Query} object with the Global Query
+     * @param path
+     */
+    public query(path: string | undefined = undefined): Query {
+        return new Query({ source: this._source }, path);
+    }
+
+    /**
+     * Returns true if the Global Query contains characters except line breaks and spaces.
+     *
+     */
+    public hasInstructions() {
+        return this._source.trim() !== GlobalQuery.empty;
+    }
+}
diff --git a/src/Config/Settings.ts b/src/Config/Settings.ts
index c92fed1e..1144556c 100644
--- a/src/Config/Settings.ts
+++ b/src/Config/Settings.ts
@@ -15,6 +15,7 @@ import { StatusSettings } from './StatusSettings';
 import { Feature } from './Feature';
 import type { FeatureFlag } from './Feature';
 import { GlobalFilter } from './GlobalFilter';
+import { GlobalQuery } from './GlobalQuery';
 
 interface SettingsMap {
     [key: string]: string | boolean;
@@ -90,7 +91,7 @@ export interface Settings {
 }
 
 const defaultSettings: Settings = {
-    globalQuery: '',
+    globalQuery: GlobalQuery.empty,
     globalFilter: GlobalFilter.empty,
     removeGlobalFilter: false,
     taskFormat: 'tasksPluginEmoji',
@@ -212,12 +213,3 @@ export const toggleFeature = (internalName: string, enabled: boolean): FeatureFl
 export function getUserSelectedTaskFormat(): TaskFormat {
     return TASK_FORMATS[getSettings().taskFormat];
 }
-
-/**
- * Retrieves the source of the global {@link Query}
- *
- * @exports
- */
-export function getGlobalQuerySource(): { source: string } {
-    return { source: getSettings().globalQuery };
-}
diff --git a/src/Config/SettingsTab.ts b/src/Config/SettingsTab.ts
index 6427384e..6d4dc6f9 100644
--- a/src/Config/SettingsTab.ts
+++ b/src/Config/SettingsTab.ts
@@ -12,6 +12,7 @@ import { StatusSettings } from './StatusSettings';
 import settingsJson from './settingsConfiguration.json';
 
 import { CustomStatusModal } from './CustomStatusModal';
+import { GlobalQuery } from './GlobalQuery';
 
 export class SettingsTab extends PluginSettingTab {
     // If the UI needs a more complex setting you can create a
@@ -143,7 +144,7 @@ export class SettingsTab extends PluginSettingTab {
                         .setValue(settings.globalQuery)
                         .onChange(async (value) => {
                             updateSettings({ globalQuery: value });
-
+                            GlobalQuery.getInstance().set(value);
                             await this.plugin.saveSettings();
                         });
                 }),
diff --git a/src/QueryRenderer.ts b/src/QueryRenderer.ts
index b430491d..08c2baa0 100644
--- a/src/QueryRenderer.ts
+++ b/src/QueryRenderer.ts
@@ -1,5 +1,6 @@
 import { App, Keymap, MarkdownRenderChild, MarkdownRenderer, Plugin, TFile } from 'obsidian';
 import type { EventRef, MarkdownPostProcessorContext } from 'obsidian';
+import { GlobalQuery } from './Config/GlobalQuery';
 
 import type { IQuery } from './IQuery';
 import { State } from './Cache';
@@ -89,12 +90,12 @@ class QueryRenderChild extends MarkdownRenderChild {
         // added later.
         switch (this.containerEl.className) {
             case 'block-language-tasks':
-                this.query = getQueryForQueryRenderer(this.source, this.filePath);
+                this.query = getQueryForQueryRenderer(this.source, GlobalQuery.getInstance(), this.filePath);
                 this.queryType = 'tasks';
                 break;
 
             default:
-                this.query = getQueryForQueryRenderer(this.source, this.filePath);
+                this.query = getQueryForQueryRenderer(this.source, GlobalQuery.getInstance(), this.filePath);
                 this.queryType = 'tasks';
                 break;
         }
@@ -135,7 +136,7 @@ class QueryRenderChild extends MarkdownRenderChild {
         const millisecondsToMidnight = midnight.getTime() - now.getTime();
 
         this.queryReloadTimeout = setTimeout(() => {
-            this.query = getQueryForQueryRenderer(this.source, this.filePath);
+            this.query = getQueryForQueryRenderer(this.source, GlobalQuery.getInstance(), this.filePath);
             // Process the current cache state:
             this.events.triggerRequestCacheUpdate(this.render.bind(this));
             this.reloadQueryAtMidnight();
@@ -198,7 +199,7 @@ class QueryRenderChild extends MarkdownRenderChild {
 
     // Use the 'explain' instruction to enable this
     private createExplanation(content: HTMLDivElement) {
-        const explanationAsString = explainResults(this.source, this.filePath);
+        const explanationAsString = explainResults(this.source, GlobalQuery.getInstance(), this.filePath);
 
         const explanationsBlock = content.createEl('pre');
         explanationsBlock.addClasses(['plugin-tasks-query-explanation']);
diff --git a/src/lib/QueryRendererHelper.ts b/src/lib/QueryRendererHelper.ts
index 021a4855..0f8b6c41 100644
--- a/src/lib/QueryRendererHelper.ts
+++ b/src/lib/QueryRendererHelper.ts
@@ -1,6 +1,5 @@
-import type { IQuery } from '../IQuery';
-import { getGlobalQuerySource } from '../Config/Settings';
 import { GlobalFilter } from '../Config/GlobalFilter';
+import type { GlobalQuery } from '../Config/GlobalQuery';
 import { Query } from '../Query/Query';
 
 /**
@@ -20,10 +19,11 @@ import { Query } from '../Query/Query';
  *     * Explains the query described by {@link source}
  *
  * @param {string} source The source of the task block to explain
+ * @param globalQuery
  * @param {string} path The location of the task block, if known
  * @returns {string}
  */
-export function explainResults(source: string, path: string | undefined = undefined): string {
+export function explainResults(source: string, globalQuery: GlobalQuery, path: string | undefined = undefined): string {
     let result = '';
 
     if (!GlobalFilter.isEmpty()) {
@@ -33,10 +33,8 @@ export function explainResults(source: string, path: string | undefined = undefi
     const tasksBlockQuery = new Query({ source }, path);
 
     if (!tasksBlockQuery.ignoreGlobalQuery) {
-        const globalQuery: IQuery = new Query(getGlobalQuerySource(), path);
-
-        if (globalQuery.source.trim() !== '') {
-            result += `Explanation of the global query:\n\n${globalQuery.explainQuery()}\n`;
+        if (globalQuery.hasInstructions()) {
+            result += `Explanation of the global query:\n\n${globalQuery.query(path).explainQuery()}\n`;
         }
     }
 
@@ -51,16 +49,16 @@ export function explainResults(source: string, path: string | undefined = undefi
  * This query is the result of joining the global query with the query in the task block
  *
  * @param {string} source The query source from the task block
+ * @param globalQuery
  * @param {string | undefined} path The path to the file containing the query, if available.
  * @returns {Query} The query to execute
  */
-export function getQueryForQueryRenderer(source: string, path: string | undefined): Query {
-    const globalQuery = new Query(getGlobalQuerySource(), path);
+export function getQueryForQueryRenderer(source: string, globalQuery: GlobalQuery, path: string | undefined): Query {
     const tasksBlockQuery = new Query({ source }, path);
 
     if (tasksBlockQuery.ignoreGlobalQuery) {
         return tasksBlockQuery;
     }
 
-    return globalQuery.append(tasksBlockQuery);
+    return globalQuery.query(path).append(tasksBlockQuery);
 }
diff --git a/src/main.ts b/src/main.ts
index c55f403e..aee6a0dd 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -2,6 +2,7 @@ import { Plugin } from 'obsidian';
 
 import { Cache } from './Cache';
 import { Commands } from './Commands';
+import { GlobalQuery } from './Config/GlobalQuery';
 import { TasksEvents } from './TasksEvents';
 import { initializeFile } from './File';
 import { InlineRenderer } from './InlineRenderer';
@@ -68,6 +69,7 @@ export default class TasksPlugin extends Plugin {
     async loadSettings() {
         const newSettings = await this.loadData();
         updateSettings(newSettings);
+        GlobalQuery.getInstance().set(newSettings.globalQuery);
         await this.loadTaskStatuses();
     }
 
diff --git a/versions.json b/versions.json
index 41595f6f..0cbcbbf4 100644
--- a/versions.json
+++ b/versions.json
@@ -76,5 +76,6 @@
   "4.5.0": "1.1.1",
   "4.6.0": "1.1.1",
   "4.6.1": "1.1.1",
-  "4.7.0": "1.1.1"
+  "4.7.0": "1.1.1",
+  "4.7.1": "1.1.1"
 }


Test patch:
diff --git a/tests/Config/GlobalQuery.test.ts b/tests/Config/GlobalQuery.test.ts
new file mode 100644
index 00000000..435e7624
--- /dev/null
+++ b/tests/Config/GlobalQuery.test.ts
@@ -0,0 +1,43 @@
+/**
+ * @jest-environment jsdom
+ */
+
+import { GlobalQuery } from '../../src/Config/GlobalQuery';
+
+describe('GlobalQuery tests', () => {
+    it('getInstance() should return the same object', () => {
+        const globalQuery1 = GlobalQuery.getInstance();
+        const globalQuery2 = GlobalQuery.getInstance();
+
+        expect(Object.is(globalQuery1, globalQuery2)).toEqual(true);
+    });
+
+    it('should have no instructions by default', () => {
+        const globalQuery = new GlobalQuery();
+
+        expect(globalQuery.hasInstructions()).toEqual(false);
+        expect(globalQuery.query().source).toEqual('');
+    });
+
+    it('should set the query', () => {
+        const globalQuery = new GlobalQuery('description includes this should be the source of the query');
+
+        expect(globalQuery.query().source).toEqual('description includes this should be the source of the query');
+    });
+
+    it('should update the query', () => {
+        const globalQuery = new GlobalQuery('# this should be overridden');
+        globalQuery.set('# this should be the new global query');
+
+        expect(globalQuery.query().source).toEqual('# this should be the new global query');
+    });
+
+    it.each(['', ' ', '\n', '\n     \n    ', '  \n    \n'])(
+        'should have no instructions if only line breaks and spaces were set in the query',
+        (globalQuerySource) => {
+            const globalQuery = new GlobalQuery(globalQuerySource);
+
+            expect(globalQuery.hasInstructions()).toEqual(false);
+        },
+    );
+});
diff --git a/tests/Query/Explain/DocsSamplesForExplain.test.ts b/tests/Query/Explain/DocsSamplesForExplain.test.ts
index 56bbe857..2eabda5b 100644
--- a/tests/Query/Explain/DocsSamplesForExplain.test.ts
+++ b/tests/Query/Explain/DocsSamplesForExplain.test.ts
@@ -2,8 +2,9 @@
  * @jest-environment jsdom
  */
 import moment from 'moment';
+import { GlobalQuery } from '../../../src/Config/GlobalQuery';
 import { verifyQuery, verifyTaskBlockExplanation } from '../../TestingTools/ApprovalTestHelpers';
-import { resetSettings, updateSettings } from '../../../src/Config/Settings';
+import { resetSettings } from '../../../src/Config/Settings';
 window.moment = moment;
 
 function checkExplainPresentAndVerify(blockQuery: string) {
@@ -33,7 +34,7 @@ explain`;
 
         // Act, Assert
         checkExplainPresentAndVerify(instructions);
-        verifyTaskBlockExplanation(instructions);
+        verifyTaskBlockExplanation(instructions, new GlobalQuery());
     });
 
     it('boolean combinations', () => {
@@ -45,7 +46,7 @@ not done
 
         // Act, Assert
         checkExplainPresentAndVerify(instructions);
-        verifyTaskBlockExplanation(instructions);
+        verifyTaskBlockExplanation(instructions, new GlobalQuery());
     });
 
     it('nested boolean combinations', () => {
@@ -56,7 +57,7 @@ explain
 
         // Act, Assert
         checkExplainPresentAndVerify(instructions);
-        verifyTaskBlockExplanation(instructions);
+        verifyTaskBlockExplanation(instructions, new GlobalQuery());
     });
 
     it('regular expression', () => {
@@ -67,20 +68,19 @@ path regex matches /^Root/Sub-Folder/Sample File\\.md/i`;
 
         // Act, Assert
         checkExplainPresentAndVerify(instructions);
-        verifyTaskBlockExplanation(instructions);
+        verifyTaskBlockExplanation(instructions, new GlobalQuery());
     });
 
-    const globalQuery = `limit 50
+    const globalQueryLine = `limit 50
 heading includes tasks`;
     it('example global query', () => {
         // Act, Assert
-        verifyQuery(globalQuery);
+        verifyQuery(globalQueryLine);
     });
 
     it('explains task block with global query active', () => {
         // Arrange
-        updateSettings({ globalQuery });
-
+        const globalQuery = new GlobalQuery(globalQueryLine);
         const blockQuery = `
 not done
 due next week
@@ -88,7 +88,7 @@ explain`;
 
         // Act, Assert
         checkExplainPresentAndVerify(blockQuery);
-        verifyTaskBlockExplanation(blockQuery);
+        verifyTaskBlockExplanation(blockQuery, globalQuery);
     });
 
     it('placeholders', () => {
@@ -102,7 +102,7 @@ filename includes {{query.file.filename}}`;
 
         // Act, Assert
         checkExplainPresentAndVerify(instructions);
-        verifyTaskBlockExplanation(instructions);
+        verifyTaskBlockExplanation(instructions, new GlobalQuery());
     });
 
     it('placeholders error', () => {
@@ -114,6 +114,6 @@ filename includes {{query.file.fileName}}`;
 
         // Act, Assert
         verifyQuery(instructions); // This does not have an explain, so does not call checkExplainPresentAndVerify()
-        verifyTaskBlockExplanation(instructions);
+        verifyTaskBlockExplanation(instructions, new GlobalQuery());
     });
 });
diff --git a/tests/TestingTools/ApprovalTestHelpers.ts b/tests/TestingTools/ApprovalTestHelpers.ts
index 098af699..3a294acf 100644
--- a/tests/TestingTools/ApprovalTestHelpers.ts
+++ b/tests/TestingTools/ApprovalTestHelpers.ts
@@ -1,5 +1,6 @@
 import { Options } from 'approvals/lib/Core/Options';
 import { verify } from 'approvals/lib/Providers/Jest/JestApprovals';
+import type { GlobalQuery } from '../../src/Config/GlobalQuery';
 import { Query } from '../../src/Query/Query';
 import { explainResults } from '../../src/lib/QueryRendererHelper';
 
@@ -70,10 +71,11 @@ export function verifyQueryExplanation(instructions: string, options?: Options):
  * See {@link verifyQueryExplanation} to just explain the query.
  *
  * @param instructions
+ * @param globalQuery
  * @param options
  */
-export function verifyTaskBlockExplanation(instructions: string, options?: Options): void {
-    const explanation = explainResults(instructions, 'some/sample/file path.md');
+export function verifyTaskBlockExplanation(instructions: string, globalQuery: GlobalQuery, options?: Options): void {
+    const explanation = explainResults(instructions, globalQuery, 'some/sample/file path.md');
 
     options = options || new Options();
     options = options.forFile().withFileExtention('explanation.text');
diff --git a/tests/lib/QueryRendererHelper.test.ts b/tests/lib/QueryRendererHelper.test.ts
index 96056ad8..5b97ccf3 100644
--- a/tests/lib/QueryRendererHelper.test.ts
+++ b/tests/lib/QueryRendererHelper.test.ts
@@ -3,15 +3,14 @@
  */
 import moment from 'moment';
 import { Query } from '../../src/Query/Query';
-import { resetSettings, updateSettings } from '../../src/Config/Settings';
 import { explainResults, getQueryForQueryRenderer } from '../../src/lib/QueryRendererHelper';
 import { GlobalFilter } from '../../src/Config/GlobalFilter';
+import { GlobalQuery } from '../../src/Config/GlobalQuery';
 
 window.moment = moment;
 
 describe('explain', () => {
     afterEach(() => {
-        resetSettings();
         GlobalFilter.reset();
     });
 
@@ -23,7 +22,7 @@ describe('explain', () => {
 
 No filters supplied. All tasks will match the query.`;
 
-        expect(explainResults(query.source)).toEqual(expectedDisplayText);
+        expect(explainResults(query.source, new GlobalQuery())).toEqual(expectedDisplayText);
     });
 
     it('should explain a task with global filter active', () => {
@@ -37,11 +36,11 @@ No filters supplied. All tasks will match the query.`;
 Explanation of this Tasks code block query:
 
 No filters supplied. All tasks will match the query.`;
-        expect(explainResults(query.source)).toEqual(expectedDisplayText);
+        expect(explainResults(query.source, new GlobalQuery())).toEqual(expectedDisplayText);
     });
 
     it('should explain a task with global query active', () => {
-        updateSettings({ globalQuery: 'description includes hello' });
+        const globalQuery = new GlobalQuery('description includes hello');
 
         const source = '';
         const query = new Query({ source });
@@ -54,11 +53,11 @@ Explanation of this Tasks code block query:
 
 No filters supplied. All tasks will match the query.`;
 
-        expect(explainResults(query.source)).toEqual(expectedDisplayText);
+        expect(explainResults(query.source, globalQuery)).toEqual(expectedDisplayText);
     });
 
     it('should explain a task with global query and global filter active', () => {
-        updateSettings({ globalQuery: 'description includes hello' });
+        const globalQuery = new GlobalQuery('description includes hello');
         GlobalFilter.set('#task');
 
         const source = '';
@@ -74,11 +73,11 @@ Explanation of this Tasks code block query:
 
 No filters supplied. All tasks will match the query.`;
 
-        expect(explainResults(query.source)).toEqual(expectedDisplayText);
+        expect(explainResults(query.source, globalQuery)).toEqual(expectedDisplayText);
     });
 
     it('should explain a task with global query set but ignored without the global query', () => {
-        updateSettings({ globalQuery: 'description includes hello' });
+        const globalQuery = new GlobalQuery('description includes hello');
 
         const source = 'ignore global query';
         const query = new Query({ source });
@@ -87,7 +86,7 @@ No filters supplied. All tasks will match the query.`;
 
 No filters supplied. All tasks will match the query.`;
 
-        expect(explainResults(query.source)).toEqual(expectedDisplayText);
+        expect(explainResults(query.source, globalQuery)).toEqual(expectedDisplayText);
     });
 });
 
@@ -97,19 +96,15 @@ No filters supplied. All tasks will match the query.`;
  *       the right query.
  */
 describe('query used for QueryRenderer', () => {
-    afterEach(() => {
-        resetSettings();
-    });
-
     it('should be the result of combining the global query and the actual query', () => {
         // Arrange
         const querySource = 'description includes world';
         const globalQuerySource = 'description includes hello';
-        updateSettings({ globalQuery: globalQuerySource });
         const filePath = 'a/b/c.md';
 
         // Act
-        const query = getQueryForQueryRenderer(querySource, filePath);
+        const globalQuery = new GlobalQuery(globalQuerySource);
+        const query = getQueryForQueryRenderer(querySource, globalQuery, filePath);
 
         // Assert
         expect(query.source).toEqual(`${globalQuerySource}\n${querySource}`);
@@ -118,11 +113,15 @@ describe('query used for QueryRenderer', () => {
 
     it('should ignore the global query if "ignore global query" is set', () => {
         // Arrange
-        updateSettings({ globalQuery: 'path includes from_global_query' });
+        const globalQuery = new GlobalQuery('path includes from_global_query');
         const filePath = 'a/b/c.md';
 
         // Act
-        const query = getQueryForQueryRenderer('description includes from_block_query\nignore global query', filePath);
+        const query = getQueryForQueryRenderer(
+            'description includes from_block_query\nignore global query',
+            globalQuery,
+            filePath,
+        );
 
         // Assert
         expect(query.source).toEqual('description includes from_block_query\nignore global query');