Skip to content

Commit 8206b42

Browse files
authored
Merge pull request #26 from lstreckeisen/CMI-83-Write-Linking-Tests-for-Language-Server
Cmi 83 write linking tests for language server
2 parents 4805133 + f35fafb commit 8206b42

24 files changed

+707
-44
lines changed

src/language/ContextMapperDslModule.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { ContextMapperDslSemanticTokenProvider } from './semantictokens/ContextM
1313
import { SemanticTokenProviderRegistry } from './semantictokens/SemanticTokenProviderRegistry.js'
1414
import { ContextMapperDslValidationRegistry } from './validation/ContextMapperDslValidationRegistry.js'
1515
import { ContextMapperValidationProviderRegistry } from './validation/ContextMapperValidationProviderRegistry.js'
16+
import { ContextMapperDslScopeProvider } from './references/ContextMapperDslScopeProvider.js'
1617

1718
/**
1819
* Declaration of custom services - add your own service classes here.
@@ -47,6 +48,9 @@ export const ContextMapperDslModule: Module<ContextMapperDslServices, ModuleType
4748
},
4849
lsp: {
4950
SemanticTokenProvider: (services) => new ContextMapperDslSemanticTokenProvider(services, semanticTokenProviderRegistry)
51+
},
52+
references: {
53+
ScopeProvider: (services) => new ContextMapperDslScopeProvider(services)
5054
}
5155
}
5256

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { AstUtils, DefaultScopeProvider, ReferenceInfo, Scope } from 'langium'
2+
import {
3+
AbstractStakeholder,
4+
Aggregate,
5+
DomainPart,
6+
isContextMappingModel,
7+
isStakeholderGroup, StakeholderGroup
8+
} from '../generated/ast.js'
9+
10+
export class ContextMapperDslScopeProvider extends DefaultScopeProvider {
11+
/*
12+
Some ContextMapper elements are not defined on the top-level of the document and should still be referencable.
13+
Langium assumes that nested elements don't belong in the global scope.
14+
Therefore, nested elements are explicitly added to the scope, while for other elements the Langium default behavior applies.
15+
*/
16+
override getScope (context: ReferenceInfo): Scope {
17+
const referenceType = this.reflection.getReferenceType(context)
18+
const model = AstUtils.getContainerOfType(context.container, isContextMappingModel)!
19+
20+
if (referenceType === DomainPart) {
21+
const domainPartDescriptions = model.domains.map(d => this.descriptions.createDescription(d, d.name))
22+
.concat(
23+
model.domains.flatMap(d => d.subdomains)
24+
.map(sd => this.descriptions.createDescription(sd, sd.name))
25+
)
26+
return this.createScope(domainPartDescriptions)
27+
}
28+
29+
if (referenceType === Aggregate) {
30+
const aggregateDescriptions = model.boundedContexts.flatMap(bc => bc.aggregates)
31+
.map(a => this.descriptions.createDescription(a, a.name))
32+
.concat(
33+
model.boundedContexts.flatMap(bc => bc.modules)
34+
.flatMap(m => m.aggregates)
35+
.map(a => this.descriptions.createDescription(a, a.name))
36+
)
37+
return this.createScope(aggregateDescriptions)
38+
}
39+
40+
if (referenceType === AbstractStakeholder) {
41+
const stakeholderDescriptions = model.stakeholders.flatMap(s => s.stakeholders)
42+
.map(s => this.descriptions.createDescription(s, s.name))
43+
.concat(
44+
model.stakeholders.flatMap(s => s.stakeholders)
45+
.filter(s => isStakeholderGroup(s))
46+
.flatMap(sg => (sg as StakeholderGroup).stakeholders)
47+
.map(s => this.descriptions.createDescription(s, s.name))
48+
)
49+
return this.createScope(stakeholderDescriptions)
50+
}
51+
52+
return super.getScope(context)
53+
}
54+
}

test/parsing/ParsingTestHelper.ts renamed to test/ParsingTestHelper.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { expect } from 'vitest'
22
import { LangiumDocument } from 'langium'
3-
import { ContextMappingModel } from '../../src/language/generated/ast.js'
3+
import { ContextMappingModel } from '../src/language/generated/ast.js'
44
import { parseHelper } from 'langium/test'
55

66
export async function parseValidInput (parse: ReturnType<typeof parseHelper<ContextMappingModel>>, input: string): Promise<LangiumDocument<ContextMappingModel>> {
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { createContextMapperDslServices } from '../../src/language/ContextMapperDslModule.js'
2+
import { clearDocuments, parseHelper } from 'langium/test'
3+
import {
4+
ContextMappingModel,
5+
CustomerSupplierRelationship,
6+
UpstreamDownstreamRelationship
7+
} from '../../src/language/generated/ast.js'
8+
import { EmptyFileSystem, LangiumDocument } from 'langium'
9+
import { afterEach, beforeAll, describe, expect, test } from 'vitest'
10+
import { parseValidInput } from '../ParsingTestHelper.js'
11+
12+
let services: ReturnType<typeof createContextMapperDslServices>
13+
let parse: ReturnType<typeof parseHelper<ContextMappingModel>>
14+
let document: LangiumDocument<ContextMappingModel> | undefined
15+
16+
beforeAll(async () => {
17+
services = createContextMapperDslServices(EmptyFileSystem)
18+
parse = parseHelper<ContextMappingModel>(services.ContextMapperDsl)
19+
})
20+
21+
afterEach(async () => {
22+
document && await clearDocuments(services.shared, [document])
23+
})
24+
25+
describe('Aggregate linking tests', () => {
26+
test('check linking of aggregate in UpstreamDownstream relationship', async () => {
27+
document = await parseValidInput(parse, `
28+
ContextMap {
29+
TestContext -> FirstContext {
30+
exposedAggregates TestAggregate
31+
}
32+
}
33+
BoundedContext FirstContext
34+
BoundedContext TestContext {
35+
Aggregate TestAggregate
36+
}
37+
`)
38+
39+
const relationship = document.parseResult.value.contextMaps[0].relationships[0] as UpstreamDownstreamRelationship
40+
expect(relationship.upstreamExposedAggregates).toHaveLength(1)
41+
expect(relationship.upstreamExposedAggregates[0]).not.toBeUndefined()
42+
expect(relationship.upstreamExposedAggregates[0].ref).not.toBeUndefined()
43+
expect(relationship.upstreamExposedAggregates[0].ref?.name).toEqual('TestAggregate')
44+
})
45+
46+
test('check linking of aggregate in CustomerSupplier relationship', async () => {
47+
document = await parseValidInput(parse, `
48+
ContextMap {
49+
TestContext [S] -> [C] FirstContext {
50+
exposedAggregates TestAggregate
51+
}
52+
}
53+
BoundedContext FirstContext
54+
BoundedContext TestContext {
55+
Aggregate TestAggregate
56+
}
57+
`)
58+
59+
const relationship = document.parseResult.value.contextMaps[0].relationships[0] as CustomerSupplierRelationship
60+
expect(relationship.upstreamExposedAggregates).toHaveLength(1)
61+
expect(relationship.upstreamExposedAggregates[0]).not.toBeUndefined()
62+
expect(relationship.upstreamExposedAggregates[0].ref).not.toBeUndefined()
63+
expect(relationship.upstreamExposedAggregates[0].ref?.name).toEqual('TestAggregate')
64+
})
65+
66+
test('check linking of module aggregate in CustomerSupplier relationship', async () => {
67+
document = await parseValidInput(parse, `
68+
ContextMap {
69+
TestContext [S] -> [C] FirstContext {
70+
exposedAggregates TestAggregate
71+
}
72+
}
73+
BoundedContext FirstContext
74+
BoundedContext TestContext {
75+
Module TestModule {
76+
Aggregate TestAggregate
77+
}
78+
}
79+
`)
80+
81+
const relationship = document.parseResult.value.contextMaps[0].relationships[0] as CustomerSupplierRelationship
82+
expect(relationship.upstreamExposedAggregates).toHaveLength(1)
83+
expect(relationship.upstreamExposedAggregates[0]).not.toBeUndefined()
84+
expect(relationship.upstreamExposedAggregates[0].ref).not.toBeUndefined()
85+
expect(relationship.upstreamExposedAggregates[0].ref?.name).toEqual('TestAggregate')
86+
})
87+
})

test/linking/BoundedContextLinking.test.ts

Lines changed: 151 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,14 @@ import { afterEach, beforeAll, describe, expect, test } from 'vitest'
22
import { EmptyFileSystem, type LangiumDocument } from 'langium'
33
import { clearDocuments, parseHelper } from 'langium/test'
44
import { createContextMapperDslServices } from '../../src/language/ContextMapperDslModule.js'
5-
import { ContextMappingModel, isSharedKernel } from '../../src/language/generated/ast.js'
6-
import { checkDocumentValid } from '../TestHelper.js'
5+
import {
6+
ContextMappingModel,
7+
CustomerSupplierRelationship,
8+
Partnership,
9+
SharedKernel,
10+
UpstreamDownstreamRelationship
11+
} from '../../src/language/generated/ast.js'
12+
import { parseValidInput } from '../ParsingTestHelper.js'
713

814
let services: ReturnType<typeof createContextMapperDslServices>
915
let parse: ReturnType<typeof parseHelper<ContextMappingModel>>
@@ -12,38 +18,155 @@ let document: LangiumDocument<ContextMappingModel> | undefined
1218
beforeAll(async () => {
1319
services = createContextMapperDslServices(EmptyFileSystem)
1420
parse = parseHelper<ContextMappingModel>(services.ContextMapperDsl)
15-
16-
// activate the following if your linking test requires elements from a built-in library, for example
17-
// await services.shared.workspace.WorkspaceManager.initializeWorkspace([]);
1821
})
1922

2023
afterEach(async () => {
2124
document && await clearDocuments(services.shared, [document])
2225
})
2326

2427
describe('Bounded context linking tests', () => {
25-
test('linking of bounded contexts in context map', async () => {
26-
document = await parse(`
27-
ContextMap {
28-
TestContext [SK] <-> [SK] FirstContext
29-
}
30-
BoundedContext FirstContext
31-
BoundedContext TestContext
32-
`)
33-
34-
const errors = checkDocumentValid(document)
35-
expect(errors == null).toBeTruthy()
36-
37-
const referencedContexts: Array<string | undefined> = []
38-
39-
document.parseResult.value.contextMaps[0].relationships.forEach(r => {
40-
if (isSharedKernel(r)) {
41-
referencedContexts.push(r.participant1.ref?.name)
42-
referencedContexts.push(r.participant2.ref?.name)
43-
}
44-
})
45-
46-
expect(referencedContexts.length).toBe(2)
47-
expect(referencedContexts).toEqual(['TestContext', 'FirstContext'])
28+
test('check linking of bounded contexts in context map SharedKernel relationship', async () => {
29+
document = await parseValidInput(parse, `
30+
ContextMap {
31+
TestContext [SK] <-> [SK] FirstContext
32+
}
33+
BoundedContext FirstContext
34+
BoundedContext TestContext
35+
`)
36+
37+
const relationship = document.parseResult.value.contextMaps[0].relationships[0] as SharedKernel
38+
expect(relationship.participant1.ref).not.toBeUndefined()
39+
expect(relationship.participant1.ref?.name).toEqual('TestContext')
40+
expect(relationship.participant2.ref).not.toBeUndefined()
41+
expect(relationship.participant2.ref?.name).toEqual('FirstContext')
42+
})
43+
44+
test('check linking of bounded contexts in context map Partnership relationship', async () => {
45+
document = await parseValidInput(parse, `
46+
ContextMap {
47+
FirstContext Partnership TestContext
48+
}
49+
BoundedContext FirstContext
50+
BoundedContext TestContext
51+
`)
52+
53+
const relationship = document.parseResult.value.contextMaps[0].relationships[0] as Partnership
54+
expect(relationship.participant1.ref).not.toBeUndefined()
55+
expect(relationship.participant1.ref?.name).toEqual('FirstContext')
56+
expect(relationship.participant2.ref).not.toBeUndefined()
57+
expect(relationship.participant2.ref?.name).toEqual('TestContext')
58+
})
59+
60+
test('check linking of bounded contexts in context map UpstreamDownstream relationship', async () => {
61+
document = await parseValidInput(parse, `
62+
ContextMap {
63+
FirstContext -> TestContext
64+
}
65+
BoundedContext FirstContext
66+
BoundedContext TestContext
67+
`)
68+
69+
const relationship = document.parseResult.value.contextMaps[0].relationships[0] as UpstreamDownstreamRelationship
70+
expect(relationship.upstream.ref).not.toBeUndefined()
71+
expect(relationship.upstream.ref?.name).toEqual('FirstContext')
72+
expect(relationship.downstream.ref).not.toBeUndefined()
73+
expect(relationship.downstream.ref?.name).toEqual('TestContext')
74+
})
75+
76+
test('check linking of bounded contexts in context map CustomerSupplier relationship', async () => {
77+
document = await parseValidInput(parse, `
78+
ContextMap {
79+
FirstContext [C] <- [S] TestContext
80+
}
81+
BoundedContext FirstContext
82+
BoundedContext TestContext
83+
`)
84+
85+
const relationship = document.parseResult.value.contextMaps[0].relationships[0] as CustomerSupplierRelationship
86+
expect(relationship.downstream.ref).not.toBeUndefined()
87+
expect(relationship.downstream.ref?.name).toEqual('FirstContext')
88+
expect(relationship.upstream.ref).not.toBeUndefined()
89+
expect(relationship.upstream.ref?.name).toEqual('TestContext')
90+
})
91+
92+
test('check linking of bounded contexts in context map', async () => {
93+
document = await parseValidInput(parse, `
94+
ContextMap {
95+
contains FirstContext
96+
contains SecondContext
97+
}
98+
BoundedContext FirstContext
99+
BoundedContext SecondContext
100+
`)
101+
102+
const contextMap = document.parseResult.value.contextMaps[0]
103+
expect(contextMap.boundedContexts).toHaveLength(2)
104+
expect(contextMap.boundedContexts[0].ref).not.toBeUndefined()
105+
expect(contextMap.boundedContexts[0].ref?.name).toEqual('FirstContext')
106+
expect(contextMap.boundedContexts[1].ref).not.toBeUndefined()
107+
expect(contextMap.boundedContexts[1].ref?.name).toEqual('SecondContext')
108+
})
109+
110+
test('check linking of bounded context from bounded context', async () => {
111+
document = await parseValidInput(parse, `
112+
BoundedContext TestContext
113+
refines RefinedContext
114+
realizes RealizedContext
115+
116+
BoundedContext RealizedContext
117+
BoundedContext RefinedContext
118+
`)
119+
120+
const boundedContext = document.parseResult.value.boundedContexts[0]
121+
expect(boundedContext.refinedBoundedContext).not.toBeUndefined()
122+
expect(boundedContext.refinedBoundedContext.ref).not.toBeUndefined()
123+
expect(boundedContext.refinedBoundedContext.ref?.name).toEqual('RefinedContext')
124+
expect(boundedContext.realizedBoundedContexts).toHaveLength(1)
125+
expect(boundedContext.realizedBoundedContexts[0].ref).not.toBeUndefined()
126+
expect(boundedContext.realizedBoundedContexts[0].ref?.name).toEqual('RealizedContext')
127+
})
128+
129+
test('check linking of aggregate owner', async () => {
130+
document = await parseValidInput(parse, `
131+
BoundedContext TestContext {
132+
Aggregate TestAggregate {
133+
owner TestContext
134+
}
135+
}
136+
`)
137+
138+
const aggregate = document.parseResult.value.boundedContexts[0].aggregates[0]
139+
expect(aggregate.owner).not.toBeUndefined()
140+
expect(aggregate.owner?.ref).not.toBeUndefined()
141+
expect(aggregate.owner?.ref?.name).toEqual('TestContext')
142+
})
143+
144+
test('check linking of shareholders context', async () => {
145+
document = await parseValidInput(parse, `
146+
BoundedContext FirstContext
147+
BoundedContext SecondContext
148+
Stakeholders of SecondContext, FirstContext
149+
`)
150+
151+
const stakeholders = document.parseResult.value.stakeholders[0]
152+
expect(stakeholders.contexts).toHaveLength(2)
153+
expect(stakeholders.contexts[0]).not.toBeUndefined()
154+
expect(stakeholders.contexts[0].ref).not.toBeUndefined()
155+
expect(stakeholders.contexts[0].ref?.name).toEqual('SecondContext')
156+
expect(stakeholders.contexts[1]).not.toBeUndefined()
157+
expect(stakeholders.contexts[1].ref).not.toBeUndefined()
158+
expect(stakeholders.contexts[1].ref?.name).toEqual('FirstContext')
159+
})
160+
161+
test('check linking of ValueRegister context', async () => {
162+
document = await parseValidInput(parse, `
163+
BoundedContext SecondContext
164+
ValueRegister TestRegister for SecondContext
165+
`)
166+
167+
const valueRegister = document.parseResult.value.valueRegisters[0]
168+
expect(valueRegister.context).not.toBeUndefined()
169+
expect(valueRegister.context?.ref).not.toBeUndefined()
170+
expect(valueRegister.context?.ref?.name).toEqual('SecondContext')
48171
})
49172
})

0 commit comments

Comments
 (0)