Dalam sistem yang saya bina, saya memerlukan keupayaan untuk menyebut ahli Umbraco dalam teks dalam tapak web. Untuk melakukan itu, saya perlu membina sambungan kepada Editor Teks Kaya Umbraco: TinyMCE.
Sebagai editor kandungan, saya ingin menandai ahli dalam mesej atau artikel supaya mereka dimaklumkan tentang kandungan baharu tentang mereka.
Saya melihat pelaksanaan yang serupa, seperti dalam Slack atau pada X. Slack menggunakan teg html khas untuk sebutan semasa menulis, tetapi kemudian menghantar data ke bahagian belakang dengan token dengan format tertentu. Saya memutuskan untuk mengambil pendekatan yang sama, tetapi buat masa ini lupakan tentang langkah terjemahan. Dalam kandungan, sebutan akan kelihatan seperti ini:
<mention user-id="1324" class="mceNonEditable">@D_Inventor</mention>
Sebelum saya mula membina, saya sedang mencari cara untuk menyambung ke TinyMCE di Umbraco. Ini adalah salah satu perkara paling saya gemari untuk dilanjutkan di pejabat belakang Umbraco. Saya telah melakukan ini sebelum ini, dan saya mendapati ia paling mudah untuk melanjutkan editor jika saya mencipta penghias pada tinyMceService Umbraco dalam AngularJS. Dalam dokumentasi TinyMCE, saya menemui ciri yang dipanggil 'autoCompleters', yang melakukan apa yang saya perlukan, jadi terdapat cangkuk saya ke dalam editor. Kod awal saya (tanpa sebarang ujian lagi), kelihatan seperti ini:
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; }
Saya menggunakan vite dan skrip taip dalam projek ini, tetapi saya tidak mempunyai sebarang jenis untuk TinyMCE dipasang. Buat masa ini saya akan menyimpan apa-apa dan hanya cuba mengelakkan TinyMCE sebanyak mungkin.
Saya memutuskan untuk menggunakan jest untuk ujian. Saya dapati permulaan yang mudah dan saya berjaya dengan cepat membuat sesuatu berfungsi.
✅ Success |
---|
I learned a new tool for unit testing in frontend code. I succesfully applied the tool to write a frontend with unit tests |
Saya menulis ujian pertama saya:
mention-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); }); });
Saya agak terkejut dengan ketegasan penyusun skrip taip. Bekerja dalam langkah-langkah di sini benar-benar bermakna tidak menambah apa-apa yang sebenarnya anda tidak gunakan lagi. Sebagai contoh, saya ingin menambah rujukan kepada "UI", kerana saya tahu saya akan menggunakannya kemudian, tetapi saya sebenarnya tidak dapat menyusun MentionsManager sehingga saya menggunakan semua yang saya masukkan dalam pembina.
Selepas beberapa pusingan merah, hijau dan refactor, saya berakhir dengan ujian ini:
mention-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); }); });
Dengan adanya logik ini, saya boleh mengambil sebutan daripada mana-mana sumber dan menunjukkannya dalam RTE melalui cangkuk 'fetch'.
Saya menggunakan pendekatan yang sama untuk mencipta kaedah 'pilih' untuk mengambil ahli yang dipilih dan memasukkan sebutan ke dalam editor. Ini ialah kod yang saya dapat:
mention-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. |
Secara keseluruhan, saya rasa saya mendapat kod ringkas yang mudah diubah. Masih terdapat bahagian kod ini yang saya tidak begitu suka. Saya mahu logik perniagaan memacu UI, tetapi kod itu berakhir lebih seperti kedai ringkas yang juga melakukan satu panggilan ke UI. Saya tertanya-tanya jika saya boleh membungkus UI dengan lebih kuat untuk memanfaatkan pengurus dengan lebih baik.
Atas ialah kandungan terperinci Belajar TDD dengan melakukan: Menandai ahli dalam Editor Teks Kaya Umbraco. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!