首頁  >  文章  >  web前端  >  透過實作學習 TDD:在 Umbraco 的富文本編輯器中標記成員

透過實作學習 TDD:在 Umbraco 的富文本編輯器中標記成員

Barbara Streisand
Barbara Streisand原創
2024-10-08 06:21:01808瀏覽

Learning TDD by doing: Tagging members in Umbraco

在我正在建立的系統中,我需要能夠在網站的文字中提及 Umbraco 成員。為此,我需要建立 Umbraco 富文本編輯器的擴充:TinyMCE。

情境

作為內容編輯者,我想在訊息或文章中標記成員,以便他們收到有關他們的新內容的通知。

我研究了類似的實現,例如 Slack 或 X 上的實現。 Slack 在編寫過程中使用特殊的 html 標籤進行提及,然後使用特定格式的令牌將資料傳送到後端。我決定採取類似的方法,但現在忘記翻譯步驟。在內容中,提及將如下所示:


<mention user-id="1324" class="mceNonEditable">@D_Inventor</mention>


初步探索

在開始建置之前,我一直在尋找連接 Umbraco 中的 TinyMCE 的方法。這是我最不喜歡在 Umbraco 後台擴展的事情之一。不過我之前已經這樣做過,我發現如果我在 AngularJS 中的 Umbraco 的tinyMceService 上建立一個裝飾器,擴充編輯器是最簡單的。在 TinyMCE 的文檔中,我發現了一個名為「autoCompleters」的功能,它完全滿足了我的需要,因此我可以使用編輯器。我的初始程式碼(尚未進行任何測試)如下所示:


rtedecorator.$inject = ["$delegate"];
export function rtedecorator($delegate: any) {
  const original = $delegate.initializeEditor;

  $delegate.initializeEditor = function (args: any) {
    original.apply($delegate, arguments);

    args.editor.contentStyles.push("mention { background-color: #f7f3c1; }");
    args.editor.ui.registry.addAutocompleter("mentions", {
      trigger: "@",
      fetch: (
        pattern: string,
        maxResults: number,
        _fetchOptions: Record<string, unknown>
      ): Promise<IMceAutocompleteItem[]>
        // TODO: fetch from backend
        => Promise.resolve([{ type: "autocompleteitem", value: "1234", text: "D_Inventor" }]),
      onAction: (api: any, rng: Range, value: string): void => {
        // TODO: business logic
        api.hide();
      },
    });
  };

  return $delegate;
}


我在這個專案中使用了 vite 和 typescript,但我沒有安裝任何 TinyMCE 類型。現在我會保留any並盡可能避免TinyMCE。

使用 TDD 進行構建

我決定使用 jest 來測試。我發現入門很簡單,很快就成功了。

✅ Success
I learned a new tool for unit testing in frontend code. I succesfully applied the tool to write a frontend with unit tests

我寫了第一個測驗:

提及-manager.test.ts


describe("MentionsManager.fetch", () => {
  let sut: MentionsManager;
  let items: IMention[];

  beforeEach(() => {
    items = [];
    sut = new MentionsManager();
  });

  test("should be able to fetch one result", async () => {
    items.push({ userId: "1234", userName: "D_Inventor" });
    const result = await sut.fetch(1);
    expect(result).toHaveLength(1);
  });
});


Typescript 編譯器的嚴格性讓我有些驚訝。此處的分步操作實際上意味著不添加任何您尚未實際使用的內容。例如,我想新增對「UI」的引用,因為我知道稍後會使用它,但在使用建構函式中放入的所有內容之前,我實際上無法編譯 MentionsManager。

經過幾輪紅、綠和重構,我最終得到了這些測試:

提及-manager.test.ts


describe("MentionsManager.fetch", () => {
  let sut: MentionsManager;
  let items: IMention[];

  beforeEach(() => {
    items = [];
    sut = new MentionsManager(() => Promise.resolve(items));
  });

  test("should be able to fetch one result", async () => {
    items.push({ userId: "1234", userName: "D_Inventor" });
    const result = await sut.fetch(1);
    expect(result).toHaveLength(1);
  });

  test("should be able to fetch empty result", async () => {
    const result = await sut.fetch(1);
    expect(result).toHaveLength(0);
  });

  test("should be able to fetch many results", async () => {
    items.push({ userId: "1324", userName: "D_Inventor" }, { userId: "3456", userName: "D_Inventor2" });
    const result = await sut.fetch(2);
    expect(result).toHaveLength(2);
  });

  test("should return empty list upon error", () => {
    const sut = new MentionsManager(() => {
      throw new Error("Something went wrong while fetching");
    }, {} as IMentionsUI);
    return expect(sut.fetch(1)).resolves.toHaveLength(0);
  });
});


有了這個邏輯,我就可以從任何來源獲取提及並透過「fetch」掛鉤在 RTE 中顯示它們。
我使用相同的方法建立一個“pick”方法來獲取選定的成員並將提及插入到編輯器中。這是我最終得到的程式碼:

提及-manager.ts


export class MentionsManager {
  private mentions: IMention[] = [];

  constructor(
    private source: MentionsAPI,
    private ui: IMentionsUI
  ) {}

  async fetch(take: number, query?: string): Promise<IMention[]> {
    try {
      const result = await this.source(take, query);
      if (result.length === 0) return [];
      this.mentions = result;

      return result;
    } catch {
      return [];
    }
  }

  pick(id: string, location: Range): void {
    const mention = this.mentions.find((m) => m.userId === id);
    if (!mention) return;

    this.ui.insertMention(mention, location);
  }
}


❓ Uncertainty
The Range interface is a built-in type that is really difficult to mock and this interface leaks an implementation detail into my business logic. I feel like there might've been a better way to do this.

回顧

總的來說,我認為我最終得到了易於更改的簡單程式碼。這段程式碼中仍有一些部分我不太喜歡。我希望業務邏輯來驅動 UI,但程式碼最終更像是一個簡單的商店,它也對 UI 進行了一次呼叫。我想知道是否可以更牢固地包裝 UI,以便更好地利用管理器。

以上是透過實作學習 TDD:在 Umbraco 的富文本編輯器中標記成員的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn