chore: import upstream snapshot with attribution
Enforce Pull-Request Rules / check (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 12:19:24 +08:00
commit dbe3ade0dc
449 changed files with 71828 additions and 0 deletions
+53
View File
@@ -0,0 +1,53 @@
const { expect } = require("playwright/test");
const helpers = require("../helpers/global-setup");
describe("Alert module", () => {
let page;
afterAll(async () => {
await helpers.stopApplication();
});
describe("with welcome_message set to false", () => {
beforeAll(async () => {
await helpers.startApplication("tests/configs/modules/alert/welcome_false.js");
await helpers.getDocument();
page = helpers.getPage();
});
it("should not show any welcome message", async () => {
// Wait a bit to ensure no message appears
await new Promise((resolve) => setTimeout(resolve, 1000));
// Check that no alert/notification elements are present
await expect(page.locator(".ns-box .ns-box-inner .light.bright.small")).toHaveCount(0);
});
});
describe("with welcome_message set to true", () => {
beforeAll(async () => {
await helpers.startApplication("tests/configs/modules/alert/welcome_true.js");
await helpers.getDocument();
page = helpers.getPage();
// Wait for the application to initialize
await new Promise((resolve) => setTimeout(resolve, 1000));
});
it("should show the translated welcome message", async () => {
await expect(page.locator(".ns-box .ns-box-inner .light.bright.small")).toContainText("Welcome, start was successful!");
});
});
describe("with welcome_message set to custom string", () => {
beforeAll(async () => {
await helpers.startApplication("tests/configs/modules/alert/welcome_string.js");
await helpers.getDocument();
page = helpers.getPage();
});
it("should show the custom welcome message", async () => {
await expect(page.locator(".ns-box .ns-box-inner .light.bright.small")).toContainText("Custom welcome message!");
});
});
});
+194
View File
@@ -0,0 +1,194 @@
const { expect } = require("playwright/test");
const helpers = require("../helpers/global-setup");
const serverBasicAuth = require("../helpers/basic-auth");
describe("Calendar module", () => {
let page;
/**
* Assert the number of matching elements.
* @param {string} selector css selector
* @param {number} expectedLength expected number of elements
* @param {string} [not] optional negation marker (use "not" to negate)
* @returns {Promise<void>}
*/
const testElementLength = async (selector, expectedLength, not) => {
const locator = page.locator(selector);
if (not === "not") {
await expect(locator).not.toHaveCount(expectedLength);
} else {
await expect(locator).toHaveCount(expectedLength);
}
};
const testTextContain = async (selector, expectedText) => {
await expect(page.locator(selector).first()).toContainText(expectedText);
};
afterAll(async () => {
await helpers.stopApplication();
});
describe("Default configuration", () => {
beforeAll(async () => {
await helpers.startApplication("tests/configs/modules/calendar/default.js");
await helpers.getDocument();
page = helpers.getPage();
});
it("should show the default maximumEntries of 10", async () => {
await testElementLength(".calendar .event", 10);
});
it("should show the default calendar symbol in each event", async () => {
await testElementLength(".calendar .event .fa-calendar-days", 0, "not");
});
});
describe("Custom configuration", () => {
beforeAll(async () => {
await helpers.startApplication("tests/configs/modules/calendar/custom.js");
await helpers.getDocument();
page = helpers.getPage();
});
it("should show the custom maximumEntries of 5", async () => {
await testElementLength(".calendar .event", 5);
});
it("should show the custom calendar symbol in four events", async () => {
await testElementLength(".calendar .event .fa-birthday-cake", 4);
});
it("should show a customEvent calendar symbol in one event", async () => {
await testElementLength(".calendar .event .fa-dice", 1);
});
it("should show a customEvent calendar eventClass in one event", async () => {
await testElementLength(".calendar .event.undo", 1);
});
it("should show two custom icons for repeating events", async () => {
await testElementLength(".calendar .event .fa-undo", 2);
});
it("should show two custom icons for day events", async () => {
await testElementLength(".calendar .event .fa-calendar-day", 2);
});
});
describe("Recurring event", () => {
beforeAll(async () => {
await helpers.startApplication("tests/configs/modules/calendar/recurring.js");
await helpers.getDocument();
page = helpers.getPage();
});
it("should show the recurring birthday event 6 times", async () => {
await testElementLength(".calendar .event", 6);
});
});
//Will contain everyday an fullDayEvent that starts today and ends tomorrow, and one starting tomorrow and ending the day after tomorrow
describe("FullDayEvent over several days should show how many days are left from the from the starting date on", () => {
beforeAll(async () => {
await helpers.startApplication("tests/configs/modules/calendar/long-fullday-event.js");
await helpers.getDocument();
page = helpers.getPage();
});
it("should contain text 'Ends in' with the left days", async () => {
await testTextContain(".calendar .today .time", "Ends in");
await testTextContain(".calendar .yesterday .time", "Today");
await testTextContain(".calendar .tomorrow .time", "Tomorrow");
});
it("should contain in total three events", async () => {
await testElementLength(".calendar .event", 3);
});
});
describe("FullDayEvent Single day, should show Today", () => {
beforeAll(async () => {
await helpers.startApplication("tests/configs/modules/calendar/single-fullday-event.js");
await helpers.getDocument();
page = helpers.getPage();
});
it("should contain text 'Today'", async () => {
await testTextContain(".calendar .time", "Today");
});
it("should contain in total two events", async () => {
await testElementLength(".calendar .event", 2);
});
});
describe("Changed port", () => {
beforeAll(async () => {
await helpers.startApplication("tests/configs/modules/calendar/changed-port.js");
serverBasicAuth.listen(8010);
await helpers.getDocument();
page = helpers.getPage();
});
afterAll(async () => {
await serverBasicAuth.close();
});
it("should return TestEvents", async () => {
await testElementLength(".calendar .event", 0, "not");
});
});
describe("Basic auth", () => {
beforeAll(async () => {
await helpers.startApplication("tests/configs/modules/calendar/basic-auth.js");
await helpers.getDocument();
page = helpers.getPage();
});
it("should return TestEvents", async () => {
await testElementLength(".calendar .event", 0, "not");
});
});
describe("Basic auth by default", () => {
beforeAll(async () => {
await helpers.startApplication("tests/configs/modules/calendar/auth-default.js");
await helpers.getDocument();
page = helpers.getPage();
});
it("should return TestEvents", async () => {
await testElementLength(".calendar .event", 0, "not");
});
});
describe("Basic auth backward compatibility configuration: DEPRECATED", () => {
beforeAll(async () => {
await helpers.startApplication("tests/configs/modules/calendar/old-basic-auth.js");
await helpers.getDocument();
page = helpers.getPage();
});
it("should return TestEvents", async () => {
await testElementLength(".calendar .event", 0, "not");
});
});
describe("Fail Basic auth", () => {
beforeAll(async () => {
await helpers.startApplication("tests/configs/modules/calendar/fail-basic-auth.js");
serverBasicAuth.listen(8020);
await helpers.getDocument();
page = helpers.getPage();
});
afterAll(async () => {
await serverBasicAuth.close();
});
it("should show Unauthorized error", async () => {
await testTextContain(".calendar", "Error in the calendar module. Authorization failed");
});
});
});
+36
View File
@@ -0,0 +1,36 @@
const { expect } = require("playwright/test");
const helpers = require("../helpers/global-setup");
describe("Clock set to german language module", () => {
let page;
afterAll(async () => {
await helpers.stopApplication();
});
describe("with showWeek config enabled", () => {
beforeAll(async () => {
await helpers.startApplication("tests/configs/modules/clock/de/clock_showWeek.js");
await helpers.getDocument();
page = helpers.getPage();
});
it("shows week with correct format", async () => {
const weekRegex = /^[0-9]{1,2}. Kalenderwoche$/;
await expect(page.locator(".clock .week")).toHaveText(weekRegex);
});
});
describe("with showWeek short config enabled", () => {
beforeAll(async () => {
await helpers.startApplication("tests/configs/modules/clock/de/clock_showWeek_short.js");
await helpers.getDocument();
page = helpers.getPage();
});
it("shows week with correct format", async () => {
const weekRegex = /^[0-9]{1,2}KW$/;
await expect(page.locator(".clock .week")).toHaveText(weekRegex);
});
});
});
+85
View File
@@ -0,0 +1,85 @@
const { expect } = require("playwright/test");
const helpers = require("../helpers/global-setup");
describe("Clock set to spanish language module", () => {
let page;
afterAll(async () => {
await helpers.stopApplication();
});
describe("with default 24hr clock config", () => {
beforeAll(async () => {
await helpers.startApplication("tests/configs/modules/clock/es/clock_24hr.js");
await helpers.getDocument();
page = helpers.getPage();
});
it("shows date with correct format", async () => {
const dateRegex = /^(?:lunes|martes|miércoles|jueves|viernes|sábado|domingo), \d{1,2} de (?:enero|febrero|marzo|abril|mayo|junio|julio|agosto|septiembre|octubre|noviembre|diciembre) de \d{4}$/;
await expect(page.locator(".clock .date")).toHaveText(dateRegex);
});
it("shows time in 24hr format", async () => {
const timeRegex = /^(?:2[0-3]|[01]\d):[0-5]\d[0-5]\d$/;
await expect(page.locator(".clock .time")).toHaveText(timeRegex);
});
});
describe("with default 12hr clock config", () => {
beforeAll(async () => {
await helpers.startApplication("tests/configs/modules/clock/es/clock_12hr.js");
await helpers.getDocument();
page = helpers.getPage();
});
it("shows date with correct format", async () => {
const dateRegex = /^(?:lunes|martes|miércoles|jueves|viernes|sábado|domingo), \d{1,2} de (?:enero|febrero|marzo|abril|mayo|junio|julio|agosto|septiembre|octubre|noviembre|diciembre) de \d{4}$/;
await expect(page.locator(".clock .date")).toHaveText(dateRegex);
});
it("shows time in 12hr format", async () => {
const timeRegex = /^(?:1[0-2]|[1-9]):[0-5]\d[0-5]\d[ap]m$/;
await expect(page.locator(".clock .time")).toHaveText(timeRegex);
});
});
describe("with showPeriodUpper config enabled", () => {
beforeAll(async () => {
await helpers.startApplication("tests/configs/modules/clock/es/clock_showPeriodUpper.js");
await helpers.getDocument();
page = helpers.getPage();
});
it("shows 12hr time with upper case AM/PM", async () => {
const timeRegex = /^(?:1[0-2]|[1-9]):[0-5]\d[0-5]\d[AP]M$/;
await expect(page.locator(".clock .time")).toHaveText(timeRegex);
});
});
describe("with showWeek config enabled", () => {
beforeAll(async () => {
await helpers.startApplication("tests/configs/modules/clock/es/clock_showWeek.js");
await helpers.getDocument();
page = helpers.getPage();
});
it("shows week with correct format", async () => {
const weekRegex = /^Semana [0-9]{1,2}$/;
await expect(page.locator(".clock .week")).toHaveText(weekRegex);
});
});
describe("with showWeek short config enabled", () => {
beforeAll(async () => {
await helpers.startApplication("tests/configs/modules/clock/es/clock_showWeek_short.js");
await helpers.getDocument();
page = helpers.getPage();
});
it("shows week with correct format", async () => {
const weekRegex = /^S[0-9]{1,2}$/;
await expect(page.locator(".clock .week")).toHaveText(weekRegex);
});
});
});
+183
View File
@@ -0,0 +1,183 @@
const { expect } = require("playwright/test");
const moment = require("moment");
const helpers = require("../helpers/global-setup");
describe("Clock module", () => {
let page;
afterAll(async () => {
await helpers.stopApplication();
});
describe("with default 24hr clock config", () => {
beforeAll(async () => {
await helpers.startApplication("tests/configs/modules/clock/clock_24hr.js");
await helpers.getDocument();
page = helpers.getPage();
});
it("should show the date in the correct format", async () => {
const dateRegex = /^(?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday), (?:January|February|March|April|May|June|July|August|September|October|November|December) \d{1,2}, \d{4}$/;
await expect(page.locator(".clock .date")).toHaveText(dateRegex);
});
it("should show the time in 24hr format", async () => {
const timeRegex = /^(?:2[0-3]|[01]\d):[0-5]\d[0-5]\d$/;
await expect(page.locator(".clock .time")).toHaveText(timeRegex);
});
});
describe("with default 12hr clock config", () => {
beforeAll(async () => {
await helpers.startApplication("tests/configs/modules/clock/clock_12hr.js");
await helpers.getDocument();
page = helpers.getPage();
});
it("should show the date in the correct format", async () => {
const dateRegex = /^(?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday), (?:January|February|March|April|May|June|July|August|September|October|November|December) \d{1,2}, \d{4}$/;
await expect(page.locator(".clock .date")).toHaveText(dateRegex);
});
it("should show the time in 12hr format", async () => {
const timeRegex = /^(?:1[0-2]|[1-9]):[0-5]\d[0-5]\d[ap]m$/;
await expect(page.locator(".clock .time")).toHaveText(timeRegex);
});
it("check for discreet elements of clock", async () => {
await expect(page.locator(".clock-hour-digital")).toBeVisible();
await expect(page.locator(".clock-minute-digital")).toBeVisible();
});
});
describe("with showPeriodUpper config enabled", () => {
beforeAll(async () => {
await helpers.startApplication("tests/configs/modules/clock/clock_showPeriodUpper.js");
await helpers.getDocument();
page = helpers.getPage();
});
it("should show 12hr time with upper case AM/PM", async () => {
const timeRegex = /^(?:1[0-2]|[1-9]):[0-5]\d[0-5]\d[AP]M$/;
await expect(page.locator(".clock .time")).toHaveText(timeRegex);
});
});
describe("with displaySeconds config disabled", () => {
beforeAll(async () => {
await helpers.startApplication("tests/configs/modules/clock/clock_displaySeconds_false.js");
await helpers.getDocument();
page = helpers.getPage();
});
it("should show 12hr time without seconds am/pm", async () => {
const timeRegex = /^(?:1[0-2]|[1-9]):[0-5]\d[ap]m$/;
await expect(page.locator(".clock .time")).toHaveText(timeRegex);
});
});
describe("with showTime config disabled", () => {
beforeAll(async () => {
await helpers.startApplication("tests/configs/modules/clock/clock_showTime.js");
await helpers.getDocument();
page = helpers.getPage();
});
it("should not show the time when digital clock is shown", async () => {
await expect(page.locator(".clock .digital .time")).toHaveCount(0);
});
});
describe("with showSun/MoonTime enabled", () => {
beforeAll(async () => {
await helpers.startApplication("tests/configs/modules/clock/clock_showSunMoon.js");
await helpers.getDocument();
page = helpers.getPage();
});
it("should show the sun times", async () => {
await expect(page.locator(".clock .digital .sun")).toBeVisible();
await expect(page.locator(".clock .digital .sun .fas.fa-sun")).toBeVisible();
});
it("should show the moon times", async () => {
await expect(page.locator(".clock .digital .moon")).toBeVisible();
});
});
describe("with showSunNextEvent disabled", () => {
beforeAll(async () => {
await helpers.startApplication("tests/configs/modules/clock/clock_showSunNoEvent.js");
await helpers.getDocument();
page = helpers.getPage();
});
it("should show the sun times", async () => {
await expect(page.locator(".clock .digital .sun")).toBeVisible();
await expect(page.locator(".clock .digital .sun .fas.fa-sun")).toHaveCount(0);
});
});
describe("with showWeek config enabled", () => {
beforeAll(async () => {
await helpers.startApplication("tests/configs/modules/clock/clock_showWeek.js");
await helpers.getDocument();
page = helpers.getPage();
});
it("should show the week in the correct format", async () => {
const weekRegex = /^Week [0-9]{1,2}$/;
await expect(page.locator(".clock .week")).toHaveText(weekRegex);
});
it("should show the week with the correct number of week of year", async () => {
const currentWeekNumber = moment().week();
const weekToShow = `Week ${currentWeekNumber}`;
await expect(page.locator(".clock .week")).toHaveText(weekToShow);
});
});
describe("with showWeek short config enabled", () => {
beforeAll(async () => {
await helpers.startApplication("tests/configs/modules/clock/clock_showWeek_short.js");
await helpers.getDocument();
page = helpers.getPage();
});
it("should show the week in the correct format", async () => {
const weekRegex = /^W[0-9]{1,2}$/;
await expect(page.locator(".clock .week")).toHaveText(weekRegex);
});
it("should show the week with the correct number of week of year", async () => {
const currentWeekNumber = moment().week();
const weekToShow = `W${currentWeekNumber}`;
await expect(page.locator(".clock .week")).toHaveText(weekToShow);
});
});
describe("with analog clock face enabled", () => {
beforeAll(async () => {
await helpers.startApplication("tests/configs/modules/clock/clock_analog.js");
await helpers.getDocument();
page = helpers.getPage();
});
it("should show the analog clock face", async () => {
await expect(page.locator(".clock-circle")).toBeVisible();
});
});
describe("with analog clock face and date enabled", () => {
beforeAll(async () => {
await helpers.startApplication("tests/configs/modules/clock/clock_showDateAnalog.js");
await helpers.getDocument();
page = helpers.getPage();
});
it("should show the analog clock face and the date", async () => {
await expect(page.locator(".clock-circle")).toBeVisible();
await expect(page.locator(".clock .date")).toBeVisible();
});
});
});
+136
View File
@@ -0,0 +1,136 @@
const { expect } = require("playwright/test");
const helpers = require("../helpers/global-setup");
describe("Compliments module", () => {
let page;
/**
* move similar tests in function doTest
* @param {Array} complimentsArray The array of compliments.
* @returns {Promise<void>}
*/
const doTest = async (complimentsArray) => {
await expect(page.locator(".compliments")).toBeVisible();
const contentLocator = page.locator(".module-content");
await contentLocator.waitFor({ state: "visible" });
const content = await contentLocator.textContent();
expect(complimentsArray).toContain(content);
};
afterAll(async () => {
await helpers.stopApplication();
});
describe("Feature anytime in compliments module", () => {
describe("Set anytime and empty compliments for morning, evening and afternoon", () => {
beforeAll(async () => {
await helpers.startApplication("tests/configs/modules/compliments/compliments_anytime.js");
await helpers.getDocument();
page = helpers.getPage();
});
it("shows anytime because if configure empty parts of day compliments and set anytime compliments", async () => {
await doTest(["Anytime here"]);
});
});
describe("Only anytime present in configuration compliments", () => {
beforeAll(async () => {
await helpers.startApplication("tests/configs/modules/compliments/compliments_only_anytime.js");
await helpers.getDocument();
page = helpers.getPage();
});
it("shows anytime compliments", async () => {
await doTest(["Anytime here"]);
});
});
});
describe("remoteFile option", () => {
beforeAll(async () => {
await helpers.startApplication("tests/configs/modules/compliments/compliments_remote.js");
await helpers.getDocument();
page = helpers.getPage();
});
it("should show compliments from a remote file", async () => {
await doTest(["Remote compliment file works!"]);
});
});
describe("Feature specialDayUnique in compliments module", () => {
describe("specialDayUnique is false", () => {
beforeAll(async () => {
await helpers.startApplication("tests/configs/modules/compliments/compliments_specialDayUnique_false.js");
await helpers.getDocument();
page = helpers.getPage();
});
it("compliments array can contain all values", async () => {
await doTest(["Special day message", "Typical message 1", "Typical message 2", "Typical message 3"]);
});
});
describe("specialDayUnique is true", () => {
beforeAll(async () => {
await helpers.startApplication("tests/configs/modules/compliments/compliments_specialDayUnique_true.js");
await helpers.getDocument();
page = helpers.getPage();
});
it("compliments array contains only special value", async () => {
await doTest(["Special day message"]);
});
});
describe("cron type key", () => {
beforeAll(async () => {
await helpers.startApplication("tests/configs/modules/compliments/compliments_e2e_cron_entry.js");
await helpers.getDocument();
page = helpers.getPage();
});
it("compliments array contains only special value", async () => {
await doTest(["anytime cron"]);
});
});
});
describe("Feature remote compliments file", () => {
describe("get list from remote file", () => {
beforeAll(async () => {
await helpers.startApplication("tests/configs/modules/compliments/compliments_file.js");
await helpers.getDocument();
page = helpers.getPage();
});
it("shows 'Remote compliment file works!' as only anytime list set", async () => {
//await helpers.startApplication("tests/configs/modules/compliments/compliments_file.js", "01 Jan 2022 10:00:00 GMT");
await doTest(["Remote compliment file works!"]);
});
// afterAll(async () =>{
// await helpers.stopApplication()
// });
});
describe("get list from remote file w update", () => {
beforeAll(async () => {
await helpers.startApplication("tests/configs/modules/compliments/compliments_file_change.js");
await helpers.getDocument();
page = helpers.getPage();
});
it("shows 'test in morning' as test time set to 10am", async () => {
//await helpers.startApplication("tests/configs/modules/compliments/compliments_file_change.js", "01 Jan 2022 10:00:00 GMT");
await doTest(["Remote compliment file works!"]);
await new Promise((r) => setTimeout(r, 10000));
await doTest(["test in morning"]);
});
// afterAll(async () =>{
// await helpers.stopApplication()
// });
});
});
});
+34
View File
@@ -0,0 +1,34 @@
const { expect } = require("playwright/test");
const helpers = require("../helpers/global-setup");
describe("Test helloworld module", () => {
let page;
afterAll(async () => {
await helpers.stopApplication();
});
describe("helloworld set config text", () => {
beforeAll(async () => {
await helpers.startApplication("tests/configs/modules/helloworld/helloworld.js");
await helpers.getDocument();
page = helpers.getPage();
});
it("Test message helloworld module", async () => {
await expect(page.locator(".helloworld")).toContainText("Test HelloWorld Module");
});
});
describe("helloworld default config text", () => {
beforeAll(async () => {
await helpers.startApplication("tests/configs/modules/helloworld/helloworld_default.js");
await helpers.getDocument();
page = helpers.getPage();
});
it("Test message helloworld module", async () => {
await expect(page.locator(".helloworld")).toContainText("Hello World!");
});
});
});
+260
View File
@@ -0,0 +1,260 @@
const fs = require("node:fs");
const { expect } = require("playwright/test");
const helpers = require("../helpers/global-setup");
const runTests = () => {
let page;
describe("Default configuration", () => {
beforeAll(async () => {
await helpers.startApplication("tests/configs/modules/newsfeed/default.js");
await helpers.getDocument();
page = helpers.getPage();
});
it("should show the newsfeed title", async () => {
await expect(page.locator(".newsfeed .newsfeed-source")).toContainText("Rodrigo Ramirez Blog");
});
it("should show the newsfeed article", async () => {
await expect(page.locator(".newsfeed .newsfeed-title")).toContainText("QPanel");
});
it("should NOT show the newsfeed description", async () => {
await page.locator(".newsfeed").waitFor({ state: "visible" });
await expect(page.locator(".newsfeed .newsfeed-desc")).toHaveCount(0);
});
});
describe("Custom configuration", () => {
beforeAll(async () => {
await helpers.startApplication("tests/configs/modules/newsfeed/prohibited_words.js");
await helpers.getDocument();
page = helpers.getPage();
});
it("should not show articles with prohibited words", async () => {
await expect(page.locator(".newsfeed .newsfeed-title")).toContainText("Problema VirtualBox");
});
it("should show the newsfeed description", async () => {
const locator = page.locator(".newsfeed .newsfeed-desc");
await expect(locator).toBeVisible();
const text = await locator.textContent();
expect(text).toMatch(/\S/);
});
});
describe("Basic HTML tags", () => {
beforeAll(async () => {
await helpers.startApplication("tests/configs/modules/newsfeed/basic_html.js");
await helpers.getDocument();
page = helpers.getPage();
});
it("should render allowlisted formatting tags in title and description", async () => {
await expect(page.locator(".newsfeed .newsfeed-desc")).toBeVisible();
const descHtml = await page.locator(".newsfeed .newsfeed-desc").innerHTML();
expect(descHtml).toContain("<em>");
expect(descHtml).toContain("<strong>");
expect(descHtml).toContain("<u>");
const titleHtml = await page.locator(".newsfeed .newsfeed-title").innerHTML();
expect(titleHtml).toContain("<em>");
});
it("should strip disallowed HTML and not execute injected scripts", async () => {
const descHtml = await page.locator(".newsfeed .newsfeed-desc").innerHTML();
expect(descHtml).not.toContain("<script");
expect(descHtml).not.toContain("onerror");
const xss = await page.evaluate(() => window.__newsfeedXss);
expect(xss).toBeUndefined();
});
});
describe("Invalid configuration", () => {
beforeAll(async () => {
await helpers.startApplication("tests/configs/modules/newsfeed/incorrect_url.js");
await helpers.getDocument();
page = helpers.getPage();
});
it("should show malformed url warning", async () => {
await expect(page.locator(".newsfeed .small")).toContainText("Error in the Newsfeed module. Malformed url.");
});
});
describe("Ignore items", () => {
beforeAll(async () => {
await helpers.startApplication("tests/configs/modules/newsfeed/ignore_items.js");
await helpers.getDocument();
page = helpers.getPage();
});
it("should show empty items info message", async () => {
await expect(page.locator(".newsfeed .small")).toContainText("No news at the moment.");
});
});
};
describe("Newsfeed module > Notifications", () => {
let page;
afterAll(async () => {
await helpers.stopApplication();
});
/**
* Helper: call notificationReceived on the newsfeed module directly.
* @param {object} p - playwright page
* @param {string} notification - notification name
* @param {object} payload - notification payload
* @returns {Promise<void>} resolves when the notification has been dispatched
*/
const notify = (p, notification, payload = {}) => p.evaluate(
({ n, pl }) => {
const nf = MM.getModules().find((m) => m.name === "newsfeed");
nf.notificationReceived(n, pl, nf);
},
{ n: notification, pl: payload }
);
beforeAll(async () => {
await helpers.startApplication("tests/configs/modules/newsfeed/notifications.js");
await helpers.getDocument();
page = helpers.getPage();
await expect(page.locator(".newsfeed .newsfeed-title")).toBeVisible();
});
it("ARTICLE_NEXT should show the next article", async () => {
const title1 = await page.locator(".newsfeed .newsfeed-title").textContent();
await notify(page, "ARTICLE_NEXT");
await expect(page.locator(".newsfeed .newsfeed-title")).not.toContainText(title1.trim());
});
it("ARTICLE_PREVIOUS should return to the previous article", async () => {
// Start at article 0, go to article 1, then back
await page.evaluate(() => {
const nf = MM.getModules().find((m) => m.name === "newsfeed");
nf.activeItem = 0;
nf.resetDescrOrFullArticleAndTimer();
nf.updateDom(0);
});
await expect(page.locator(".newsfeed .newsfeed-title")).toContainText("QPanel");
const title0 = await page.locator(".newsfeed .newsfeed-title").textContent();
await notify(page, "ARTICLE_NEXT");
await expect(page.locator(".newsfeed .newsfeed-title")).not.toContainText(title0.trim());
await notify(page, "ARTICLE_PREVIOUS");
await expect(page.locator(".newsfeed .newsfeed-title")).toContainText(title0.trim());
});
it("ARTICLE_NEXT should wrap around from the last article to the first", async () => {
// Jump to the last article
await page.evaluate(() => {
const nf = MM.getModules().find((m) => m.name === "newsfeed");
nf.activeItem = nf.newsItems.length - 1;
nf.resetDescrOrFullArticleAndTimer();
nf.updateDom(0);
});
await expect(page.locator(".newsfeed .newsfeed-title")).toBeVisible();
const titleLast = await page.locator(".newsfeed .newsfeed-title").textContent();
await notify(page, "ARTICLE_NEXT");
await expect(page.locator(".newsfeed .newsfeed-title")).not.toContainText(titleLast.trim());
// activeItem should now be 0
const activeItem = await page.evaluate(() => MM.getModules().find((m) => m.name === "newsfeed").activeItem);
expect(activeItem).toBe(0);
});
it("ARTICLE_PREVIOUS should wrap around from the first article to the last", async () => {
await page.evaluate(() => {
const nf = MM.getModules().find((m) => m.name === "newsfeed");
nf.activeItem = 0;
nf.resetDescrOrFullArticleAndTimer();
});
await notify(page, "ARTICLE_PREVIOUS");
const activeItem = await page.evaluate(() => {
const nf = MM.getModules().find((m) => m.name === "newsfeed");
return { activeItem: nf.activeItem, total: nf.newsItems.length };
});
expect(activeItem.activeItem).toBe(activeItem.total - 1);
});
it("ARTICLE_INFO_REQUEST should respond with title, source, date, desc and raw url", async () => {
await page.evaluate(() => {
const nf = MM.getModules().find((m) => m.name === "newsfeed");
nf.activeItem = 0;
nf.resetDescrOrFullArticleAndTimer();
});
const info = await page.evaluate(() => new Promise((resolve, reject) => {
const timer = setTimeout(() => reject(new Error("ARTICLE_INFO_RESPONSE timeout")), 3000);
const origSend = MM.sendNotification.bind(MM);
MM.sendNotification = function (n, p, s) {
if (n === "ARTICLE_INFO_RESPONSE") {
clearTimeout(timer);
MM.sendNotification = origSend;
resolve(p);
}
return origSend(n, p, s);
};
const nf = MM.getModules().find((m) => m.name === "newsfeed");
nf.notificationReceived("ARTICLE_INFO_REQUEST", {}, nf);
}));
expect(info).toHaveProperty("title");
expect(info).toHaveProperty("source");
expect(info).toHaveProperty("date");
expect(info).toHaveProperty("desc");
expect(info).toHaveProperty("url");
expect(info.title).toBe("QPanel 0.13.0");
expect(info.source).toBe("Rodrigo Ramirez Blog");
// URL must be the raw article URL, not a CORS proxy URL
expect(info.url).toMatch(/^https?:\/\//);
expect(info.url).not.toContain("localhost");
});
it("ARTICLE_LESS_DETAILS should reset the full article view", async () => {
// Simulate full article view being active
await page.evaluate(() => {
const nf = MM.getModules().find((m) => m.name === "newsfeed");
nf.config.showFullArticle = true;
nf.articleFrameCheckPending = false;
nf.articleUnavailable = false;
});
await notify(page, "ARTICLE_LESS_DETAILS");
const state = await page.evaluate(() => {
const nf = MM.getModules().find((m) => m.name === "newsfeed");
return { showFullArticle: nf.config.showFullArticle };
});
expect(state.showFullArticle).toBe(false);
// Normal newsfeed title should be visible again
await expect(page.locator(".newsfeed .newsfeed-title")).toBeVisible();
});
});
describe("Newsfeed module", () => {
afterAll(async () => {
await helpers.stopApplication();
});
runTests();
});
describe("Newsfeed module located in config directory", () => {
beforeAll(() => {
fs.cpSync(`${global.root_path}/${global.defaultModulesDir}/newsfeed`, `${global.root_path}/config/newsfeed`, { recursive: true });
process.env.MM_MODULES_DIR = "config";
});
afterAll(async () => {
await helpers.stopApplication();
});
runTests();
});
+98
View File
@@ -0,0 +1,98 @@
const { expect } = require("playwright/test");
const helpers = require("../helpers/global-setup");
const weatherFunc = require("../helpers/weather-functions");
describe("Weather module", () => {
let page;
afterAll(async () => {
await weatherFunc.stopApplication();
});
describe("Current weather", () => {
describe("Default configuration", () => {
beforeAll(async () => {
await weatherFunc.startApplication("tests/configs/modules/weather/currentweather_default.js", "weather_onecall_current.json");
page = helpers.getPage();
});
it("should render wind speed and wind direction", async () => {
await expect(page.locator(".weather .normal.medium span:nth-child(2)")).toHaveText("12 WSW");
});
it("should render temperature with icon", async () => {
await expect(page.locator(".weather .large span.light.bright")).toHaveText("1.5°");
await expect(page.locator(".weather .large span.weathericon")).toBeVisible();
});
it("should render feels like temperature", async () => {
// Template contains &nbsp; which renders as \xa0
await expect(page.locator(".weather .normal.medium.feelslike span.dimmed")).toHaveText("93.7\xa0 Feels like -5.6°");
});
it("should render humidity next to feels-like", async () => {
await expect(page.locator(".weather .normal.medium.feelslike span.dimmed .humidity")).toHaveText("93.7");
});
});
});
describe("Compliments Integration", () => {
beforeAll(async () => {
await weatherFunc.startApplication("tests/configs/modules/weather/currentweather_compliments.js", "weather_onecall_current.json");
page = helpers.getPage();
});
it("should render a compliment based on the current weather", async () => {
const compliment = page.locator(".compliments .module-content span");
await compliment.waitFor({ state: "visible" });
await expect(compliment).toHaveText("snow");
});
});
describe("Configuration Options", () => {
beforeAll(async () => {
await weatherFunc.startApplication("tests/configs/modules/weather/currentweather_options.js", "weather_onecall_current.json");
page = helpers.getPage();
});
it("should render windUnits in beaufort", async () => {
await expect(page.locator(".weather .normal.medium span:nth-child(2)")).toHaveText("6");
});
it("should render windDirection with an arrow", async () => {
const arrow = page.locator(".weather .normal.medium sup i.fa-long-arrow-alt-down");
await expect(arrow).toHaveAttribute("style", "transform:rotate(250deg)");
});
it("should render humidity next to wind", async () => {
await expect(page.locator(".weather .normal.medium .humidity")).toHaveText("93.7");
});
it("should render degreeLabel for temp", async () => {
await expect(page.locator(".weather .large span.bright.light")).toHaveText("1°C");
});
it("should render degreeLabel for feels like", async () => {
await expect(page.locator(".weather .normal.medium.feelslike span.dimmed")).toHaveText("Feels like -6°C");
});
});
describe("Current weather with imperial units", () => {
beforeAll(async () => {
await weatherFunc.startApplication("tests/configs/modules/weather/currentweather_units.js", "weather_onecall_current.json");
page = helpers.getPage();
});
it("should render wind in imperial units", async () => {
await expect(page.locator(".weather .normal.medium span:nth-child(2)")).toHaveText("26 WSW");
});
it("should render temperatures in fahrenheit", async () => {
await expect(page.locator(".weather .large span.bright.light")).toHaveText("34,7°");
});
it("should render 'feels like' in fahrenheit", async () => {
await expect(page.locator(".weather .normal.medium.feelslike span.dimmed")).toHaveText("Feels like 21,9°");
});
});
});
+128
View File
@@ -0,0 +1,128 @@
const { expect } = require("playwright/test");
const helpers = require("../helpers/global-setup");
const weatherFunc = require("../helpers/weather-functions");
describe("Weather module: Weather Forecast", () => {
let page;
afterAll(async () => {
await weatherFunc.stopApplication();
});
describe("Default configuration", () => {
beforeAll(async () => {
await weatherFunc.startApplication("tests/configs/modules/weather/forecastweather_default.js", "weather_onecall_forecast.json");
page = helpers.getPage();
});
const days = ["Today", "Tomorrow", "Sun", "Mon", "Tue"];
for (const [index, day] of days.entries()) {
it(`should render day ${day}`, async () => {
const dayCell = page.locator(`.weather table.small tr:nth-child(${index + 1}) td:nth-child(1)`);
await expect(dayCell).toHaveText(day);
});
}
const icons = ["day-cloudy", "rain", "day-sunny", "day-sunny", "day-sunny"];
for (const [index, icon] of icons.entries()) {
it(`should render icon ${icon}`, async () => {
const iconElement = page.locator(`.weather table.small tr:nth-child(${index + 1}) td:nth-child(2) span.wi-${icon}`);
await expect(iconElement).toBeVisible();
});
}
const maxTemps = ["24.4°", "21.0°", "22.9°", "23.4°", "20.6°"];
for (const [index, temp] of maxTemps.entries()) {
it(`should render max temperature ${temp}`, async () => {
const maxTempCell = page.locator(`.weather table.small tr:nth-child(${index + 1}) td:nth-child(3)`);
await expect(maxTempCell).toHaveText(temp);
});
}
const minTemps = ["15.3°", "13.6°", "13.8°", "13.9°", "10.9°"];
for (const [index, temp] of minTemps.entries()) {
it(`should render min temperature ${temp}`, async () => {
const minTempCell = page.locator(`.weather table.small tr:nth-child(${index + 1}) td:nth-child(4)`);
await expect(minTempCell).toHaveText(temp);
});
}
const opacities = [1, 1, 0.8, 0.5333333333333333, 0.2666666666666667];
for (const [index, opacity] of opacities.entries()) {
it(`should render fading of rows with opacity=${opacity}`, async () => {
const row = page.locator(`.weather table.small tr:nth-child(${index + 1})`);
await expect(row).toHaveAttribute("style", `opacity: ${opacity};`);
});
}
});
describe("Absolute configuration", () => {
beforeAll(async () => {
await weatherFunc.startApplication("tests/configs/modules/weather/forecastweather_absolute.js", "weather_onecall_forecast.json");
page = helpers.getPage();
});
const days = ["Fri", "Sat", "Sun", "Mon", "Tue"];
for (const [index, day] of days.entries()) {
it(`should render day ${day}`, async () => {
const dayCell = page.locator(`.weather table.small tr:nth-child(${index + 1}) td:nth-child(1)`);
await expect(dayCell).toHaveText(day);
});
}
});
describe("Configuration Options", () => {
beforeAll(async () => {
await weatherFunc.startApplication("tests/configs/modules/weather/forecastweather_options.js", "weather_onecall_forecast.json");
page = helpers.getPage();
});
it("should render custom table class", async () => {
await expect(page.locator(".weather table.myTableClass")).toBeVisible();
});
it("should render colored rows", async () => {
const rows = page.locator(".weather table.myTableClass tr");
await expect(rows).toHaveCount(5);
});
const precipitations = [undefined, "2.51 mm"];
for (const [index, precipitation] of precipitations.entries()) {
if (precipitation) {
it(`should render precipitation amount ${precipitation}`, async () => {
const precipCell = page.locator(`.weather table tr:nth-child(${index + 1}) td.precipitation-amount`);
await expect(precipCell).toHaveText(precipitation);
});
}
}
});
describe("Forecast weather with imperial units", () => {
beforeAll(async () => {
await weatherFunc.startApplication("tests/configs/modules/weather/forecastweather_units.js", "weather_onecall_forecast.json");
page = helpers.getPage();
});
describe("Temperature units", () => {
const temperatures = ["75_9°", "69_8°", "73_2°", "74_1°", "69_1°"];
for (const [index, temp] of temperatures.entries()) {
it(`should render custom decimalSymbol = '_' for temp ${temp}`, async () => {
const tempCell = page.locator(`.weather table.small tr:nth-child(${index + 1}) td:nth-child(3)`);
await expect(tempCell).toHaveText(temp);
});
}
});
describe("Precipitation units", () => {
const precipitations = [undefined, "0.10 in"];
for (const [index, precipitation] of precipitations.entries()) {
if (precipitation) {
it(`should render precipitation amount ${precipitation}`, async () => {
const precipCell = page.locator(`.weather table.small tr:nth-child(${index + 1}) td.precipitation-amount`);
await expect(precipCell).toHaveText(precipitation);
});
}
}
});
});
});
+74
View File
@@ -0,0 +1,74 @@
const { expect } = require("playwright/test");
const helpers = require("../helpers/global-setup");
const weatherFunc = require("../helpers/weather-functions");
describe("Weather module: Weather Hourly Forecast", () => {
let page;
afterAll(async () => {
await weatherFunc.stopApplication();
});
describe("Default configuration", () => {
beforeAll(async () => {
await weatherFunc.startApplication("tests/configs/modules/weather/hourlyweather_default.js", "weather_onecall_hourly.json");
page = helpers.getPage();
});
const minTemps = ["7:00 pm", "8:00 pm", "9:00 pm", "10:00 pm", "11:00 pm"];
for (const [index, hour] of minTemps.entries()) {
it(`should render forecast for hour ${hour}`, async () => {
const dayCell = page.locator(`.weather table.small tr:nth-child(${index + 1}) td.day`);
await expect(dayCell).toHaveText(hour);
});
}
});
describe("Hourly weather options", () => {
beforeAll(async () => {
await weatherFunc.startApplication("tests/configs/modules/weather/hourlyweather_options.js", "weather_onecall_hourly.json");
page = helpers.getPage();
});
describe("Hourly increments of 2", () => {
const minTemps = ["7:00 pm", "9:00 pm", "11:00 pm", "1:00 am", "3:00 am"];
for (const [index, hour] of minTemps.entries()) {
it(`should render forecast for hour ${hour}`, async () => {
const dayCell = page.locator(`.weather table.small tr:nth-child(${index + 1}) td.day`);
await expect(dayCell).toHaveText(hour);
});
}
});
});
describe("Show precipitations", () => {
beforeAll(async () => {
await weatherFunc.startApplication("tests/configs/modules/weather/hourlyweather_showPrecipitation.js", "weather_onecall_hourly.json");
page = helpers.getPage();
});
describe("Shows precipitation amount", () => {
const amounts = [undefined, undefined, undefined, "0.13 mm", "0.13 mm"];
for (const [index, amount] of amounts.entries()) {
if (amount) {
it(`should render precipitation amount ${amount}`, async () => {
const amountCell = page.locator(`.weather table.small tr:nth-child(${index + 1}) td.precipitation-amount`);
await expect(amountCell).toHaveText(amount);
});
}
}
});
describe("Shows precipitation probability", () => {
const probabilities = [undefined, undefined, "12 %", "36 %", "44 %"];
for (const [index, probability] of probabilities.entries()) {
if (probability) {
it(`should render probability ${probability}`, async () => {
const probabilityCell = page.locator(`.weather table.small tr:nth-child(${index + 1}) td.precipitation-prob`);
await expect(probabilityCell).toHaveText(probability);
});
}
}
});
});
});