Skip to content
Open
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
229 changes: 187 additions & 42 deletions tests/core/tests/schema-dsl/resource-decorator-test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import schemas from 'virtual:warp-drive-schemas';
import schemas, { objects, traits } from 'virtual:warp-drive-schemas';

import { withDefaults } from '@warp-drive/core/reactive';
import { module, setupTest, test } from '@warp-drive/diagnostic/ember';
Expand All @@ -7,13 +7,6 @@ module('Schema DSL | @Resource compilation', function (hooks) {
setupTest(hooks);

test('@Resource derives type from class name and compiles fields', function (assert) {
// @Resource
// class User {
// @field declare firstName: string;
// @field declare lastName: string;
// @field declare email: string;
// }

const schema = schemas.find((s: { type: string }) => s.type === 'user');

assert.deepEqual(schema, {
Expand All @@ -24,17 +17,15 @@ module('Schema DSL | @Resource compilation', function (hooks) {
{ kind: 'field', name: 'firstName' },
{ kind: 'field', name: 'lastName' },
{ kind: 'field', name: 'email' },
{ kind: '@local', name: 'isEditing' },
{ kind: '@local', name: 'dirtyCount', options: { defaultValue: 0 } },
{ kind: 'derived', name: 'displayName', type: '@concat' },
{ kind: 'derived', name: 'constructor', type: '@constructor' },
],
});
});

test('@Resource("person") overrides the type name', function (assert) {
// @Resource('person')
// class CustomUser {
// @field declare name: string;
// }

const schema = schemas.find((s: { type: string }) => s.type === 'person');

assert.deepEqual(schema, {
Expand All @@ -49,13 +40,6 @@ module('Schema DSL | @Resource compilation', function (hooks) {
});

test('@id sets a custom identity field', function (assert) {
// @Resource
// class Post {
// @id declare uuid: string;
// @field declare title: string;
// @field({ type: 'date-time' }) declare createdAt: Date;
// }

const schema = schemas.find((s: { type: string }) => s.type === 'post');

assert.deepEqual(schema, {
Expand All @@ -64,19 +48,15 @@ module('Schema DSL | @Resource compilation', function (hooks) {
fields: [
{ kind: 'derived', name: '$type', type: '@identity', options: { key: 'type' } },
{ kind: 'field', name: 'title' },
{ kind: 'field', name: 'createdAt', type: 'date-time' },
{ kind: 'field', name: 'createdAt' },
{ kind: 'object', name: 'metadata' },
{ kind: 'array', name: 'tags' },
{ kind: 'derived', name: 'constructor', type: '@constructor' },
],
});
});

test('@field({ sourceKey }) maps the API key to the field name', function (assert) {
// @Resource
// class Product {
// @field({ sourceKey: 'product_name' }) declare name: string;
// @field({ type: 'number', sourceKey: 'unit_price' }) declare price: number;
// }

const schema = schemas.find((s: { type: string }) => s.type === 'product');

assert.deepEqual(schema, {
Expand All @@ -86,18 +66,13 @@ module('Schema DSL | @Resource compilation', function (hooks) {
{ kind: 'derived', name: '$type', type: '@identity', options: { key: 'type' } },
{ kind: 'field', name: 'name', sourceKey: 'product_name' },
{ kind: 'field', name: 'price', type: 'number', sourceKey: 'unit_price' },
{ kind: 'alias', name: 'productName', type: null, options: { kind: 'field', name: 'name' } },
{ kind: 'derived', name: 'constructor', type: '@constructor' },
],
});
});

test('@field({ sourceKey }) calling withDefaults setups up the defaults', function (assert) {
// @Resource
// class Product {
// @field({ sourceKey: 'product_name' }) declare name: string;
// @field({ type: 'number', sourceKey: 'unit_price' }) declare price: number;
// }

const schema = schemas.find((s: { type: string }) => s.type === 'product');

assert.deepEqual(
Expand All @@ -108,32 +83,202 @@ module('Schema DSL | @Resource compilation', function (hooks) {
fields: [
{ kind: 'field', name: 'name', sourceKey: 'product_name' },
{ kind: 'field', name: 'price', type: 'number', sourceKey: 'unit_price' },
{ kind: 'alias', name: 'productName', type: null, options: { kind: 'field', name: 'name' } },
],
})
);
});

test('@Resource({ legacy: true }) omits derived fields and sets legacy flag', function (assert) {
// @Resource({ legacy: true })
// class Comment {
// @field declare body: string;
// }

const schema = schemas.find((s: { type: string }) => s.type === 'comment');

assert.deepEqual(schema, {
type: 'comment',
identity: { kind: '@id', name: 'id' },
fields: [{ kind: 'field', name: 'body' }],
fields: [
{ kind: 'field', name: 'body' },
{ kind: 'attribute', name: 'author', type: 'string' },
{ kind: 'belongsTo', name: 'post', type: 'post', options: { async: true, inverse: 'comments' } },
{ kind: 'hasMany', name: 'replies', type: 'comment', options: { async: false, inverse: null } },
],
legacy: true,
traits: ['timestamped'],
});
});
});

module('Schema DSL | @local', function (hooks) {
setupTest(hooks);

test('@local compiles to @local field', function (assert) {
const schema = schemas.find((s: { type: string }) => s.type === 'user');
const fields = (schema as { fields: Array<{ kind: string; name: string }> }).fields;

test('compiles all schema files in the glob', function (assert) {
const isEditing = fields.find((f) => f.name === 'isEditing');
assert.deepEqual(isEditing, { kind: '@local', name: 'isEditing' });
});

test('@local({ defaultValue }) includes options', function (assert) {
const schema = schemas.find((s: { type: string }) => s.type === 'user');
const fields = (schema as { fields: Array<{ kind: string; name: string }> }).fields;

const dirtyCount = fields.find((f) => f.name === 'dirtyCount');
assert.deepEqual(dirtyCount, { kind: '@local', name: 'dirtyCount', options: { defaultValue: 0 } });
});
});

module('Schema DSL | @derived', function (hooks) {
setupTest(hooks);

test('@derived compiles to derived field with type', function (assert) {
const schema = schemas.find((s: { type: string }) => s.type === 'user');
const fields = (schema as { fields: Array<{ kind: string; name: string }> }).fields;

const displayName = fields.find((f) => f.name === 'displayName');
assert.deepEqual(displayName, { kind: 'derived', name: 'displayName', type: '@concat' });
});
});

module('Schema DSL | @object and @array', function (hooks) {
setupTest(hooks);

test('@object compiles to object field', function (assert) {
const schema = schemas.find((s: { type: string }) => s.type === 'post');
const fields = (schema as { fields: Array<{ kind: string; name: string }> }).fields;

const metadata = fields.find((f) => f.name === 'metadata');
assert.deepEqual(metadata, { kind: 'object', name: 'metadata' });
});

test('@array compiles to array field', function (assert) {
const schema = schemas.find((s: { type: string }) => s.type === 'post');
const fields = (schema as { fields: Array<{ kind: string; name: string }> }).fields;

const tags = fields.find((f) => f.name === 'tags');
assert.deepEqual(tags, { kind: 'array', name: 'tags' });
});
});

module('Schema DSL | @alias', function (hooks) {
setupTest(hooks);

test('@alias compiles to alias field pointing to source', function (assert) {
const schema = schemas.find((s: { type: string }) => s.type === 'product');
const fields = (schema as { fields: Array<{ kind: string; name: string }> }).fields;

const productName = fields.find((f) => f.name === 'productName');
assert.deepEqual(productName, {
kind: 'alias',
name: 'productName',
type: null,
options: { kind: 'field', name: 'name' },
});
});
});

module('Schema DSL | legacy decorators', function (hooks) {
setupTest(hooks);

test('@attribute compiles to attribute field', function (assert) {
const schema = schemas.find((s: { type: string }) => s.type === 'comment');
const fields = (schema as { fields: Array<{ kind: string; name: string }> }).fields;

const author = fields.find((f) => f.name === 'author');
assert.deepEqual(author, { kind: 'attribute', name: 'author', type: 'string' });
});

test('@belongsTo compiles to belongsTo field with options', function (assert) {
const schema = schemas.find((s: { type: string }) => s.type === 'comment');
const fields = (schema as { fields: Array<{ kind: string; name: string }> }).fields;

const post = fields.find((f) => f.name === 'post');
assert.deepEqual(post, {
kind: 'belongsTo',
name: 'post',
type: 'post',
options: { async: true, inverse: 'comments' },
});
});

test('@hasMany compiles to hasMany field with options', function (assert) {
const schema = schemas.find((s: { type: string }) => s.type === 'comment');
const fields = (schema as { fields: Array<{ kind: string; name: string }> }).fields;

const replies = fields.find((f) => f.name === 'replies');
assert.deepEqual(replies, {
kind: 'hasMany',
name: 'replies',
type: 'comment',
options: { async: false, inverse: null },
});
});
});

module('Schema DSL | @trait composition', function (hooks) {
setupTest(hooks);

test('@trait adds traits array to resource schema', function (assert) {
const schema = schemas.find((s: { type: string }) => s.type === 'comment');
assert.deepEqual((schema as { traits?: string[] }).traits, ['timestamped']);
});
});

module('Schema DSL | @Object compilation', function (hooks) {
setupTest(hooks);

test('@ObjectSchema compiles to ObjectSchema with hash identity', function (assert) {
assert.ok(Array.isArray(objects), 'objects export is an array');
const schema = objects.find((s: { type: string }) => s.type === 'address');
assert.ok(schema, 'address object schema found');

assert.deepEqual(schema, {
type: 'address',
identity: { kind: '@hash', name: 'addressHash', type: '@identity' },
fields: [
{ kind: 'field', name: 'street' },
{ kind: 'field', name: 'city' },
],
});
});
});

module('Schema DSL | @Trait compilation', function (hooks) {
setupTest(hooks);

test('@Trait compiles to Trait schema', function (assert) {
assert.ok(Array.isArray(traits), 'traits export is an array');
const schema = traits.find((s: { name: string }) => s.name === 'timestamped');
assert.ok(schema, 'timestamped trait found');

assert.deepEqual(schema, {
name: 'timestamped',
mode: 'polaris',
fields: [
{ kind: 'field', name: 'createdAt' },
{ kind: 'field', name: 'updatedAt' },
],
});
});
});

module('Schema DSL | compilation totals', function (hooks) {
setupTest(hooks);

test('compiles all resource schemas', function (assert) {
assert.ok(Array.isArray(schemas), 'compiled output is an array');
assert.equal(schemas.length, 5, 'all five schema files were compiled');
assert.equal(schemas.length, 5, 'five resource schemas compiled');

const types = schemas.map((s: { type: string }) => s.type).sort();
assert.deepEqual(types, ['comment', 'person', 'post', 'product', 'user'], 'all expected types present');
assert.deepEqual(types, ['comment', 'person', 'post', 'product', 'user']);
});

test('compiles object schemas', function (assert) {
assert.ok(Array.isArray(objects), 'objects export is an array');
assert.equal(objects.length, 1, 'one object schema compiled');
});

test('compiles trait schemas', function (assert) {
assert.ok(Array.isArray(traits), 'traits export is an array');
assert.equal(traits.length, 1, 'one trait schema compiled');
});
});
8 changes: 8 additions & 0 deletions tests/core/tests/schema-dsl/schemas/address.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { field, hash, ObjectSchema } from '@warp-drive/schema-dsl';

@ObjectSchema
export class Address {
@hash({ type: '@identity' }) declare addressHash: string;
@field declare street: string;
@field declare city: string;
}
8 changes: 7 additions & 1 deletion tests/core/tests/schema-dsl/schemas/comment.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { field, Resource } from '@warp-drive/schema-dsl';
import { attribute, belongsTo, field, hasMany, Resource, trait } from '@warp-drive/schema-dsl';

import { Timestamped } from './timestamped.ts';

@Resource({ legacy: true })
@trait(Timestamped)
export class Comment {
@field declare body: string;
@attribute({ type: 'string' }) declare author: string;
@belongsTo({ type: 'post', inverse: 'comments', async: true }) declare post: unknown;
@hasMany({ type: 'comment', inverse: null, async: false }) declare replies: unknown[];
}
6 changes: 4 additions & 2 deletions tests/core/tests/schema-dsl/schemas/post.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { field, id, Resource } from '@warp-drive/schema-dsl';
import { array, field, id, object, Resource } from '@warp-drive/schema-dsl';

@Resource
export class Post {
@id declare uuid: string;
@field declare title: string;
@field({ type: 'date-time' }) declare createdAt: Date;
@field declare createdAt: string;
@object declare metadata: Record<string, unknown>;
@array declare tags: string[];
}
3 changes: 2 additions & 1 deletion tests/core/tests/schema-dsl/schemas/product.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { field, Resource } from '@warp-drive/schema-dsl';
import { alias, field, Resource } from '@warp-drive/schema-dsl';

@Resource
export class Product {
@field({ sourceKey: 'product_name' }) declare name: string;
@field({ type: 'number', sourceKey: 'unit_price' }) declare price: number;
@alias({ kind: 'field', name: 'name' }) declare productName: string;
}
7 changes: 7 additions & 0 deletions tests/core/tests/schema-dsl/schemas/timestamped.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { field, Trait } from '@warp-drive/schema-dsl';

@Trait
export class Timestamped {
@field declare createdAt: string;
@field declare updatedAt: string;
}
5 changes: 4 additions & 1 deletion tests/core/tests/schema-dsl/schemas/user.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { field, Resource } from '@warp-drive/schema-dsl';
import { derived, field, local, Resource } from '@warp-drive/schema-dsl';

@Resource
export class User {
@field declare firstName: string;
@field declare lastName: string;
@field declare email: string;
@local declare isEditing: boolean;
@local({ defaultValue: 0 }) declare dirtyCount: number;
@derived({ type: '@concat' }) declare displayName: string;
}
Loading
Loading