1import type { Page } from "@playwright/test";
2import { expect, test, uid, usePersona } from "../../helpers/fixtures";
3import {
4 createArticle,
5 createCategory,
6 pickOption,
7} from "../../helpers/portal";
8
9usePersona("manager");
10
11test("create a category and article, publish it, customer can read it", async ({
12 page,
13 api,
14 pageAs,
15}) => {
16 const categoryName = `E2E Category ${uid()}`;
17 const title = `E2E Published ${uid()}`;
18 await page.goto("/opendesk/kb");
19 await page.getByRole("button", { name: "Create" }).click();
20 await page.getByRole("menuitem", { name: "Category" }).click();
21 await page.getByPlaceholder("Support Issues").fill(categoryName);
22 await page.getByRole("dialog").getByRole("button", { name: "Create" }).click();
23
24 await expect(page).toHaveURL(/\/opendesk\/kb\/articles\/\w+\?.*isEdit=1/);
25 await page.getByPlaceholder("Title").fill(title);
26 await page.locator(".ProseMirror").fill("Steps to reset a password");
27 await page.getByRole("button", { name: "Save" }).click();
28 await expect(page.getByText("Article updated successfully.")).toBeVisible();
29
30 await page.getByRole("button", { name: "Publish" }).click();
31 // Publishing asks for the access first.
32 await page.getByRole("dialog", { name: "Publish article as" }).getByRole("button", { name: "Publish" }).click();
33 await expect(page.getByRole("button", { name: "Unpublish" })).toBeVisible();
34 const [article] = await api.list("OD Article", {
35 filters: { title },
36 fields: ["name", "status", "category"],
37 });
38 expect(article.status).toBe("Published");
39
40 const customer = await pageAs("customer");
41 await customer.goto(`/help/category/${article.category}`);
42 // The sidebar lists it too; the category list comes last.
43 await customer.getByText(title).last().click();
44 await expect(customer).toHaveURL(new RegExp(`/help/articles/${article.name}`));
45 await expect(customer.getByText("Steps to reset a password")).toBeVisible();
46});
47
48test("edit, unpublish, move and delete an article", async ({ page, api }) => {
49 const from = await createCategory(api);
50 const to = await createCategory(api);
51 await createArticle(api, from.name);
52 const article = await createArticle(api, from.name);
53 const renamed = `E2E Renamed ${uid()}`;
54
55 await page.goto(`/opendesk/kb/articles/${article.name}`);
56 await openArticleMenu(page, "Edit");
57 await page.getByPlaceholder("Title").fill(renamed);
58 await page.getByRole("button", { name: "Save" }).click();
59 await expect.poll(async () => (await api.get("OD Article", article.name)).title).toBe(renamed);
60
61 await page.getByRole("button", { name: "Unpublish" }).click();
62 await expect.poll(async () => (await api.get("OD Article", article.name)).status).toBe("Draft");
63
64 await openArticleMenu(page, "Move To");
65 await pickOption(page, "Category", to.category_name, { opensItself: true });
66 await page.getByRole("dialog").getByRole("button", { name: "Move" }).click();
67 await expect.poll(async () => (await api.get("OD Article", article.name)).category).toBe(to.name);
68
69 // a category must keep one article, so give the target another before deleting
70 await createArticle(api, to.name);
71 await openArticleMenu(page, "Delete");
72 await page.getByRole("menuitem", { name: "Confirm Delete" }).click();
73 await expect(page).toHaveURL(/\/opendesk\/kb$/);
74 expect(await api.exists("OD Article", { name: article.name })).toBeUndefined();
75});
76
77test("merge a category into another, then delete one", async ({ page, api }) => {
78 const source = await createCategory(api);
79 const target = await createCategory(api);
80 const doomed = await createCategory(api);
81 const moved = await createArticle(api, source.name);
82 const orphan = await createArticle(api, doomed.name);
83
84 await page.goto("/opendesk/kb");
85 await openCategoryMenu(page, source.category_name, "Merge");
86 await pickOption(page, "Category", target.category_name);
87 await page.getByRole("dialog").getByRole("button", { name: "Merge" }).click();
88 await expect.poll(async () => (await api.get("OD Article", moved.name)).category).toBe(target.name);
89 expect(await api.exists("OD Article Category", { name: source.name })).toBeUndefined();
90
91 await openCategoryMenu(page, doomed.category_name, "Delete");
92 await page.getByRole("dialog").getByRole("button", { name: "Confirm" }).click();
93 await expect.poll(() => api.exists("OD Article Category", { name: doomed.name })).toBeUndefined();
94 const general = await api.call("opendesk.api.knowledge_base.get_general_category");
95 expect((await api.get("OD Article", orphan.name)).category).toBe(general);
96});
97
98async function openArticleMenu(page: Page, item: string) {
99 await page
100 .locator("button[aria-haspopup=menu]:not(nav *)")
101 .filter({ hasNotText: /\S/ })
102 .click();
103 await page.getByRole("menuitem", { name: item }).click();
104}
105
106async function openCategoryMenu(page: Page, category: string, item: string) {
107 await page
108 .locator("div")
109 .filter({ has: page.getByText(category, { exact: true }) })
110 .filter({ has: page.locator("button") })
111 .last()
112 .getByRole("button")
113 .click();
114 await page.getByRole("menuitem", { name: item }).click();
115}
116