@uxf/scripts 11.99.0 → 11.105.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/uxf-audit/manager/yarn-1.test.js +46 -0
- package/src/uxf-audit/manager/yarn-4.test.js +31 -0
- package/src/uxf-audit/utils/json-exec.test.js +32 -0
- package/src/uxf-audit/utils/parser.test.js +19 -0
- package/src/uxf-dependencies-check/index.js +1 -0
- package/src/uxf-dependencies-check/uxf-dependencies-check.test.js +38 -1
- package/src/uxf-i18n-namespaces-gen/index.js +9 -0
- package/src/uxf-i18n-namespaces-gen/index.test.js +86 -0
- package/src/uxf-release/index.js +7 -0
- package/src/uxf-release/index.test.js +110 -0
- package/src/uxf-sitemap-check/index.js +9 -0
- package/src/uxf-sitemap-check/index.test.js +148 -0
package/package.json
CHANGED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
jest.mock("../utils/json-exec");
|
|
2
|
+
|
|
3
|
+
const { executeJson } = require("../utils/json-exec");
|
|
4
|
+
const { Yarn1 } = require("./yarn-1");
|
|
5
|
+
|
|
6
|
+
describe("Yarn1.uxfPackagesInfo", () => {
|
|
7
|
+
it("parses yarn v1 tree output into a package map", async () => {
|
|
8
|
+
executeJson.mockResolvedValue([
|
|
9
|
+
{
|
|
10
|
+
data: {
|
|
11
|
+
trees: [
|
|
12
|
+
{ name: "@uxf/ui@1.0.0", children: [] },
|
|
13
|
+
{ name: "@uxf/form@2.0.0", children: [{ name: "react@17" }] },
|
|
14
|
+
],
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
]);
|
|
18
|
+
|
|
19
|
+
const result = await Yarn1.uxfPackagesInfo();
|
|
20
|
+
|
|
21
|
+
expect(result["@uxf/ui"]).toEqual({ name: "@uxf/ui", version: "1.0.0", isError: false });
|
|
22
|
+
expect(result["@uxf/form"]).toEqual({ name: "@uxf/form", version: "2.0.0", isError: true });
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("marks packages with children as errors", async () => {
|
|
26
|
+
executeJson.mockResolvedValue([
|
|
27
|
+
{
|
|
28
|
+
data: {
|
|
29
|
+
trees: [{ name: "@uxf/cms@3.1.0", children: [{ name: "dep@1.0" }] }],
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
]);
|
|
33
|
+
|
|
34
|
+
const result = await Yarn1.uxfPackagesInfo();
|
|
35
|
+
expect(result["@uxf/cms"].isError).toBe(true);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe("Yarn1.packageVersion", () => {
|
|
40
|
+
it("returns the version of a given package", async () => {
|
|
41
|
+
executeJson.mockResolvedValue([{ data: { trees: [{ name: "react@17.0.2" }] } }]);
|
|
42
|
+
|
|
43
|
+
const version = await Yarn1.packageVersion("react");
|
|
44
|
+
expect(version).toBe("17.0.2");
|
|
45
|
+
});
|
|
46
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
jest.mock("../utils/json-exec");
|
|
2
|
+
|
|
3
|
+
const { executeJson } = require("../utils/json-exec");
|
|
4
|
+
const { Yarn4 } = require("./yarn-4");
|
|
5
|
+
|
|
6
|
+
describe("Yarn4.uxfPackagesInfo", () => {
|
|
7
|
+
it("parses yarn v4 JSON output into a package map", async () => {
|
|
8
|
+
executeJson.mockResolvedValue([{ value: "@uxf/ui@npm:1.0.0" }, { value: "@uxf/form@npm:2.0.0" }]);
|
|
9
|
+
|
|
10
|
+
const result = await Yarn4.uxfPackagesInfo();
|
|
11
|
+
|
|
12
|
+
expect(result["@uxf/ui"]).toEqual({ name: "@uxf/ui", version: "1.0.0", isError: false });
|
|
13
|
+
expect(result["@uxf/form"]).toEqual({ name: "@uxf/form", version: "2.0.0", isError: false });
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("marks duplicated packages as errors", async () => {
|
|
17
|
+
executeJson.mockResolvedValue([{ value: "@uxf/ui@npm:1.0.0" }, { value: "@uxf/ui@npm:1.1.0" }]);
|
|
18
|
+
|
|
19
|
+
const result = await Yarn4.uxfPackagesInfo();
|
|
20
|
+
expect(result["@uxf/ui"].isError).toBe(true);
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe("Yarn4.packageVersion", () => {
|
|
25
|
+
it("returns the version of a given package", async () => {
|
|
26
|
+
executeJson.mockResolvedValue([{ value: "next@npm:14.2.0" }]);
|
|
27
|
+
|
|
28
|
+
const version = await Yarn4.packageVersion("next");
|
|
29
|
+
expect(version).toBe("14.2.0");
|
|
30
|
+
});
|
|
31
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
jest.mock("child_process");
|
|
2
|
+
|
|
3
|
+
const { exec } = require("child_process");
|
|
4
|
+
const { execute, executeJson } = require("./json-exec");
|
|
5
|
+
|
|
6
|
+
describe("execute", () => {
|
|
7
|
+
it("resolves with stdout", async () => {
|
|
8
|
+
exec.mockImplementation((_cmd, cb) => cb(null, "hello world\n", ""));
|
|
9
|
+
const result = await execute("echo hello");
|
|
10
|
+
expect(result).toBe("hello world\n");
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("resolves with stdout even when there is an error", async () => {
|
|
14
|
+
exec.mockImplementation((_cmd, cb) => cb(new Error("fail"), "partial output", "stderr"));
|
|
15
|
+
const result = await execute("bad-command");
|
|
16
|
+
expect(result).toBe("partial output");
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe("executeJson", () => {
|
|
21
|
+
it("splits stdout by newline and parses each line as JSON", async () => {
|
|
22
|
+
exec.mockImplementation((_cmd, cb) => cb(null, '{"a":1}\n{"b":2}\n', ""));
|
|
23
|
+
const result = await executeJson("cmd");
|
|
24
|
+
expect(result).toEqual([{ a: 1 }, { b: 2 }]);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("ignores empty lines", async () => {
|
|
28
|
+
exec.mockImplementation((_cmd, cb) => cb(null, '{"a":1}\n\n', ""));
|
|
29
|
+
const result = await executeJson("cmd");
|
|
30
|
+
expect(result).toEqual([{ a: 1 }]);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const { parsePackageName } = require("./parser");
|
|
2
|
+
|
|
3
|
+
describe("parsePackageName", () => {
|
|
4
|
+
it("parses unscoped package", () => {
|
|
5
|
+
expect(parsePackageName("react@17.0.2")).toEqual(["react", "17.0.2"]);
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
it("parses scoped package", () => {
|
|
9
|
+
expect(parsePackageName("@uxf/ui@1.2.3")).toEqual(["@uxf/ui", "1.2.3"]);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("strips npm: prefix from unscoped version", () => {
|
|
13
|
+
expect(parsePackageName("lodash@npm:4.17.21")).toEqual(["lodash", "4.17.21"]);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("strips npm: prefix from scoped version", () => {
|
|
17
|
+
expect(parsePackageName("@uxf/form@npm:2.0.0")).toEqual(["@uxf/form", "2.0.0"]);
|
|
18
|
+
});
|
|
19
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const { findUxfPackagesInDependencies, collectDirectDependencies } = require("./index"); // Adjust the path if needed
|
|
1
|
+
const { findUxfPackagesInDependencies, collectDirectDependencies, checkMultipleVersions } = require("./index"); // Adjust the path if needed
|
|
2
2
|
|
|
3
3
|
describe("findUxfPackagesInDependencies", () => {
|
|
4
4
|
const lockData = {
|
|
@@ -72,3 +72,40 @@ describe("collectDirectDependencies", () => {
|
|
|
72
72
|
expect(result).toEqual({});
|
|
73
73
|
});
|
|
74
74
|
});
|
|
75
|
+
|
|
76
|
+
describe("checkMultipleVersions", () => {
|
|
77
|
+
it("returns empty array when all packages have a single version", () => {
|
|
78
|
+
const deps = {
|
|
79
|
+
react: new Set(["17.0.2"]),
|
|
80
|
+
lodash: new Set(["4.17.21"]),
|
|
81
|
+
};
|
|
82
|
+
expect(checkMultipleVersions(deps)).toEqual([]);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("returns conflicts when a package has multiple versions", () => {
|
|
86
|
+
const deps = {
|
|
87
|
+
react: new Set(["17.0.2", "18.0.0"]),
|
|
88
|
+
lodash: new Set(["4.17.21"]),
|
|
89
|
+
};
|
|
90
|
+
const conflicts = checkMultipleVersions(deps);
|
|
91
|
+
expect(conflicts).toHaveLength(1);
|
|
92
|
+
expect(conflicts[0].name).toBe("react");
|
|
93
|
+
expect(conflicts[0].versions).toEqual(expect.arrayContaining(["17.0.2", "18.0.0"]));
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("excludes packages that are in the excludedPackages set", () => {
|
|
97
|
+
const deps = {
|
|
98
|
+
react: new Set(["17.0.2", "18.0.0"]),
|
|
99
|
+
};
|
|
100
|
+
const result = checkMultipleVersions(deps, new Set(["react"]));
|
|
101
|
+
expect(result).toEqual([]);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("returns multiple conflicts when several packages have version mismatches", () => {
|
|
105
|
+
const deps = {
|
|
106
|
+
react: new Set(["17.0.2", "18.0.0"]),
|
|
107
|
+
lodash: new Set(["4.17.0", "4.17.21"]),
|
|
108
|
+
};
|
|
109
|
+
expect(checkMultipleVersions(deps)).toHaveLength(2);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
@@ -334,9 +334,18 @@ async function main(include, output, defaultNamespaces, pagesDirectory) {
|
|
|
334
334
|
}
|
|
335
335
|
}
|
|
336
336
|
|
|
337
|
+
if (result["/_app"]) {
|
|
338
|
+
result["*"] = Array.from(new Set([...result["*"], ...result["/_app"]])).sort();
|
|
339
|
+
delete result["/_app"];
|
|
340
|
+
}
|
|
341
|
+
|
|
337
342
|
writeFileSync(path.resolve(process.cwd(), output), JSON.stringify(result, null, 4));
|
|
338
343
|
|
|
339
344
|
console.log("Namespaces generated!");
|
|
340
345
|
}
|
|
341
346
|
|
|
342
347
|
module.exports = main;
|
|
348
|
+
module.exports.isAllowedFile = isAllowedFile;
|
|
349
|
+
module.exports.removeTrailingSlash = removeTrailingSlash;
|
|
350
|
+
module.exports.filePathToRoute = filePathToRoute;
|
|
351
|
+
module.exports.findNamespaces = findNamespaces;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/** @jest-environment node */
|
|
2
|
+
const { isAllowedFile, removeTrailingSlash, filePathToRoute, findNamespaces } = require("./index");
|
|
3
|
+
|
|
4
|
+
describe("isAllowedFile", () => {
|
|
5
|
+
it("allows .ts files outside node_modules", () => {
|
|
6
|
+
expect(isAllowedFile("src/components/Button.ts")).toBe(true);
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it("allows .tsx files outside node_modules", () => {
|
|
10
|
+
expect(isAllowedFile("src/pages/index.tsx")).toBe(true);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("allows files inside node_modules/@uxf/", () => {
|
|
14
|
+
expect(isAllowedFile("node_modules/@uxf/ui/src/Button.tsx")).toBe(true);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("blocks files inside other node_modules packages", () => {
|
|
18
|
+
expect(isAllowedFile("node_modules/react/index.js")).toBe(false);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("blocks non-text extensions like .png", () => {
|
|
22
|
+
expect(isAllowedFile("src/assets/logo.png")).toBe(false);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("blocks .css files", () => {
|
|
26
|
+
expect(isAllowedFile("src/styles/globals.css")).toBe(false);
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe("removeTrailingSlash", () => {
|
|
31
|
+
it("removes trailing slash", () => {
|
|
32
|
+
expect(removeTrailingSlash("/foo/bar/")).toBe("/foo/bar");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("leaves string without trailing slash unchanged", () => {
|
|
36
|
+
expect(removeTrailingSlash("/foo/bar")).toBe("/foo/bar");
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("returns empty string for single slash", () => {
|
|
40
|
+
expect(removeTrailingSlash("/")).toBe("");
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe("filePathToRoute", () => {
|
|
45
|
+
it("converts index page to /", () => {
|
|
46
|
+
expect(filePathToRoute("src/pages/index.tsx")).toBe("/");
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("converts top-level page to /page-name", () => {
|
|
50
|
+
expect(filePathToRoute("src/pages/about.tsx")).toBe("/about");
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("converts nested index to /directory (no trailing slash)", () => {
|
|
54
|
+
expect(filePathToRoute("src/pages/blog/index.tsx")).toBe("/blog");
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("converts nested page to /directory/page", () => {
|
|
58
|
+
expect(filePathToRoute("src/pages/blog/post.tsx")).toBe("/blog/post");
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe("findNamespaces", () => {
|
|
63
|
+
it("finds namespaces from both t() and Trans components", () => {
|
|
64
|
+
const content = `
|
|
65
|
+
<Trans i18nKey="auth:login.title" />
|
|
66
|
+
{t("common:button.save")}
|
|
67
|
+
`;
|
|
68
|
+
const namespaces = findNamespaces(content);
|
|
69
|
+
expect(namespaces).toContain("auth");
|
|
70
|
+
expect(namespaces).toContain("common");
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("deduplicates namespaces", () => {
|
|
74
|
+
const content = `
|
|
75
|
+
{t("common:title")}
|
|
76
|
+
{t("common:subtitle")}
|
|
77
|
+
`;
|
|
78
|
+
const namespaces = findNamespaces(content);
|
|
79
|
+
expect(namespaces.filter((ns) => ns === "common")).toHaveLength(1);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("returns empty array when no translation calls present", () => {
|
|
83
|
+
const content = `<div>Hello world</div>`;
|
|
84
|
+
expect(findNamespaces(content)).toEqual([]);
|
|
85
|
+
});
|
|
86
|
+
});
|
package/src/uxf-release/index.js
CHANGED
|
@@ -121,3 +121,10 @@ module.exports = async function (config) {
|
|
|
121
121
|
|
|
122
122
|
await GitLab.createRelease(fullReleaseNotes, config.tagPrefix, config.dryRun);
|
|
123
123
|
};
|
|
124
|
+
|
|
125
|
+
module.exports.generateSlackCommitMessage = generateSlackCommitMessage;
|
|
126
|
+
module.exports.generateSlackMessage = generateSlackMessage;
|
|
127
|
+
module.exports.generateGoogleCommitMessage = generateGoogleCommitMessage;
|
|
128
|
+
module.exports.generateGoogleMessage = generateGoogleMessage;
|
|
129
|
+
module.exports.generateReleaseCommitMessage = generateReleaseCommitMessage;
|
|
130
|
+
module.exports.generateMigrationWarning = generateMigrationWarning;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
const {
|
|
2
|
+
generateSlackCommitMessage,
|
|
3
|
+
generateSlackMessage,
|
|
4
|
+
generateGoogleCommitMessage,
|
|
5
|
+
generateGoogleMessage,
|
|
6
|
+
generateReleaseCommitMessage,
|
|
7
|
+
generateMigrationWarning,
|
|
8
|
+
} = require("./index");
|
|
9
|
+
|
|
10
|
+
const baseCommit = {
|
|
11
|
+
title: "fix(bo): [KLK-123] fix the bug",
|
|
12
|
+
short_id: "abc1234",
|
|
13
|
+
web_url: "https://gitlab.com/commit/abc1234",
|
|
14
|
+
author_email: "dev@example.com",
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
describe("generateMigrationWarning", () => {
|
|
18
|
+
it("returns empty string for empty array", () => {
|
|
19
|
+
expect(generateMigrationWarning([])).toBe("");
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("returns empty string when called with no args", () => {
|
|
23
|
+
expect(generateMigrationWarning()).toBe("");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("returns warning text with listed files", () => {
|
|
27
|
+
const result = generateMigrationWarning(["V1__init.sql", "V2__add_column.sql"]);
|
|
28
|
+
expect(result).toContain("VAROVÁNÍ");
|
|
29
|
+
expect(result).toContain("- V1__init.sql");
|
|
30
|
+
expect(result).toContain("- V2__add_column.sql");
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe("generateSlackCommitMessage", () => {
|
|
35
|
+
it("formats commit with type using uppercase type label", () => {
|
|
36
|
+
const result = generateSlackCommitMessage(baseCommit);
|
|
37
|
+
expect(result).toContain("_FIX_");
|
|
38
|
+
expect(result).toContain("fix the bug");
|
|
39
|
+
expect(result).toContain("abc1234");
|
|
40
|
+
expect(result).toContain("dev@example.com");
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("falls back to plain title when commit format is unrecognized", () => {
|
|
44
|
+
const commit = { ...baseCommit, title: "bump dependencies" };
|
|
45
|
+
const result = generateSlackCommitMessage(commit);
|
|
46
|
+
expect(result).toContain("bump dependencies");
|
|
47
|
+
expect(result).not.toContain("_");
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe("generateGoogleCommitMessage", () => {
|
|
52
|
+
it("formats commit with type, message, and youtrack issue link", () => {
|
|
53
|
+
const result = generateGoogleCommitMessage(baseCommit);
|
|
54
|
+
expect(result).toContain("_FIX_");
|
|
55
|
+
expect(result).toContain("fix the bug");
|
|
56
|
+
expect(result).toContain("youtrack.uxf.dev/issue/KLK-123");
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("falls back to plain title when format is unrecognized", () => {
|
|
60
|
+
const commit = { ...baseCommit, title: "bump dependencies" };
|
|
61
|
+
const result = generateGoogleCommitMessage(commit);
|
|
62
|
+
expect(result).toMatch(/^\* bump dependencies/);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("omits type prefix when type is null (bracket-only format)", () => {
|
|
66
|
+
const commit = { ...baseCommit, title: "[KLK-456] standalone issue" };
|
|
67
|
+
const result = generateGoogleCommitMessage(commit);
|
|
68
|
+
expect(result).toContain("standalone issue");
|
|
69
|
+
expect(result).not.toContain("_NULL_");
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe("generateReleaseCommitMessage", () => {
|
|
74
|
+
it("formats commit with bold type for release notes (markdown)", () => {
|
|
75
|
+
const result = generateReleaseCommitMessage(baseCommit);
|
|
76
|
+
expect(result).toContain("**FIX**");
|
|
77
|
+
expect(result).toContain("fix the bug");
|
|
78
|
+
expect(result).toMatch(/^-/);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("uses markdown link format for issue and commit", () => {
|
|
82
|
+
const result = generateReleaseCommitMessage(baseCommit);
|
|
83
|
+
expect(result).toContain("[abc1234](https://gitlab.com/commit/abc1234)");
|
|
84
|
+
expect(result).toContain("[KLK-123](https://youtrack.uxf.dev/issue/KLK-123)");
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("falls back to plain title for unrecognized format", () => {
|
|
88
|
+
const commit = { ...baseCommit, title: "bump dependencies" };
|
|
89
|
+
const result = generateReleaseCommitMessage(commit);
|
|
90
|
+
expect(result).toMatch(/^- bump dependencies/);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
describe("generateSlackMessage", () => {
|
|
95
|
+
it("includes message title and formatted commits", () => {
|
|
96
|
+
const commits = [baseCommit];
|
|
97
|
+
const result = generateSlackMessage(commits, "Release v1.0.0");
|
|
98
|
+
expect(result.text).toContain("Release v1.0.0");
|
|
99
|
+
expect(result.text).toContain("_FIX_");
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe("generateGoogleMessage", () => {
|
|
104
|
+
it("includes message title and formatted commits", () => {
|
|
105
|
+
const commits = [baseCommit];
|
|
106
|
+
const result = generateGoogleMessage(commits, "Release v1.0.0");
|
|
107
|
+
expect(result.text).toContain("Release v1.0.0");
|
|
108
|
+
expect(result.text).toContain("_FIX_");
|
|
109
|
+
});
|
|
110
|
+
});
|
|
@@ -660,3 +660,12 @@ module.exports = async function run(sitemapUrl, skip, withNested, withImages, ch
|
|
|
660
660
|
process.exit(1);
|
|
661
661
|
}
|
|
662
662
|
};
|
|
663
|
+
|
|
664
|
+
module.exports.createTabSpace = createTabSpace;
|
|
665
|
+
module.exports.isImageUrl = isImageUrl;
|
|
666
|
+
module.exports.validURL = validURL;
|
|
667
|
+
module.exports.convertTime = convertTime;
|
|
668
|
+
module.exports.createErrorList = createErrorList;
|
|
669
|
+
module.exports.createErrorResult = createErrorResult;
|
|
670
|
+
module.exports.createSkippedResult = createSkippedResult;
|
|
671
|
+
module.exports.getUrlOrigin = getUrlOrigin;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jest-environment node
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
jest.mock("../Sitemap");
|
|
6
|
+
jest.mock("../GoogleChat");
|
|
7
|
+
jest.mock("cheerio");
|
|
8
|
+
jest.mock("got");
|
|
9
|
+
jest.mock("robots-txt-parser", () => () => ({ useRobotsFor: jest.fn(), canCrawl: jest.fn() }));
|
|
10
|
+
|
|
11
|
+
const {
|
|
12
|
+
createTabSpace,
|
|
13
|
+
isImageUrl,
|
|
14
|
+
validURL,
|
|
15
|
+
convertTime,
|
|
16
|
+
createErrorList,
|
|
17
|
+
createErrorResult,
|
|
18
|
+
createSkippedResult,
|
|
19
|
+
getUrlOrigin,
|
|
20
|
+
} = require("./index");
|
|
21
|
+
|
|
22
|
+
describe("createTabSpace", () => {
|
|
23
|
+
it("returns 2 spaces by default", () => {
|
|
24
|
+
expect(createTabSpace()).toBe(" ");
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("returns 4 spaces for length 2", () => {
|
|
28
|
+
expect(createTabSpace(2)).toBe(" ");
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("returns 6 spaces for length 3", () => {
|
|
32
|
+
expect(createTabSpace(3)).toBe(" ");
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe("isImageUrl", () => {
|
|
37
|
+
it.each([".jpg", ".png", ".webp", ".avif", ".gif"])("returns true for %s extension", (ext) => {
|
|
38
|
+
expect(isImageUrl(`https://example.com/photo${ext}`)).toBe(true);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("returns false for html pages", () => {
|
|
42
|
+
expect(isImageUrl("https://example.com/page.html")).toBe(false);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("returns false for URLs with no extension", () => {
|
|
46
|
+
expect(isImageUrl("https://example.com/page")).toBe(false);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe("validURL", () => {
|
|
51
|
+
it("returns true for a valid URL", () => {
|
|
52
|
+
expect(validURL("https://example.com/path")).toBe(true);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("returns false for a plain string", () => {
|
|
56
|
+
expect(validURL("not-a-url")).toBe(false);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("returns false for empty string", () => {
|
|
60
|
+
expect(validURL("")).toBe(false);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe("convertTime", () => {
|
|
65
|
+
it("converts 0 ms to 0 min 00 sec", () => {
|
|
66
|
+
expect(convertTime(0)).toBe("0 min 00 sec");
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("converts 90000 ms to 1 min 30 sec", () => {
|
|
70
|
+
expect(convertTime(90000)).toBe("1 min 30 sec");
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("pads seconds below 10 with leading zero", () => {
|
|
74
|
+
expect(convertTime(65000)).toBe("1 min 05 sec");
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("converts 3600000 ms to 60 min 00 sec", () => {
|
|
78
|
+
expect(convertTime(3600000)).toBe("60 min 00 sec");
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe("getUrlOrigin", () => {
|
|
83
|
+
it("returns origin of a regular URL", () => {
|
|
84
|
+
expect(getUrlOrigin("https://example.com/some/path")).toBe("https://example.com");
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("replaces fb.me hostname with facebook.com", () => {
|
|
88
|
+
expect(getUrlOrigin("https://fb.me/some-post")).toBe("https://facebook.com");
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe("createErrorList", () => {
|
|
93
|
+
it("formats a list of errors with url, status, and optional message", () => {
|
|
94
|
+
const errors = [
|
|
95
|
+
{ url: "https://example.com/broken", status: 404, message: "Not Found" },
|
|
96
|
+
{ url: "https://example.com/other", status: 500, message: undefined },
|
|
97
|
+
];
|
|
98
|
+
const result = createErrorList(errors);
|
|
99
|
+
expect(result).toContain("https://example.com/broken");
|
|
100
|
+
expect(result).toContain("404");
|
|
101
|
+
expect(result).toContain("– Not Found");
|
|
102
|
+
expect(result).toContain("https://example.com/other");
|
|
103
|
+
expect(result).toContain("500");
|
|
104
|
+
expect(result).not.toContain("undefined");
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
describe("createErrorResult", () => {
|
|
109
|
+
it("separates errors from sitemap pages vs nested pages", () => {
|
|
110
|
+
const errors = [
|
|
111
|
+
{ url: "https://example.com/page1", parentUrl: undefined, status: 404, message: undefined, isImg: false },
|
|
112
|
+
{
|
|
113
|
+
url: "https://example.com/img.jpg",
|
|
114
|
+
parentUrl: "https://example.com/page2",
|
|
115
|
+
status: 404,
|
|
116
|
+
message: undefined,
|
|
117
|
+
isImg: true,
|
|
118
|
+
},
|
|
119
|
+
];
|
|
120
|
+
const result = createErrorResult(errors);
|
|
121
|
+
expect(result).toContain("Pages from sitemap");
|
|
122
|
+
expect(result).toContain("Nested pages");
|
|
123
|
+
expect(result).toContain("https://example.com/page1");
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("returns empty string when no errors", () => {
|
|
127
|
+
expect(createErrorResult([])).toBe("");
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
describe("createSkippedResult", () => {
|
|
132
|
+
it("returns empty string for empty list", () => {
|
|
133
|
+
expect(createSkippedResult([])).toBe("");
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("returns unique origins of skipped URLs", () => {
|
|
137
|
+
const skipped = [
|
|
138
|
+
{ url: "https://twitter.com/post1" },
|
|
139
|
+
{ url: "https://twitter.com/post2" },
|
|
140
|
+
{ url: "https://linkedin.com/profile" },
|
|
141
|
+
];
|
|
142
|
+
const result = createSkippedResult(skipped);
|
|
143
|
+
expect(result).toContain("https://twitter.com");
|
|
144
|
+
expect(result).toContain("https://linkedin.com");
|
|
145
|
+
// twitter appears only once despite two URLs
|
|
146
|
+
expect(result.split("https://twitter.com")).toHaveLength(2);
|
|
147
|
+
});
|
|
148
|
+
});
|