Repository: oliverschwendener/ueli (ts)

Issue:
[Feature Request] Default currency for currency converter
In v8 we could set the default currency, let's say EUR.

So instead typing:

```
10 usd to eur
```

it was enough to just type:


```
10 usd
```

That was super helpful and fast to convert currencies. Please consider adding back this option, thanks! 🙌 


Pull request:
:sparkles: Add option for setting a default target currency
# Description

Hello (again)! I am back with the second round of default target currency! (I was the person who implemented this for the old version as well) 😅

Still love the app and still use it very frequently 🤘 So kudos! 🙏 

Fixes #1207 

## :sparkles: Default Target Currency
I added the option for setting a default target currency. This includes updating the settings panel and a bit of the code to handle the input in the case of just writing the shorthand version.

The settings part let's the user pick only from the already selected list of currencies.

> [!note]
> _Note that I tried adding the Swiss German translation for the setting, but since I do not actually speak this language very well a quick review from someone who does would be appreciated_ 🙏

## 📷  How It Looks
![image](https://github.com/user-attachments/assets/3b673ed5-d286-4a24-a15c-b3977b96f91b)


## 📝 Notes
Couple of things of note:
- The chosen currency list from the settings does not seem to be used in any way in the extension (or I am just too stupid to understand it)
- The validation of the input got slightly more messy. I think it is tolerable but feel free to add a comment 👍
- When setting a default and then removing that currency from the `Currencies` list, the default still stays. Due to some weird state management in React I was not able to get around this properly.
- No testing code has been written. If this is required/desired I would need a bit of time to familiarize myself with how to do this. Manual testing has been done and seems successful.

Patch:
diff --git a/src/main/Extensions/CurrencyConversion/CurrencyConversion.ts b/src/main/Extensions/CurrencyConversion/CurrencyConversion.ts
index 9bc25d23..de96995c 100644
--- a/src/main/Extensions/CurrencyConversion/CurrencyConversion.ts
+++ b/src/main/Extensions/CurrencyConversion/CurrencyConversion.ts
@@ -26,6 +26,7 @@ export class CurrencyConversion implements Extension {
 
     private readonly defaultSettings = {
         currencies: ["usd", "chf", "eur"],
+        defaultTargetCurrency: "eur",
     };
 
     private rates: Rates;
@@ -42,11 +43,14 @@ export class CurrencyConversion implements Extension {
         const parts = searchTerm.trim().split(" ");
 
         const validators = [
-            () => parts.length === 4,
+            () => parts.length === 2 || parts.length === 4,
             () => !isNaN(Number(parts[0])),
             () => Object.keys(this.rates).includes(parts[1].toLowerCase()),
-            () => ["in", "to"].includes(parts[2].toLowerCase()),
-            () => Object.keys(this.rates[parts[1].toLowerCase()]).includes(parts[3].toLowerCase()),
+            () => parts.length === 2 || (parts.length === 4 && ["in", "to"].includes(parts[2].toLowerCase())),
+            () =>
+                Object.keys(this.rates[parts[1].toLowerCase()]).includes(
+                    parts.length === 4 ? parts[3].toLowerCase() : this.getDefaultTargetCurrency(),
+                ),
         ];
 
         for (const validator of validators) {
@@ -57,7 +61,7 @@ export class CurrencyConversion implements Extension {
 
         const value = Number(parts[0]);
         const base = parts[1];
-        const target = parts[3];
+        const target = parts.length === 4 ? parts[3].toLowerCase() : this.getDefaultTargetCurrency();
 
         const conversionResult = convert({ value, base, target, rates: this.rates });
 
@@ -78,7 +82,7 @@ export class CurrencyConversion implements Extension {
                 },
                 id: `currency-conversion:instant-result`,
                 image: this.getImage(),
-                name: `${conversionResult.result.toFixed(2)} ${parts[3].toUpperCase()}`,
+                name: `${conversionResult.result.toFixed(2)} ${target.toUpperCase()}`,
             },
         ];
     }
@@ -107,6 +111,7 @@ export class CurrencyConversion implements Extension {
             "en-US": {
                 extensionName: "Currency Conversion",
                 currencies: "Currencies",
+                defaultTargetCurrency: "Default Target Currency",
                 selectCurrencies: "Select currencies",
                 copyToClipboard: "Copy to clipboard",
                 currencyConversion: "Currency Conversion",
@@ -114,6 +119,7 @@ export class CurrencyConversion implements Extension {
             "de-CH": {
                 extensionName: "Währungsumrechnung",
                 currencies: "Währungen",
+                defaultTargetCurrency: "Standard-Zielwährung",
                 selectCurrencies: "Währungen wählen",
                 copyToClipboard: "In Zwischenablage kopieren",
                 currencyConversion: "Währungsumrechnung",
@@ -122,7 +128,10 @@ export class CurrencyConversion implements Extension {
     }
 
     public getSettingKeysTriggeringRescan(): string[] {
-        return [getExtensionSettingKey(this.id, "currencies")];
+        return [
+            getExtensionSettingKey(this.id, "currencies"),
+            getExtensionSettingKey(this.id, "defaultTargetCurrency"),
+        ];
     }
 
     private async setRates(): Promise<void> {
@@ -143,4 +152,11 @@ export class CurrencyConversion implements Extension {
 
         this.rates[currency] = responseJson[currency];
     }
+
+    private getDefaultTargetCurrency(): string {
+        return this.settingsManager.getValue<string>(
+            getExtensionSettingKey(this.id, "defaultTargetCurrency"),
+            this.getSettingDefaultValue<string>("defaultTargetCurrency"),
+        );
+    }
 }
diff --git a/src/renderer/Extensions/CurrencyConversion/CurrencyConversionSettings.tsx b/src/renderer/Extensions/CurrencyConversion/CurrencyConversionSettings.tsx
index 8e7e0faa..de4e93da 100644
--- a/src/renderer/Extensions/CurrencyConversion/CurrencyConversionSettings.tsx
+++ b/src/renderer/Extensions/CurrencyConversion/CurrencyConversionSettings.tsx
@@ -14,6 +14,11 @@ export const CurrencyConversionSettings = () => {
         key: "currencies",
     });
 
+    const { value: defaultTargetCurrency, updateValue: setDefaultTargetCurrency } = useExtensionSetting<string>({
+        extensionId: "CurrencyConversion",
+        key: "defaultTargetCurrency",
+    });
+
     return (
         <SettingGroupList>
             <SettingGroup title={t("extensionName")}>
@@ -35,6 +40,25 @@ export const CurrencyConversionSettings = () => {
                         </Dropdown>
                     }
                 />
+                <Setting
+                    label={t("defaultTargetCurrency")}
+                    control={
+                        <Dropdown
+                            selectedOptions={[defaultTargetCurrency]}
+                            value={defaultTargetCurrency.toUpperCase()}
+                            placeholder={t("selectDefaultTargetCurrency")}
+                            onOptionSelect={(_, { optionValue }) =>
+                                optionValue && setDefaultTargetCurrency(optionValue)
+                            }
+                        >
+                            {currencies.map((currency) => (
+                                <Option key={currency} value={currency}>
+                                    {`${currency.toUpperCase()}`}
+                                </Option>
+                            ))}
+                        </Dropdown>
+                    }
+                />
             </SettingGroup>
         </SettingGroupList>
     );


Test patch:
diff --git a/src/main/Extensions/CurrencyConversion/CurrencyConversion.test.ts b/src/main/Extensions/CurrencyConversion/CurrencyConversion.test.ts
index 00003489..da1ed41f 100644
--- a/src/main/Extensions/CurrencyConversion/CurrencyConversion.test.ts
+++ b/src/main/Extensions/CurrencyConversion/CurrencyConversion.test.ts
@@ -1,4 +1,5 @@
 import type { AssetPathResolver } from "@Core/AssetPathResolver";
+import type { SettingsManager } from "@Core/SettingsManager";
 import { describe, expect, it, vi } from "vitest";
 import { CurrencyConversion } from "./CurrencyConversion";
 import type { Rates } from "./Rates";
@@ -9,10 +10,12 @@ describe(CurrencyConversion, () => {
             expectedResult,
             userInput,
             rates,
+            defaultTarget,
         }: {
             expectedResult: string;
             userInput: string;
             rates: Rates;
+            defaultTarget?: string;
         }) => {
             const imageFilePath = "/path/to/image";
             const getExtensionAssetPathMock = vi.fn().mockReturnValue(imageFilePath);
@@ -22,12 +25,19 @@ describe(CurrencyConversion, () => {
                 getModuleAssetPath: () => null,
             };
 
-            const currencyConversion = new CurrencyConversion(null, null, assetPathResolver);
+            const getValueMock = vi.fn().mockReturnValue(defaultTarget);
+
+            const settingsManager = <SettingsManager>{
+                getValue: (k, d, s) => getValueMock(k, d, s),
+            };
+
+            const currencyConversion = new CurrencyConversion(settingsManager, null, assetPathResolver);
 
             currencyConversion["rates"] = rates;
 
             const actual = currencyConversion.getInstantSearchResultItems(userInput);
 
+            expect(actual.length).toEqual(1);
             expect(actual[0].name).toEqual(expectedResult);
             expect(actual[0].image).toEqual({ url: `file://${imageFilePath}` });
             expect(getExtensionAssetPathMock).toHaveBeenCalledWith(currencyConversion.id, "currency-conversion.png");
@@ -38,7 +48,9 @@ describe(CurrencyConversion, () => {
 
             currencyConversion["rates"] = { chf: { usd: 2 } };
 
+            expect(currencyConversion.getInstantSearchResultItems("1")).toEqual([]);
             expect(currencyConversion.getInstantSearchResultItems("1 CHF to")).toEqual([]);
+            expect(currencyConversion.getInstantSearchResultItems("1 CHF to USD else")).toEqual([]);
         });
 
         it("should return empty array when first part in user input is not numerical", () => {
@@ -77,8 +89,15 @@ describe(CurrencyConversion, () => {
         it("should convert currencies based on the rates when user input matches expected pattern", () => {
             const rates: Rates = { chf: { usd: 2, eur: 0.5 } };
 
+            testSuccessfulConversion({ expectedResult: "2.00 USD", userInput: "    1 CHF to USD    ", rates });
             testSuccessfulConversion({ expectedResult: "2.00 USD", userInput: "1 CHF to USD", rates });
             testSuccessfulConversion({ expectedResult: "0.50 EUR", userInput: "1 CHF in EUR", rates });
         });
+
+        it("should convert currencies to default currency when user input matches expected pattern", () => {
+            const rates: Rates = { chf: { usd: 2, eur: 0.5 } };
+
+            testSuccessfulConversion({ expectedResult: "2.00 USD", userInput: "1 CHF", rates, defaultTarget: "usd" });
+        });
     });
 });