fix: TypeScript errors, test quality, and admin panel correctness
All checks were successful
build-and-release / build (push) Successful in 1m19s
All checks were successful
build-and-release / build (push) Successful in 1m19s
- tsconfig.json: add @types/node for playwright/test configs - lib/api.ts: handle both array and wrapped packages response from tracecoin endpoint - lib/server/gateway.ts: use import.meta.env instead of process.env - routes/admin/pricing.tsx: fix packages() usage after api.ts fix - routes/admin/roles/*.tsx: fix Set<string> types and module filter types - routes/admin/users/[id]/edit.tsx: add phone? to local User type - routes/admin/users/details/[id].tsx: fix roleFilter param type narrowing - components/admin/DashboardDesignPreview.tsx: fix budget→budget, responses→responseTag - vite.config.ts: type proxy callbacks as any - test/setup.ts: fix IntersectionObserver mock, add vitest/globals reference - tests/e2e: fix PNG type mismatches, add as any for mock overrides - tests/vitest: fix global.createElement mock type, vitest globals reference - tests/tsconfig.json: test-specific tsconfig with node types Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
87fe3c0c59
commit
bed7035ec5
23 changed files with 91 additions and 30 deletions
29
package-lock.json
generated
29
package-lock.json
generated
|
|
@ -26,6 +26,8 @@
|
||||||
"@storybook/addon-docs": "^10.3.3",
|
"@storybook/addon-docs": "^10.3.3",
|
||||||
"@storybook/addon-vitest": "^10.3.3",
|
"@storybook/addon-vitest": "^10.3.3",
|
||||||
"@testing-library/jest-dom": "^6.6.3",
|
"@testing-library/jest-dom": "^6.6.3",
|
||||||
|
"@types/node": "^26.1.1",
|
||||||
|
"@types/pngjs": "^6.0.5",
|
||||||
"@vitest/browser-playwright": "^4.1.1",
|
"@vitest/browser-playwright": "^4.1.1",
|
||||||
"@vitest/coverage-v8": "^4.1.1",
|
"@vitest/coverage-v8": "^4.1.1",
|
||||||
"jsdom": "^25.0.1",
|
"jsdom": "^25.0.1",
|
||||||
|
|
@ -3517,6 +3519,33 @@
|
||||||
"@types/braces": "*"
|
"@types/braces": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/node": {
|
||||||
|
"version": "26.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.1.tgz",
|
||||||
|
"integrity": "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"undici-types": "~8.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/node/node_modules/undici-types": {
|
||||||
|
"version": "8.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz",
|
||||||
|
"integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@types/pngjs": {
|
||||||
|
"version": "6.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/pngjs/-/pngjs-6.0.5.tgz",
|
||||||
|
"integrity": "sha512-0k5eKfrA83JOZPppLtS2C7OUtyNAl2wKNxfyYl9Q5g9lPkgBl/9hNyAu6HuEH2J4XmIv2znEpkDd0SaZVxW6iQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/react": {
|
"node_modules/@types/react": {
|
||||||
"version": "19.2.14",
|
"version": "19.2.14",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,8 @@
|
||||||
"@storybook/addon-docs": "^10.3.3",
|
"@storybook/addon-docs": "^10.3.3",
|
||||||
"@storybook/addon-vitest": "^10.3.3",
|
"@storybook/addon-vitest": "^10.3.3",
|
||||||
"@testing-library/jest-dom": "^6.6.3",
|
"@testing-library/jest-dom": "^6.6.3",
|
||||||
|
"@types/node": "^26.1.1",
|
||||||
|
"@types/pngjs": "^6.0.5",
|
||||||
"@vitest/browser-playwright": "^4.1.1",
|
"@vitest/browser-playwright": "^4.1.1",
|
||||||
"@vitest/coverage-v8": "^4.1.1",
|
"@vitest/coverage-v8": "^4.1.1",
|
||||||
"jsdom": "^25.0.1",
|
"jsdom": "^25.0.1",
|
||||||
|
|
|
||||||
|
|
@ -2516,9 +2516,9 @@ export default function DashboardDesignPreview(props: {
|
||||||
return (
|
return (
|
||||||
<div style="display:grid;grid-template-columns:2fr 1fr 1fr 1fr;gap:8px;padding:8px;border:1px solid #E5E7EB;border-radius:8px;background:#F9FAFB">
|
<div style="display:grid;grid-template-columns:2fr 1fr 1fr 1fr;gap:8px;padding:8px;border:1px solid #E5E7EB;border-radius:8px;background:#F9FAFB">
|
||||||
<span style="font-size:12px;font-weight:700;color:#111827">{row.title}</span>
|
<span style="font-size:12px;font-weight:700;color:#111827">{row.title}</span>
|
||||||
<span style="font-size:12px;color:#6B7280">{row.amount}</span>
|
<span style="font-size:12px;color:#6B7280">{row.budget}</span>
|
||||||
<span style={`display:inline-flex;align-items:center;justify-content:center;height:22px;border-radius:999px;background:${chip.bg};color:${chip.c};font-size:11px;font-weight:700;text-transform:uppercase`}>{row.status}</span>
|
<span style={`display:inline-flex;align-items:center;justify-content:center;height:22px;border-radius:999px;background:${chip.bg};color:${chip.c};font-size:11px;font-weight:700;text-transform:uppercase`}>{row.status}</span>
|
||||||
<span style="font-size:12px;color:#374151">{row.responses} responses</span>
|
<span style="font-size:12px;color:#374151">{row.responseTag}</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}}</For>
|
}}</For>
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ export default function ProfessionAdminListPage(props: {
|
||||||
emptyLabel: string;
|
emptyLabel: string;
|
||||||
viewHref: (id: string) => string;
|
viewHref: (id: string) => string;
|
||||||
nameField?: string;
|
nameField?: string;
|
||||||
|
createHref?: string;
|
||||||
}) {
|
}) {
|
||||||
const nameField = props.nameField || 'first_name';
|
const nameField = props.nameField || 'first_name';
|
||||||
const [items] = createResource(() => props.endpoint, fetchProfessionList);
|
const [items] = createResource(() => props.endpoint, fetchProfessionList);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
import { isExternalIdentity, pickManagementLoginError } from './admin-auth.ts';
|
import { isExternalIdentity, pickManagementLoginError } from './admin-auth';
|
||||||
|
|
||||||
describe('admin-auth', () => {
|
describe('admin-auth', () => {
|
||||||
it('pickManagementLoginError prefers wrong-portal message', () => {
|
it('pickManagementLoginError prefers wrong-portal message', () => {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
import { normalizeAllowedModules } from './module-access.ts';
|
import { normalizeAllowedModules } from './module-access';
|
||||||
|
|
||||||
describe('module-access', () => {
|
describe('module-access', () => {
|
||||||
it('normalizeAllowedModules reads explicit module arrays', () => {
|
it('normalizeAllowedModules reads explicit module arrays', () => {
|
||||||
|
|
|
||||||
|
|
@ -231,8 +231,10 @@ export interface UpdatePackageInput {
|
||||||
features?: any;
|
features?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function listAdminPackages() {
|
export async function listAdminPackages(): Promise<AdminPackage[]> {
|
||||||
return api.get<{ packages: AdminPackage[] } | AdminPackage[]>(`/api/admin/tracecoin-packages`);
|
const res = await api.get<{ packages: AdminPackage[] } | AdminPackage[]>(`/api/admin/tracecoin-packages`);
|
||||||
|
const raw = res.data;
|
||||||
|
return Array.isArray(raw) ? raw : (raw as { packages: AdminPackage[] }).packages ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createAdminPackage(input: CreatePackageInput) {
|
export async function createAdminPackage(input: CreatePackageInput) {
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,7 @@ export async function saveRuntimeConfig(type: RuntimeRecordType, key: string, pa
|
||||||
role: '/api/runtime-config', // Storing the "role config wrapper" as runtime_config
|
role: '/api/runtime-config', // Storing the "role config wrapper" as runtime_config
|
||||||
dashboard: '/api/admin/dashboard-config',
|
dashboard: '/api/admin/dashboard-config',
|
||||||
onboarding: '/api/admin/onboarding-config',
|
onboarding: '/api/admin/onboarding-config',
|
||||||
|
knowledge_base: '/api/admin/knowledge-base',
|
||||||
};
|
};
|
||||||
|
|
||||||
const bodyPayload: Record<string, unknown> = {
|
const bodyPayload: Record<string, unknown> = {
|
||||||
|
|
@ -119,6 +120,7 @@ export async function listRuntimeConfigs<T>(type: RuntimeRecordType): Promise<Ar
|
||||||
role: '/api/admin/roles',
|
role: '/api/admin/roles',
|
||||||
dashboard: '/api/admin/dashboard-config',
|
dashboard: '/api/admin/dashboard-config',
|
||||||
onboarding: '/api/admin/onboarding-config',
|
onboarding: '/api/admin/onboarding-config',
|
||||||
|
knowledge_base: '/api/admin/knowledge-base',
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
@ -147,6 +149,7 @@ export async function getRuntimeConfig<T>(type: RuntimeRecordType, roleId: strin
|
||||||
role: `/api/admin/roles/${roleId}`, // Assume roleId is key if testing
|
role: `/api/admin/roles/${roleId}`, // Assume roleId is key if testing
|
||||||
dashboard: `/api/admin/dashboard-config/${roleId}?audience=EXTERNAL`,
|
dashboard: `/api/admin/dashboard-config/${roleId}?audience=EXTERNAL`,
|
||||||
onboarding: `/api/admin/onboarding-config/${roleId}`,
|
onboarding: `/api/admin/onboarding-config/${roleId}`,
|
||||||
|
knowledge_base: `/api/admin/knowledge-base/${roleId}`,
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
// Server-side helper: all backend calls go through the Rust gateway
|
// Server-side helper: all backend calls go through the Rust gateway
|
||||||
const GATEWAY_URL = (process.env.GATEWAY_URL || 'http://localhost:9100').replace(/\/+$/, '');
|
const GATEWAY_URL = (import.meta.env.VITE_GATEWAY_URL || 'http://localhost:9100').replace(/\/+$/, '');
|
||||||
|
|
||||||
export function gatewayUrl(path: string): string {
|
export function gatewayUrl(path: string): string {
|
||||||
const normalized = path.startsWith('/') ? path : `/${path}`;
|
const normalized = path.startsWith('/') ? path : `/${path}`;
|
||||||
|
|
|
||||||
|
|
@ -153,6 +153,7 @@ export default function CouponPage() {
|
||||||
value: coupon.value,
|
value: coupon.value,
|
||||||
min_order_amount: coupon.min_order_amount || 0,
|
min_order_amount: coupon.min_order_amount || 0,
|
||||||
max_uses: coupon.usage_limit != null ? String(coupon.usage_limit) : "",
|
max_uses: coupon.usage_limit != null ? String(coupon.usage_limit) : "",
|
||||||
|
applies_to: 'ALL' as "ALL" | "ROLE",
|
||||||
role_keys: Array.isArray(coupon.role_keys) ? coupon.role_keys : [],
|
role_keys: Array.isArray(coupon.role_keys) ? coupon.role_keys : [],
|
||||||
});
|
});
|
||||||
setActiveTab("create");
|
setActiveTab("create");
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { createResource, createSignal, For, onMount, Show } from "solid-js";
|
||||||
|
|
||||||
const API = "";
|
const API = "";
|
||||||
|
|
||||||
type Role = { id: string; name: string };
|
type Role = { id: string; name: string; key: string };
|
||||||
type Dept = { id: string; name: string };
|
type Dept = { id: string; name: string };
|
||||||
type Desig = { id: string; name: string };
|
type Desig = { id: string; name: string };
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -72,8 +72,18 @@ const SORT_LABELS: Record<SortMode, string> = {
|
||||||
coins_desc: 'TraceCoins ↓',
|
coins_desc: 'TraceCoins ↓',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const API = '';
|
||||||
|
|
||||||
|
function authHeaders(): Record<string, string> {
|
||||||
|
const token = typeof sessionStorage !== 'undefined'
|
||||||
|
? sessionStorage.getItem('nxtgauge_admin_access_token') || ''
|
||||||
|
: '';
|
||||||
|
return token ? { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' } : { 'Content-Type': 'application/json' };
|
||||||
|
}
|
||||||
|
|
||||||
export default function PricingPage() {
|
export default function PricingPage() {
|
||||||
const [rows, setRows] = createSignal<Package[]>([]);
|
const [packages, { refetch }] = createResource(listAdminPackages);
|
||||||
|
const [rows, setRows] = createSignal<AdminPackage[]>([]);
|
||||||
const [loading, setLoading] = createSignal(true);
|
const [loading, setLoading] = createSignal(true);
|
||||||
const [loadError, setLoadError] = createSignal("");
|
const [loadError, setLoadError] = createSignal("");
|
||||||
const [view, setView] = createSignal<"packages" | "create" | "ai_packages">("packages");
|
const [view, setView] = createSignal<"packages" | "create" | "ai_packages">("packages");
|
||||||
|
|
@ -256,7 +266,7 @@ export default function PricingPage() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onMount(() => void load());
|
onMount(() => { void load(); void loadAiPackages(); });
|
||||||
|
|
||||||
const filteredRows = createMemo(() => {
|
const filteredRows = createMemo(() => {
|
||||||
let r = packages() ?? [];
|
let r = packages() ?? [];
|
||||||
|
|
@ -924,8 +934,7 @@ export default function PricingPage() {
|
||||||
|
|
||||||
{/* AI Credit Packages Tab */}
|
{/* AI Credit Packages Tab */}
|
||||||
<Show when={view() === "ai_packages"}>
|
<Show when={view() === "ai_packages"}>
|
||||||
<div style="position:relative;margin-top:1.5rem;margin-left:-24px;margin-right:-24px;border-radius:0;border-left:none;border-right:none;overflow:visible;border-top:1px solid #E5E7EB;border-bottom:1px solid #E5E7EB;background:white;box-shadow:0 1px 3px rgba(0,0,0,0.06)"
|
<div style="position:relative;margin-top:1.5rem;margin-left:-24px;margin-right:-24px;border-radius:0;border-left:none;border-right:none;overflow:visible;border-top:1px solid #E5E7EB;border-bottom:1px solid #E5E7EB;background:white;box-shadow:0 1px 3px rgba(0,0,0,0.06)">
|
||||||
onMount={() => loadAiPackages()}>
|
|
||||||
<div style="display:flex;align-items:center;justify-content:space-between;gap:8px;padding:14px 20px;border-bottom:1px solid #F3F4F6;flex-wrap:wrap">
|
<div style="display:flex;align-items:center;justify-content:space-between;gap:8px;padding:14px 20px;border-bottom:1px solid #F3F4F6;flex-wrap:wrap">
|
||||||
<h2 style="margin:0;font-size:18px;font-weight:700;color:#111827">AI Credit Packages</h2>
|
<h2 style="margin:0;font-size:18px;font-weight:700;color:#111827">AI Credit Packages</h2>
|
||||||
<button
|
<button
|
||||||
|
|
|
||||||
|
|
@ -158,9 +158,9 @@ export default function EditInternalRolePage() {
|
||||||
|
|
||||||
const selectAll = () => {
|
const selectAll = () => {
|
||||||
const all = STATIC_MODULES.flatMap((m) => ACTIONS.map((a) => makeKey(m, a)));
|
const all = STATIC_MODULES.flatMap((m) => ACTIONS.map((a) => makeKey(m, a)));
|
||||||
setSelectedKeys(new Set(all));
|
setSelectedKeys(new Set(all as string[]));
|
||||||
};
|
};
|
||||||
const deselectAll = () => setSelectedKeys(new Set());
|
const deselectAll = () => setSelectedKeys(new Set<string>());
|
||||||
const allSelected = () => {
|
const allSelected = () => {
|
||||||
const total = STATIC_MODULES.length * ACTIONS.length;
|
const total = STATIC_MODULES.length * ACTIONS.length;
|
||||||
return selectedKeys().size === total;
|
return selectedKeys().size === total;
|
||||||
|
|
|
||||||
|
|
@ -155,9 +155,9 @@ export default function CreateInternalRolePage() {
|
||||||
// Select all / deselect all
|
// Select all / deselect all
|
||||||
const selectAll = () => {
|
const selectAll = () => {
|
||||||
const all = (permissions() ?? STATIC_PERMISSIONS).map((p) => p.key);
|
const all = (permissions() ?? STATIC_PERMISSIONS).map((p) => p.key);
|
||||||
setSelectedKeys(new Set(all));
|
setSelectedKeys(new Set(all as string[]));
|
||||||
};
|
};
|
||||||
const deselectAll = () => setSelectedKeys(new Set());
|
const deselectAll = () => setSelectedKeys(new Set<string>());
|
||||||
const allSelected = () => {
|
const allSelected = () => {
|
||||||
const src = permissions() ?? STATIC_PERMISSIONS;
|
const src = permissions() ?? STATIC_PERMISSIONS;
|
||||||
return src.length > 0 && src.every((p) => selectedKeys().has(p.key));
|
return src.length > 0 && src.every((p) => selectedKeys().has(p.key));
|
||||||
|
|
|
||||||
|
|
@ -333,8 +333,8 @@ export default function RoleManagementPage() {
|
||||||
const fromApi = Array.from(
|
const fromApi = Array.from(
|
||||||
new Set((permissions() ?? []).map((p) => String(p.module || "").trim()).filter(Boolean))
|
new Set((permissions() ?? []).map((p) => String(p.module || "").trim()).filter(Boolean))
|
||||||
);
|
);
|
||||||
const ordered = [...STATIC_MODULES.filter((m) => fromApi.includes(m))];
|
const ordered = [...STATIC_MODULES.filter((m) => fromApi.includes(m as string))];
|
||||||
const extras = fromApi.filter((m) => !ordered.includes(m)).sort();
|
const extras = fromApi.filter((m) => !(ordered as string[]).includes(m)).sort();
|
||||||
return [...ordered, ...extras];
|
return [...ordered, ...extras];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -399,7 +399,7 @@ export default function RoleManagementPage() {
|
||||||
setDescription("");
|
setDescription("");
|
||||||
setDepartmentId("");
|
setDepartmentId("");
|
||||||
setStatus("ACTIVE");
|
setStatus("ACTIVE");
|
||||||
setSelectedPermissions(new Set());
|
setSelectedPermissions(new Set<string>());
|
||||||
setFormTab("general");
|
setFormTab("general");
|
||||||
setFormError("");
|
setFormError("");
|
||||||
};
|
};
|
||||||
|
|
@ -416,7 +416,7 @@ export default function RoleManagementPage() {
|
||||||
setDescription(row.description || "");
|
setDescription(row.description || "");
|
||||||
setDepartmentId(row.departmentId || "");
|
setDepartmentId(row.departmentId || "");
|
||||||
setStatus(row.status === "INACTIVE" ? "INACTIVE" : "ACTIVE");
|
setStatus(row.status === "INACTIVE" ? "INACTIVE" : "ACTIVE");
|
||||||
setSelectedPermissions(new Set());
|
setSelectedPermissions(new Set<string>());
|
||||||
setFormTab("general");
|
setFormTab("general");
|
||||||
setView("form");
|
setView("form");
|
||||||
setOpenMenuId(null);
|
setOpenMenuId(null);
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ type User = {
|
||||||
name?: string;
|
name?: string;
|
||||||
full_name?: string;
|
full_name?: string;
|
||||||
email: string;
|
email: string;
|
||||||
|
phone?: string;
|
||||||
roleId?: string;
|
roleId?: string;
|
||||||
role_id?: string;
|
role_id?: string;
|
||||||
role?: Role;
|
role?: Role;
|
||||||
|
|
|
||||||
|
|
@ -140,7 +140,7 @@ export default function UserDetailPage() {
|
||||||
|
|
||||||
const user = createMemo(() => bundle()?.user || null);
|
const user = createMemo(() => bundle()?.user || null);
|
||||||
const roleProfiles = createMemo(() => bundle()?.roleProfiles || []);
|
const roleProfiles = createMemo(() => bundle()?.roleProfiles || []);
|
||||||
const roleFilter = createMemo(() => searchParams.roleFilter || '');
|
const roleFilter = createMemo(() => (typeof searchParams.roleFilter === 'string' ? searchParams.roleFilter : Array.isArray(searchParams.roleFilter) ? searchParams.roleFilter[0] : '') || '');
|
||||||
const displayName = createMemo(() => user()?.name || user()?.full_name || 'Unknown User');
|
const displayName = createMemo(() => user()?.name || user()?.full_name || 'Unknown User');
|
||||||
const userCreatedAt = createMemo(() => user()?.createdAt || user()?.created_at || '');
|
const userCreatedAt = createMemo(() => user()?.createdAt || user()?.created_at || '');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
/// <reference types="vitest/globals" />
|
||||||
import { beforeAll, afterEach, afterAll } from 'vitest';
|
import { beforeAll, afterEach, afterAll } from 'vitest';
|
||||||
import { setupServer } from 'msw/node';
|
import { setupServer } from 'msw/node';
|
||||||
import { http, HttpResponse } from 'msw';
|
import { http, HttpResponse } from 'msw';
|
||||||
|
|
@ -46,12 +47,15 @@ Object.defineProperty(window, 'matchMedia', {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Mock IntersectionObserver
|
// Mock IntersectionObserver
|
||||||
global.IntersectionObserver = class IntersectionObserver {
|
(global as any).IntersectionObserver = class IntersectionObserver {
|
||||||
|
root = null;
|
||||||
|
rootMargin = '';
|
||||||
|
thresholds: number[] = [];
|
||||||
constructor() {}
|
constructor() {}
|
||||||
disconnect() {}
|
disconnect() {}
|
||||||
observe() {}
|
observe() {}
|
||||||
|
unobserve() {}
|
||||||
takeRecords() { return []; }
|
takeRecords() { return []; }
|
||||||
trigger() {}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Mock ResizeObserver
|
// Mock ResizeObserver
|
||||||
|
|
|
||||||
|
|
@ -130,17 +130,17 @@ async function comparePng(actualPath: string, expectedPath: string, diffPath: st
|
||||||
if (actualPng.height > expectedPng.height) {
|
if (actualPng.height > expectedPng.height) {
|
||||||
const cropped = new PNG({ width: actualPng.width, height: expectedPng.height });
|
const cropped = new PNG({ width: actualPng.width, height: expectedPng.height });
|
||||||
PNG.bitblt(actualPng, cropped, 0, 0, actualPng.width, expectedPng.height, 0, 0);
|
PNG.bitblt(actualPng, cropped, 0, 0, actualPng.width, expectedPng.height, 0, 0);
|
||||||
actualPng = cropped;
|
actualPng = cropped as any;
|
||||||
} else {
|
} else {
|
||||||
const padded = new PNG({ width: actualPng.width, height: expectedPng.height });
|
const padded = new PNG({ width: actualPng.width, height: expectedPng.height });
|
||||||
PNG.bitblt(actualPng, padded, 0, 0, actualPng.width, actualPng.height, 0, 0);
|
PNG.bitblt(actualPng, padded, 0, 0, actualPng.width, actualPng.height, 0, 0);
|
||||||
actualPng = padded;
|
actualPng = padded as any;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we intentionally capture at a smaller viewport, resize the reference down for comparison.
|
// If we intentionally capture at a smaller viewport, resize the reference down for comparison.
|
||||||
if (actualPng.width !== expectedPng.width || actualPng.height !== expectedPng.height) {
|
if (actualPng.width !== expectedPng.width || actualPng.height !== expectedPng.height) {
|
||||||
expectedPng = resizePngNearest(expectedPng, actualPng.width, actualPng.height);
|
expectedPng = resizePngNearest(expectedPng, actualPng.width, actualPng.height) as any;
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(actualPng.width, `Width mismatch for ${path.basename(actualPath)}`).toBe(expectedPng.width);
|
expect(actualPng.width, `Width mismatch for ${path.basename(actualPath)}`).toBe(expectedPng.width);
|
||||||
|
|
|
||||||
8
tests/tsconfig.json
Normal file
8
tests/tsconfig.json
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"extends": "../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"types": ["vite/client", "node"],
|
||||||
|
"lib": ["ES2020", "DOM"]
|
||||||
|
},
|
||||||
|
"include": ["./**/*.ts", "./**/*.tsx"]
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
/// <reference types="vitest/globals" />
|
||||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||||
import { render, screen, fireEvent, waitFor } from "@solidjs/testing-library";
|
import { render, screen, fireEvent, waitFor } from "@solidjs/testing-library";
|
||||||
import { createSignal } from "solid-js";
|
import { createSignal } from "solid-js";
|
||||||
|
|
@ -158,7 +159,7 @@ describe("ProfessionAdminListPage", () => {
|
||||||
// Mock URL.createObjectURL and link.click
|
// Mock URL.createObjectURL and link.click
|
||||||
const mockClick = vi.fn();
|
const mockClick = vi.fn();
|
||||||
global.URL.createObjectURL = vi.fn(() => "blob:test");
|
global.URL.createObjectURL = vi.fn(() => "blob:test");
|
||||||
global.document.createElement = vi.fn(() => ({ click: mockClick, href: "" }));
|
global.document.createElement = vi.fn(() => ({ click: mockClick, href: "" })) as any;
|
||||||
|
|
||||||
render(() =>
|
render(() =>
|
||||||
ProfessionAdminListPage({
|
ProfessionAdminListPage({
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"types": ["vite/client"],
|
"types": ["vite/client", "node"],
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"paths": {
|
"paths": {
|
||||||
"~/*": ["./src/*"]
|
"~/*": ["./src/*"]
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,8 @@ export default defineConfig({
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
secure: false,
|
secure: false,
|
||||||
ws: true,
|
ws: true,
|
||||||
configureProxy: (proxy) => {
|
configureProxy: (proxy: any) => {
|
||||||
proxy.on("proxyReq", (proxyReq, req) => {
|
proxy.on("proxyReq", (proxyReq: any, req: any) => {
|
||||||
// Forward cookies and auth headers
|
// Forward cookies and auth headers
|
||||||
const cookie = req.headers.cookie;
|
const cookie = req.headers.cookie;
|
||||||
if (cookie) {
|
if (cookie) {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue