Files
clinicpro/assets/admin/lib/tour/tour.test.ts
T
hamed ecdefa3c24 feat(tour): add onboarding tours for various admin pages
- Integrated TourButton component into SettingsMenuPage, SkillsPage, SmsWalletPage, StaffPage, StaffSessionDetailPage, StaffTreatmentSessionsPage, SubscriptionPage, TagsSettingsPage, TreatmentCasesPage to enhance user onboarding experience.
- Created new tour definitions for appointments, clinics, staff management, financial management, and patient management, ensuring comprehensive guidance for users navigating the admin panel.
- Updated documentation to reflect the addition of tours and their implementation details.
2026-08-10 10:10:20 +03:30

109 lines
4.4 KiB
TypeScript

import { describe, it, expect, afterEach } from 'vitest';
import { anchorSelector, resolveSteps } from './resolveSteps';
import { getTour, TOURS } from './registry';
import type { TourStep } from './types';
const STEPS: TourStep[] = [
{ anchor: 'one', title: 'یک', body: '…' },
{ anchor: 'two', title: 'دو', body: '…' },
{ anchor: 'three', title: 'سه', body: '…' },
];
function mount(...anchors: string[]) {
document.body.innerHTML = anchors.map((a) => `<div data-tour="${a}"></div>`).join('');
}
afterEach(() => {
document.body.innerHTML = '';
});
describe('resolveSteps', () => {
it('استپ‌های موجود را با حفظ ترتیب نگه می‌دارد', () => {
mount('three', 'one');
expect(resolveSteps(STEPS).map((s) => s.anchor)).toEqual(['one', 'three']);
});
it('وقتی هیچ المانی نیست، خروجی خالی است', () => {
expect(resolveSteps(STEPS)).toEqual([]);
});
it('آرایهٔ خالی خروجی خالی می‌دهد', () => {
mount('one');
expect(resolveSteps([])).toEqual([]);
});
it('سلکتور از روی anchor ساخته می‌شود', () => {
expect(anchorSelector('appointments-stats')).toBe('[data-tour="appointments-stats"]');
});
});
describe('registry', () => {
it('برای شناسهٔ ناشناس یا خالی null می‌دهد', () => {
expect(getTour('does-not-exist')).toBeNull();
expect(getTour(undefined)).toBeNull();
});
it('تور نوبت‌ها ثبت شده است', () => {
expect(getTour('appointments')?.id).toBe('appointments');
});
it('هر تور نسخهٔ معتبر و anchorهای بدون تکرار دارد', () => {
for (const [id, tour] of Object.entries(TOURS)) {
expect(tour.id).toBe(id);
expect(tour.version).toBeGreaterThanOrEqual(1);
expect(tour.steps.length).toBeGreaterThan(0);
const anchors = tour.steps.map((s) => s.anchor);
expect(new Set(anchors).size).toBe(anchors.length);
}
});
it('متن هر استپ فارسی و غیرخالی است', () => {
for (const tour of Object.values(TOURS)) {
for (const step of tour.steps) {
expect(step.title.trim().length).toBeGreaterThan(2);
expect(step.body.trim().length).toBeGreaterThan(10);
// متن راهنما برای کاربر فارسی‌زبان است؛ استپِ انگلیسی‌مانده یعنی جا افتاده.
expect(step.title).toMatch(/[؀-ۿ]/);
expect(step.body).toMatch(/[؀-ۿ]/);
}
}
});
});
/**
* لنگرِ بی‌المان بی‌صدا حذف می‌شود، پس تورِ خراب هیچ خطایی نمی‌دهد و فقط استپش را
* از دست می‌دهد. این تست همان سکوت را می‌شکند: هر anchor باید در کدِ صفحات وجود
* داشته باشد.
*/
describe('انطباق لنگرها با کد صفحات', () => {
it('هیچ استپی به data-tour ناموجود اشاره نمی‌کند', async () => {
// خواندن مستقیم فایل‌ها، نه import.meta.glob: آن فقط زیر Vite کار می‌کند و
// ts-loaderِ Encore همین فایل را هم type-check می‌کند و build را قرمز می‌کرد.
const { readdirSync, readFileSync } = await import('node:fs');
const { join } = await import('node:path');
const walk = (dir: string): string[] =>
readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
const path = join(dir, entry.name);
if (entry.isDirectory()) return walk(path);
return entry.name.endsWith('.tsx') && !entry.name.includes('.test.') ? [path] : [];
});
const sources = walk('assets/admin').map((path) => readFileSync(path, 'utf8'));
const declared = new Set(
sources.flatMap((src) => [...src.matchAll(/data-tour="([a-z0-9-]+)"/g)].map((m) => m[1])),
);
expect(declared.size).toBeGreaterThan(0);
const orphans = Object.values(TOURS).flatMap((tour) =>
tour.steps.filter((s) => !declared.has(s.anchor)).map((s) => `${tour.id}:${s.anchor}`),
);
expect(orphans).toEqual([]);
});
});