Skip to content

Commit

Permalink
wip: module feature
Browse files Browse the repository at this point in the history
  • Loading branch information
Yogu committed Jul 18, 2024
1 parent 46adf4f commit 27856e6
Show file tree
Hide file tree
Showing 36 changed files with 2,204 additions and 42 deletions.
140 changes: 140 additions & 0 deletions spec/model/compatibility-check.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import { DocumentNode, print } from 'graphql';
import gql from 'graphql-tag';
import { Project, ProjectSource, ValidationResult } from '../../core-exports';
import { ModuleSelectionOptions } from '../../src/project/select-modules-in-sources';
import {
expectSingleCompatibilityIssueToInclude,
expectToBeValid,
} from './implementation/validation-utils';

describe('checkCompatibility', () => {
it('accepts a simple case', () => {
const result = run(
gql`
type Test @rootEntity @modules(in: "module1") {
field: String @modules(all: true)
}
`,
gql`
type Test @rootEntity {
field: String
}
`,
['module1'],
);
expectToBeValid(result);
});

it('rejects if a type is missing', () => {
const result = run(
gql`
type Test @rootEntity @modules(in: "module1") {
field: String @modules(all: true)
}
`,
gql`
type WrongTypeName @rootEntity {
field: String
}
`,
['module1'],
);
expectSingleCompatibilityIssueToInclude(result, 'Type "Test" is missing.');
});

it('rejects if a field is missing', () => {
const result = run(
gql`
type Test @rootEntity @modules(in: "module1") {
field: String @modules(all: true)
}
`,
gql`
type Test @rootEntity {
wrongFieldName: String
}
`,
['module1'],
);
expectSingleCompatibilityIssueToInclude(result, 'Field "Test.field" is missing.');
});

it('rejects if a field has the wrong type', () => {
const result = run(
gql`
type Test @rootEntity @modules(in: "module1") {
field: String @modules(all: true)
}
`,
gql`
type Test @rootEntity {
field: Int
}
`,
['module1'],
);
expectSingleCompatibilityIssueToInclude(
result,
'Field "Test.field" needs to be of type "String".',
);
});

it('rejects if a field should be a list', () => {
const result = run(
gql`
type Test @rootEntity @modules(in: "module1") {
field: [String] @modules(all: true)
}
`,
gql`
type Test @rootEntity {
field: String
}
`,
['module1'],
);
expectSingleCompatibilityIssueToInclude(result, 'Field "Test.field" needs to be a list.');
});

it('rejects if a field wrongly is a list', () => {
const result = run(
gql`
type Test @rootEntity @modules(in: "module1") {
field: String @modules(all: true)
}
`,
gql`
type Test @rootEntity {
field: [String]
}
`,
['module1'],
);
expectSingleCompatibilityIssueToInclude(result, 'Field "Test.field" should not be a list.');
});
});

function run(
baselineDoc: DocumentNode,
docToCheck: DocumentNode,
selectedModules: ReadonlyArray<string>,
options: ModuleSelectionOptions = {},
): ValidationResult {
const projectToCheck = new Project({
sources: [new ProjectSource('to-check.graphql', print(docToCheck))],
});

const projectWithModules = new Project({
sources: [
new ProjectSource('baseline.graphql', print(baselineDoc)),
new ProjectSource(
'modules.json',
JSON.stringify({ modules: ['module1', 'module2', 'module3', 'extra1', 'extra2'] }),
),
],
modelOptions: { withModuleDefinitions: true },
});
const baselineProject = projectWithModules.withModuleSelection(selectedModules);

return projectToCheck.checkCompatibility(baselineProject);
}
84 changes: 84 additions & 0 deletions spec/model/create-model.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ import { expect } from 'chai';
import { DocumentNode } from 'graphql';
import gql from 'graphql-tag';
import { createSimpleModel } from './model-spec.helper';
import { parseProject } from '../../src/schema/schema-builder';
import { Project, ProjectSource } from '../../core-exports';
import { Severity, ValidationContext, createModel } from '../../src/model';
import { expectSingleErrorToInclude, expectToBeValid } from './implementation/validation-utils';

describe('createModel', () => {
it('translates _key: String @key properly', () => {
Expand Down Expand Up @@ -376,4 +380,84 @@ describe('createModel', () => {
expect(type.getFieldOrThrow('createdAt').isHidden).to.be.false;
expect(type.getFieldOrThrow('test2').isHidden).to.be.true;
});

describe('with modules', () => {
it('extracts the module definitions', () => {
const validationContext = new ValidationContext();
const parsedProject = parseProject(
new Project([
new ProjectSource(
'modules.json',
JSON.stringify({
modules: ['module1', 'module2'],
}),
),
]),
validationContext,
);
expectToBeValid(validationContext.asResult());
const model = createModel(parsedProject, {
withModuleDefinitions: true,
});
expectToBeValid(model);
const modules = model.modules;
expect(modules).to.have.lengthOf(2);
const module1 = modules[0];
expect(module1.name).to.equal('module1');
expect(module1.loc?.start.offset).to.equal(12);
expect(module1.loc?.end.offset).to.equal(21);
});

it('does not allow module declarations if withModuleDeclarations is not set', () => {
const validationContext = new ValidationContext();
const parsedProject = parseProject(
new Project([
new ProjectSource(
'modules.json',
JSON.stringify({
modules: ['module1', 'module2'],
}),
),
]),
validationContext,
);
expectToBeValid(validationContext.asResult());
const model = createModel(parsedProject);
expectSingleErrorToInclude(
model,
'Module declarations are not supported in this context.',
);
const modules = model.modules;
expect(modules).to.have.lengthOf(0);
});

it('does not allow duplicate module names', () => {
const validationContext = new ValidationContext();
const parsedProject = parseProject(
new Project([
new ProjectSource(
'modules.json',
JSON.stringify({
modules: ['module1', 'module1'],
}),
),
]),
validationContext,
);
expectToBeValid(validationContext.asResult());
const model = createModel(parsedProject, {
withModuleDefinitions: true,
});
const validationResult = model.validate();
expect(validationResult.messages.length).to.equal(2);
expect(validationResult.messages[0].severity).to.equal(Severity.ERROR);
expect(validationResult.messages[0].message).to.equal(
'Duplicate module declaration: "module1".',
);
expect(validationResult.messages[1].severity).to.equal(Severity.ERROR);
expect(validationResult.messages[1].message).to.equal(
'Duplicate module declaration: "module1".',
);
});
});
});
26 changes: 21 additions & 5 deletions spec/model/implementation/validation-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,43 @@ import {
ValidationContext,
} from '../../../src/model/validation/validation-context';

export function validate(component: ModelComponent): ValidationResult {
export function validate(component: ModelComponent | ValidationResult): ValidationResult {
if (component instanceof ValidationResult) {
return component;
}
const context = new ValidationContext();
component.validate(context);
return context.asResult();
}

export function expectToBeValid(component: ModelComponent) {
export function expectToBeValid(component: ModelComponent | ValidationResult) {
const result = validate(component);
expect(result.hasMessages(), result.toString()).to.be.false;
}

export function expectSingleErrorToInclude(component: ModelComponent, errorPart: string) {
export function expectSingleErrorToInclude(
component: ModelComponent | ValidationResult,
errorPart: string,
) {
expectSingleMessageToInclude(component, errorPart, Severity.ERROR);
}

export function expectSingleWarningToInclude(component: ModelComponent, errorPart: string) {
export function expectSingleCompatibilityIssueToInclude(
component: ModelComponent | ValidationResult,
errorPart: string,
) {
expectSingleMessageToInclude(component, errorPart, Severity.COMPATIBILITY_ISSUE);
}

export function expectSingleWarningToInclude(
component: ModelComponent | ValidationResult,
errorPart: string,
) {
expectSingleMessageToInclude(component, errorPart, Severity.WARNING);
}

export function expectSingleMessageToInclude(
component: ModelComponent,
component: ModelComponent | ValidationResult,
errorPart: string,
severity: Severity,
) {
Expand Down
Loading

0 comments on commit 27856e6

Please sign in to comment.