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 CHANGED
@@ -6,6 +6,7 @@ A modern, visually rich CLI for checking prayer times in Morocco, built with **R
6
6
 
7
7
  [![npm version](https://img.shields.io/npm/v/salat.svg)](https://www.npmjs.com/package/salat)
8
8
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
9
+ [![codecov](https://codecov.io/gh/kafiln/salat-cli/graph/badge.svg?token=E6CULOJPAS)](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,4 +1,5 @@
1
1
  export const API_URL = "https://www.habous.gov.ma/prieres/horaire-api.php";
2
+ export const HIJRI_API_URL = "https://apisearch.hadithm6.ma/api/hijridate";
2
3
  export const BANNER = ``;
3
4
  export const NOT_FOUND_ERROR = `
4
5
  Your city was not found in the list
@@ -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 'vitest';
2
- import * as constants from '../constants.js';
3
- import { getData } from './api.js';
4
- describe('api utils', () => {
5
- describe('getData', () => {
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.stubGlobal('fetch', vi.fn());
10
+ vi.clearAllMocks();
8
11
  });
9
- it('should fetch data from the correct URL', async () => {
10
- const mockResponse = 'mock html content';
11
- (vi.mocked(fetch)).mockResolvedValue({
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('should throw error if fetch fails', async () => {
19
- (vi.mocked(fetch)).mockRejectedValue(new Error('Network error'));
20
- await expect(getData(1)).rejects.toThrow('Network error');
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
+ });
@@ -1,4 +1,5 @@
1
1
  export * from "./api.js";
2
2
  export * from "./city.js";
3
+ export * from "./hijri.js";
3
4
  export * from "./parser.js";
4
5
  export * from "./time.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "salat",
3
- "version": "4.9.1",
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
  },