Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ node_modules
# production
dist
/tmp
.tmp
/out-tsc
**/build

Expand Down
45 changes: 45 additions & 0 deletions convex/orm/mutations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,51 @@ describe('M7 Mutations', () => {
});
});

it('should allow returning hydrated Date values from t.run in convex-test', async () => {
const localSubscriptions = convexTable('localSubscriptionsForRunReturn', {
plan: text().notNull(),
referenceId: text().notNull(),
status: text(),
createdAt: timestamp().notNull().defaultNow(),
updatedAt: timestamp().notNull().defaultNow(),
});
const localSchema = defineSchema({
localSubscriptions,
});
const localRelations = defineRelations(
{
localSubscriptions,
},
() => ({})
);

const sub = await convexTest(localSchema).run(async (baseCtx) => {
const ctx = withOrm(baseCtx, localRelations);
const [inserted] = await ctx.orm
.insert(localSubscriptions)
.values({ plan: 'pro', referenceId: 'ref_1', status: 'active' })
.returning();

expect(inserted.createdAt).toBeInstanceOf(Date);
expect(inserted.updatedAt).toBeInstanceOf(Date);

return inserted;
});

expect(typeof sub.createdAt).toBe('number');
expect(typeof sub.updatedAt).toBe('number');
});

it('should preserve convex-test rejection for unsupported class returns', async () => {
class UnsupportedDateReturn {
createdAt = new Date();
}

await expect(
convexTest(schema).run(async () => new UnsupportedDateReturn())
).rejects.toThrow('not a supported Convex type');
});

it('should update rows and return updated values', async ({ ctx }) => {
const db = ctx.orm;
const [user] = await db.insert(users).values(baseUser).returning();
Expand Down
88 changes: 87 additions & 1 deletion convex/setup.testing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,96 @@ const convexModules = (import.meta as ImportMetaWithGlob).glob([
]);
const relations = requireSchemaRelations(schema);

type TestIdentity = Parameters<
ReturnType<typeof baseConvexTest>['withIdentity']
>[0];

const serializeDatesForConvexTest = (value: unknown): unknown => {
if (value instanceof Date) {
return value.getTime();
}

if (Array.isArray(value)) {
let serialized: unknown[] | undefined;

for (let index = 0; index < value.length; index += 1) {
const entry = value[index];
const encoded = serializeDatesForConvexTest(entry);
if (encoded !== entry) {
if (!serialized) {
serialized = value.slice();
}
serialized[index] = encoded;
}
}

return serialized ?? value;
}

if (!value || typeof value !== 'object') {
return value;
}

const prototype = Object.getPrototypeOf(value);
const isSimpleObject =
prototype === null ||
prototype === Object.prototype ||
prototype?.constructor?.name === 'Object';
if (!isSimpleObject) {
return value;
}

const record = value as Record<string, unknown>;
let serialized: Record<string, unknown> | undefined;

for (const key in record) {
if (!Object.hasOwn(record, key)) {
continue;
}

const entry = record[key];
const encoded = serializeDatesForConvexTest(entry);
if (encoded !== entry) {
if (!serialized) {
serialized = { ...record };
}
serialized[key] = encoded;
}
}

return serialized ?? value;
};

const wrapConvexTestDateReturns = <Test extends object>(test: Test): Test => {
const runnable = test as Test & {
run: <Output>(fn: (ctx: unknown) => Promise<Output>) => Promise<Output>;
withIdentity?: (identity: TestIdentity) => object;
};
const withIdentity = runnable.withIdentity;

const wrapped = {
...runnable,
run: async <Output>(fn: (ctx: unknown) => Promise<Output>) =>
runnable.run(
async (ctx) => serializeDatesForConvexTest(await fn(ctx)) as Output
),
};

if (!withIdentity) {
return wrapped as Test;
}

return {
...wrapped,
withIdentity: (identity: TestIdentity) =>
wrapConvexTestDateReturns(withIdentity(identity)),
} as Test;
};

export function convexTest<Schema extends SchemaDefinition<any, any>>(
schema: Schema
) {
return baseConvexTest(schema, convexModules);
return wrapConvexTestDateReturns(baseConvexTest(schema, convexModules));
}

export const withOrm = <
Expand Down
Loading
Loading