Repository: obsidian-tasks-group/obsidian-tasks (ts)

Issue:
Unicode Variant selectors on some emojis work, while others are parsed incorrectly
### Please check that this issue hasn't been reported before.

- [X] I searched previous [Bug Reports](https://github.com/obsidian-tasks-group/obsidian-tasks/issues?q=is%3Aopen+is%3Aissue+label%3A%22type%3A+bug%22+sort%3Acomments-desc) didn't find any similar reports.

### Steps to reproduce

1. Launch an Obsidian vault the latest Tasks 7.13.0 plugin
2. Create a task by manually entering scheduled or due date emojis (hour glass not done, or calendar) using an emoji picker, not the prompt
3. Create a tasks query that uses the emojis, e.g.
```tasks
not done
has scheduled date
sort by description
group by scheduled
```
Result:
Recognized: `- [ ] test ⏳ 2024-11-18`, Not recognized: `- [ ] test ⏳️ 2024-11-18`

### Expected Behavior

Tasks should ignore emoji variants like what is described in https://github.com/obsidian-tasks-group/obsidian-tasks/issues/2273, except on _all_ emojis. Emoji variants should have no effect on its parsing behavior.

### Current behaviour

Currently, Tasks ignores the variant selector on the priority arrows (a previous issue), however it does not ignore it on other emojis like Scheduled. The solution appears to be focused on a specific emoji case, when really it can show up in all emojis. I was initially confused as to why tasks wouldn't show up using a filter, until I played around with different methods of getting the emoji and realized that it must be a variant selector. Sure enough, I was getting `\ufe0f\u23f3` on the hour glass emoji that didn't work, and just `\ufe0f` on the one that did.

### Which Operating Systems are you using?

- [ ] Android
- [ ] iPhone/iPad
- [X] Linux
- [ ] macOS
- [ ] Windows

### Obsidian Version

1.6.7

### Tasks Plugin Version

7.13.0

### Checks

- [X] I have tried it with all other plugins disabled and the error still occurs

### Possible solution

Implement a solution similar to https://github.com/obsidian-tasks-group/obsidian-tasks/pull/2695, except ignoring variant selectors in _all_ emojis.

Pull request:
fix: - Allow multiple spaces after onCompletion emoji
# Types of changes

<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->

Changes visible to users:

- [x] **Bug fix** (prefix: `fix` - non-breaking change which fixes an issue)

Internal changes:

- [x] **Tests** (prefix: `test` - additions and improvements to unit tests and the smoke tests)

## Description

Multiple spaces are now allowed between `🏁` and its value.

I found this whilst doing preparatory refactoring to fix #3179.

Also, adjust test to confirm that parsing of `on completion` field is case-insensitive.

## Motivation and Context

Fix unreported bug that the Tasks Emoji format did not allow more than one space between the emoji and value.

- `🏁 delete` worked
- `🏁  delete` was not parsed, and was considered part of the task description 

## How has this been tested?

- Added failing test to show the problem
- Did exploratory testing to confirm it's fixed

## 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`.
- [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 follows 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/TaskSerializer/DefaultTaskSerializer.ts b/src/TaskSerializer/DefaultTaskSerializer.ts
index 2a7c5ce8..e319346f 100644
--- a/src/TaskSerializer/DefaultTaskSerializer.ts
+++ b/src/TaskSerializer/DefaultTaskSerializer.ts
@@ -91,7 +91,7 @@ export const DEFAULT_SYMBOLS: DefaultTaskSerializerSymbols = {
         doneDateRegex: /✅ *(\d{4}-\d{2}-\d{2})$/u,
         cancelledDateRegex: /❌ *(\d{4}-\d{2}-\d{2})$/u,
         recurrenceRegex: /🔁 ?([a-zA-Z0-9, !]+)$/iu,
-        onCompletionRegex: /🏁 ?([a-zA-Z]+)$/iu,
+        onCompletionRegex: /🏁 *([a-zA-Z]+)$/iu,
         dependsOnRegex: new RegExp('⛔\uFE0F? *(' + taskIdSequenceRegex.source + ')$', 'iu'),
         idRegex: new RegExp('🆔 *(' + taskIdRegex.source + ')$', 'iu'),
     },


Test patch:
diff --git a/tests/TaskSerializer/DefaultTaskSerializer.test.ts b/tests/TaskSerializer/DefaultTaskSerializer.test.ts
index c3bfe5b4..8014eb70 100644
--- a/tests/TaskSerializer/DefaultTaskSerializer.test.ts
+++ b/tests/TaskSerializer/DefaultTaskSerializer.test.ts
@@ -71,7 +71,7 @@ describe('validate emoji regular expressions', () => {
             doneDateRegex: /✅ *(\\d{4}-\\d{2}-\\d{2})$/u
             cancelledDateRegex: /❌ *(\\d{4}-\\d{2}-\\d{2})$/u
             recurrenceRegex: /🔁 ?([a-zA-Z0-9, !]+)$/iu
-            onCompletionRegex: /🏁 ?([a-zA-Z]+)$/iu
+            onCompletionRegex: /🏁 *([a-zA-Z]+)$/iu
             dependsOnRegex: /⛔️? *([a-zA-Z0-9-_]+( *, *[a-zA-Z0-9-_]+ *)*)$/iu
             idRegex: /🆔 *([a-zA-Z0-9-_]+)$/iu"
         `);
@@ -170,10 +170,16 @@ describe.each(symbolMap)("DefaultTaskSerializer with '$taskFormat' symbols", ({
 
         describe('should parse onCompletion', () => {
             it('should parse delete action', () => {
-                const onCompletion = `${onCompletionSymbol} delete`;
+                const onCompletion = `${onCompletionSymbol} Delete`;
                 const taskDetails = deserialize(onCompletion);
                 expect(taskDetails).toMatchTaskDetails({ onCompletion: OnCompletion.Delete });
             });
+
+            it('should allow multiple spaces', () => {
+                const onCompletion = `${onCompletionSymbol}  Keep`;
+                const taskDetails = deserialize(onCompletion);
+                expect(taskDetails).toMatchTaskDetails({ onCompletion: OnCompletion.Keep });
+            });
         });
 
         describe('should parse depends on', () => {