@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.
@@ -1,35 +1,37 @@
1
- import { isNullish, isString } from 'remeda'
1
+ import { isNullish } from 'remeda'
2
2
  import { nonNullable } from '../errors'
3
- import type { ElementShape, Tag } from '../types/element'
4
- import type { Color } from '../types/theme'
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 { EdgeId, Fqn } from './types'
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
- #rootElements = new Set<LikeC4ViewModel.Element>()
17
+ private _rootElements = new Set<LikeC4ViewModel.Element>()
16
18
 
17
- #elements = new Map<Fqn, LikeC4ViewModel.Element>()
19
+ private _elements = new Map<Fqn, LikeC4ViewModel.Element>()
18
20
 
19
- #connections: Map<EdgeId, LikeC4ViewModel.Connection>
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.#elements.set(node.id, el)
29
+ this._elements.set(node.id, el)
28
30
  if (isNullish(node.parent)) {
29
- this.#rootElements.add(el)
31
+ this._rootElements.add(el)
30
32
  }
31
33
  }
32
- this.#connections = new Map(view.edges.map(e => [e.id, new LikeC4ViewModel.Connection(e, 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(): Tag[] {
52
+ get tags(): C4Tag[] {
51
53
  return this.view.tags ?? []
52
54
  }
53
55
 
54
56
  public roots(): ReadonlyArray<LikeC4ViewModel.Element> {
55
- return [...this.#rootElements]
57
+ return [...this._rootElements]
56
58
  }
57
59
 
58
60
  public elements(): ReadonlyArray<LikeC4ViewModel.Element> {
59
- return [...this.#elements.values()]
61
+ return [...this._elements.values()]
60
62
  }
61
63
 
62
64
  public element(id: Fqn): LikeC4ViewModel.Element {
63
- return nonNullable(this.#elements.get(id), `LikeC4ViewModel.Element ${id} in view ${this.view.id} not found`)
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.#elements.has(id)
69
+ return this._elements.has(id)
68
70
  }
69
71
 
70
72
  public connections(): ReadonlyArray<LikeC4ViewModel.Connection> {
71
- return [...this.#connections.values()]
73
+ return [...this._connections.values()]
72
74
  }
73
75
 
74
76
  public connection(id: EdgeId): LikeC4ViewModel.Connection {
75
- return nonNullable(this.#connections.get(id), `Connection ${id} in view ${this.view.id} not found`)
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 = isString(source) ? source : source.id
84
- const targetId = isString(target) ? target : target.id
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(isString(element) ? element : element.id)
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(isString(element) ? element : element.id)
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 = isString(element) ? element : element.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 = isString(element) ? element : element.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: 'all' | 'direct' | 'to-descendants' = 'all'
133
+ filter: IncomingFilter = 'all'
132
134
  ): ReadonlyArray<LikeC4ViewModel.Connection> {
133
- const el = this.element(isString(element) ? element : element.id)
134
- const edges = el.node.inEdges.map(e => nonNullable(this.#connections.get(e), `Edge ${e} not found`))
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: 'all' | 'direct' | 'to-descendants' = 'all'
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: 'all' | 'direct' | 'from-descendants' = 'all'
162
+ filter: OutgoingFilter = 'all'
161
163
  ): ReadonlyArray<LikeC4ViewModel.Connection> {
162
- const el = this.element(isString(element) ? element : element.id)
163
- const edges = el.node.outEdges.map(e => nonNullable(this.#connections.get(e), `Edge ${e} not found`))
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: 'all' | 'direct' | 'from-descendants' = 'all'
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(): ElementShape {
219
+ get shape(): C4ElementShape {
218
220
  return this.node.shape
219
221
  }
220
222
 
221
- get color(): Color {
223
+ get color(): C4Color {
222
224
  return this.node.color
223
225
  }
224
226
 
225
- get tags(): Tag[] {
227
+ get tags(): C4Tag[] {
226
228
  return this.node.tags ?? []
227
229
  }
228
230
 
229
- public model(): LikeC4Model.Element {
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: 'all' | 'direct' | 'from-descendants' = 'all'): ReadonlyArray<LikeC4ViewModel.Connection> {
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: 'all' | 'direct' | 'from-descendants' = 'all'): ReadonlyArray<LikeC4ViewModel.Element> {
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(): Tag[] {
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
  }
@@ -1,2 +1,3 @@
1
- export * from './LikeC4Model'
2
- export * from './LikeC4ViewModel'
1
+ export { LikeC4DiagramModel } from './LikeC4DiagramModel'
2
+ export { LikeC4Model } from './LikeC4Model'
3
+ export { LikeC4ViewModel } from './LikeC4ViewModel'
@@ -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
+ }
@@ -9,5 +9,3 @@ export * from './theme'
9
9
  export * from './view'
10
10
  export * from './view-changes'
11
11
  export * from './view-notation'
12
-
13
- export * as Expr from './expression'
@@ -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
+ }
@@ -1,9 +1,20 @@
1
+ import { randomInteger } from 'remeda'
2
+
1
3
  const DELAY = 'LIKEC4_DELAY'
2
4
 
3
- export function delay(ms: number = 100) {
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
- })
@@ -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
- })