Skip to content

Commit 45441b7

Browse files
test(newsfeed): add e2e tests for notification handling
Add a dedicated 'Newsfeed module > Notifications' describe block with tests for: ARTICLE_NEXT, ARTICLE_PREVIOUS (including wrap-around in both directions), ARTICLE_INFO_REQUEST (verifying response payload and that the URL is the raw article URL, not a CORS proxy URL), and ARTICLE_LESS_DETAILS. Add tests/configs/modules/newsfeed/notifications.js with a long updateInterval (1 hour) to prevent auto-rotation during tests.
1 parent ef3dff9 commit 45441b7

2 files changed

Lines changed: 168 additions & 0 deletions

File tree

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
let config = {
2+
address: "0.0.0.0",
3+
ipWhitelist: [],
4+
timeFormat: 12,
5+
6+
modules: [
7+
{
8+
module: "newsfeed",
9+
position: "bottom_bar",
10+
config: {
11+
feeds: [
12+
{
13+
title: "Rodrigo Ramirez Blog",
14+
url: "http://localhost:8080/tests/mocks/newsfeed_test.xml"
15+
}
16+
],
17+
updateInterval: 3600 * 1000 // 1 hour - prevent auto-rotation during tests
18+
}
19+
}
20+
]
21+
};
22+
23+
/*************** DO NOT EDIT THE LINE BELOW ***************/
24+
if (typeof module !== "undefined") {
25+
module.exports = config;
26+
}

tests/e2e/modules/newsfeed_spec.js

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,148 @@ const runTests = () => {
7070
});
7171
};
7272

73+
describe("Newsfeed module > Notifications", () => {
74+
let page;
75+
76+
afterAll(async () => {
77+
await helpers.stopApplication();
78+
});
79+
80+
/**
81+
* Helper: call notificationReceived on the newsfeed module directly.
82+
* @param {object} p - playwright page
83+
* @param {string} notification - notification name
84+
* @param {object} payload - notification payload
85+
* @returns {Promise<void>} resolves when the notification has been dispatched
86+
*/
87+
const notify = (p, notification, payload = {}) => p.evaluate(
88+
({ n, pl }) => {
89+
const nf = MM.getModules().find((m) => m.name === "newsfeed");
90+
nf.notificationReceived(n, pl, nf);
91+
},
92+
{ n: notification, pl: payload }
93+
);
94+
95+
beforeAll(async () => {
96+
await helpers.startApplication("tests/configs/modules/newsfeed/notifications.js");
97+
await helpers.getDocument();
98+
page = helpers.getPage();
99+
await expect(page.locator(".newsfeed .newsfeed-title")).toBeVisible();
100+
});
101+
102+
it("ARTICLE_NEXT should show the next article", async () => {
103+
const title1 = await page.locator(".newsfeed .newsfeed-title").textContent();
104+
await notify(page, "ARTICLE_NEXT");
105+
await expect(page.locator(".newsfeed .newsfeed-title")).not.toContainText(title1.trim());
106+
});
107+
108+
it("ARTICLE_PREVIOUS should return to the previous article", async () => {
109+
// Start at article 0, go to article 1, then back
110+
await page.evaluate(() => {
111+
const nf = MM.getModules().find((m) => m.name === "newsfeed");
112+
nf.activeItem = 0;
113+
nf.resetDescrOrFullArticleAndTimer();
114+
nf.updateDom(0);
115+
});
116+
await expect(page.locator(".newsfeed .newsfeed-title")).toContainText("QPanel");
117+
const title0 = await page.locator(".newsfeed .newsfeed-title").textContent();
118+
119+
await notify(page, "ARTICLE_NEXT");
120+
await expect(page.locator(".newsfeed .newsfeed-title")).not.toContainText(title0.trim());
121+
122+
await notify(page, "ARTICLE_PREVIOUS");
123+
await expect(page.locator(".newsfeed .newsfeed-title")).toContainText(title0.trim());
124+
});
125+
126+
it("ARTICLE_NEXT should wrap around from the last article to the first", async () => {
127+
// Jump to the last article
128+
await page.evaluate(() => {
129+
const nf = MM.getModules().find((m) => m.name === "newsfeed");
130+
nf.activeItem = nf.newsItems.length - 1;
131+
nf.resetDescrOrFullArticleAndTimer();
132+
nf.updateDom(0);
133+
});
134+
await expect(page.locator(".newsfeed .newsfeed-title")).toBeVisible();
135+
const titleLast = await page.locator(".newsfeed .newsfeed-title").textContent();
136+
137+
await notify(page, "ARTICLE_NEXT");
138+
await expect(page.locator(".newsfeed .newsfeed-title")).not.toContainText(titleLast.trim());
139+
140+
// activeItem should now be 0
141+
const activeItem = await page.evaluate(() => MM.getModules().find((m) => m.name === "newsfeed").activeItem);
142+
expect(activeItem).toBe(0);
143+
});
144+
145+
it("ARTICLE_PREVIOUS should wrap around from the first article to the last", async () => {
146+
await page.evaluate(() => {
147+
const nf = MM.getModules().find((m) => m.name === "newsfeed");
148+
nf.activeItem = 0;
149+
nf.resetDescrOrFullArticleAndTimer();
150+
});
151+
await notify(page, "ARTICLE_PREVIOUS");
152+
153+
const activeItem = await page.evaluate(() => {
154+
const nf = MM.getModules().find((m) => m.name === "newsfeed");
155+
return { activeItem: nf.activeItem, total: nf.newsItems.length };
156+
});
157+
expect(activeItem.activeItem).toBe(activeItem.total - 1);
158+
});
159+
160+
it("ARTICLE_INFO_REQUEST should respond with title, source, date, desc and raw url", async () => {
161+
await page.evaluate(() => {
162+
const nf = MM.getModules().find((m) => m.name === "newsfeed");
163+
nf.activeItem = 0;
164+
nf.resetDescrOrFullArticleAndTimer();
165+
});
166+
167+
const info = await page.evaluate(() => new Promise((resolve, reject) => {
168+
const timer = setTimeout(() => reject(new Error("ARTICLE_INFO_RESPONSE timeout")), 3000);
169+
const origSend = MM.sendNotification.bind(MM);
170+
MM.sendNotification = function (n, p, s) {
171+
if (n === "ARTICLE_INFO_RESPONSE") {
172+
clearTimeout(timer);
173+
MM.sendNotification = origSend;
174+
resolve(p);
175+
}
176+
return origSend(n, p, s);
177+
};
178+
const nf = MM.getModules().find((m) => m.name === "newsfeed");
179+
nf.notificationReceived("ARTICLE_INFO_REQUEST", {}, nf);
180+
}));
181+
182+
expect(info).toHaveProperty("title");
183+
expect(info).toHaveProperty("source");
184+
expect(info).toHaveProperty("date");
185+
expect(info).toHaveProperty("desc");
186+
expect(info).toHaveProperty("url");
187+
expect(info.title).toBe("QPanel 0.13.0");
188+
expect(info.source).toBe("Rodrigo Ramirez Blog");
189+
// URL must be the raw article URL, not a CORS proxy URL
190+
expect(info.url).toMatch(/^https?:\/\//);
191+
expect(info.url).not.toContain("localhost");
192+
});
193+
194+
it("ARTICLE_LESS_DETAILS should reset the full article view", async () => {
195+
// Simulate full article view being active
196+
await page.evaluate(() => {
197+
const nf = MM.getModules().find((m) => m.name === "newsfeed");
198+
nf.config.showFullArticle = true;
199+
nf.articleFrameCheckPending = false;
200+
nf.articleUnavailable = false;
201+
});
202+
203+
await notify(page, "ARTICLE_LESS_DETAILS");
204+
205+
const state = await page.evaluate(() => {
206+
const nf = MM.getModules().find((m) => m.name === "newsfeed");
207+
return { showFullArticle: nf.config.showFullArticle };
208+
});
209+
expect(state.showFullArticle).toBe(false);
210+
// Normal newsfeed title should be visible again
211+
await expect(page.locator(".newsfeed .newsfeed-title")).toBeVisible();
212+
});
213+
});
214+
73215
describe("Newsfeed module", () => {
74216
afterAll(async () => {
75217
await helpers.stopApplication();

0 commit comments

Comments
 (0)