coh-content-db 2.0.0-rc.9 → 2.0.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/.github/workflows/build.yml +3 -1
- package/CHANGELOG.md +11 -2
- package/README.md +33 -19
- package/dist/coh-content-db.d.ts +230 -170
- package/dist/coh-content-db.js +495 -296
- package/dist/coh-content-db.js.map +1 -1
- package/dist/coh-content-db.mjs +488 -294
- package/dist/coh-content-db.mjs.map +1 -1
- package/jest.config.mjs +1 -0
- package/package.json +14 -14
- package/src/main/api/badge-data.ts +13 -7
- package/src/main/api/bundle-data.ts +1 -1
- package/src/main/api/bundle-header-data.ts +13 -6
- package/src/main/api/contact-data.ts +2 -1
- package/src/main/api/level-range-data.ts +4 -0
- package/src/main/api/mission-data.ts +3 -29
- package/src/main/api/mission-flashback-data.ts +31 -0
- package/src/main/api/morality.ts +27 -9
- package/src/main/api/set-title-data.ts +4 -0
- package/src/main/api/variant-context.ts +11 -0
- package/src/main/api/{alternate-data.ts → variant-data.ts} +4 -4
- package/src/main/api/zone-data.ts +24 -0
- package/src/main/api/zone-type.ts +59 -0
- package/src/main/db/abstract-index.ts +12 -16
- package/src/main/db/badge-index.ts +53 -27
- package/src/main/db/badge-requirement.ts +1 -1
- package/src/main/db/badge-search-options.ts +15 -14
- package/src/main/db/badge.ts +46 -29
- package/src/main/db/bundle-header.ts +18 -10
- package/src/main/db/coh-content-database.ts +17 -17
- package/src/main/db/contact.ts +5 -4
- package/src/main/db/level-range.ts +15 -0
- package/src/main/db/mission.ts +11 -10
- package/src/main/db/paged.ts +7 -3
- package/src/main/db/set-title-ids.ts +10 -0
- package/src/main/db/variants.ts +84 -0
- package/src/main/db/zone.ts +29 -0
- package/src/main/index.ts +11 -4
- package/src/main/util/coalesce-to-array.ts +13 -0
- package/src/main/{util.ts → util/links.ts} +8 -22
- package/src/main/util/to-date.ts +9 -0
- package/src/test/api/alignment.test.ts +2 -2
- package/src/test/api/badge-data.fixture.ts +1 -0
- package/src/test/api/badge-data.test.ts +1 -0
- package/src/test/api/bundle-data.fixture.ts +3 -2
- package/src/test/api/bundle-header-data.fixture.ts +4 -2
- package/src/test/api/morality.test.ts +31 -0
- package/src/test/api/sex.test.ts +2 -2
- package/src/test/api/zone-data.fixture.ts +1 -0
- package/src/test/db/abstract-index.test.ts +12 -43
- package/src/test/db/badge-index.test.ts +197 -101
- package/src/test/db/badge.test.ts +122 -16
- package/src/test/db/bundle-header.test.ts +25 -12
- package/src/test/db/coh-content-database.test.ts +134 -175
- package/src/test/db/contact.test.ts +2 -1
- package/src/test/db/level-range.test.ts +47 -0
- package/src/test/db/mission.test.ts +8 -6
- package/src/test/db/morality-list.test.ts +1 -1
- package/src/test/db/set-title-ids.test.ts +19 -0
- package/src/test/db/{alternates.test.ts → variants.test.ts} +24 -24
- package/src/test/db/zone.test.ts +45 -0
- package/src/test/integration.test.ts +3 -3
- package/src/test/util/coalese-to-array.test.ts +17 -0
- package/src/test/{util.test.ts → util/links.test.ts} +5 -21
- package/src/test/util/to-date.test.ts +15 -0
- package/src/main/db/alternates.ts +0 -67
|
@@ -3,42 +3,42 @@ import { Archetype } from './archetype'
|
|
|
3
3
|
import { Zone } from './zone'
|
|
4
4
|
import { Badge } from './badge'
|
|
5
5
|
import { BundleHeader } from './bundle-header'
|
|
6
|
-
import { BadgeIndex } from './badge-index'
|
|
7
6
|
import { BadgeSearchOptions } from './badge-search-options'
|
|
8
7
|
import { Paged } from './paged'
|
|
9
8
|
import { Contact } from './contact'
|
|
10
9
|
import { Mission } from './mission'
|
|
11
10
|
import { AbstractIndex } from './abstract-index'
|
|
11
|
+
import { BadgeIndex } from './badge-index'
|
|
12
12
|
|
|
13
13
|
export class CohContentDatabase {
|
|
14
|
-
#archetypeIndex
|
|
15
|
-
#zoneIndex
|
|
16
|
-
#contactIndex
|
|
17
|
-
#missionIndex
|
|
18
|
-
#badgeIndex
|
|
14
|
+
readonly #archetypeIndex
|
|
15
|
+
readonly #zoneIndex
|
|
16
|
+
readonly #contactIndex
|
|
17
|
+
readonly #missionIndex
|
|
18
|
+
readonly #badgeIndex
|
|
19
19
|
|
|
20
|
-
#header
|
|
21
|
-
#servers
|
|
20
|
+
readonly #header: BundleHeader
|
|
21
|
+
readonly #servers: string[]
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
|
-
*
|
|
24
|
+
* Create a db instance from the given content bundle.
|
|
25
25
|
* @param bundle The bundle to load.
|
|
26
26
|
*/
|
|
27
|
-
|
|
27
|
+
constructor(bundle: BundleData) {
|
|
28
28
|
this.#header = new BundleHeader(bundle.header)
|
|
29
29
|
this.#servers = bundle.servers ?? []
|
|
30
30
|
|
|
31
|
-
this.#archetypeIndex
|
|
32
|
-
this.#zoneIndex
|
|
33
|
-
this.#contactIndex
|
|
34
|
-
this.#missionIndex
|
|
35
|
-
this.#badgeIndex
|
|
31
|
+
this.#archetypeIndex = new AbstractIndex<Archetype>('key', bundle.archetypes?.map(x => new Archetype(x)))
|
|
32
|
+
this.#zoneIndex = new AbstractIndex<Zone>('key', bundle.zones?.map(x => new Zone(x)))
|
|
33
|
+
this.#contactIndex = new AbstractIndex<Contact>('key', bundle.contacts?.map(x => new Contact(x)))
|
|
34
|
+
this.#missionIndex = new AbstractIndex<Mission>('key', bundle.missions?.map(x => new Mission(x)))
|
|
35
|
+
this.#badgeIndex = new BadgeIndex(bundle.badges?.map(x => new Badge(x)))
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
/**
|
|
39
39
|
* Header information about the content bundle.
|
|
40
40
|
*/
|
|
41
|
-
get header(): BundleHeader
|
|
41
|
+
get header(): BundleHeader {
|
|
42
42
|
return this.#header
|
|
43
43
|
}
|
|
44
44
|
|
|
@@ -48,7 +48,7 @@ export class CohContentDatabase {
|
|
|
48
48
|
* Torchbearer, Excelsior, etc.
|
|
49
49
|
*/
|
|
50
50
|
get servers(): string[] {
|
|
51
|
-
return this.#servers
|
|
51
|
+
return this.#servers
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
/**
|
package/src/main/db/contact.ts
CHANGED
|
@@ -4,7 +4,8 @@ import { MarkdownString } from '../api/markdown-string'
|
|
|
4
4
|
import { ContactData } from '../api/contact-data'
|
|
5
5
|
import { Location } from './location'
|
|
6
6
|
import { MoralityList } from './morality-list'
|
|
7
|
-
import { coalesceToArray } from '../util'
|
|
7
|
+
import { coalesceToArray } from '../util/coalesce-to-array'
|
|
8
|
+
import { LevelRange } from './level-range'
|
|
8
9
|
|
|
9
10
|
export class Contact {
|
|
10
11
|
/**
|
|
@@ -27,7 +28,7 @@ export class Contact {
|
|
|
27
28
|
/**
|
|
28
29
|
* The character moralities that this contact will interact with.
|
|
29
30
|
*/
|
|
30
|
-
readonly morality
|
|
31
|
+
readonly morality: MoralityList
|
|
31
32
|
|
|
32
33
|
/**
|
|
33
34
|
* The location of this contact.
|
|
@@ -37,7 +38,7 @@ export class Contact {
|
|
|
37
38
|
/**
|
|
38
39
|
* The level range this contact will offer missions for.
|
|
39
40
|
*/
|
|
40
|
-
readonly levelRange?:
|
|
41
|
+
readonly levelRange?: LevelRange
|
|
41
42
|
|
|
42
43
|
/**
|
|
43
44
|
* Freeform notes or tips about the contact.
|
|
@@ -55,7 +56,7 @@ export class Contact {
|
|
|
55
56
|
this.title = data.title
|
|
56
57
|
this.morality = new MoralityList(coalesceToArray(data.morality))
|
|
57
58
|
this.location = data.location
|
|
58
|
-
this.levelRange = data.levelRange
|
|
59
|
+
this.levelRange = data.levelRange ? new LevelRange(data.levelRange) : undefined
|
|
59
60
|
this.notes = data.notes
|
|
60
61
|
this.links = data.links ?? []
|
|
61
62
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { LevelRangeData } from '../api/level-range-data'
|
|
2
|
+
|
|
3
|
+
export class LevelRange {
|
|
4
|
+
readonly min: number
|
|
5
|
+
readonly max?: number
|
|
6
|
+
|
|
7
|
+
constructor(value: LevelRangeData) {
|
|
8
|
+
if (Array.isArray(value)) {
|
|
9
|
+
this.min = value[0]
|
|
10
|
+
this.max = value[1] === undefined ? undefined : value[1]
|
|
11
|
+
} else {
|
|
12
|
+
this.min = value
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
package/src/main/db/mission.ts
CHANGED
|
@@ -3,8 +3,9 @@ import { MarkdownString } from '../api/markdown-string'
|
|
|
3
3
|
import { Link } from '../api/link'
|
|
4
4
|
import { MissionData } from '../api/mission-data'
|
|
5
5
|
import { Key } from './key'
|
|
6
|
-
import { coalesceToArray } from '../util'
|
|
7
6
|
import { MoralityList } from './morality-list'
|
|
7
|
+
import { coalesceToArray } from '../util/coalesce-to-array'
|
|
8
|
+
import { LevelRange } from './level-range'
|
|
8
9
|
|
|
9
10
|
export class Mission {
|
|
10
11
|
/**
|
|
@@ -39,7 +40,7 @@ export class Mission {
|
|
|
39
40
|
/**
|
|
40
41
|
* The level range this mission is available for.
|
|
41
42
|
*/
|
|
42
|
-
readonly levelRange?:
|
|
43
|
+
readonly levelRange?: LevelRange
|
|
43
44
|
|
|
44
45
|
/**
|
|
45
46
|
* Freeform notes or tips about the mission.
|
|
@@ -62,19 +63,19 @@ export class Mission {
|
|
|
62
63
|
readonly id: string
|
|
63
64
|
|
|
64
65
|
/**
|
|
65
|
-
* The level range this mission appears under as a Flashback.
|
|
66
|
+
* The level range this mission appears under as a Flashback.
|
|
66
67
|
*/
|
|
67
|
-
readonly levelRange?:
|
|
68
|
+
readonly levelRange?: LevelRange
|
|
68
69
|
|
|
69
70
|
/**
|
|
70
|
-
* The name as it appears in the Flashback list.
|
|
71
|
+
* The name as it appears in the Flashback list.
|
|
71
72
|
*/
|
|
72
|
-
readonly name
|
|
73
|
+
readonly name: string
|
|
73
74
|
|
|
74
75
|
/**
|
|
75
|
-
* The character moralities that the mission will appear for in the Flashback list.
|
|
76
|
+
* The character moralities that the mission will appear for in the Flashback list.
|
|
76
77
|
*/
|
|
77
|
-
readonly morality
|
|
78
|
+
readonly morality: MoralityList
|
|
78
79
|
|
|
79
80
|
/**
|
|
80
81
|
* Freeform notes or tips about the Flashback version of the mission.
|
|
@@ -88,7 +89,7 @@ export class Mission {
|
|
|
88
89
|
this.type = data.type
|
|
89
90
|
this.morality = new MoralityList(coalesceToArray(data.morality))
|
|
90
91
|
this.contactKeys = coalesceToArray(data.contactKeys)
|
|
91
|
-
this.levelRange = data.levelRange
|
|
92
|
+
this.levelRange = data.levelRange ? new LevelRange(data.levelRange) : undefined
|
|
92
93
|
this.notes = data.notes
|
|
93
94
|
this.links = data.links ?? []
|
|
94
95
|
this.flashback = createFlashback(data)
|
|
@@ -99,7 +100,7 @@ function createFlashback(data: MissionData): Mission['flashback'] {
|
|
|
99
100
|
if (!data.flashback) return undefined
|
|
100
101
|
return {
|
|
101
102
|
id: data.flashback.id,
|
|
102
|
-
levelRange: data.flashback.levelRange
|
|
103
|
+
levelRange: data.flashback.levelRange ? new LevelRange(data.flashback.levelRange) : undefined,
|
|
103
104
|
name: data.flashback.name ?? data.name,
|
|
104
105
|
morality: new MoralityList(coalesceToArray(data.flashback.morality ?? data.morality)),
|
|
105
106
|
notes: data.flashback.notes,
|
package/src/main/db/paged.ts
CHANGED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { VariantData } from '../api/variant-data'
|
|
2
|
+
import { compareSex } from '../api/sex'
|
|
3
|
+
import { compareAlignment } from '../api/alignment'
|
|
4
|
+
import { VariantContext } from '../api/variant-context'
|
|
5
|
+
import { MoralityMap } from '../api/morality'
|
|
6
|
+
|
|
7
|
+
export class Variants<T> {
|
|
8
|
+
readonly #sortedValues: VariantData<T>[] = []
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Create a variant set from either a list of categorized values, or a single value when there are no variants.
|
|
12
|
+
* @param value List of variants, or a single value.
|
|
13
|
+
*/
|
|
14
|
+
constructor(value: VariantData<T>[] | T) {
|
|
15
|
+
if (Array.isArray(value)) {
|
|
16
|
+
this.#sortedValues = value.toSorted()
|
|
17
|
+
this.#sortedValues.sort((a, b) => this.#compareVariants(a, b))
|
|
18
|
+
} else {
|
|
19
|
+
this.#sortedValues = [{ value }]
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Get a variant by context
|
|
25
|
+
* @param context The context
|
|
26
|
+
*/
|
|
27
|
+
getVariant(context?: VariantContext): VariantData<T> | undefined {
|
|
28
|
+
const alignment = context?.morality ? MoralityMap[context.morality] : undefined
|
|
29
|
+
const sex = context?.sex
|
|
30
|
+
|
|
31
|
+
for (let index = this.#sortedValues.length; index--;) {
|
|
32
|
+
const entry = this.#sortedValues[index]
|
|
33
|
+
if ((entry.alignment === undefined || entry.alignment === alignment)
|
|
34
|
+
&& (entry.sex === undefined || entry.sex === sex)
|
|
35
|
+
) return entry
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return this.default
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Get a value by variant context
|
|
43
|
+
* @param context The context
|
|
44
|
+
*/
|
|
45
|
+
getValue(context?: VariantContext): T | undefined {
|
|
46
|
+
return this.getVariant(context)?.value
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Get the default value for this list of variants, the value with the highest priority and lowest specificity.
|
|
51
|
+
*/
|
|
52
|
+
get default(): VariantData<T> | undefined {
|
|
53
|
+
return this.#sortedValues[0]
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Get the list of variants sorted in canonical order (alignment then sex, low to high specificity).
|
|
58
|
+
*/
|
|
59
|
+
get canonical(): VariantData<T>[] {
|
|
60
|
+
return this.#sortedValues
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Create a joined string from the variant values in canonical order.
|
|
65
|
+
* @param separator Separator to use. Default is ' / '
|
|
66
|
+
*/
|
|
67
|
+
toString(separator: string): string {
|
|
68
|
+
return this.canonical.map(x => x.value).join(separator)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
#compareVariants(a: VariantData<T>, b: VariantData<T>): number {
|
|
72
|
+
const aSpecificity = (a.alignment ? 2 : 0) + (a.sex ? 1 : 0)
|
|
73
|
+
const bSpecificity = (b.alignment ? 2 : 0) + (b.sex ? 1 : 0)
|
|
74
|
+
if (aSpecificity !== bSpecificity) return aSpecificity - bSpecificity // Order first by least-specific
|
|
75
|
+
|
|
76
|
+
const alignmentComparison = compareAlignment(a.alignment, b.alignment) // Next by alignment
|
|
77
|
+
if (alignmentComparison !== 0) return alignmentComparison
|
|
78
|
+
|
|
79
|
+
const sexComparison = compareSex(a.sex, b.sex) // Last by sex
|
|
80
|
+
if (sexComparison !== 0) return sexComparison
|
|
81
|
+
|
|
82
|
+
return String(a.value).localeCompare(String(b.value))
|
|
83
|
+
}
|
|
84
|
+
}
|
package/src/main/db/zone.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { Link } from '../api/link'
|
|
2
2
|
import { ZoneData } from '../api/zone-data'
|
|
3
3
|
import { Key } from './key'
|
|
4
|
+
import { MoralityList } from './morality-list'
|
|
5
|
+
import { coalesceToArray } from '../util/coalesce-to-array'
|
|
6
|
+
import { ZoneType } from '../api/zone-type'
|
|
7
|
+
import { MarkdownString } from '../api/markdown-string'
|
|
8
|
+
import { LevelRange } from './level-range'
|
|
4
9
|
|
|
5
10
|
export class Zone {
|
|
6
11
|
/**
|
|
@@ -15,6 +20,26 @@ export class Zone {
|
|
|
15
20
|
*/
|
|
16
21
|
readonly name: string
|
|
17
22
|
|
|
23
|
+
/**
|
|
24
|
+
* The type of zone.
|
|
25
|
+
*/
|
|
26
|
+
readonly type: ZoneType
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The character moralities that this zone is accessible by.
|
|
30
|
+
*/
|
|
31
|
+
readonly morality: MoralityList
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The level range this zone is recommended for.
|
|
35
|
+
*/
|
|
36
|
+
readonly levelRange?: LevelRange
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Freeform notes or tips about the zone.
|
|
40
|
+
*/
|
|
41
|
+
readonly notes?: MarkdownString
|
|
42
|
+
|
|
18
43
|
/**
|
|
19
44
|
* List of external links. Wiki, forums, etc.
|
|
20
45
|
*/
|
|
@@ -23,6 +48,10 @@ export class Zone {
|
|
|
23
48
|
constructor(data: ZoneData) {
|
|
24
49
|
this.key = new Key(data.key).value
|
|
25
50
|
this.name = data.name
|
|
51
|
+
this.type = data.type
|
|
52
|
+
this.morality = new MoralityList(coalesceToArray(data.morality))
|
|
53
|
+
this.levelRange = data.levelRange ? new LevelRange(data.levelRange) : undefined
|
|
54
|
+
this.notes = data.notes
|
|
26
55
|
this.links = data.links ?? []
|
|
27
56
|
}
|
|
28
57
|
}
|
package/src/main/index.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
// API
|
|
2
2
|
export * from './api/alignment'
|
|
3
|
-
export * from './api/alternate-data'
|
|
4
3
|
export * from './api/archetype-data'
|
|
5
4
|
export * from './api/badge-data'
|
|
6
5
|
export * from './api/badge-requirement-data'
|
|
@@ -12,16 +11,21 @@ export * from './api/contact-data'
|
|
|
12
11
|
export * from './api/enhancement-category'
|
|
13
12
|
export * from './api/link'
|
|
14
13
|
export * from './api/location-data'
|
|
14
|
+
export * from './api/level-range-data'
|
|
15
15
|
export * from './api/markdown-string'
|
|
16
16
|
export * from './api/mission-data'
|
|
17
|
+
export * from './api/mission-flashback-data'
|
|
17
18
|
export * from './api/mission-type'
|
|
18
19
|
export * from './api/morality'
|
|
20
|
+
export * from './api/set-title-data'
|
|
19
21
|
export * from './api/sex'
|
|
22
|
+
export * from './api/variant-context'
|
|
23
|
+
export * from './api/variant-data'
|
|
20
24
|
export * from './api/zone-data'
|
|
25
|
+
export * from './api/zone-type'
|
|
21
26
|
|
|
22
27
|
// DB
|
|
23
28
|
export * from './db/alignment-list'
|
|
24
|
-
export * from './db/alternates'
|
|
25
29
|
export * from './db/archetype'
|
|
26
30
|
export * from './db/badge'
|
|
27
31
|
export * from './db/badge-index'
|
|
@@ -32,10 +36,13 @@ export * from './db/coh-content-database'
|
|
|
32
36
|
export * from './db/contact'
|
|
33
37
|
export * from './db/key'
|
|
34
38
|
export * from './db/location'
|
|
39
|
+
export * from './db/level-range'
|
|
35
40
|
export * from './db/mission'
|
|
36
41
|
export * from './db/morality-list'
|
|
37
42
|
export * from './db/paged'
|
|
43
|
+
export * from './db/set-title-ids'
|
|
44
|
+
export * from './db/variants'
|
|
38
45
|
export * from './db/zone'
|
|
39
46
|
|
|
40
|
-
//
|
|
41
|
-
export * from './util'
|
|
47
|
+
// UTILS
|
|
48
|
+
export * from './util/links'
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* For fields that accept either an array of values or a single value, coalesces the value to an array.
|
|
3
|
+
*
|
|
4
|
+
* Arrays are returned as-is.
|
|
5
|
+
* Single values are returned as a single-value array.
|
|
6
|
+
* Undefined values are returned as undefined.
|
|
7
|
+
*
|
|
8
|
+
* @param value The value to coalesce.
|
|
9
|
+
*/
|
|
10
|
+
export function coalesceToArray<T>(value?: T | T[]): T[] | undefined {
|
|
11
|
+
if (!value) return undefined
|
|
12
|
+
return Array.isArray(value) ? value as T[] : [value]
|
|
13
|
+
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { BadgeData } from '
|
|
2
|
-
import { Badge } from '
|
|
3
|
-
import { ZoneData } from '
|
|
4
|
-
import { Zone } from '
|
|
5
|
-
import { Contact } from '
|
|
6
|
-
import { ContactData } from '
|
|
7
|
-
import { Mission } from '
|
|
8
|
-
import { MissionData } from '
|
|
1
|
+
import { BadgeData } from '../api/badge-data'
|
|
2
|
+
import { Badge } from '../db/badge'
|
|
3
|
+
import { ZoneData } from '../api/zone-data'
|
|
4
|
+
import { Zone } from '../db/zone'
|
|
5
|
+
import { Contact } from '../db/contact'
|
|
6
|
+
import { ContactData } from '../api/contact-data'
|
|
7
|
+
import { Mission } from '../db/mission'
|
|
8
|
+
import { MissionData } from '../api/mission-data'
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Returns the URI of the given badge that can be used in {@link MarkdownString} fields.
|
|
@@ -102,17 +102,3 @@ export function zoneLink(target: string | Zone | ZoneData): string {
|
|
|
102
102
|
const key = typeof target === 'string' ? target : target.key
|
|
103
103
|
return `[${key}](${zoneUri(target)})`
|
|
104
104
|
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* For fields that accept either an array of values or a single value, coalesces the value to an array.
|
|
108
|
-
*
|
|
109
|
-
* Arrays are returned as-is.
|
|
110
|
-
* Single values are returned as a single-value array.
|
|
111
|
-
* Undefined values are returned as undefined.
|
|
112
|
-
*
|
|
113
|
-
* @param value The value to coalesce.
|
|
114
|
-
*/
|
|
115
|
-
export function coalesceToArray<T>(value?: T | T[]): T[] | undefined {
|
|
116
|
-
if (!value) return undefined
|
|
117
|
-
return Array.isArray(value) ? value as T[] : [value]
|
|
118
|
-
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts an iso string to a Date object, throwing an error if the iso string is invalid.
|
|
3
|
+
* @param iso ISO-8601 Date string
|
|
4
|
+
*/
|
|
5
|
+
export function toDate(iso: string): Date {
|
|
6
|
+
const date = new Date(iso)
|
|
7
|
+
if (!date || Number.isNaN(date.getTime())) throw new Error(`Invalid date format: [${iso}]`)
|
|
8
|
+
return date
|
|
9
|
+
}
|
|
@@ -51,14 +51,14 @@ describe('compareAlignment', () => {
|
|
|
51
51
|
|
|
52
52
|
test('should work as a compare function', () => {
|
|
53
53
|
const unsorted: (Alignment | undefined)[] = [undefined, 'hero', 'villain', 'praetorian', undefined, 'villain', 'praetorian']
|
|
54
|
-
const sorted = unsorted.
|
|
54
|
+
const sorted = unsorted.toSorted(compareAlignment)
|
|
55
55
|
|
|
56
56
|
expect(sorted).toStrictEqual(['hero', 'villain', 'villain', 'praetorian', 'praetorian', undefined, undefined])
|
|
57
57
|
})
|
|
58
58
|
|
|
59
59
|
test('should sort against undefined', () => {
|
|
60
60
|
const unsorted: (Alignment | undefined)[] = [undefined, 'hero']
|
|
61
|
-
const sorted = unsorted.
|
|
61
|
+
const sorted = unsorted.toSorted(compareAlignment)
|
|
62
62
|
|
|
63
63
|
expect(sorted).toStrictEqual(['hero', undefined])
|
|
64
64
|
})
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { defineFixture } from 'efate'
|
|
2
2
|
import { BundleData } from '../../main'
|
|
3
|
+
import { bundleHeaderDataFixture } from './bundle-header-data.fixture'
|
|
3
4
|
|
|
4
|
-
export const bundleDataFixture = defineFixture<BundleData>(() => {
|
|
5
|
-
|
|
5
|
+
export const bundleDataFixture = defineFixture<BundleData>((t) => {
|
|
6
|
+
t.header.fromFixture(bundleHeaderDataFixture)
|
|
6
7
|
})
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { defineFixture } from 'efate'
|
|
2
2
|
import { BundleHeaderData } from '../../main'
|
|
3
3
|
|
|
4
|
-
export const bundleHeaderDataFixture = defineFixture<BundleHeaderData>(() => {
|
|
5
|
-
|
|
4
|
+
export const bundleHeaderDataFixture = defineFixture<BundleHeaderData>((t) => {
|
|
5
|
+
t.name.as(index => `Bundle ${index}`)
|
|
6
|
+
t.version.as(index => `${index}.${index}.${index}`)
|
|
7
|
+
t.lastUpdateTime.as(() => new Date().toISOString())
|
|
6
8
|
})
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { MoralityMap } from '../../main'
|
|
2
|
+
|
|
3
|
+
describe('MoralityMap', () => {
|
|
4
|
+
test('should map hero to hero', () => {
|
|
5
|
+
expect(MoralityMap.hero).toEqual('hero')
|
|
6
|
+
})
|
|
7
|
+
|
|
8
|
+
test('should map vigilante to hero', () => {
|
|
9
|
+
expect(MoralityMap.vigilante).toEqual('hero')
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
test('should map villain to villain', () => {
|
|
13
|
+
expect(MoralityMap.villain).toEqual('villain')
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
test('should map rogue to villain', () => {
|
|
17
|
+
expect(MoralityMap.rogue).toEqual('villain')
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
test('should map resistance to praetorian', () => {
|
|
21
|
+
expect(MoralityMap.resistance).toEqual('praetorian')
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
test('should map loyalist to praetorian', () => {
|
|
25
|
+
expect(MoralityMap.loyalist).toEqual('praetorian')
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
test('should map praetorian to praetorian', () => {
|
|
29
|
+
expect(MoralityMap.loyalist).toEqual('praetorian')
|
|
30
|
+
})
|
|
31
|
+
})
|
package/src/test/api/sex.test.ts
CHANGED
|
@@ -49,14 +49,14 @@ describe('compareSex', () => {
|
|
|
49
49
|
|
|
50
50
|
test('should work as a compare function', () => {
|
|
51
51
|
const unsorted: (Sex | undefined)[] = [undefined, 'M', 'F', 'M', undefined, 'F', 'M']
|
|
52
|
-
const sorted = unsorted.
|
|
52
|
+
const sorted = unsorted.toSorted(compareSex)
|
|
53
53
|
|
|
54
54
|
expect(sorted).toStrictEqual(['M', 'M', 'M', 'F', 'F', undefined, undefined])
|
|
55
55
|
})
|
|
56
56
|
|
|
57
57
|
test('should sort against undefined', () => {
|
|
58
58
|
const unsorted: (Sex | undefined)[] = [undefined, 'M']
|
|
59
|
-
const sorted = unsorted.
|
|
59
|
+
const sorted = unsorted.toSorted(compareSex)
|
|
60
60
|
|
|
61
61
|
expect(sorted).toStrictEqual(['M', undefined])
|
|
62
62
|
})
|
|
@@ -4,5 +4,6 @@ import { defineFixture } from 'efate'
|
|
|
4
4
|
export const zoneDataFixture = defineFixture<ZoneData>((t) => {
|
|
5
5
|
t.key.as(index => `zone-${index}`)
|
|
6
6
|
t.name.as(index => `Zone ${index}`)
|
|
7
|
+
t.type.withValue('city')
|
|
7
8
|
t.links?.as(() => [{ title: 'foo', href: 'https://nouri.org' }])
|
|
8
9
|
})
|
|
@@ -8,7 +8,14 @@ interface TestObject {
|
|
|
8
8
|
describe(AbstractIndex.name, () => {
|
|
9
9
|
describe('Constructor', () => {
|
|
10
10
|
test(`should accept the key field`, () => {
|
|
11
|
-
new AbstractIndex<TestObject>('key')
|
|
11
|
+
new AbstractIndex<TestObject>('key', [])
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
test(`should throw an error on duplicate key`, () => {
|
|
15
|
+
expect(() => new AbstractIndex<TestObject>('key', [
|
|
16
|
+
{ key: '1', otherValue: 1 },
|
|
17
|
+
{ key: '1', otherValue: 1 },
|
|
18
|
+
])).toThrow('Duplicate key [1]')
|
|
12
19
|
})
|
|
13
20
|
})
|
|
14
21
|
|
|
@@ -18,17 +25,14 @@ describe(AbstractIndex.name, () => {
|
|
|
18
25
|
{ key: '1', otherValue: 1 },
|
|
19
26
|
{ key: '2', otherValue: 2 },
|
|
20
27
|
]
|
|
21
|
-
const index = new AbstractIndex<TestObject>('key')
|
|
22
|
-
index.load(values)
|
|
23
|
-
|
|
28
|
+
const index = new AbstractIndex<TestObject>('key', values)
|
|
24
29
|
expect(index.values).toStrictEqual(values)
|
|
25
30
|
})
|
|
26
31
|
})
|
|
27
32
|
|
|
28
33
|
describe('get', () => {
|
|
29
34
|
test(`should return the indexed value on match`, () => {
|
|
30
|
-
const index = new AbstractIndex<TestObject>('key'
|
|
31
|
-
index.load([
|
|
35
|
+
const index = new AbstractIndex<TestObject>('key', [
|
|
32
36
|
{ key: '1', otherValue: 1 },
|
|
33
37
|
{ key: '2', otherValue: 2 },
|
|
34
38
|
])
|
|
@@ -37,50 +41,15 @@ describe(AbstractIndex.name, () => {
|
|
|
37
41
|
})
|
|
38
42
|
|
|
39
43
|
test(`should return undefined on no match`, () => {
|
|
40
|
-
const index = new AbstractIndex<TestObject>('key')
|
|
44
|
+
const index = new AbstractIndex<TestObject>('key', [])
|
|
41
45
|
|
|
42
46
|
expect(index.get('2')).toBeUndefined()
|
|
43
47
|
})
|
|
44
48
|
|
|
45
49
|
test(`should return undefined on undefined key`, () => {
|
|
46
|
-
const index = new AbstractIndex<TestObject>('key')
|
|
50
|
+
const index = new AbstractIndex<TestObject>('key', [])
|
|
47
51
|
const key = undefined
|
|
48
52
|
expect(index.get(key)).toBeUndefined()
|
|
49
53
|
})
|
|
50
54
|
})
|
|
51
|
-
|
|
52
|
-
describe('load', () => {
|
|
53
|
-
test(`should reset the index when used`, () => {
|
|
54
|
-
const index = new AbstractIndex<TestObject>('key')
|
|
55
|
-
index.load([
|
|
56
|
-
{ key: '1', otherValue: 1 },
|
|
57
|
-
{ key: '2', otherValue: 2 },
|
|
58
|
-
])
|
|
59
|
-
|
|
60
|
-
expect(index.get('2')).toStrictEqual({ key: '2', otherValue: 2 })
|
|
61
|
-
|
|
62
|
-
index.load([
|
|
63
|
-
{ key: '3', otherValue: 3 },
|
|
64
|
-
])
|
|
65
|
-
expect(index.get('1')).toBeUndefined()
|
|
66
|
-
expect(index.get('2')).toBeUndefined()
|
|
67
|
-
expect(index.get('3')).toStrictEqual({ key: '3', otherValue: 3 })
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
test(`should accept an undefined value`, () => {
|
|
71
|
-
const index = new AbstractIndex<TestObject>('key')
|
|
72
|
-
const values = undefined
|
|
73
|
-
index.load(values)
|
|
74
|
-
|
|
75
|
-
expect(index.values).toHaveLength(0)
|
|
76
|
-
})
|
|
77
|
-
|
|
78
|
-
test(`should throw an error on duplicate key`, () => {
|
|
79
|
-
const index = new AbstractIndex<TestObject>('key')
|
|
80
|
-
expect(() => index.load([
|
|
81
|
-
{ key: '1', otherValue: 1 },
|
|
82
|
-
{ key: '1', otherValue: 1 },
|
|
83
|
-
])).toThrow('Duplicate key [1]')
|
|
84
|
-
})
|
|
85
|
-
})
|
|
86
55
|
})
|