Skip to content
This repository was archived by the owner on Mar 1, 2026. It is now read-only.

Commit fd1d986

Browse files
committed
feat: add tests for update, upsert, and multiple virtual fields functionality
1 parent 9bbd365 commit fd1d986

1 file changed

Lines changed: 265 additions & 0 deletions

File tree

tests/e2e/orm/client-api/virtual-fields.test.ts

Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -500,4 +500,269 @@ model Post extends Content {
500500
preview: 'This is the full body content of the post',
501501
});
502502
});
503+
504+
it('works with update operations', async () => {
505+
const db = await createTestClient(
506+
`
507+
model User {
508+
id Int @id @default(autoincrement())
509+
firstName String
510+
lastName String
511+
fullName String @virtual
512+
}
513+
`,
514+
{
515+
virtualFields: {
516+
User: {
517+
fullName: (row: any) => `${row.firstName} ${row.lastName}`,
518+
},
519+
},
520+
} as any,
521+
);
522+
523+
await db.user.create({
524+
data: { id: 1, firstName: 'Alex', lastName: 'Smith' },
525+
});
526+
527+
// Update should return the virtual field
528+
const updated = await db.user.update({
529+
where: { id: 1 },
530+
data: { firstName: 'John' },
531+
});
532+
533+
expect(updated.fullName).toBe('John Smith');
534+
});
535+
536+
it('works with upsert operations', async () => {
537+
const db = await createTestClient(
538+
`
539+
model User {
540+
id Int @id @default(autoincrement())
541+
firstName String
542+
lastName String
543+
fullName String @virtual
544+
}
545+
`,
546+
{
547+
virtualFields: {
548+
User: {
549+
fullName: (row: any) => `${row.firstName} ${row.lastName}`,
550+
},
551+
},
552+
} as any,
553+
);
554+
555+
// Upsert create path
556+
const created = await db.user.upsert({
557+
where: { id: 1 },
558+
create: { id: 1, firstName: 'Alex', lastName: 'Smith' },
559+
update: { firstName: 'John' },
560+
});
561+
562+
expect(created.fullName).toBe('Alex Smith');
563+
564+
// Upsert update path
565+
const updated = await db.user.upsert({
566+
where: { id: 1 },
567+
create: { id: 1, firstName: 'Alex', lastName: 'Smith' },
568+
update: { firstName: 'John' },
569+
});
570+
571+
expect(updated.fullName).toBe('John Smith');
572+
});
573+
574+
it('works with multiple virtual fields on same model', async () => {
575+
const db = await createTestClient(
576+
`
577+
model User {
578+
id Int @id @default(autoincrement())
579+
firstName String
580+
lastName String
581+
email String
582+
fullName String @virtual
583+
displayEmail String @virtual
584+
initials String @virtual
585+
}
586+
`,
587+
{
588+
virtualFields: {
589+
User: {
590+
fullName: (row: any) => `${row.firstName} ${row.lastName}`,
591+
displayEmail: (row: any) => row.email.toLowerCase(),
592+
initials: (row: any) => `${row.firstName[0]}${row.lastName[0]}`.toUpperCase(),
593+
},
594+
},
595+
} as any,
596+
);
597+
598+
const user = await db.user.create({
599+
data: { id: 1, firstName: 'Alex', lastName: 'Smith', email: 'ALEX@EXAMPLE.COM' },
600+
});
601+
602+
expect(user.fullName).toBe('Alex Smith');
603+
expect(user.displayEmail).toBe('alex@example.com');
604+
expect(user.initials).toBe('AS');
605+
});
606+
607+
it('works with relations and virtual fields on MySQL (lateral join dialect)', async () => {
608+
// This test targets the lateral join dialect used by MySQL
609+
const db = await createTestClient(
610+
`
611+
model User {
612+
id Int @id @default(autoincrement())
613+
name String
614+
displayName String @virtual
615+
posts Post[]
616+
}
617+
618+
model Post {
619+
id Int @id @default(autoincrement())
620+
title String
621+
author User @relation(fields: [authorId], references: [id])
622+
authorId Int
623+
}
624+
`,
625+
{
626+
provider: 'mysql',
627+
virtualFields: {
628+
User: {
629+
displayName: (row: any) => `@${row.name}`,
630+
},
631+
},
632+
} as any,
633+
);
634+
635+
await db.user.create({
636+
data: { id: 1, name: 'alex', posts: { create: { title: 'Post1' } } },
637+
});
638+
639+
await expect(
640+
db.post.findFirst({
641+
include: { author: true },
642+
}),
643+
).resolves.toMatchObject({
644+
title: 'Post1',
645+
author: expect.objectContaining({
646+
name: 'alex',
647+
displayName: '@alex',
648+
}),
649+
});
650+
});
651+
652+
it('virtual field can access included relation data', async () => {
653+
const db = await createTestClient(
654+
`
655+
model User {
656+
id Int @id @default(autoincrement())
657+
name String
658+
posts Post[]
659+
}
660+
661+
model Post {
662+
id Int @id @default(autoincrement())
663+
title String
664+
author User @relation(fields: [authorId], references: [id])
665+
authorId Int
666+
authorDisplay String @virtual
667+
}
668+
`,
669+
{
670+
virtualFields: {
671+
Post: {
672+
authorDisplay: (row: any) => {
673+
// Virtual field can access included relation data
674+
if (row.author) {
675+
return `by ${row.author.name}`;
676+
}
677+
return `by user #${row.authorId}`;
678+
},
679+
},
680+
},
681+
} as any,
682+
);
683+
684+
await db.user.create({
685+
data: { id: 1, name: 'Alex', posts: { create: { title: 'My Post' } } },
686+
});
687+
688+
// Without including author
689+
const postWithoutAuthor = await db.post.findFirst();
690+
expect(postWithoutAuthor?.authorDisplay).toBe('by user #1');
691+
692+
// With including author
693+
const postWithAuthor = await db.post.findFirst({
694+
include: { author: true },
695+
});
696+
expect(postWithAuthor?.authorDisplay).toBe('by Alex');
697+
});
698+
699+
it('respects omit clause - skips virtual field computation', async () => {
700+
let virtualFieldCalled = false;
701+
702+
const db = await createTestClient(
703+
`
704+
model User {
705+
id Int @id @default(autoincrement())
706+
name String
707+
displayName String @virtual
708+
}
709+
`,
710+
{
711+
virtualFields: {
712+
User: {
713+
displayName: (row: any) => {
714+
virtualFieldCalled = true;
715+
return `@${row.name}`;
716+
},
717+
},
718+
},
719+
} as any,
720+
);
721+
722+
await db.user.create({
723+
data: { id: 1, name: 'Alex' },
724+
});
725+
726+
virtualFieldCalled = false;
727+
728+
// When omitting the virtual field, it should NOT be computed
729+
const result = await db.user.findUnique({
730+
where: { id: 1 },
731+
omit: { displayName: true },
732+
});
733+
734+
expect(result).toMatchObject({ id: 1, name: 'Alex' });
735+
expect(result).not.toHaveProperty('displayName');
736+
expect(virtualFieldCalled).toBe(false);
737+
});
738+
739+
it('propagates errors from virtual field functions', async () => {
740+
const db = await createTestClient(
741+
`
742+
model User {
743+
id Int @id @default(autoincrement())
744+
name String
745+
problematic String @virtual
746+
}
747+
`,
748+
{
749+
virtualFields: {
750+
User: {
751+
problematic: () => {
752+
throw new Error('Virtual field computation failed');
753+
},
754+
},
755+
},
756+
} as any,
757+
);
758+
759+
await db.user.create({
760+
data: { id: 1, name: 'Alex' },
761+
});
762+
763+
// The error should propagate
764+
await expect(db.user.findUnique({ where: { id: 1 } })).rejects.toThrow(
765+
'Virtual field computation failed',
766+
);
767+
});
503768
});

0 commit comments

Comments
 (0)