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

Issue:
Blocking query doesn't recognize "no entry" emoji from Emoji Shortcodes plugin
### 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.

### Expected Behavior

When I add a  ⛔ character with the Emoji Shortcodes plugin (using the `:no_entry:` shortcode), it should indicate that this task is blocked.

### Current behaviour

The Tasks plugin reads that character as a part of the task. Editing the task shows it as part of the name. If you use the edit menu to add the block in, it adds a _different_ "No Entry" emoji. I had Ruby dump the bytes of both characters (in decimal):

- Emoji from Emoji Shortcodes: ⛔, [226, 155, 148]
- Emoji from Tasks: ⛔️, [226, 155, 148, 239, 184, 143]

I'm not an expert on encodings, so I'm not sure what the three extra bytes indicate. UTF-8 vs UTF-16?

To be perfectly frank, I'm not sure if this is an issue with Tasks or Emoji Shortcodes. But every other emoji I've tried with shortcodes lines up with what Tasks is expecting.


### Steps to reproduce

1. Install Tasks and Emoji Shortcodes plugins
2. Create a task with an ID
3. Create a task using the `:no_entry:` shortcode  and put the first tasks ID after it
4. Write a `is blocking` or `is blocked` query and tasks do not show up

### Which Operating Systems are you using?

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

### Obsidian Version

1.5.8

### Tasks Plugin Version

6.1.0

### Checks

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

### Possible solution

When checking for emoji, look for both versions of the character.


Discussion:
There's a good chance this is the same issue as #2273
Thanks for the report.

> When checking for emoji, look for both versions of the character.

Absolutely this is the solution. 

But the question is how? I have spent may hours trying to find out how to do so, with no luck.

> There's a good chance this is the same issue as #2273

Yes it is, it happened to me several times whilst working on releasing the Task Dependencies feature  - see this comment:

https://github.com/obsidian-tasks-group/obsidian-tasks/issues/2273#issuecomment-1964361097

I normally close duplicate reports, but I'll leave this one open for a few days, for visibility.

> ### Steps to reproduce
> 1. Install Tasks and Emoji Shortcodes plugins
> 2. Create a task with an ID
> 3. Create a task using the `:no_entry:` shortcode  and put the first tasks ID after it
> 4. Write a `is blocking` or `is blocked` query and tasks do not show up

There is content in the report that is not provided as exact text for us to copy, so for reasons explained in the following post, please paste in a sample markdown block that you have generated from the above instructions:

https://publish.obsidian.md/tasks/Support+and+Help/Report+a+Bug

Thank you.
> There's a good chance this is the same issue as #2273

Actually it's kind-of the opposite of that.

As far as I can tell:

- In #2273, the emoji in Tasks source code does not contain a variant selector, and so Tasks fails to match emojis that _do_ contain the variant selector
- In this one, the variant selector is actually in the emoji in the Tasks source code, and so Tasks fails to match emojis _without_ the variant selector.

My attempts to ensure that the Task code matches both versions have been unsuccessful so far.
> > When checking for emoji, look for both versions of the character.
> 
> Absolutely this is the solution.
> 
> But the question is how? I have spent may hours trying to find out how to do so, with no luck.

ChatGPT has provided the answer, with explanation:

https://chat.openai.com/share/909488a1-3a3d-4a93-9553-fec0ac2a4bbc

```ts
const emojiRegex = /\u26D4(?:\uFE0F|\u{1F3FE})?/u;
```

I will add failing tests and then apply it to the Tasks code.
The above code only matched one specific skin tone, and realistically I don't think skin tones are going to be applied to the current batch of Tasks emojis...

So this is better code:

```ts
const emojiRegex = /\u26D4\uFE0F?/u;
```

Thanks for reporting this @abeger!
In #2697 I have stopped Tasks adding hidden VS16 characters to the dependsOn ⛔ emoji.
Just released in [Tasks 6.1.1](https://github.com/obsidian-tasks-group/obsidian-tasks/releases/tag/6.1.1).
@abeger Don't worry about this following for this issue now...

> > ### Steps to reproduce
> > 
> > 1. Install Tasks and Emoji Shortcodes plugins
> > 2. Create a task with an ID
> > 3. Create a task using the `:no_entry:` shortcode  and put the first tasks ID after it
> > 4. Write a `is blocking` or `is blocked` query and tasks do not show up
> 
> There is content in the report that is not provided as exact text for us to copy, so for reasons explained in the following post, please paste in a sample markdown block that you have generated from the above instructions:
> 
> https://publish.obsidian.md/tasks/Support+and+Help/Report+a+Bug
> 
> Thank you.





Patch:
diff --git a/src/TaskSerializer/DefaultTaskSerializer.ts b/src/TaskSerializer/DefaultTaskSerializer.ts
index 584f76ce81..3a7a13d2b5 100644
--- a/src/TaskSerializer/DefaultTaskSerializer.ts
+++ b/src/TaskSerializer/DefaultTaskSerializer.ts
@@ -69,7 +69,7 @@ export const DEFAULT_SYMBOLS: DefaultTaskSerializerSymbols = {
     doneDateSymbol: '✅',
     cancelledDateSymbol: '❌',
     recurrenceSymbol: '🔁',
-    dependsOnSymbol: '⛔️',
+    dependsOnSymbol: '⛔️', // TODO Remove Variant Selector 16 from this string
     idSymbol: '🆔',
     TaskFormatRegularExpressions: {
         // The following regex's end with `$` because they will be matched and
@@ -83,7 +83,10 @@ 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,
-        dependsOnRegex: new RegExp('⛔️ *(' + taskIdRegex.source + '( *, *' + taskIdRegex.source + ' *)*)$', 'iu'),
+        dependsOnRegex: new RegExp(
+            '⛔\uFE0F? *(' + taskIdRegex.source + '( *, *' + taskIdRegex.source + ' *)*)$',
+            'iu',
+        ),
         idRegex: new RegExp('🆔 *(' + taskIdRegex.source + ')$', 'iu'),
     },
 } as const;


Test patch:
diff --git a/tests/TaskSerializer/DefaultTaskSerializer.test.ts b/tests/TaskSerializer/DefaultTaskSerializer.test.ts
index e8cdc85cf7..cf55535503 100644
--- a/tests/TaskSerializer/DefaultTaskSerializer.test.ts
+++ b/tests/TaskSerializer/DefaultTaskSerializer.test.ts
@@ -5,7 +5,11 @@ import moment from 'moment';
 import type { Settings } from '../../src/Config/Settings';
 import { DefaultTaskSerializer } from '../../src/TaskSerializer';
 import { RecurrenceBuilder } from '../TestingTools/RecurrenceBuilder';
-import { DEFAULT_SYMBOLS, type DefaultTaskSerializerSymbols } from '../../src/TaskSerializer/DefaultTaskSerializer';
+import {
+    DEFAULT_SYMBOLS,
+    type DefaultTaskSerializerSymbols,
+    allTaskPluginEmojis,
+} from '../../src/TaskSerializer/DefaultTaskSerializer';
 import { TaskBuilder } from '../TestingTools/TaskBuilder';
 import { Priority } from '../../src/Task/Priority';
 
@@ -30,6 +34,19 @@ function hasVariantSelector16(text: string) {
     return text.match(vs16Regex) !== null;
 }
 
+describe('validate emojis', () => {
+    // If these tests fail, paste the problem emoji in to https://apps.timwhitlock.info/unicode/inspect
+    it.each(allTaskPluginEmojis())('emoji does not contain Variant Selector 16: "%s"', (emoji: string) => {
+        if (emoji === DEFAULT_SYMBOLS.dependsOnSymbol) {
+            // TODO Remove the VS16 from the dependsOn symbol:
+            //      see https://github.com/obsidian-tasks-group/obsidian-tasks/issues/2693
+            expect(hasVariantSelector16(emoji)).toBe(true);
+        } else {
+            expect(hasVariantSelector16(emoji)).toBe(false);
+        }
+    });
+});
+
 // NEW_TASK_FIELD_EDIT_REQUIRED
 
 describe.each(symbolMap)("DefaultTaskSerializer with '$taskFormat' symbols", ({ symbols }) => {
@@ -88,7 +105,7 @@ describe.each(symbolMap)("DefaultTaskSerializer with '$taskFormat' symbols", ({
             });
 
             it('should parse a high priority with Variant Selector 16', () => {
-                // This test showed the existing of https://github.com/obsidian-tasks-group/obsidian-tasks/issues/2273
+                // This test showed the existence of https://github.com/obsidian-tasks-group/obsidian-tasks/issues/2273
                 const line = '⏫️'; // There is a hidden Variant Selector 16 character at the end of this string
                 expect(hasVariantSelector16(line)).toBe(true);
 
@@ -111,6 +128,23 @@ describe.each(symbolMap)("DefaultTaskSerializer with '$taskFormat' symbols", ({
                 expect(taskDetails).toMatchTaskDetails({ dependsOn: ['F12345'] });
             });
 
+            it('should parse depends on one task - without Variant Selector 16', () => {
+                // This test showed the existence of https://github.com/obsidian-tasks-group/obsidian-tasks/issues/2693
+                const id = '⛔ F12345';
+                expect(hasVariantSelector16(id)).toBe(false);
+
+                const taskDetails = deserialize(id);
+                expect(taskDetails).toMatchTaskDetails({ dependsOn: ['F12345'] });
+            });
+
+            it('should parse depends on one task - with Variant Selector 16', () => {
+                const id = '⛔️ F12345'; // There is a hidden Variant Selector 16 character at the end of this string
+                expect(hasVariantSelector16(id)).toBe(true);
+
+                const taskDetails = deserialize(id);
+                expect(taskDetails).toMatchTaskDetails({ dependsOn: ['F12345'] });
+            });
+
             it('should parse depends on two tasks', () => {
                 const id = `${dependsOnSymbol} 123456,abC123`;
                 const taskDetails = deserialize(id);