coh-content-db 2.0.0-rc.19 → 2.0.0-rc.20
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/CHANGELOG.md +1 -1
- package/dist/coh-content-db.d.ts +81 -56
- package/dist/coh-content-db.js +105 -82
- package/dist/coh-content-db.js.map +1 -1
- package/dist/coh-content-db.mjs +103 -81
- package/dist/coh-content-db.mjs.map +1 -1
- package/package.json +1 -1
- package/src/main/api/badge-data.ts +6 -6
- package/src/main/api/morality.ts +15 -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/db/badge-index.ts +6 -6
- package/src/main/db/badge-search-options.ts +6 -0
- package/src/main/db/badge.ts +11 -10
- package/src/main/db/variants.ts +84 -0
- package/src/main/index.ts +3 -2
- package/src/test/api/morality.test.ts +31 -0
- package/src/test/db/badge-index.test.ts +12 -0
- package/src/test/db/badge.test.ts +30 -8
- package/src/test/db/{alternates.test.ts → variants.test.ts} +24 -24
- package/src/main/db/alternates.ts +0 -67
|
@@ -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/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'
|
|
@@ -20,12 +19,13 @@ export * from './api/mission-type'
|
|
|
20
19
|
export * from './api/morality'
|
|
21
20
|
export * from './api/set-title-data'
|
|
22
21
|
export * from './api/sex'
|
|
22
|
+
export * from './api/variant-context'
|
|
23
|
+
export * from './api/variant-data'
|
|
23
24
|
export * from './api/zone-data'
|
|
24
25
|
export * from './api/zone-type'
|
|
25
26
|
|
|
26
27
|
// DB
|
|
27
28
|
export * from './db/alignment-list'
|
|
28
|
-
export * from './db/alternates'
|
|
29
29
|
export * from './db/archetype'
|
|
30
30
|
export * from './db/badge'
|
|
31
31
|
export * from './db/badge-index'
|
|
@@ -41,6 +41,7 @@ export * from './db/mission'
|
|
|
41
41
|
export * from './db/morality-list'
|
|
42
42
|
export * from './db/paged'
|
|
43
43
|
export * from './db/set-title-ids'
|
|
44
|
+
export * from './db/variants'
|
|
44
45
|
export * from './db/zone'
|
|
45
46
|
|
|
46
47
|
// UTILS
|
|
@@ -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
|
+
})
|
|
@@ -478,6 +478,18 @@ describe(BadgeIndex.name, () => {
|
|
|
478
478
|
expect(keys).toStrictEqual(['badge-3', 'badge-1', 'badge-2'])
|
|
479
479
|
})
|
|
480
480
|
|
|
481
|
+
test(`should sort by badge name, taking variant context into account`, () => {
|
|
482
|
+
const index = new BadgeIndex([
|
|
483
|
+
new Badge(badgeDataFixture.create({ key: 'badge-1', name: [{ value: 'A' }, { value: 'C', alignment: 'praetorian', sex: 'F' }] })),
|
|
484
|
+
new Badge(badgeDataFixture.create({ key: 'badge-2', name: [{ value: 'B' }] })),
|
|
485
|
+
new Badge(badgeDataFixture.create({ key: 'badge-3', name: [{ value: 'C' }, { value: 'A', alignment: 'praetorian' }] })),
|
|
486
|
+
])
|
|
487
|
+
|
|
488
|
+
const result = index.search({ sort: 'name.asc', variantContext: { morality: 'resistance', sex: 'F' } })
|
|
489
|
+
const keys = result.items.map(x => x.key)
|
|
490
|
+
expect(keys).toStrictEqual(['badge-3', 'badge-2', 'badge-1'])
|
|
491
|
+
})
|
|
492
|
+
|
|
481
493
|
test(`should sort by badge name descending`, () => {
|
|
482
494
|
const index = new BadgeIndex([
|
|
483
495
|
new Badge(badgeDataFixture.create({ key: 'badge-1', name: [{ value: 'Abc' }] })),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Badge,
|
|
1
|
+
import { Badge, compareByName, compareByReleaseDate, compareByZoneKey } from '../../main'
|
|
2
2
|
import { badgeDataFixture } from '../api/badge-data.fixture'
|
|
3
3
|
import { badgeRequirementDataFixture } from '../api/badge-requirement-data.fixture'
|
|
4
4
|
|
|
@@ -299,31 +299,53 @@ describe(Badge.name, () => {
|
|
|
299
299
|
})
|
|
300
300
|
})
|
|
301
301
|
|
|
302
|
-
describe(
|
|
302
|
+
describe(compareByName.name, () => {
|
|
303
303
|
test(`should compare two badges by name`, () => {
|
|
304
304
|
const badgeA = new Badge(badgeDataFixture.create({ name: 'A' }))
|
|
305
305
|
const badgeB = new Badge(badgeDataFixture.create({ name: 'B' }))
|
|
306
|
-
expect(
|
|
307
|
-
expect([badgeB, badgeA].toSorted(
|
|
306
|
+
expect(compareByName(badgeA, badgeB)).toBeLessThan(0)
|
|
307
|
+
expect([badgeB, badgeA].toSorted(compareByName)).toStrictEqual([badgeA, badgeB])
|
|
308
308
|
})
|
|
309
309
|
|
|
310
310
|
test(`should return 0 for equal names`, () => {
|
|
311
311
|
const badgeA = new Badge(badgeDataFixture.create({ name: 'A' }))
|
|
312
312
|
const badgeB = new Badge(badgeDataFixture.create({ name: 'A' }))
|
|
313
|
-
expect(
|
|
313
|
+
expect(compareByName(badgeA, badgeB)).toEqual(0)
|
|
314
314
|
})
|
|
315
315
|
|
|
316
316
|
test(`should compare two undefined values`, () => {
|
|
317
317
|
const badgeA = new Badge(badgeDataFixture.create({ name: [] }))
|
|
318
318
|
const badgeB = new Badge(badgeDataFixture.create({ name: [] }))
|
|
319
|
-
expect(
|
|
319
|
+
expect(compareByName(badgeA, badgeB)).toEqual(0)
|
|
320
320
|
})
|
|
321
321
|
|
|
322
322
|
test(`should sort undefined values last`, () => {
|
|
323
323
|
const badgeA = new Badge(badgeDataFixture.create({ name: 'A' }))
|
|
324
324
|
const badgeB = new Badge(badgeDataFixture.create({ name: [] }))
|
|
325
|
-
expect([badgeA, badgeB].toSorted(
|
|
326
|
-
expect([badgeB, badgeA].toSorted(
|
|
325
|
+
expect([badgeA, badgeB].toSorted(compareByName)).toStrictEqual([badgeA, badgeB])
|
|
326
|
+
expect([badgeB, badgeA].toSorted(compareByName)).toStrictEqual([badgeA, badgeB])
|
|
327
|
+
})
|
|
328
|
+
|
|
329
|
+
test(`should take morality context into account`, () => {
|
|
330
|
+
const badgeA = new Badge(badgeDataFixture.create({ name: [{ value: 'ZZZ' }, { alignment: 'villain', value: 'AAA' }] }))
|
|
331
|
+
const badgeB = new Badge(badgeDataFixture.create({ name: 'B' }))
|
|
332
|
+
expect([badgeA, badgeB].toSorted((a, b) => compareByName(a, b, { morality: 'villain' }))).toStrictEqual([badgeA, badgeB])
|
|
333
|
+
expect([badgeA, badgeB].toSorted((a, b) => compareByName(a, b, {}))).toStrictEqual([badgeB, badgeA])
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
test(`should take sex context into account`, () => {
|
|
337
|
+
const badgeA = new Badge(badgeDataFixture.create({ name: [{ value: 'ZZZ' }, { sex: 'F', value: 'AAA' }] }))
|
|
338
|
+
const badgeB = new Badge(badgeDataFixture.create({ name: 'B' }))
|
|
339
|
+
expect([badgeA, badgeB].toSorted((a, b) => compareByName(a, b, { sex: 'F' }))).toStrictEqual([badgeA, badgeB])
|
|
340
|
+
expect([badgeA, badgeB].toSorted((a, b) => compareByName(a, b, {}))).toStrictEqual([badgeB, badgeA])
|
|
341
|
+
})
|
|
342
|
+
|
|
343
|
+
test(`should take full variant context into account`, () => {
|
|
344
|
+
const badgeA = new Badge(badgeDataFixture.create({ name: [{ value: 'A' }, { sex: 'F', value: 'B' }] }))
|
|
345
|
+
const badgeB = new Badge(badgeDataFixture.create({ name: [{ value: 'B' }, { alignment: 'praetorian', sex: 'F', value: 'A' }] }))
|
|
346
|
+
const badgeC = new Badge(badgeDataFixture.create({ name: 'C' }))
|
|
347
|
+
expect([badgeA, badgeB, badgeC].toSorted((a, b) => compareByName(a, b, { morality: 'resistance', sex: 'F' }))).toStrictEqual([badgeB, badgeA, badgeC])
|
|
348
|
+
expect([badgeA, badgeB, badgeC].toSorted((a, b) => compareByName(a, b, {}))).toStrictEqual([badgeA, badgeB, badgeC])
|
|
327
349
|
})
|
|
328
350
|
})
|
|
329
351
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Variants } from '../../main'
|
|
2
2
|
|
|
3
|
-
describe(
|
|
3
|
+
describe(Variants.name, () => {
|
|
4
4
|
describe('Constructor', () => {
|
|
5
|
-
test('should accept a list of
|
|
6
|
-
new
|
|
5
|
+
test('should accept a list of variant values', () => {
|
|
6
|
+
new Variants([
|
|
7
7
|
{ value: 'Default' },
|
|
8
8
|
{ sex: 'M', value: 'Male' },
|
|
9
9
|
{ alignment: 'hero', value: 'Hero' },
|
|
@@ -13,17 +13,17 @@ describe(Alternates.name, () => {
|
|
|
13
13
|
})
|
|
14
14
|
|
|
15
15
|
test('should accept a single value', () => {
|
|
16
|
-
expect(new
|
|
16
|
+
expect(new Variants('foo').default?.value).toBe('foo')
|
|
17
17
|
})
|
|
18
18
|
})
|
|
19
19
|
|
|
20
20
|
describe('getValue', () => {
|
|
21
21
|
test('should return undefined if there are no values', () => {
|
|
22
|
-
expect(new
|
|
22
|
+
expect(new Variants([]).getValue()).toBeUndefined()
|
|
23
23
|
})
|
|
24
24
|
|
|
25
25
|
test('should return the least-specific value when no classifiers are provided', () => {
|
|
26
|
-
expect(new
|
|
26
|
+
expect(new Variants([
|
|
27
27
|
{ value: 'Default' },
|
|
28
28
|
{ sex: 'M', value: 'Male' },
|
|
29
29
|
{ alignment: 'hero', value: 'Hero' },
|
|
@@ -33,7 +33,7 @@ describe(Alternates.name, () => {
|
|
|
33
33
|
})
|
|
34
34
|
|
|
35
35
|
test('should return the least-specific value when no classifiers are provided, regardless of insert order', () => {
|
|
36
|
-
expect(new
|
|
36
|
+
expect(new Variants([
|
|
37
37
|
{ alignment: 'villain', sex: 'M', value: 'Male Villain' },
|
|
38
38
|
{ alignment: 'hero', value: 'Hero' },
|
|
39
39
|
{ value: 'Default' },
|
|
@@ -43,27 +43,27 @@ describe(Alternates.name, () => {
|
|
|
43
43
|
})
|
|
44
44
|
|
|
45
45
|
test('should return the most specific match', () => {
|
|
46
|
-
expect(new
|
|
46
|
+
expect(new Variants([
|
|
47
47
|
{ value: 'Default' },
|
|
48
48
|
{ sex: 'M', value: 'Male' },
|
|
49
49
|
{ alignment: 'hero', value: 'Hero' },
|
|
50
50
|
{ alignment: 'villain', sex: 'M', value: 'Male Villain' },
|
|
51
51
|
{ alignment: 'praetorian', sex: 'F', value: 'Praetorian Female' },
|
|
52
|
-
]).getValue('villain', 'M')).toBe('Male Villain')
|
|
52
|
+
]).getValue({ morality: 'villain', sex: 'M' })).toBe('Male Villain')
|
|
53
53
|
})
|
|
54
54
|
|
|
55
55
|
test('should return the most specific match, regardless of insert order', () => {
|
|
56
|
-
expect(new
|
|
56
|
+
expect(new Variants([
|
|
57
57
|
{ alignment: 'praetorian', sex: 'F', value: 'Praetorian Female' },
|
|
58
58
|
{ sex: 'M', value: 'Male' },
|
|
59
59
|
{ alignment: 'hero', value: 'Hero' },
|
|
60
60
|
{ alignment: 'villain', sex: 'M', value: 'Male Villain' },
|
|
61
61
|
{ value: 'Default' },
|
|
62
|
-
]).getValue('villain', 'M')).toBe('Male Villain')
|
|
62
|
+
]).getValue({ morality: 'villain', sex: 'M' })).toBe('Male Villain')
|
|
63
63
|
})
|
|
64
64
|
|
|
65
65
|
test('should return the lowest canonical value if there is no default', () => {
|
|
66
|
-
expect(new
|
|
66
|
+
expect(new Variants([
|
|
67
67
|
{ alignment: 'hero', value: 'Hero' },
|
|
68
68
|
{ sex: 'M', value: 'Male' },
|
|
69
69
|
{ alignment: 'praetorian', sex: 'F', value: 'Praetorian Female' },
|
|
@@ -72,22 +72,22 @@ describe(Alternates.name, () => {
|
|
|
72
72
|
})
|
|
73
73
|
|
|
74
74
|
test('should return the lowest canonical value if a specific is requested that does not exist', () => {
|
|
75
|
-
expect(new
|
|
75
|
+
expect(new Variants([
|
|
76
76
|
{ alignment: 'hero', value: 'Hero' },
|
|
77
77
|
{ alignment: 'villain', value: 'Villain' },
|
|
78
78
|
{ alignment: 'villain', sex: 'M', value: 'Male Villain' },
|
|
79
79
|
{ alignment: 'praetorian', sex: 'F', value: 'Praetorian Female' },
|
|
80
|
-
]).getValue(
|
|
80
|
+
]).getValue({ sex: 'F' })).toBe('Hero')
|
|
81
81
|
})
|
|
82
82
|
})
|
|
83
83
|
|
|
84
84
|
describe('default', () => {
|
|
85
85
|
test('should return undefined if there are no values', () => {
|
|
86
|
-
expect(new
|
|
86
|
+
expect(new Variants([]).default).toBeUndefined()
|
|
87
87
|
})
|
|
88
88
|
|
|
89
89
|
test('should return the lowest priority value', () => {
|
|
90
|
-
expect(new
|
|
90
|
+
expect(new Variants([
|
|
91
91
|
{ value: 'Default' },
|
|
92
92
|
{ sex: 'M', value: 'Male' },
|
|
93
93
|
{ alignment: 'hero', value: 'Hero' },
|
|
@@ -95,7 +95,7 @@ describe(Alternates.name, () => {
|
|
|
95
95
|
{ alignment: 'praetorian', sex: 'F', value: 'Praetorian Female' },
|
|
96
96
|
]).default?.value).toBe('Default')
|
|
97
97
|
|
|
98
|
-
expect(new
|
|
98
|
+
expect(new Variants([
|
|
99
99
|
{ alignment: 'villain', sex: 'M', value: 'Male Villain' },
|
|
100
100
|
{ alignment: 'praetorian', sex: 'F', value: 'Praetorian Female' },
|
|
101
101
|
{ sex: 'M', value: 'Male' },
|
|
@@ -103,7 +103,7 @@ describe(Alternates.name, () => {
|
|
|
103
103
|
{ alignment: 'hero', value: 'Hero' },
|
|
104
104
|
]).default?.value).toBe('Default')
|
|
105
105
|
|
|
106
|
-
expect(new
|
|
106
|
+
expect(new Variants([
|
|
107
107
|
{ alignment: 'villain', sex: 'M', value: 'Male Villain' },
|
|
108
108
|
{ alignment: 'praetorian', sex: 'F', value: 'Praetorian Female' },
|
|
109
109
|
{ sex: 'M', value: 'Male' },
|
|
@@ -114,11 +114,11 @@ describe(Alternates.name, () => {
|
|
|
114
114
|
|
|
115
115
|
describe('canonical', () => {
|
|
116
116
|
test('should be empty if there are no values', () => {
|
|
117
|
-
expect(new
|
|
117
|
+
expect(new Variants([]).canonical).toHaveLength(0)
|
|
118
118
|
})
|
|
119
119
|
|
|
120
120
|
test('should return values sorted in canonical order', () => {
|
|
121
|
-
const result = new
|
|
121
|
+
const result = new Variants([
|
|
122
122
|
{ alignment: 'hero', sex: 'F', value: 'Female Hero' },
|
|
123
123
|
{ alignment: 'praetorian', value: 'Praetorian' },
|
|
124
124
|
{ sex: 'F', value: 'Female' },
|
|
@@ -150,7 +150,7 @@ describe(Alternates.name, () => {
|
|
|
150
150
|
})
|
|
151
151
|
|
|
152
152
|
test('should sort unspecified values by alpha', () => {
|
|
153
|
-
expect(new
|
|
153
|
+
expect(new Variants([
|
|
154
154
|
{ value: 'A' },
|
|
155
155
|
{ value: 'C' },
|
|
156
156
|
{ value: 'B' },
|
|
@@ -162,7 +162,7 @@ describe(Alternates.name, () => {
|
|
|
162
162
|
})
|
|
163
163
|
|
|
164
164
|
test('should sort identical values by value alpha', () => {
|
|
165
|
-
expect(new
|
|
165
|
+
expect(new Variants([
|
|
166
166
|
{ alignment: 'villain', value: 'B' },
|
|
167
167
|
{ sex: 'M', value: 'D' },
|
|
168
168
|
{ alignment: 'villain', value: 'A' },
|
|
@@ -178,7 +178,7 @@ describe(Alternates.name, () => {
|
|
|
178
178
|
|
|
179
179
|
describe('toString', () => {
|
|
180
180
|
test('should create a string separated by the separator', () => {
|
|
181
|
-
expect(new
|
|
181
|
+
expect(new Variants([
|
|
182
182
|
{ sex: 'M', value: 'A' },
|
|
183
183
|
{ sex: 'F', value: 'B' },
|
|
184
184
|
{ alignment: 'hero', value: 'C' },
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { AlternateData } from '../api/alternate-data'
|
|
2
|
-
import { compareSex, Sex } from '../api/sex'
|
|
3
|
-
import { Alignment, compareAlignment } from '../api/alignment'
|
|
4
|
-
|
|
5
|
-
export class Alternates<T> {
|
|
6
|
-
readonly #sortedValues: AlternateData<T>[] = []
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Create an alternate set from either a list of categorized values, or a single value when there are no alternates.
|
|
10
|
-
* @param value List of alternates, or a single value.
|
|
11
|
-
*/
|
|
12
|
-
constructor(value: AlternateData<T>[] | T) {
|
|
13
|
-
if (Array.isArray(value)) {
|
|
14
|
-
this.#sortedValues = value.toSorted()
|
|
15
|
-
this.#sortedValues.sort((a, b) => this.#compareAlternates(a, b))
|
|
16
|
-
} else {
|
|
17
|
-
this.#sortedValues = [{ value }]
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
getValue(alignment?: Alignment, sex?: Sex): T | undefined {
|
|
22
|
-
for (let index = this.#sortedValues.length; index--;) {
|
|
23
|
-
const entry = this.#sortedValues[index]
|
|
24
|
-
if ((entry.alignment === undefined || entry.alignment === alignment)
|
|
25
|
-
&& (entry.sex === undefined || entry.sex === sex)
|
|
26
|
-
) return entry.value
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return this.default?.value
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Get the default value for this list of alternates, the value with the highest priority and lowest specificity.
|
|
34
|
-
*/
|
|
35
|
-
get default(): AlternateData<T> | undefined {
|
|
36
|
-
return this.#sortedValues[0]
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Get the list of alternates sorted in canonical order (alignment then sex, low to high specificity).
|
|
41
|
-
*/
|
|
42
|
-
get canonical(): AlternateData<T>[] {
|
|
43
|
-
return this.#sortedValues
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Create a joined string from the alternate values in canonical order.
|
|
48
|
-
* @param separator Separator to use. Default is ' / '
|
|
49
|
-
*/
|
|
50
|
-
toString(separator: string): string {
|
|
51
|
-
return this.canonical.map(x => x.value).join(separator)
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
#compareAlternates(a: AlternateData<T>, b: AlternateData<T>): number {
|
|
55
|
-
const aSpecificity = (a.alignment ? 2 : 0) + (a.sex ? 1 : 0)
|
|
56
|
-
const bSpecificity = (b.alignment ? 2 : 0) + (b.sex ? 1 : 0)
|
|
57
|
-
if (aSpecificity !== bSpecificity) return aSpecificity - bSpecificity // Order first by least-specific
|
|
58
|
-
|
|
59
|
-
const alignmentComparison = compareAlignment(a.alignment, b.alignment) // Next by alignment
|
|
60
|
-
if (alignmentComparison !== 0) return alignmentComparison
|
|
61
|
-
|
|
62
|
-
const sexComparison = compareSex(a.sex, b.sex) // Last by sex
|
|
63
|
-
if (sexComparison !== 0) return sexComparison
|
|
64
|
-
|
|
65
|
-
return String(a.value).localeCompare(String(b.value))
|
|
66
|
-
}
|
|
67
|
-
}
|