Commit 1: Create individual form components per route, remove generic AdminForm dependency
- Add per-route form components: UserRoleForm, UserForm, ProclamationForm (with category cascade), CategoryForm, FreqForm, LevelForm - Update all admin +page.svelte files to use AdminTable + AdminModal + their specific form component instead of AdminPage - Update profiles page to inline fields instead of using AdminForm - Add custom Proclamation server action with category fallback logic (categoryId falls back to parentCategoryId when no sub-category) - Remove cascade/were/fieldLabels logic from generic crud.ts - Remove outdated cascade/where tests from crud.spec.ts - Remove fieldLabels from AdminModelConfig type
This commit is contained in:
@@ -6,38 +6,44 @@ import { getModelConfig } from './registry';
|
||||
import { superValidate } from 'sveltekit-superforms/server';
|
||||
import { zod4 } from 'sveltekit-superforms/adapters';
|
||||
|
||||
vi.mock('$lib/db', () => ({
|
||||
prisma: {
|
||||
userRole: {
|
||||
findMany: vi.fn(),
|
||||
create: vi.fn(),
|
||||
update: vi.fn(),
|
||||
delete: vi.fn(),
|
||||
findUnique: vi.fn(),
|
||||
findFirst: vi.fn()
|
||||
vi.mock('$lib/db', () => {
|
||||
const mkdel = () => ({
|
||||
findMany: vi.fn(),
|
||||
create: vi.fn(),
|
||||
update: vi.fn(),
|
||||
delete: vi.fn(),
|
||||
findUnique: vi.fn(),
|
||||
findFirst: vi.fn()
|
||||
});
|
||||
return {
|
||||
prisma: {
|
||||
userRole: mkdel(),
|
||||
proclamation: mkdel(),
|
||||
proclamationCategory: mkdel(),
|
||||
solfeggioFrequency: mkdel(),
|
||||
subscriptionLevel: mkdel()
|
||||
}
|
||||
}
|
||||
}));
|
||||
};
|
||||
});
|
||||
|
||||
import { prisma } from '$lib/db';
|
||||
import { loadRecords, upsertRecord, deleteRecord } from './crud';
|
||||
|
||||
const findMany = prisma.userRole.findMany as ReturnType<typeof vi.fn>;
|
||||
const userRoleFindMany = prisma.userRole.findMany as ReturnType<typeof vi.fn>;
|
||||
const create = prisma.userRole.create as ReturnType<typeof vi.fn>;
|
||||
const update = prisma.userRole.update as ReturnType<typeof vi.fn>;
|
||||
const deletes = prisma.userRole.delete as ReturnType<typeof vi.fn>;
|
||||
const findUnique = prisma.userRole.findUnique as ReturnType<typeof vi.fn>;
|
||||
const findFirst = prisma.userRole.findFirst as ReturnType<typeof vi.fn>;
|
||||
|
||||
const config = getModelConfig('user-roles')!;
|
||||
|
||||
describe('crud loadRecords', () => {
|
||||
beforeEach(() => {
|
||||
findMany.mockReset();
|
||||
userRoleFindMany.mockReset();
|
||||
});
|
||||
|
||||
it('returns records', async () => {
|
||||
findMany.mockResolvedValue([{ id: 1, name: 'Admin' }]);
|
||||
userRoleFindMany.mockResolvedValue([{ id: 1, name: 'Admin' }]);
|
||||
|
||||
const locals = createLocals(mockUser() as any);
|
||||
const result = await loadRecords(locals, config);
|
||||
@@ -46,7 +52,7 @@ describe('crud loadRecords', () => {
|
||||
});
|
||||
|
||||
it('returns empty records array', async () => {
|
||||
findMany.mockResolvedValue([]);
|
||||
userRoleFindMany.mockResolvedValue([]);
|
||||
|
||||
const locals = createLocals(mockUser() as any);
|
||||
const result = await loadRecords(locals, config);
|
||||
@@ -127,6 +133,64 @@ describe('crud deleteRecord', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('P2002 unique constraint handling', () => {
|
||||
beforeEach(() => {
|
||||
create.mockReset();
|
||||
});
|
||||
|
||||
it('returns error with field names when meta.target is populated', async () => {
|
||||
const p2002 = new Error('Unique constraint failed');
|
||||
(p2002 as any).code = 'P2002';
|
||||
(p2002 as any).meta = { target: ['name'], modelName: 'UserRole' };
|
||||
create.mockRejectedValue(p2002);
|
||||
|
||||
const fd = new FormData();
|
||||
fd.set('name', 'Admin');
|
||||
const locals = createLocals(mockUser() as any);
|
||||
const result = await upsertRecord(fd, config, locals);
|
||||
|
||||
expect(result).toEqual({
|
||||
success: false,
|
||||
error: 'A record with this name already exists.',
|
||||
fieldErrors: { name: 'Already taken' }
|
||||
});
|
||||
});
|
||||
|
||||
it('returns generic message when meta.target is empty', async () => {
|
||||
const p2002 = new Error('Unique constraint failed');
|
||||
(p2002 as any).code = 'P2002';
|
||||
(p2002 as any).meta = { target: [], modelName: 'UserRole' };
|
||||
create.mockRejectedValue(p2002);
|
||||
|
||||
const fd = new FormData();
|
||||
fd.set('name', 'Admin');
|
||||
const locals = createLocals(mockUser() as any);
|
||||
const result = await upsertRecord(fd, config, locals);
|
||||
|
||||
expect(result).toEqual({
|
||||
success: false,
|
||||
error: 'A database constraint violation occurred. The record could not be created.'
|
||||
});
|
||||
});
|
||||
|
||||
it('returns generic message when meta.target is missing', async () => {
|
||||
const p2002 = new Error('Unique constraint failed');
|
||||
(p2002 as any).code = 'P2002';
|
||||
(p2002 as any).meta = { modelName: 'UserRole' };
|
||||
create.mockRejectedValue(p2002);
|
||||
|
||||
const fd = new FormData();
|
||||
fd.set('name', 'Admin');
|
||||
const locals = createLocals(mockUser() as any);
|
||||
const result = await upsertRecord(fd, config, locals);
|
||||
|
||||
expect(result).toEqual({
|
||||
success: false,
|
||||
error: 'A database constraint violation occurred. The record could not be created.'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('sveltekit-superforms import compatibility', () => {
|
||||
it('imports superValidate from server path without pulling in SuperDebug.svelte', async () => {
|
||||
expect(superValidate).toBeDefined();
|
||||
@@ -151,17 +215,11 @@ describe('prisma client version consistency', () => {
|
||||
it('generated client references existing query compiler files', async () => {
|
||||
const fs = await import('fs');
|
||||
const path = await import('path');
|
||||
const runtimeDir = path.resolve(
|
||||
'node_modules/@prisma/client/runtime'
|
||||
);
|
||||
const runtimeDir = path.resolve('node_modules/@prisma/client/runtime');
|
||||
|
||||
const generatedFile = path.resolve(
|
||||
'src/lib/generated/prisma/internal/class.ts'
|
||||
);
|
||||
const generatedFile = path.resolve('src/lib/generated/prisma/internal/class.ts');
|
||||
const content = fs.readFileSync(generatedFile, 'utf-8');
|
||||
const compilerMatch = content.match(
|
||||
/import\(["']@prisma\/client\/runtime\/([^"']+)["']\)/
|
||||
);
|
||||
const compilerMatch = content.match(/import\(["']@prisma\/client\/runtime\/([^"']+)["']\)/);
|
||||
expect(compilerMatch).not.toBeNull();
|
||||
const compilerPath = path.join(runtimeDir, compilerMatch![1]);
|
||||
expect(fs.existsSync(compilerPath)).toBe(true);
|
||||
|
||||
Reference in New Issue
Block a user