@likec4/core 1.10.1 → 1.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +406 -120
- package/dist/index.d.cts +202 -242
- package/dist/index.d.mts +202 -242
- package/dist/index.d.ts +202 -242
- package/dist/index.mjs +407 -122
- package/dist/shared/{core.DeifT5lC.mjs → core.CUlHblBQ.mjs} +5 -5
- package/dist/shared/{core.Czd51Dd8.d.cts → core.aVt8heZL.d.cts} +198 -354
- package/dist/shared/{core.Czd51Dd8.d.mts → core.aVt8heZL.d.mts} +198 -354
- package/dist/shared/{core.Czd51Dd8.d.ts → core.aVt8heZL.d.ts} +198 -354
- package/dist/types/index.cjs +1 -2
- package/dist/types/index.d.cts +2 -1
- package/dist/types/index.d.mts +2 -1
- package/dist/types/index.d.ts +2 -1
- package/dist/types/index.mjs +1 -1
- package/package.json +10 -6
- package/src/index.ts +2 -0
- package/src/model/LikeC4DiagramModel.ts +338 -0
- package/src/model/LikeC4Model.ts +227 -145
- package/src/model/LikeC4ViewModel.ts +45 -43
- package/src/model/index.ts +3 -2
- package/src/model/types.ts +13 -0
- package/src/types/index.ts +0 -2
- package/src/types/model.ts +10 -1
- package/src/utils/promises.ts +13 -2
- package/src/model/__test__/LikeC4Model.spec.ts +0 -79
- package/src/utils/compare-natural.spec.ts +0 -37
- package/src/utils/fqn.spec.ts +0 -202
- package/src/utils/relations.spec.ts +0 -266
- package/dist/shared/{core.Db3ntVII.cjs → core.D78YmZ_A.cjs} +4 -4
|
@@ -1,35 +1,37 @@
|
|
|
1
|
-
import { isNullish
|
|
1
|
+
import { isNullish } from 'remeda'
|
|
2
2
|
import { nonNullable } from '../errors'
|
|
3
|
-
import
|
|
4
|
-
|
|
3
|
+
import {
|
|
4
|
+
type ElementKind as C4ElementKind,
|
|
5
|
+
type ElementShape as C4ElementShape,
|
|
6
|
+
type Tag as C4Tag
|
|
7
|
+
} from '../types/element'
|
|
8
|
+
import type { Color as C4Color } from '../types/theme'
|
|
5
9
|
import type { ComputedEdge, ComputedNode, ComputedView } from '../types/view'
|
|
6
10
|
import type { LikeC4Model } from './LikeC4Model'
|
|
7
|
-
import type
|
|
8
|
-
|
|
9
|
-
type ElementOrFqn = Fqn | LikeC4ViewModel.Element
|
|
11
|
+
import { type EdgeId, type ElementOrFqn, type Fqn, getId, type IncomingFilter, type OutgoingFilter } from './types'
|
|
10
12
|
|
|
11
13
|
/**
|
|
12
14
|
* All methods are view-scoped, i.e. only return elements and connections in the view.
|
|
13
15
|
*/
|
|
14
16
|
export class LikeC4ViewModel {
|
|
15
|
-
|
|
17
|
+
private _rootElements = new Set<LikeC4ViewModel.Element>()
|
|
16
18
|
|
|
17
|
-
|
|
19
|
+
private _elements = new Map<Fqn, LikeC4ViewModel.Element>()
|
|
18
20
|
|
|
19
|
-
|
|
21
|
+
private _connections: Map<EdgeId, LikeC4ViewModel.Connection>
|
|
20
22
|
|
|
21
23
|
constructor(
|
|
22
24
|
public readonly view: ComputedView,
|
|
23
|
-
public readonly model: LikeC4Model
|
|
25
|
+
public readonly model: LikeC4Model<LikeC4ViewModel>
|
|
24
26
|
) {
|
|
25
27
|
for (const node of view.nodes) {
|
|
26
28
|
const el = new LikeC4ViewModel.Element(node, this)
|
|
27
|
-
this
|
|
29
|
+
this._elements.set(node.id, el)
|
|
28
30
|
if (isNullish(node.parent)) {
|
|
29
|
-
this
|
|
31
|
+
this._rootElements.add(el)
|
|
30
32
|
}
|
|
31
33
|
}
|
|
32
|
-
this
|
|
34
|
+
this._connections = new Map(view.edges.map(e => [e.id, new LikeC4ViewModel.Connection(e, this)]))
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
get id() {
|
|
@@ -47,32 +49,32 @@ export class LikeC4ViewModel {
|
|
|
47
49
|
return null
|
|
48
50
|
}
|
|
49
51
|
|
|
50
|
-
get tags():
|
|
52
|
+
get tags(): C4Tag[] {
|
|
51
53
|
return this.view.tags ?? []
|
|
52
54
|
}
|
|
53
55
|
|
|
54
56
|
public roots(): ReadonlyArray<LikeC4ViewModel.Element> {
|
|
55
|
-
return [...this
|
|
57
|
+
return [...this._rootElements]
|
|
56
58
|
}
|
|
57
59
|
|
|
58
60
|
public elements(): ReadonlyArray<LikeC4ViewModel.Element> {
|
|
59
|
-
return [...this
|
|
61
|
+
return [...this._elements.values()]
|
|
60
62
|
}
|
|
61
63
|
|
|
62
64
|
public element(id: Fqn): LikeC4ViewModel.Element {
|
|
63
|
-
return nonNullable(this
|
|
65
|
+
return nonNullable(this._elements.get(id), `LikeC4ViewModel.Element ${id} in view ${this.id} not found`)
|
|
64
66
|
}
|
|
65
67
|
|
|
66
68
|
public hasElement(id: Fqn): boolean {
|
|
67
|
-
return this
|
|
69
|
+
return this._elements.has(id)
|
|
68
70
|
}
|
|
69
71
|
|
|
70
72
|
public connections(): ReadonlyArray<LikeC4ViewModel.Connection> {
|
|
71
|
-
return [...this
|
|
73
|
+
return [...this._connections.values()]
|
|
72
74
|
}
|
|
73
75
|
|
|
74
76
|
public connection(id: EdgeId): LikeC4ViewModel.Connection {
|
|
75
|
-
return nonNullable(this
|
|
77
|
+
return nonNullable(this._connections.get(id), `Connection ${id} in view ${this.id} not found`)
|
|
76
78
|
}
|
|
77
79
|
|
|
78
80
|
public findConnections(
|
|
@@ -80,8 +82,8 @@ export class LikeC4ViewModel {
|
|
|
80
82
|
target: ElementOrFqn,
|
|
81
83
|
direction: 'both' | 'direct' = 'direct'
|
|
82
84
|
): ReadonlyArray<LikeC4ViewModel.Connection> {
|
|
83
|
-
const sourceId =
|
|
84
|
-
const targetId =
|
|
85
|
+
const sourceId = getId(source)
|
|
86
|
+
const targetId = getId(target)
|
|
85
87
|
return this.connections().filter(c =>
|
|
86
88
|
(c.source.id === sourceId && c.target.id === targetId)
|
|
87
89
|
|| (direction === 'both' && c.source.id === targetId && c.target.id === sourceId)
|
|
@@ -89,18 +91,18 @@ export class LikeC4ViewModel {
|
|
|
89
91
|
}
|
|
90
92
|
|
|
91
93
|
public parent(element: ElementOrFqn): LikeC4ViewModel.Element | null {
|
|
92
|
-
const el = this.element(
|
|
94
|
+
const el = this.element(getId(element))
|
|
93
95
|
return el.node.parent ? this.element(el.node.parent) : null
|
|
94
96
|
}
|
|
95
97
|
|
|
96
98
|
public children(element: ElementOrFqn): ReadonlyArray<LikeC4ViewModel.Element> {
|
|
97
|
-
const el = this.element(
|
|
99
|
+
const el = this.element(getId(element))
|
|
98
100
|
return el.node.children.map(c => this.element(c))
|
|
99
101
|
}
|
|
100
102
|
|
|
101
103
|
// Get all sibling (i.e. same parent)
|
|
102
104
|
public siblings(element: ElementOrFqn): ReadonlyArray<LikeC4ViewModel.Element> {
|
|
103
|
-
const id =
|
|
105
|
+
const id = getId(element)
|
|
104
106
|
const parent = this.parent(id)
|
|
105
107
|
const siblings = parent ? this.children(parent) : this.roots()
|
|
106
108
|
return siblings.filter(e => e.id !== id)
|
|
@@ -111,7 +113,7 @@ export class LikeC4ViewModel {
|
|
|
111
113
|
* (from closest to root)
|
|
112
114
|
*/
|
|
113
115
|
public ancestors(element: ElementOrFqn): ReadonlyArray<LikeC4ViewModel.Element> {
|
|
114
|
-
let id =
|
|
116
|
+
let id = getId(element)
|
|
115
117
|
const result = [] as LikeC4ViewModel.Element[]
|
|
116
118
|
let parent
|
|
117
119
|
while (parent = this.parent(id)) {
|
|
@@ -128,10 +130,10 @@ export class LikeC4ViewModel {
|
|
|
128
130
|
|
|
129
131
|
public incoming(
|
|
130
132
|
element: ElementOrFqn,
|
|
131
|
-
filter:
|
|
133
|
+
filter: IncomingFilter = 'all'
|
|
132
134
|
): ReadonlyArray<LikeC4ViewModel.Connection> {
|
|
133
|
-
const el = this.element(
|
|
134
|
-
const edges = el.node.inEdges.map(e => nonNullable(this
|
|
135
|
+
const el = this.element(getId(element))
|
|
136
|
+
const edges = el.node.inEdges.map(e => nonNullable(this._connections.get(e), `Edge ${e} not found`))
|
|
135
137
|
|
|
136
138
|
if (filter === 'all' || edges.length === 0) {
|
|
137
139
|
return edges
|
|
@@ -147,7 +149,7 @@ export class LikeC4ViewModel {
|
|
|
147
149
|
|
|
148
150
|
public incomers(
|
|
149
151
|
element: ElementOrFqn,
|
|
150
|
-
filter:
|
|
152
|
+
filter: IncomingFilter = 'all'
|
|
151
153
|
): ReadonlyArray<LikeC4ViewModel.Element> {
|
|
152
154
|
return this.incoming(element, filter).map(r => r.source)
|
|
153
155
|
}
|
|
@@ -157,10 +159,10 @@ export class LikeC4ViewModel {
|
|
|
157
159
|
*/
|
|
158
160
|
public outgoing(
|
|
159
161
|
element: ElementOrFqn,
|
|
160
|
-
filter:
|
|
162
|
+
filter: OutgoingFilter = 'all'
|
|
161
163
|
): ReadonlyArray<LikeC4ViewModel.Connection> {
|
|
162
|
-
const el = this.element(
|
|
163
|
-
const edges = el.node.outEdges.map(e => nonNullable(this
|
|
164
|
+
const el = this.element(getId(element))
|
|
165
|
+
const edges = el.node.outEdges.map(e => nonNullable(this._connections.get(e), `Edge ${e} not found`))
|
|
164
166
|
|
|
165
167
|
if (filter === 'all' || edges.length === 0) {
|
|
166
168
|
return edges
|
|
@@ -176,7 +178,7 @@ export class LikeC4ViewModel {
|
|
|
176
178
|
|
|
177
179
|
public outgoers(
|
|
178
180
|
element: ElementOrFqn,
|
|
179
|
-
filter:
|
|
181
|
+
filter: OutgoingFilter = 'all'
|
|
180
182
|
): ReadonlyArray<LikeC4ViewModel.Element> {
|
|
181
183
|
return this.outgoing(element, filter).map(r => r.target)
|
|
182
184
|
}
|
|
@@ -202,7 +204,7 @@ export namespace LikeC4ViewModel {
|
|
|
202
204
|
return this.node.title
|
|
203
205
|
}
|
|
204
206
|
|
|
205
|
-
get kind() {
|
|
207
|
+
get kind(): C4ElementKind {
|
|
206
208
|
return this.node.kind
|
|
207
209
|
}
|
|
208
210
|
|
|
@@ -214,19 +216,19 @@ export namespace LikeC4ViewModel {
|
|
|
214
216
|
return this.node.children.length > 0
|
|
215
217
|
}
|
|
216
218
|
|
|
217
|
-
get shape():
|
|
219
|
+
get shape(): C4ElementShape {
|
|
218
220
|
return this.node.shape
|
|
219
221
|
}
|
|
220
222
|
|
|
221
|
-
get color():
|
|
223
|
+
get color(): C4Color {
|
|
222
224
|
return this.node.color
|
|
223
225
|
}
|
|
224
226
|
|
|
225
|
-
get tags():
|
|
227
|
+
get tags(): C4Tag[] {
|
|
226
228
|
return this.node.tags ?? []
|
|
227
229
|
}
|
|
228
230
|
|
|
229
|
-
public model(): LikeC4Model.
|
|
231
|
+
public model(): LikeC4Model.ElementModel<LikeC4ViewModel> {
|
|
230
232
|
return this.viewmodel.model.element(this.id)
|
|
231
233
|
}
|
|
232
234
|
|
|
@@ -268,11 +270,11 @@ export namespace LikeC4ViewModel {
|
|
|
268
270
|
return this.viewmodel.incomers(this, filter)
|
|
269
271
|
}
|
|
270
272
|
|
|
271
|
-
public outgoing(filter:
|
|
273
|
+
public outgoing(filter: OutgoingFilter = 'all'): ReadonlyArray<LikeC4ViewModel.Connection> {
|
|
272
274
|
return this.viewmodel.outgoing(this, filter)
|
|
273
275
|
}
|
|
274
276
|
|
|
275
|
-
public outgoers(filter:
|
|
277
|
+
public outgoers(filter: OutgoingFilter = 'all'): ReadonlyArray<LikeC4ViewModel.Element> {
|
|
276
278
|
return this.viewmodel.outgoers(this, filter)
|
|
277
279
|
}
|
|
278
280
|
|
|
@@ -304,14 +306,14 @@ export namespace LikeC4ViewModel {
|
|
|
304
306
|
return this.viewmodel.element(this.edge.target)
|
|
305
307
|
}
|
|
306
308
|
|
|
307
|
-
get tags():
|
|
309
|
+
get tags(): C4Tag[] {
|
|
308
310
|
return this.edge.tags ?? []
|
|
309
311
|
}
|
|
310
312
|
|
|
311
313
|
/**
|
|
312
314
|
* Model relationships
|
|
313
315
|
*/
|
|
314
|
-
relationships(): ReadonlyArray<LikeC4Model.Relationship
|
|
316
|
+
relationships(): ReadonlyArray<LikeC4Model.Relationship<LikeC4ViewModel>> {
|
|
315
317
|
return this.edge.relations.map(r => nonNullable(this.viewmodel.model.relationship(r)))
|
|
316
318
|
}
|
|
317
319
|
}
|
package/src/model/index.ts
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
export
|
|
2
|
-
export
|
|
1
|
+
export { LikeC4DiagramModel } from './LikeC4DiagramModel'
|
|
2
|
+
export { LikeC4Model } from './LikeC4Model'
|
|
3
|
+
export { LikeC4ViewModel } from './LikeC4ViewModel'
|
package/src/model/types.ts
CHANGED
|
@@ -2,9 +2,22 @@ import type { Fqn as CoreFqn } from '../types/element'
|
|
|
2
2
|
import type { RelationID as CoreRelationID } from '../types/relation'
|
|
3
3
|
import type { EdgeId as CoreEdgeId, ViewID as CoreViewID } from '../types/view'
|
|
4
4
|
|
|
5
|
+
import { isString } from 'remeda'
|
|
5
6
|
import type { LiteralUnion } from 'type-fest'
|
|
6
7
|
|
|
7
8
|
export type Fqn = LiteralUnion<CoreFqn, string>
|
|
8
9
|
export type RelationID = LiteralUnion<CoreRelationID, string>
|
|
9
10
|
export type ViewID = LiteralUnion<CoreViewID, string>
|
|
10
11
|
export type EdgeId = LiteralUnion<CoreEdgeId, string>
|
|
12
|
+
|
|
13
|
+
export type IncomingFilter = 'all' | 'direct' | 'to-descendants'
|
|
14
|
+
export type OutgoingFilter = 'all' | 'direct' | 'from-descendants'
|
|
15
|
+
|
|
16
|
+
export type ElementOrFqn = Fqn | { id: Fqn }
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Utility function to extract `id` from the given element.
|
|
20
|
+
*/
|
|
21
|
+
export function getId(element: ElementOrFqn): Fqn {
|
|
22
|
+
return isString(element) ? element : element.id
|
|
23
|
+
}
|
package/src/types/index.ts
CHANGED
package/src/types/model.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { UnwrapTagged } from 'type-fest'
|
|
2
2
|
import type { Element, ElementKind, ElementKindSpecification, Fqn, Tag } from './element'
|
|
3
3
|
import type { Relation, RelationID, RelationshipKindSpecification } from './relation'
|
|
4
|
-
import type { ComputedView, LikeC4View, ViewID } from './view'
|
|
4
|
+
import type { ComputedView, DiagramView, LikeC4View, ViewID } from './view'
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Parsed elements, relations, and views.
|
|
@@ -21,5 +21,14 @@ export interface ParsedLikeC4Model {
|
|
|
21
21
|
* Same as `ParsedLikeC4Model` but with computed views.
|
|
22
22
|
*/
|
|
23
23
|
export interface ComputedLikeC4Model extends Omit<ParsedLikeC4Model, 'views'> {
|
|
24
|
+
__?: never
|
|
24
25
|
views: Record<ViewID, ComputedView>
|
|
25
26
|
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Same as `ParsedLikeC4Model` but with layouted views (DiagramView)
|
|
30
|
+
*/
|
|
31
|
+
export interface LayoutedLikeC4Model extends Omit<ParsedLikeC4Model, 'views'> {
|
|
32
|
+
__: 'layouted'
|
|
33
|
+
views: Record<ViewID, DiagramView>
|
|
34
|
+
}
|
package/src/utils/promises.ts
CHANGED
|
@@ -1,9 +1,20 @@
|
|
|
1
|
+
import { randomInteger } from 'remeda'
|
|
2
|
+
|
|
1
3
|
const DELAY = 'LIKEC4_DELAY'
|
|
2
4
|
|
|
3
|
-
export function delay(
|
|
5
|
+
export function delay(): Promise<string>
|
|
6
|
+
export function delay(ms: number): Promise<string>
|
|
7
|
+
export function delay(randomFrom: number, randomTo: number): Promise<string>
|
|
8
|
+
export function delay(...args: number[]): Promise<string> {
|
|
9
|
+
let ms = 100
|
|
10
|
+
if (args.length === 2) {
|
|
11
|
+
ms = randomInteger(args[0]!, args[1]!)
|
|
12
|
+
} else if (args.length === 1) {
|
|
13
|
+
ms = args[0]!
|
|
14
|
+
}
|
|
4
15
|
return new Promise(resolve => {
|
|
5
16
|
setTimeout(() => {
|
|
6
17
|
resolve(DELAY)
|
|
7
|
-
}, ms)
|
|
18
|
+
}, ms ?? 100)
|
|
8
19
|
})
|
|
9
20
|
}
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import { prop } from 'remeda'
|
|
2
|
-
import { describe, expect, it } from 'vitest'
|
|
3
|
-
import { LikeC4Model } from '../LikeC4Model'
|
|
4
|
-
import { fakeComputedModel, fakeElements } from './fixture'
|
|
5
|
-
|
|
6
|
-
describe('LikeC4Model', () => {
|
|
7
|
-
const model = LikeC4Model.from(fakeComputedModel)
|
|
8
|
-
|
|
9
|
-
it('roots', () => {
|
|
10
|
-
expect(model.roots().map(prop('id'))).toEqual([
|
|
11
|
-
'customer',
|
|
12
|
-
'support',
|
|
13
|
-
'cloud',
|
|
14
|
-
'email',
|
|
15
|
-
'amazon'
|
|
16
|
-
])
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
it('parent and chilren', () => {
|
|
20
|
-
const parent = model.parent('cloud.backend.graphql')!
|
|
21
|
-
expect(parent.id).toEqual('cloud.backend')
|
|
22
|
-
expect(parent.element).toStrictEqual(fakeElements['cloud.backend'])
|
|
23
|
-
|
|
24
|
-
const children = parent.children()
|
|
25
|
-
|
|
26
|
-
expect(children.map(prop('id'))).toEqual(['cloud.backend.graphql', 'cloud.backend.storage'])
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
it('ancestors in right order', () => {
|
|
30
|
-
const ancestors = model.element('cloud.frontend.dashboard').ancestors()
|
|
31
|
-
expect(ancestors).toHaveLength(2)
|
|
32
|
-
expect(ancestors[0]).toMatchObject({
|
|
33
|
-
id: 'cloud.frontend',
|
|
34
|
-
element: fakeElements['cloud.frontend']
|
|
35
|
-
})
|
|
36
|
-
expect(ancestors[1]).toMatchObject({
|
|
37
|
-
id: 'cloud',
|
|
38
|
-
element: fakeElements['cloud']
|
|
39
|
-
})
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
it('siblings of root', () => {
|
|
43
|
-
const siblings = model.element('cloud').siblings()
|
|
44
|
-
expect(siblings.map(prop('id'))).toEqual([
|
|
45
|
-
'customer',
|
|
46
|
-
'support',
|
|
47
|
-
'email',
|
|
48
|
-
'amazon'
|
|
49
|
-
])
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
it('siblings', () => {
|
|
53
|
-
const backend = model.element('cloud.backend')
|
|
54
|
-
|
|
55
|
-
const siblings = backend.siblings()
|
|
56
|
-
|
|
57
|
-
expect(siblings.map(prop('id'))).toEqual(['cloud.frontend'])
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
it('descendants in right order', () => {
|
|
61
|
-
const descendants = model.element('cloud').descendants().map(prop('id'))
|
|
62
|
-
expect(descendants).toEqual([
|
|
63
|
-
'cloud.backend',
|
|
64
|
-
'cloud.backend.graphql',
|
|
65
|
-
'cloud.backend.storage',
|
|
66
|
-
'cloud.frontend',
|
|
67
|
-
'cloud.frontend.adminPanel',
|
|
68
|
-
'cloud.frontend.dashboard'
|
|
69
|
-
])
|
|
70
|
-
})
|
|
71
|
-
|
|
72
|
-
it('internal relations', () => {
|
|
73
|
-
expect(
|
|
74
|
-
model.internal('cloud.backend').map(prop('id'))
|
|
75
|
-
).toEqual([
|
|
76
|
-
'cloud.backend.graphql:cloud.backend.storage'
|
|
77
|
-
])
|
|
78
|
-
})
|
|
79
|
-
})
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { sort as remedaSort } from 'remeda'
|
|
2
|
-
import { describe, expect, it } from 'vitest'
|
|
3
|
-
import { compareNatural } from './compare-natural'
|
|
4
|
-
|
|
5
|
-
describe('compareNatural', () => {
|
|
6
|
-
const sort = (array: Array<string | undefined>) => remedaSort(array, compareNatural)
|
|
7
|
-
|
|
8
|
-
it('should move undefined to the end', () => {
|
|
9
|
-
expect(
|
|
10
|
-
sort([
|
|
11
|
-
undefined,
|
|
12
|
-
'apple',
|
|
13
|
-
undefined,
|
|
14
|
-
'banana'
|
|
15
|
-
])
|
|
16
|
-
).toEqual([
|
|
17
|
-
'apple',
|
|
18
|
-
'banana',
|
|
19
|
-
undefined,
|
|
20
|
-
undefined
|
|
21
|
-
])
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
it('should sort array of strings', () => {
|
|
25
|
-
expect(sort([
|
|
26
|
-
'apple1',
|
|
27
|
-
'apple11',
|
|
28
|
-
'apple2',
|
|
29
|
-
'apple10'
|
|
30
|
-
])).toEqual([
|
|
31
|
-
'apple1',
|
|
32
|
-
'apple2',
|
|
33
|
-
'apple10',
|
|
34
|
-
'apple11'
|
|
35
|
-
])
|
|
36
|
-
})
|
|
37
|
-
})
|
package/src/utils/fqn.spec.ts
DELETED
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
import { prop } from 'remeda'
|
|
2
|
-
import { describe, expect, it } from 'vitest'
|
|
3
|
-
import type { Element, Fqn } from '../types'
|
|
4
|
-
import {
|
|
5
|
-
ancestorsFqn,
|
|
6
|
-
commonAncestor,
|
|
7
|
-
compareFqnHierarchically,
|
|
8
|
-
isAncestor,
|
|
9
|
-
isDescendantOf,
|
|
10
|
-
notDescendantOf,
|
|
11
|
-
parentFqn,
|
|
12
|
-
sortByFqnHierarchically,
|
|
13
|
-
sortNaturalByFqn
|
|
14
|
-
} from './fqn'
|
|
15
|
-
|
|
16
|
-
const el = (id: string): Element => ({ id }) as unknown as Element
|
|
17
|
-
|
|
18
|
-
describe('parentFqn', () => {
|
|
19
|
-
it('should return null if no parent', () => {
|
|
20
|
-
expect(parentFqn('a' as Fqn)).toBeNull()
|
|
21
|
-
})
|
|
22
|
-
it('should return parent', () => {
|
|
23
|
-
expect(parentFqn('a.b' as Fqn)).toBe('a')
|
|
24
|
-
expect(parentFqn('a.b.c' as Fqn)).toBe('a.b')
|
|
25
|
-
})
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
describe('ancestorsFqn', () => {
|
|
29
|
-
it('should return empty array if no parent', () => {
|
|
30
|
-
expect(ancestorsFqn('a' as Fqn)).toEqual([])
|
|
31
|
-
})
|
|
32
|
-
it('should return ancestors', () => {
|
|
33
|
-
expect(ancestorsFqn('a.b.c.d.e' as Fqn)).toEqual(['a.b.c.d', 'a.b.c', 'a.b', 'a'])
|
|
34
|
-
})
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
describe('commonAncestor', () => {
|
|
38
|
-
it('should return null if no common ancestor', () => {
|
|
39
|
-
expect(commonAncestor('a' as Fqn, 'b' as Fqn)).toBeNull()
|
|
40
|
-
expect(commonAncestor('a.b' as Fqn, 'c.d' as Fqn)).toBeNull()
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
it('should return common ancestor', () => {
|
|
44
|
-
expect(commonAncestor('a.b' as Fqn, 'a.c' as Fqn)).toBe('a')
|
|
45
|
-
expect(commonAncestor('a.b.c' as Fqn, 'a.b.e' as Fqn)).toBe('a.b')
|
|
46
|
-
expect(commonAncestor('a.b.c.d.e' as Fqn, 'a.b.c.d' as Fqn)).toBe('a.b.c')
|
|
47
|
-
})
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
describe('isAncestor', () => {
|
|
51
|
-
it('should return true if ancestor', () => {
|
|
52
|
-
expect(isAncestor('a' as Fqn, 'a.b' as Fqn)).toBe(true)
|
|
53
|
-
expect(isAncestor('a.b' as Fqn, 'a.b.c' as Fqn)).toBe(true)
|
|
54
|
-
})
|
|
55
|
-
it('should return false if not ancestor', () => {
|
|
56
|
-
expect(isAncestor('a' as Fqn, 'b' as Fqn)).toBe(false)
|
|
57
|
-
expect(isAncestor('a.b' as Fqn, 'a' as Fqn)).toBe(false)
|
|
58
|
-
expect(isAncestor('a.b' as Fqn, 'b.a' as Fqn)).toBe(false)
|
|
59
|
-
})
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
describe('isDescendantOf', () => {
|
|
63
|
-
const predicate = isDescendantOf(['a', 'b', 'a.b', 'a.b.c'].map(el))
|
|
64
|
-
|
|
65
|
-
it('should return true if isDescendantOf', () => {
|
|
66
|
-
expect(predicate(el('a'))).toBe(true)
|
|
67
|
-
expect(predicate(el('b.c'))).toBe(true)
|
|
68
|
-
expect(predicate(el('a.b.c.d.e'))).toBe(true)
|
|
69
|
-
})
|
|
70
|
-
it('should return false if not descendantOf', () => {
|
|
71
|
-
expect(predicate(el('c'))).toBe(false)
|
|
72
|
-
expect(predicate(el('ac'))).toBe(false)
|
|
73
|
-
expect(predicate(el('d.a.c'))).toBe(false)
|
|
74
|
-
})
|
|
75
|
-
})
|
|
76
|
-
|
|
77
|
-
describe('notDescendantOf', () => {
|
|
78
|
-
const predicate = notDescendantOf(['a', 'b', 'a.b', 'a.b.c'].map(el))
|
|
79
|
-
|
|
80
|
-
it('should return true if notDescendantOf', () => {
|
|
81
|
-
expect(predicate(el('c'))).toBe(true)
|
|
82
|
-
expect(predicate(el('ac'))).toBe(true)
|
|
83
|
-
expect(predicate(el('d.a.c'))).toBe(true)
|
|
84
|
-
})
|
|
85
|
-
it('should return false if descendantOf', () => {
|
|
86
|
-
expect(predicate(el('a'))).toBe(false)
|
|
87
|
-
expect(predicate(el('b.c'))).toBe(false)
|
|
88
|
-
expect(predicate(el('a.b.c.d.e'))).toBe(false)
|
|
89
|
-
})
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
describe('compareFqnHierarchically', () => {
|
|
93
|
-
it('should compare hierarchically', () => {
|
|
94
|
-
expect([
|
|
95
|
-
'a',
|
|
96
|
-
'b',
|
|
97
|
-
'a.b',
|
|
98
|
-
'a.b.c',
|
|
99
|
-
'a.c.c'
|
|
100
|
-
].sort(compareFqnHierarchically)).toEqual([
|
|
101
|
-
'a',
|
|
102
|
-
'b',
|
|
103
|
-
'a.b',
|
|
104
|
-
'a.b.c',
|
|
105
|
-
'a.c.c'
|
|
106
|
-
])
|
|
107
|
-
})
|
|
108
|
-
|
|
109
|
-
it('should preserve initial order', () => {
|
|
110
|
-
expect(
|
|
111
|
-
['aaa', 'aa', 'a', 'aaa.c', 'aa.b', 'a.c', 'a.b'].sort(compareFqnHierarchically)
|
|
112
|
-
).toEqual(['aaa', 'aa', 'a', 'aaa.c', 'aa.b', 'a.c', 'a.b'])
|
|
113
|
-
})
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
describe('sortNaturalByFqn', () => {
|
|
117
|
-
it('should sort hierarchically', () => {
|
|
118
|
-
expect(
|
|
119
|
-
sortNaturalByFqn([
|
|
120
|
-
el('a.b'),
|
|
121
|
-
el('a'),
|
|
122
|
-
el('a.c.a.b'),
|
|
123
|
-
el('a.c.c'),
|
|
124
|
-
el('b'),
|
|
125
|
-
el('a.b.c')
|
|
126
|
-
]).map(prop('id'))
|
|
127
|
-
).toEqual([
|
|
128
|
-
'a',
|
|
129
|
-
'b',
|
|
130
|
-
'a.b',
|
|
131
|
-
'a.b.c',
|
|
132
|
-
'a.c.c',
|
|
133
|
-
'a.c.a.b'
|
|
134
|
-
])
|
|
135
|
-
})
|
|
136
|
-
|
|
137
|
-
it('should sort natural', () => {
|
|
138
|
-
expect(
|
|
139
|
-
sortNaturalByFqn([
|
|
140
|
-
el('b'),
|
|
141
|
-
el('a.c.c'),
|
|
142
|
-
el('a'),
|
|
143
|
-
el('a.b1'),
|
|
144
|
-
el('a.b2'),
|
|
145
|
-
el('a.b10'),
|
|
146
|
-
el('a.b2.c')
|
|
147
|
-
]).map(prop('id'))
|
|
148
|
-
).toEqual([
|
|
149
|
-
'a',
|
|
150
|
-
'b',
|
|
151
|
-
'a.b1',
|
|
152
|
-
'a.b2',
|
|
153
|
-
'a.b10',
|
|
154
|
-
'a.b2.c',
|
|
155
|
-
'a.c.c'
|
|
156
|
-
])
|
|
157
|
-
})
|
|
158
|
-
})
|
|
159
|
-
|
|
160
|
-
describe('sortByFqnHierarchically', () => {
|
|
161
|
-
it('should sort hierarchically', () => {
|
|
162
|
-
expect(
|
|
163
|
-
sortByFqnHierarchically([
|
|
164
|
-
el('a.b'),
|
|
165
|
-
el('a'),
|
|
166
|
-
el('a.b.c'),
|
|
167
|
-
el('a.c.a.b'),
|
|
168
|
-
el('a.c.c'),
|
|
169
|
-
el('b')
|
|
170
|
-
]).map(prop('id'))
|
|
171
|
-
).toEqual([
|
|
172
|
-
'a',
|
|
173
|
-
'b',
|
|
174
|
-
'a.b',
|
|
175
|
-
'a.b.c',
|
|
176
|
-
'a.c.c',
|
|
177
|
-
'a.c.a.b'
|
|
178
|
-
])
|
|
179
|
-
})
|
|
180
|
-
|
|
181
|
-
it('should preserve initial order', () => {
|
|
182
|
-
expect(
|
|
183
|
-
sortByFqnHierarchically([
|
|
184
|
-
el('b'),
|
|
185
|
-
el('a.c.c'),
|
|
186
|
-
el('a'),
|
|
187
|
-
el('a.b10'),
|
|
188
|
-
el('a.b2.c'),
|
|
189
|
-
el('a.b1'),
|
|
190
|
-
el('a.b2')
|
|
191
|
-
]).map(prop('id'))
|
|
192
|
-
).toEqual([
|
|
193
|
-
'b',
|
|
194
|
-
'a',
|
|
195
|
-
'a.b10',
|
|
196
|
-
'a.b1',
|
|
197
|
-
'a.b2',
|
|
198
|
-
'a.c.c',
|
|
199
|
-
'a.b2.c'
|
|
200
|
-
])
|
|
201
|
-
})
|
|
202
|
-
})
|