feat: Refactor specialty display logic and enhance specialty filtering
- Introduced SpecialtyChips component to manage the display of doctor's specialties with a primary specialty and a count of additional specialties. - Updated ItemDoctor component to utilize SpecialtyChips for better UI presentation. - Enhanced PosterLight and Poster components to display primary specialties and a text line for sub-specialties. - Implemented nextSpecialtyFilter function to improve specialty selection logic in the Content component. - Updated search functionality to allow searching by both doctor name and specialty. - Added new specialties to specialties.json for better coverage. - Created sync-specialties script to synchronize specialties data with the backend during build. - Added tests for new components and helper functions to ensure functionality and reliability.
This commit is contained in:
+41
-8
@@ -267,20 +267,53 @@ export function clearDoctorClinicParams(router, slug) {
|
||||
const newUrl = `/clinic/${clinicslug}`;
|
||||
router.push(newUrl)
|
||||
}
|
||||
/** گزینهٔ «کل گروه» در فهرست زیرتخصصها؛ با id تهی، چون حالتش نبودِ زیرتخصص است. */
|
||||
export const allOfCategoryOption = (category) => ({
|
||||
id: null,
|
||||
name: `همه تخصصهای ${category.name}`,
|
||||
isAll: true,
|
||||
});
|
||||
|
||||
/**
|
||||
* فیلتر بعدی وقتی کاربر گروه یا زیرتخصص را عوض میکند.
|
||||
*
|
||||
* تابع خالص است چون همین قاعده بود که شکسته بود: انتخاب یک گروه بیصدا اولین
|
||||
* زیرتخصصش را مینشاند و جستجوی کاربر را تنگ میکرد. داخل کامپوننت، تستپذیر نبود.
|
||||
*/
|
||||
export const nextSpecialtyFilter = (filter, name, value) => {
|
||||
const next = { ...filter, [name]: value };
|
||||
|
||||
// گروه یعنی کل گروه، نه اولین فرزندش.
|
||||
if (name === "category") next.specialty = null;
|
||||
|
||||
// «همه تخصصهای X» زیرتخصص واقعی نیست؛ حالتش همان نبودِ زیرتخصص است. اگر خودش
|
||||
// در فیلتر مینشست، نامش در URL میرفت و چون در specialties.json نیست، با رفرش
|
||||
// یا اشتراک لینک بازسازی نمیشد.
|
||||
if (name === "specialty" && value?.isAll) next.specialty = null;
|
||||
|
||||
return next;
|
||||
};
|
||||
|
||||
export const filterList = (data) => {
|
||||
const parentList = specialties.filter((item) => !item.parent_id);
|
||||
const childrenList = specialties.filter((item) => item.parent_id);
|
||||
|
||||
const filteredChildrenList =
|
||||
data && data.category
|
||||
? childrenList.filter(
|
||||
(item) => String(item.parent_id) === String(data.category.id)
|
||||
)
|
||||
: [];
|
||||
if (!data?.category) {
|
||||
return { parentList, childrenList: [] };
|
||||
}
|
||||
|
||||
const ofCategory = childrenList.filter(
|
||||
(item) => String(item.parent_id) === String(data.category.id)
|
||||
);
|
||||
|
||||
// «همه» اول فهرست مینشیند تا برگشت از یک زیرتخصص به کل گروه ممکن باشد.
|
||||
// خودش وارد فیلتر نمیشود؛ انتخابش یعنی specialty تهی، و آنوقت QueryForDoctorsReq
|
||||
// به category.id برمیگردد که بکاند به همهٔ زیرشاخهها گسترشش میدهد.
|
||||
return {
|
||||
parentList: parentList,
|
||||
childrenList: filteredChildrenList,
|
||||
parentList,
|
||||
childrenList: ofCategory.length
|
||||
? [allOfCategoryOption(data.category), ...ofCategory]
|
||||
: [],
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import specialtiesData from "@/data/specialties.json";
|
||||
import {
|
||||
QueryForDoctorsFilter,
|
||||
QueryForDoctorsReq,
|
||||
filterList,
|
||||
nextSpecialtyFilter,
|
||||
} from "@/helper";
|
||||
|
||||
const byName = (name) => specialtiesData.find((s) => s.name === name);
|
||||
|
||||
const GENERAL_SURGERY = byName("جراحی عمومی");
|
||||
|
||||
describe("filterList — گزینهٔ «همه تخصصهای X»", () => {
|
||||
it("بدون گروه، فهرست زیرتخصص خالی است", () => {
|
||||
expect(filterList({}).childrenList).toEqual([]);
|
||||
expect(filterList(null).childrenList).toEqual([]);
|
||||
});
|
||||
|
||||
it("با گروه، «همه» اولین گزینه است و id گروه را ندارد", () => {
|
||||
const { childrenList } = filterList({ category: GENERAL_SURGERY });
|
||||
|
||||
expect(childrenList[0]).toMatchObject({
|
||||
id: null,
|
||||
isAll: true,
|
||||
name: `همه تخصصهای ${GENERAL_SURGERY.name}`,
|
||||
});
|
||||
});
|
||||
|
||||
it("زیرتخصصهای واقعی بعد از «همه» میآیند", () => {
|
||||
const { childrenList } = filterList({ category: GENERAL_SURGERY });
|
||||
const real = childrenList.slice(1);
|
||||
|
||||
expect(real.length).toBeGreaterThan(0);
|
||||
expect(
|
||||
real.every((s) => String(s.parent_id) === String(GENERAL_SURGERY.id))
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("زیرتخصصهای تازهٔ همگامشده هم در فهرست هستند", () => {
|
||||
// این پنج تا در اسنپشات دستیِ قبلی نبودند؛ اگر همگامسازی برگردد، اینجا قرمز میشود.
|
||||
const names = filterList({ category: GENERAL_SURGERY }).childrenList.map(
|
||||
(s) => s.name
|
||||
);
|
||||
|
||||
expect(names).toContain("جراح گوارش");
|
||||
expect(names).toContain("جراح تیروئید");
|
||||
});
|
||||
|
||||
it("گروهِ بدون زیرشاخه گزینهٔ «همه» نمیگیرد", () => {
|
||||
const leafRoot = specialtiesData.find(
|
||||
(s) =>
|
||||
!s.parent_id &&
|
||||
!specialtiesData.some((c) => String(c.parent_id) === String(s.id))
|
||||
);
|
||||
|
||||
expect(filterList({ category: leafRoot }).childrenList).toEqual([]);
|
||||
});
|
||||
|
||||
it("فهرست گروهها فقط ریشههاست", () => {
|
||||
expect(filterList({}).parentList.every((s) => !s.parent_id)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("nextSpecialtyFilter — قاعدهٔ انتخاب", () => {
|
||||
it("انتخاب گروه، زیرتخصص را تهی میگذارد نه اولین فرزند", () => {
|
||||
const firstChild = filterList({ category: GENERAL_SURGERY }).childrenList[1];
|
||||
const next = nextSpecialtyFilter({}, "category", GENERAL_SURGERY);
|
||||
|
||||
expect(next.category).toBe(GENERAL_SURGERY);
|
||||
expect(next.specialty).toBeNull();
|
||||
expect(next.specialty).not.toEqual(firstChild);
|
||||
});
|
||||
|
||||
it("عوض کردن گروه، زیرتخصصِ گروه قبلی را پاک میکند", () => {
|
||||
const child = filterList({ category: GENERAL_SURGERY }).childrenList[1];
|
||||
const other = specialtiesData.find(
|
||||
(s) => !s.parent_id && s.id !== GENERAL_SURGERY.id
|
||||
);
|
||||
|
||||
const next = nextSpecialtyFilter(
|
||||
{ category: GENERAL_SURGERY, specialty: child },
|
||||
"category",
|
||||
other
|
||||
);
|
||||
|
||||
expect(next.specialty).toBeNull();
|
||||
});
|
||||
|
||||
it("انتخاب «همه» زیرتخصص را تهی میکند", () => {
|
||||
const all = filterList({ category: GENERAL_SURGERY }).childrenList[0];
|
||||
const next = nextSpecialtyFilter(
|
||||
{ category: GENERAL_SURGERY },
|
||||
"specialty",
|
||||
all
|
||||
);
|
||||
|
||||
expect(next.specialty).toBeNull();
|
||||
});
|
||||
|
||||
it("انتخاب یک زیرتخصص واقعی همان را مینشاند", () => {
|
||||
const child = filterList({ category: GENERAL_SURGERY }).childrenList[1];
|
||||
const next = nextSpecialtyFilter(
|
||||
{ category: GENERAL_SURGERY },
|
||||
"specialty",
|
||||
child
|
||||
);
|
||||
|
||||
expect(next.specialty).toBe(child);
|
||||
});
|
||||
|
||||
it("پاک کردن گروه هر دو را تهی میکند", () => {
|
||||
const next = nextSpecialtyFilter(
|
||||
{ category: GENERAL_SURGERY, specialty: { id: 1 } },
|
||||
"category",
|
||||
null
|
||||
);
|
||||
|
||||
expect(next.category).toBeNull();
|
||||
expect(next.specialty).toBeNull();
|
||||
});
|
||||
|
||||
it("فیلترهای دیگر دستنخورده میمانند", () => {
|
||||
const next = nextSpecialtyFilter(
|
||||
{ city: { id: 7 }, gender: "زن" },
|
||||
"category",
|
||||
GENERAL_SURGERY
|
||||
);
|
||||
|
||||
expect(next.city).toEqual({ id: 7 });
|
||||
expect(next.gender).toBe("زن");
|
||||
});
|
||||
});
|
||||
|
||||
describe("انتخاب کل گروه — پارامترها", () => {
|
||||
it("بدون زیرتخصص، درخواست با شناسهٔ گروه میرود", () => {
|
||||
// بکاند specialty_id را به همهٔ زیرشاخهها گسترش میدهد.
|
||||
const params = QueryForDoctorsReq({ category: GENERAL_SURGERY, specialty: null });
|
||||
|
||||
expect(params.specialty_id).toBe(GENERAL_SURGERY.id);
|
||||
});
|
||||
|
||||
it("با زیرتخصص، همان زیرتخصص بر گروه مقدم است", () => {
|
||||
const child = filterList({ category: GENERAL_SURGERY }).childrenList[1];
|
||||
const params = QueryForDoctorsReq({ category: GENERAL_SURGERY, specialty: child });
|
||||
|
||||
expect(params.specialty_id).toBe(child.id);
|
||||
});
|
||||
|
||||
it("URL نام گروه را میگیرد، نه عنوان مصنوعیِ «همه …»", () => {
|
||||
// عنوان «همه تخصصهای …» در specialties.json نیست؛ اگر در URL مینشست،
|
||||
// با رفرش یا اشتراک لینک بازسازی نمیشد.
|
||||
const query = QueryForDoctorsFilter({ category: GENERAL_SURGERY, specialty: null });
|
||||
|
||||
expect(query.specialty).toBe(GENERAL_SURGERY.name);
|
||||
expect(specialtiesData.some((s) => s.name === query.specialty)).toBe(true);
|
||||
});
|
||||
|
||||
it("نامِ رفته در URL همیشه در فایل تخصصها پیدا میشود", () => {
|
||||
const child = filterList({ category: GENERAL_SURGERY }).childrenList[1];
|
||||
const query = QueryForDoctorsFilter({ category: GENERAL_SURGERY, specialty: child });
|
||||
|
||||
expect(specialtiesData.some((s) => s.name === query.specialty)).toBe(true);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user