salat 4.9.1 → 4.9.5
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/README.md +1 -0
- package/dist/app.js +2 -0
- package/dist/commands/hijri.js +10 -0
- package/dist/components/HijriApp.js +32 -0
- package/dist/components/HijriApp.test.js +31 -0
- package/dist/services/constants.js +1 -0
- package/dist/services/utils/api.js +6 -1
- package/dist/services/utils/api.test.js +18 -13
- package/dist/services/utils/hijri.js +20 -0
- package/dist/services/utils/hijri.test.js +37 -0
- package/dist/services/utils/index.js +1 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -6,6 +6,7 @@ A modern, visually rich CLI for checking prayer times in Morocco, built with **R
|
|
|
6
6
|
|
|
7
7
|
[](https://www.npmjs.com/package/salat)
|
|
8
8
|
[](https://opensource.org/licenses/MIT)
|
|
9
|
+
[](https://codecov.io/gh/kafiln/salat-cli)
|
|
9
10
|
|
|
10
11
|
---
|
|
11
12
|
|
package/dist/app.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// Project's setup
|
|
3
3
|
import { citiesCommand } from "#commands/cities";
|
|
4
4
|
import { guideCommand } from "#commands/guide";
|
|
5
|
+
import { hijriCommand } from "#commands/hijri";
|
|
5
6
|
import { timesCommand } from "#commands/times";
|
|
6
7
|
import { Command } from "commander";
|
|
7
8
|
import pkg from "../package.json" with { type: "json" };
|
|
@@ -11,6 +12,7 @@ program
|
|
|
11
12
|
.description(pkg.description)
|
|
12
13
|
.version(pkg.version)
|
|
13
14
|
.addCommand(timesCommand, { isDefault: true })
|
|
15
|
+
.addCommand(hijriCommand)
|
|
14
16
|
.addCommand(guideCommand)
|
|
15
17
|
.addCommand(citiesCommand);
|
|
16
18
|
program.parse();
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import HijriApp from "#components/HijriApp";
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { render } from "ink";
|
|
4
|
+
import React from "react";
|
|
5
|
+
export const hijriCommand = new Command("hijri")
|
|
6
|
+
.description("Display the hijri date")
|
|
7
|
+
.option("-1, --once", "Run once and exit", false)
|
|
8
|
+
.action(() => {
|
|
9
|
+
render(React.createElement(HijriApp));
|
|
10
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { getHijriDate } from "#services/utils/hijri";
|
|
3
|
+
import { Box, Text } from "ink";
|
|
4
|
+
import { useEffect, useState } from "react";
|
|
5
|
+
const HijriApp = () => {
|
|
6
|
+
const [hijriDate, setHijriDate] = useState(null);
|
|
7
|
+
const [error, setError] = useState(null);
|
|
8
|
+
const [loading, setLoading] = useState(true);
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
const fetchHijri = async () => {
|
|
11
|
+
try {
|
|
12
|
+
const result = await getHijriDate();
|
|
13
|
+
setHijriDate(result.date);
|
|
14
|
+
}
|
|
15
|
+
catch (err) {
|
|
16
|
+
setError(err.message || "Failed to fetch hijri date");
|
|
17
|
+
}
|
|
18
|
+
finally {
|
|
19
|
+
setLoading(false);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
fetchHijri();
|
|
23
|
+
}, []);
|
|
24
|
+
if (loading) {
|
|
25
|
+
return _jsx(Text, { children: "Loading hijri date..." });
|
|
26
|
+
}
|
|
27
|
+
if (error) {
|
|
28
|
+
return _jsxs(Text, { color: "red", children: ["Error: ", error] });
|
|
29
|
+
}
|
|
30
|
+
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: "blue", children: "\uD83D\uDD4C Hijri Date" }) }), _jsx(Box, { padding: 1, children: _jsx(Text, { children: `\u061C${hijriDate}` }) })] }));
|
|
31
|
+
};
|
|
32
|
+
export default HijriApp;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { getHijriDate } from "#services/utils/hijri";
|
|
3
|
+
import { render } from "ink-testing-library";
|
|
4
|
+
import { describe, expect, it, vi } from "vitest";
|
|
5
|
+
import HijriApp from "./HijriApp.js";
|
|
6
|
+
vi.mock("#services/utils/hijri", () => ({
|
|
7
|
+
getHijriDate: vi.fn(),
|
|
8
|
+
}));
|
|
9
|
+
describe("HijriApp", () => {
|
|
10
|
+
it("should render loading state", () => {
|
|
11
|
+
vi.mocked(getHijriDate).mockImplementation(() => new Promise(() => { }));
|
|
12
|
+
const { lastFrame } = render(_jsx(HijriApp, {}));
|
|
13
|
+
expect(lastFrame()).toContain("Loading hijri date...");
|
|
14
|
+
});
|
|
15
|
+
it("should render error state", async () => {
|
|
16
|
+
vi.mocked(getHijriDate).mockRejectedValue(new Error("Network error"));
|
|
17
|
+
const { lastFrame } = render(_jsx(HijriApp, {}));
|
|
18
|
+
// Wait for async operation
|
|
19
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
20
|
+
expect(lastFrame()).toContain("Error: Network error");
|
|
21
|
+
});
|
|
22
|
+
it("should render hijri date", async () => {
|
|
23
|
+
const mockDate = "السبت 18 شعبان 1447هـ | الموافق 07 فبراير 2026م";
|
|
24
|
+
vi.mocked(getHijriDate).mockResolvedValue({ date: mockDate });
|
|
25
|
+
const { lastFrame } = render(_jsx(HijriApp, {}));
|
|
26
|
+
// Wait for async operation
|
|
27
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
28
|
+
expect(lastFrame()).toContain("Hijri Date");
|
|
29
|
+
expect(lastFrame()).toContain(mockDate);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { API_URL } from "#services/constants";
|
|
2
|
+
import fetch from "node-fetch";
|
|
3
|
+
import https from "https";
|
|
4
|
+
const agent = new https.Agent({
|
|
5
|
+
rejectUnauthorized: false,
|
|
6
|
+
});
|
|
2
7
|
export const getData = async (cityId) => {
|
|
3
|
-
const response = await fetch(`${API_URL}?ville=${cityId}
|
|
8
|
+
const response = await fetch(`${API_URL}?ville=${cityId}`, { agent });
|
|
4
9
|
return await response.text();
|
|
5
10
|
};
|
|
@@ -1,23 +1,28 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, it, vi } from
|
|
2
|
-
import * as constants from
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import * as constants from "../constants.js";
|
|
3
|
+
vi.mock("node-fetch");
|
|
4
|
+
vi.mock("https");
|
|
5
|
+
import fetch from "node-fetch";
|
|
6
|
+
import { getData } from "./api.js";
|
|
7
|
+
describe("api utils", () => {
|
|
8
|
+
describe("getData", () => {
|
|
6
9
|
beforeEach(() => {
|
|
7
|
-
vi.
|
|
10
|
+
vi.clearAllMocks();
|
|
8
11
|
});
|
|
9
|
-
it(
|
|
10
|
-
const mockResponse =
|
|
11
|
-
|
|
12
|
+
it("should fetch data from the correct URL", async () => {
|
|
13
|
+
const mockResponse = "mock html content";
|
|
14
|
+
vi.mocked(fetch).mockResolvedValue({
|
|
12
15
|
text: async () => mockResponse,
|
|
13
16
|
});
|
|
14
17
|
const result = await getData(1);
|
|
15
|
-
expect(fetch).toHaveBeenCalledWith(`${constants.API_URL}?ville=1
|
|
18
|
+
expect(fetch).toHaveBeenCalledWith(`${constants.API_URL}?ville=1`, expect.objectContaining({
|
|
19
|
+
agent: expect.any(Object),
|
|
20
|
+
}));
|
|
16
21
|
expect(result).toBe(mockResponse);
|
|
17
22
|
});
|
|
18
|
-
it(
|
|
19
|
-
|
|
20
|
-
await expect(getData(1)).rejects.toThrow(
|
|
23
|
+
it("should throw error if fetch fails", async () => {
|
|
24
|
+
vi.mocked(fetch).mockRejectedValue(new Error("Network error"));
|
|
25
|
+
await expect(getData(1)).rejects.toThrow("Network error");
|
|
21
26
|
});
|
|
22
27
|
});
|
|
23
28
|
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { HIJRI_API_URL } from "../constants.js";
|
|
2
|
+
export const getHijriDate = async () => {
|
|
3
|
+
try {
|
|
4
|
+
const response = await fetch(HIJRI_API_URL);
|
|
5
|
+
const text = await response.text();
|
|
6
|
+
// The API returns a plain text string in Arabic
|
|
7
|
+
// e.g., "السبت 18 شعبان 1447هـ | الموافق 07 فبراير 2026م"
|
|
8
|
+
if (!text) {
|
|
9
|
+
throw new Error("Empty response from hijri date API");
|
|
10
|
+
}
|
|
11
|
+
// Clean up the text by removing any trailing % or whitespace
|
|
12
|
+
const cleanedDate = text.trim().replace(/%\s*$/, "");
|
|
13
|
+
return {
|
|
14
|
+
date: cleanedDate,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
throw new Error(`Failed to fetch hijri date: ${error.message}`);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { getHijriDate } from "./hijri.js";
|
|
3
|
+
describe("hijri service", () => {
|
|
4
|
+
beforeEach(() => {
|
|
5
|
+
vi.clearAllMocks();
|
|
6
|
+
});
|
|
7
|
+
afterEach(() => {
|
|
8
|
+
vi.resetAllMocks();
|
|
9
|
+
});
|
|
10
|
+
it("should fetch hijri date successfully", async () => {
|
|
11
|
+
const mockResponse = "السبت 18 شعبان 1447هـ | الموافق 07 فبراير 2026م";
|
|
12
|
+
global.fetch = vi.fn(() => Promise.resolve({
|
|
13
|
+
text: () => Promise.resolve(mockResponse),
|
|
14
|
+
}));
|
|
15
|
+
const result = await getHijriDate();
|
|
16
|
+
expect(result.date).toBe(mockResponse);
|
|
17
|
+
expect(global.fetch).toHaveBeenCalledWith("https://apisearch.hadithm6.ma/api/hijridate");
|
|
18
|
+
});
|
|
19
|
+
it("should handle empty response", async () => {
|
|
20
|
+
global.fetch = vi.fn(() => Promise.resolve({
|
|
21
|
+
text: () => Promise.resolve(""),
|
|
22
|
+
}));
|
|
23
|
+
await expect(getHijriDate()).rejects.toThrow("Empty response from hijri date API");
|
|
24
|
+
});
|
|
25
|
+
it("should handle network errors", async () => {
|
|
26
|
+
global.fetch = vi.fn(() => Promise.reject(new Error("Network error")));
|
|
27
|
+
await expect(getHijriDate()).rejects.toThrow("Failed to fetch hijri date");
|
|
28
|
+
});
|
|
29
|
+
it("should clean up trailing % from response", async () => {
|
|
30
|
+
const mockResponse = "السبت 18 شعبان 1447هـ | الموافق 07 فبراير 2026م%";
|
|
31
|
+
global.fetch = vi.fn(() => Promise.resolve({
|
|
32
|
+
text: () => Promise.resolve(mockResponse),
|
|
33
|
+
}));
|
|
34
|
+
const result = await getHijriDate();
|
|
35
|
+
expect(result.date).toBe("السبت 18 شعبان 1447هـ | الموافق 07 فبراير 2026م");
|
|
36
|
+
});
|
|
37
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "salat",
|
|
3
|
-
"version": "4.9.
|
|
3
|
+
"version": "4.9.5",
|
|
4
4
|
"imports": {
|
|
5
5
|
"#*": "./dist/*.js"
|
|
6
6
|
},
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"date-fns": "^4.1.0",
|
|
20
20
|
"domino": "^2.1.6",
|
|
21
21
|
"ink": "^6.6.0",
|
|
22
|
+
"node-fetch": "^3.3.1",
|
|
22
23
|
"node-localstorage": "^3.0.5",
|
|
23
24
|
"react": "^19.2.4"
|
|
24
25
|
},
|