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.
Files changed (66) hide show
  1. package/.github/workflows/build.yml +3 -1
  2. package/CHANGELOG.md +11 -2
  3. package/README.md +33 -19
  4. package/dist/coh-content-db.d.ts +230 -170
  5. package/dist/coh-content-db.js +495 -296
  6. package/dist/coh-content-db.js.map +1 -1
  7. package/dist/coh-content-db.mjs +488 -294
  8. package/dist/coh-content-db.mjs.map +1 -1
  9. package/jest.config.mjs +1 -0
  10. package/package.json +14 -14
  11. package/src/main/api/badge-data.ts +13 -7
  12. package/src/main/api/bundle-data.ts +1 -1
  13. package/src/main/api/bundle-header-data.ts +13 -6
  14. package/src/main/api/contact-data.ts +2 -1
  15. package/src/main/api/level-range-data.ts +4 -0
  16. package/src/main/api/mission-data.ts +3 -29
  17. package/src/main/api/mission-flashback-data.ts +31 -0
  18. package/src/main/api/morality.ts +27 -9
  19. package/src/main/api/set-title-data.ts +4 -0
  20. package/src/main/api/variant-context.ts +11 -0
  21. package/src/main/api/{alternate-data.ts → variant-data.ts} +4 -4
  22. package/src/main/api/zone-data.ts +24 -0
  23. package/src/main/api/zone-type.ts +59 -0
  24. package/src/main/db/abstract-index.ts +12 -16
  25. package/src/main/db/badge-index.ts +53 -27
  26. package/src/main/db/badge-requirement.ts +1 -1
  27. package/src/main/db/badge-search-options.ts +15 -14
  28. package/src/main/db/badge.ts +46 -29
  29. package/src/main/db/bundle-header.ts +18 -10
  30. package/src/main/db/coh-content-database.ts +17 -17
  31. package/src/main/db/contact.ts +5 -4
  32. package/src/main/db/level-range.ts +15 -0
  33. package/src/main/db/mission.ts +11 -10
  34. package/src/main/db/paged.ts +7 -3
  35. package/src/main/db/set-title-ids.ts +10 -0
  36. package/src/main/db/variants.ts +84 -0
  37. package/src/main/db/zone.ts +29 -0
  38. package/src/main/index.ts +11 -4
  39. package/src/main/util/coalesce-to-array.ts +13 -0
  40. package/src/main/{util.ts → util/links.ts} +8 -22
  41. package/src/main/util/to-date.ts +9 -0
  42. package/src/test/api/alignment.test.ts +2 -2
  43. package/src/test/api/badge-data.fixture.ts +1 -0
  44. package/src/test/api/badge-data.test.ts +1 -0
  45. package/src/test/api/bundle-data.fixture.ts +3 -2
  46. package/src/test/api/bundle-header-data.fixture.ts +4 -2
  47. package/src/test/api/morality.test.ts +31 -0
  48. package/src/test/api/sex.test.ts +2 -2
  49. package/src/test/api/zone-data.fixture.ts +1 -0
  50. package/src/test/db/abstract-index.test.ts +12 -43
  51. package/src/test/db/badge-index.test.ts +197 -101
  52. package/src/test/db/badge.test.ts +122 -16
  53. package/src/test/db/bundle-header.test.ts +25 -12
  54. package/src/test/db/coh-content-database.test.ts +134 -175
  55. package/src/test/db/contact.test.ts +2 -1
  56. package/src/test/db/level-range.test.ts +47 -0
  57. package/src/test/db/mission.test.ts +8 -6
  58. package/src/test/db/morality-list.test.ts +1 -1
  59. package/src/test/db/set-title-ids.test.ts +19 -0
  60. package/src/test/db/{alternates.test.ts → variants.test.ts} +24 -24
  61. package/src/test/db/zone.test.ts +45 -0
  62. package/src/test/integration.test.ts +3 -3
  63. package/src/test/util/coalese-to-array.test.ts +17 -0
  64. package/src/test/{util.test.ts → util/links.test.ts} +5 -21
  65. package/src/test/util/to-date.test.ts +15 -0
  66. package/src/main/db/alternates.ts +0 -67
@@ -5,8 +5,7 @@ import { badgeRequirementDataFixture } from '../api/badge-requirement-data.fixtu
5
5
  describe(BadgeIndex.name, () => {
6
6
  describe('Constructor', () => {
7
7
  test(`should throw an error on duplicate key`, () => {
8
- const index = new BadgeIndex()
9
- expect(() => index.load([
8
+ expect(() => new BadgeIndex([
10
9
  new Badge(badgeDataFixture.create({ key: 'foo' })),
11
10
  new Badge(badgeDataFixture.create({ key: 'foo' })),
12
11
  ])).toThrow('Duplicate key [foo]')
@@ -15,27 +14,23 @@ describe(BadgeIndex.name, () => {
15
14
 
16
15
  describe('get', () => {
17
16
  test(`should retrieve badge from the index`, () => {
18
- const data = [new Badge(badgeDataFixture.create({ key: 'foo' }))]
19
-
20
- const index = new BadgeIndex()
21
- index.load(data)
17
+ const index = new BadgeIndex([new Badge(badgeDataFixture.create({ key: 'foo' }))])
22
18
  expect(index.get('foo')).not.toBeUndefined()
23
19
  })
24
20
 
25
21
  test(`should return undefined for unknown badge`, () => {
26
- expect(new BadgeIndex().get('foo')).toBeUndefined()
22
+ expect(new BadgeIndex([]).get('foo')).toBeUndefined()
27
23
  })
28
24
 
29
25
  test(`should return undefined for undefined key`, () => {
30
26
  const key = undefined
31
- expect(new BadgeIndex().get(key)).toBeUndefined()
27
+ expect(new BadgeIndex([]).get(key)).toBeUndefined()
32
28
  })
33
29
  })
34
30
 
35
31
  describe('search', () => {
36
32
  test(`should return everything for an empty query`, () => {
37
- const index = new BadgeIndex()
38
- index.load([
33
+ const index = new BadgeIndex([
39
34
  new Badge(badgeDataFixture.create({ key: 'foo-1', acquisition: 'Foo 1' })),
40
35
  new Badge(badgeDataFixture.create({ key: 'foo-2', acquisition: 'Foo 2' })),
41
36
  new Badge(badgeDataFixture.create({ key: 'bar-1', acquisition: 'Bar 1' })),
@@ -51,22 +46,20 @@ describe(BadgeIndex.name, () => {
51
46
 
52
47
  describe('query', () => {
53
48
  test(`should match on badge name`, () => {
54
- const index = new BadgeIndex()
55
- index.load([
49
+ const index = new BadgeIndex([
56
50
  new Badge(badgeDataFixture.create({ key: 'match-1', name: [{ value: 'Foo 1' }] })),
57
51
  new Badge(badgeDataFixture.create({ key: 'match-2', name: [{ value: 'Foo 2' }, { value: 'Bar 2' }] })),
58
52
  new Badge(badgeDataFixture.create({ key: 'match-3', name: [{ value: 'Bar 3' }, { value: 'Foo 3' }] })),
59
53
  new Badge(badgeDataFixture.create({ key: 'miss-1', name: [{ value: 'Bar 4' }] })),
60
54
  ])
61
55
 
62
- const result = index.search({ query: { str: 'Foo', on: { name: true } } })
56
+ const result = index.search({ query: { str: 'Foo', fields: ['name'] } })
63
57
  const keys = result.items.map(x => x.key)
64
58
  expect(keys).toStrictEqual(['match-1', 'match-2', 'match-3'])
65
59
  })
66
60
 
67
61
  test(`should match on badge text`, () => {
68
- const index = new BadgeIndex()
69
- index.load([
62
+ const index = new BadgeIndex([
70
63
  new Badge(badgeDataFixture.create({ key: 'match-1', badgeText: [{ value: 'Foo 1' }] })),
71
64
  new Badge(badgeDataFixture.create({ key: 'match-2', badgeText: [{ value: 'Foo 2' }, { value: 'Bar 2' }] })),
72
65
  new Badge(badgeDataFixture.create({ key: 'match-3', badgeText: [{ value: 'Bar 3' }, { value: 'Foo 3' }] })),
@@ -74,63 +67,74 @@ describe(BadgeIndex.name, () => {
74
67
  new Badge(badgeDataFixture.create({ key: 'miss-2', badgeText: undefined })),
75
68
  ])
76
69
 
77
- const result = index.search({ query: { str: 'Foo', on: { badgeText: true } } })
70
+ const result = index.search({ query: { str: 'Foo', fields: ['badge-text'] } })
78
71
  const keys = result.items.map(x => x.key)
79
72
  expect(keys).toStrictEqual(['match-1', 'match-2', 'match-3'])
80
73
  })
81
74
 
82
75
  test(`should match on acquisition`, () => {
83
- const index = new BadgeIndex()
84
- index.load([
76
+ const index = new BadgeIndex([
85
77
  new Badge(badgeDataFixture.create({ key: 'match-1', acquisition: 'Foo 1' })),
86
78
  new Badge(badgeDataFixture.create({ key: 'match-2', acquisition: 'Foo 2' })),
87
79
  new Badge(badgeDataFixture.create({ key: 'miss-1', acquisition: 'Bar 1' })),
88
80
  new Badge(badgeDataFixture.create({ key: 'miss-2', acquisition: undefined })),
89
81
  ])
90
82
 
91
- const result = index.search({ query: { str: 'Foo', on: { acquisition: true } } })
83
+ const result = index.search({ query: { str: 'Foo', fields: ['acquisition'] } })
92
84
  const keys = result.items.map(x => x.key)
93
85
  expect(keys).toStrictEqual(['match-1', 'match-2'])
94
86
  })
95
87
 
96
88
  test(`should match on effect`, () => {
97
- const index = new BadgeIndex()
98
- index.load([
89
+ const index = new BadgeIndex([
99
90
  new Badge(badgeDataFixture.create({ key: 'match-1', effect: 'Foo 1' })),
100
91
  new Badge(badgeDataFixture.create({ key: 'match-2', effect: 'Foo 2' })),
101
92
  new Badge(badgeDataFixture.create({ key: 'miss-1', effect: 'Bar 1' })),
102
93
  new Badge(badgeDataFixture.create({ key: 'miss-2', effect: undefined })),
103
94
  ])
104
95
 
105
- const result = index.search({ query: { str: 'Foo', on: { effect: true } } })
96
+ const result = index.search({ query: { str: 'Foo', fields: ['effect'] } })
106
97
  const keys = result.items.map(x => x.key)
107
98
  expect(keys).toStrictEqual(['match-1', 'match-2'])
108
99
  })
109
100
 
110
101
  test(`should match on notes`, () => {
111
- const index = new BadgeIndex()
112
- index.load([
102
+ const index = new BadgeIndex([
113
103
  new Badge(badgeDataFixture.create({ key: 'match-1', notes: 'Foo 1' })),
114
104
  new Badge(badgeDataFixture.create({ key: 'match-2', notes: 'Foo 2' })),
115
105
  new Badge(badgeDataFixture.create({ key: 'miss-1', notes: 'Bar 1' })),
116
106
  new Badge(badgeDataFixture.create({ key: 'miss-2', notes: undefined })),
117
107
  ])
118
108
 
119
- const result = index.search({ query: { str: 'Foo', on: { notes: true } } })
109
+ const result = index.search({ query: { str: 'Foo', fields: ['notes'] } })
120
110
  const keys = result.items.map(x => x.key)
121
111
  expect(keys).toStrictEqual(['match-1', 'match-2'])
122
112
  })
123
113
 
124
114
  test(`should match on setTitle`, () => {
125
- const index = new BadgeIndex()
126
- index.load([
115
+ const index = new BadgeIndex([
127
116
  new Badge(badgeDataFixture.create({ key: 'match-1', setTitleId: [123] })),
128
117
  new Badge(badgeDataFixture.create({ key: 'match-2', setTitleId: [456, 123] })),
129
118
  new Badge(badgeDataFixture.create({ key: 'miss-1', setTitleId: [456] })),
130
119
  new Badge(badgeDataFixture.create({ key: 'miss-2', setTitleId: undefined })),
131
120
  ])
132
121
 
133
- const result = index.search({ query: { str: '123', on: { setTitle: true } } })
122
+ const result = index.search({ query: { str: '123', fields: ['set-title-id'] } })
123
+
124
+ expect(result.items).toHaveLength(2)
125
+ const keys = result.items.map(x => x.key)
126
+ expect(keys).toStrictEqual(['match-1', 'match-2'])
127
+ })
128
+
129
+ test(`should match on multiple fields`, () => {
130
+ const index = new BadgeIndex([
131
+ new Badge(badgeDataFixture.create({ key: 'match-1', name: 'foo' })),
132
+ new Badge(badgeDataFixture.create({ key: 'match-2', notes: 'foo' })),
133
+ new Badge(badgeDataFixture.create({ key: 'miss-1', name: 'bar' })),
134
+ new Badge(badgeDataFixture.create({ key: 'miss-2', notes: 'bar' })),
135
+ ])
136
+
137
+ const result = index.search({ query: { str: 'foo', fields: ['name', 'notes'] } })
134
138
 
135
139
  expect(result.items).toHaveLength(2)
136
140
  const keys = result.items.map(x => x.key)
@@ -138,34 +142,31 @@ describe(BadgeIndex.name, () => {
138
142
  })
139
143
 
140
144
  test(`should match the start of a string`, () => {
141
- const index = new BadgeIndex()
142
- index.load([
145
+ const index = new BadgeIndex([
143
146
  new Badge(badgeDataFixture.create({ key: 'match-1', acquisition: 'Foo 1' })),
144
147
  new Badge(badgeDataFixture.create({ key: 'match-2', acquisition: 'Foo 2' })),
145
148
  new Badge(badgeDataFixture.create({ key: 'miss-1', acquisition: 'Bar 1' })),
146
149
  ])
147
150
 
148
- const result = index.search({ query: { str: 'Fo', on: { acquisition: true } } })
151
+ const result = index.search({ query: { str: 'Fo', fields: ['acquisition'] } })
149
152
  const keys = result.items.map(x => x.key)
150
153
  expect(keys).toStrictEqual(['match-1', 'match-2'])
151
154
  })
152
155
 
153
156
  test(`should be case insensitive`, () => {
154
- const index = new BadgeIndex()
155
- index.load([
157
+ const index = new BadgeIndex([
156
158
  new Badge(badgeDataFixture.create({ key: 'match-1', acquisition: 'Foo 1' })),
157
159
  new Badge(badgeDataFixture.create({ key: 'match-2', acquisition: 'Foo 2' })),
158
160
  new Badge(badgeDataFixture.create({ key: 'miss-1', acquisition: 'Bar 1' })),
159
161
  ])
160
162
 
161
- const result = index.search({ query: { str: 'foo', on: { acquisition: true } } })
163
+ const result = index.search({ query: { str: 'foo', fields: ['acquisition'] } })
162
164
  const keys = result.items.map(x => x.key)
163
165
  expect(keys).toStrictEqual(['match-1', 'match-2'])
164
166
  })
165
167
 
166
168
  test(`should default to querying on name only`, () => {
167
- const index = new BadgeIndex()
168
- index.load([
169
+ const index = new BadgeIndex([
169
170
  new Badge(badgeDataFixture.create({ key: 'match-1', name: [{ value: 'Foo 1' }] })),
170
171
  new Badge(badgeDataFixture.create({ key: 'miss-1', acquisition: 'Foo 2' })),
171
172
  new Badge(badgeDataFixture.create({ key: 'miss-2', name: [{ value: 'Bar 1' }] })),
@@ -176,12 +177,24 @@ describe(BadgeIndex.name, () => {
176
177
  const keys = result.items.map(x => x.key)
177
178
  expect(keys).toStrictEqual(['match-1'])
178
179
  })
180
+
181
+ test(`should return everything if no query fields are set`, () => {
182
+ const index = new BadgeIndex([
183
+ new Badge(badgeDataFixture.create({ key: 'match-1', name: [{ value: 'Foo 1' }] })),
184
+ new Badge(badgeDataFixture.create({ key: 'match-2', acquisition: 'Foo 2' })),
185
+ new Badge(badgeDataFixture.create({ key: 'match-3', name: [{ value: 'Bar 1' }] })),
186
+ ])
187
+
188
+ const result = index.search({ query: { str: 'no-hit', fields: [] } })
189
+
190
+ const keys = result.items.map(x => x.key)
191
+ expect(keys).toStrictEqual(['match-1', 'match-2', 'match-3'])
192
+ })
179
193
  })
180
194
 
181
195
  describe('filter', () => {
182
196
  test(`should filter nothing if not specified`, () => {
183
- const index = new BadgeIndex()
184
- index.load([
197
+ const index = new BadgeIndex([
185
198
  new Badge(badgeDataFixture.create({ key: 'badge-1' })),
186
199
  new Badge(badgeDataFixture.create({ key: 'badge-2' })),
187
200
  new Badge(badgeDataFixture.create({ key: 'badge-3' })),
@@ -196,8 +209,7 @@ describe(BadgeIndex.name, () => {
196
209
  })
197
210
 
198
211
  test(`should filter on badge type`, () => {
199
- const index = new BadgeIndex()
200
- index.load([
212
+ const index = new BadgeIndex([
201
213
  new Badge(badgeDataFixture.create({ key: 'badge-1', type: 'exploration' })),
202
214
  new Badge(badgeDataFixture.create({ key: 'badge-2', type: 'exploration' })),
203
215
  new Badge(badgeDataFixture.create({ key: 'badge-3', type: 'history' })),
@@ -212,8 +224,7 @@ describe(BadgeIndex.name, () => {
212
224
  })
213
225
 
214
226
  test(`should filter on badge zone`, () => {
215
- const index = new BadgeIndex()
216
- index.load([
227
+ const index = new BadgeIndex([
217
228
  new Badge(badgeDataFixture.create({ key: 'badge-1', requirements: [{ location: { zoneKey: 'atlas-park' } }] })),
218
229
  new Badge(badgeDataFixture.create({ key: 'badge-2', requirements: [{ location: { zoneKey: 'perez-park' } }] })),
219
230
  new Badge(badgeDataFixture.create({ key: 'badge-3', requirements: [{ location: { zoneKey: 'abandoned-sewer-network' } }] })),
@@ -233,8 +244,7 @@ describe(BadgeIndex.name, () => {
233
244
  })
234
245
 
235
246
  test(`should filter on alignment`, () => {
236
- const index = new BadgeIndex()
237
- index.load([
247
+ const index = new BadgeIndex([
238
248
  new Badge(badgeDataFixture.create({ key: 'badge-1', morality: ['hero'] })),
239
249
  new Badge(badgeDataFixture.create({ key: 'badge-2', morality: ['villain'] })),
240
250
  new Badge(badgeDataFixture.create({ key: 'badge-3', morality: ['loyalist'] })),
@@ -247,12 +257,26 @@ describe(BadgeIndex.name, () => {
247
257
  const keys = result.items.map(x => x.key)
248
258
  expect(keys).toStrictEqual(['badge-1', 'badge-4', 'badge-6'])
249
259
  })
260
+
261
+ test(`should filter on arbitrary predicate`, () => {
262
+ const index = new BadgeIndex([
263
+ new Badge(badgeDataFixture.create({ key: 'badge-1', effect: 'foo' })),
264
+ new Badge(badgeDataFixture.create({ key: 'badge-2', effect: 'foo' })),
265
+ new Badge(badgeDataFixture.create({ key: 'badge-3', effect: 'foo' })),
266
+ new Badge(badgeDataFixture.create({ key: 'badge-4', effect: 'bar' })),
267
+ new Badge(badgeDataFixture.create({ key: 'badge-5', effect: 'bar' })),
268
+ new Badge(badgeDataFixture.create({ key: 'badge-6', effect: 'foo' })),
269
+ ])
270
+
271
+ const result = index.search({ filter: { predicate: badge => badge.effect === 'bar' } })
272
+ const keys = result.items.map(x => x.key)
273
+ expect(keys).toStrictEqual(['badge-4', 'badge-5'])
274
+ })
250
275
  })
251
276
 
252
277
  describe('pagination', () => {
253
278
  test(`should return all results with no pagination data`, () => {
254
- const index = new BadgeIndex()
255
- index.load([
279
+ const index = new BadgeIndex([
256
280
  new Badge(badgeDataFixture.create({ key: 'badge-1' })),
257
281
  new Badge(badgeDataFixture.create({ key: 'badge-2' })),
258
282
  new Badge(badgeDataFixture.create({ key: 'badge-3' })),
@@ -267,13 +291,32 @@ describe(BadgeIndex.name, () => {
267
291
  })
268
292
 
269
293
  test(`should be 1-based for page number`, () => {
270
- const result = new BadgeIndex().search()
271
- expect(result.page).toBe(1)
294
+ const result = new BadgeIndex([]).search()
295
+ expect(result.pageNumber).toBe(1)
296
+ })
297
+
298
+ test(`should be 0-based for page index`, () => {
299
+ const result = new BadgeIndex([]).search()
300
+ expect(result.pageIndex).toBe(0)
301
+ })
302
+
303
+ test(`should return the correct page number and index`, () => {
304
+ const index = new BadgeIndex([
305
+ new Badge(badgeDataFixture.create({ key: 'badge-1' })),
306
+ new Badge(badgeDataFixture.create({ key: 'badge-2' })),
307
+ new Badge(badgeDataFixture.create({ key: 'badge-3' })),
308
+ new Badge(badgeDataFixture.create({ key: 'badge-4' })),
309
+ new Badge(badgeDataFixture.create({ key: 'badge-5' })),
310
+ new Badge(badgeDataFixture.create({ key: 'badge-6' })),
311
+ ])
312
+
313
+ const result = index.search({ page: 2, pageSize: 2 })
314
+ expect(result.pageIndex).toBe(1)
315
+ expect(result.pageNumber).toBe(2)
272
316
  })
273
317
 
274
318
  test(`should return the requested page size`, () => {
275
- const index = new BadgeIndex()
276
- index.load([
319
+ const index = new BadgeIndex([
277
320
  new Badge(badgeDataFixture.create({ key: 'badge-1' })),
278
321
  new Badge(badgeDataFixture.create({ key: 'badge-2' })),
279
322
  new Badge(badgeDataFixture.create({ key: 'badge-3' })),
@@ -287,8 +330,7 @@ describe(BadgeIndex.name, () => {
287
330
  })
288
331
 
289
332
  test(`should return the start of the array with no page specified`, () => {
290
- const index = new BadgeIndex()
291
- index.load([
333
+ const index = new BadgeIndex([
292
334
  new Badge(badgeDataFixture.create({ key: 'badge-1' })),
293
335
  new Badge(badgeDataFixture.create({ key: 'badge-2' })),
294
336
  new Badge(badgeDataFixture.create({ key: 'badge-3' })),
@@ -303,8 +345,7 @@ describe(BadgeIndex.name, () => {
303
345
  })
304
346
 
305
347
  test(`should return results from the middle of the array with a page specified`, () => {
306
- const index = new BadgeIndex()
307
- index.load([
348
+ const index = new BadgeIndex([
308
349
  new Badge(badgeDataFixture.create({ key: 'badge-1' })),
309
350
  new Badge(badgeDataFixture.create({ key: 'badge-2' })),
310
351
  new Badge(badgeDataFixture.create({ key: 'badge-3' })),
@@ -319,8 +360,7 @@ describe(BadgeIndex.name, () => {
319
360
  })
320
361
 
321
362
  test(`should return a partial page if at the end of the array`, () => {
322
- const index = new BadgeIndex()
323
- index.load([
363
+ const index = new BadgeIndex([
324
364
  new Badge(badgeDataFixture.create({ key: 'badge-1' })),
325
365
  new Badge(badgeDataFixture.create({ key: 'badge-2' })),
326
366
  new Badge(badgeDataFixture.create({ key: 'badge-3' })),
@@ -334,8 +374,7 @@ describe(BadgeIndex.name, () => {
334
374
  })
335
375
 
336
376
  test(`should return the correct total entry count`, () => {
337
- const index = new BadgeIndex()
338
- index.load([
377
+ const index = new BadgeIndex([
339
378
  new Badge(badgeDataFixture.create({ key: 'badge-1' })),
340
379
  new Badge(badgeDataFixture.create({ key: 'badge-2' })),
341
380
  new Badge(badgeDataFixture.create({ key: 'badge-3' })),
@@ -344,12 +383,25 @@ describe(BadgeIndex.name, () => {
344
383
  ])
345
384
 
346
385
  const result = index.search({ page: 1, pageSize: 2 })
347
- expect(result.totalItems).toBe(5)
386
+ expect(result.totalItemCount).toBe(5)
387
+ })
388
+
389
+ test(`should return the correct matched entry count`, () => {
390
+ const index = new BadgeIndex([
391
+ new Badge(badgeDataFixture.create({ key: 'badge-1', type: 'exploration' })),
392
+ new Badge(badgeDataFixture.create({ key: 'badge-2', type: 'history' })),
393
+ new Badge(badgeDataFixture.create({ key: 'badge-3', type: 'exploration' })),
394
+ new Badge(badgeDataFixture.create({ key: 'badge-4', type: 'accomplishment' })),
395
+ new Badge(badgeDataFixture.create({ key: 'badge-5', type: 'exploration' })),
396
+ ])
397
+
398
+ const result = index.search({ page: 1, pageSize: 2, filter: { type: 'exploration' } })
399
+ expect(result.totalItemCount).toBe(5)
400
+ expect(result.matchedItemCount).toBe(3)
348
401
  })
349
402
 
350
403
  test(`should return the page size`, () => {
351
- const index = new BadgeIndex()
352
- index.load([
404
+ const index = new BadgeIndex([
353
405
  new Badge(badgeDataFixture.create({ key: 'badge-1' })),
354
406
  new Badge(badgeDataFixture.create({ key: 'badge-2' })),
355
407
  new Badge(badgeDataFixture.create({ key: 'badge-3' })),
@@ -362,8 +414,7 @@ describe(BadgeIndex.name, () => {
362
414
  })
363
415
 
364
416
  test(`should return the correct total page count`, () => {
365
- const index = new BadgeIndex()
366
- index.load([
417
+ const index = new BadgeIndex([
367
418
  new Badge(badgeDataFixture.create({ key: 'badge-1' })),
368
419
  new Badge(badgeDataFixture.create({ key: 'badge-2' })),
369
420
  new Badge(badgeDataFixture.create({ key: 'badge-3' })),
@@ -372,12 +423,11 @@ describe(BadgeIndex.name, () => {
372
423
  ])
373
424
 
374
425
  const result = index.search({ pageSize: 2 })
375
- expect(result.totalPages).toBe(3)
426
+ expect(result.totalPageCount).toBe(3)
376
427
  })
377
428
 
378
429
  test(`should return a total page count of 1 when no page size is provided`, () => {
379
- const index = new BadgeIndex()
380
- index.load([
430
+ const index = new BadgeIndex([
381
431
  new Badge(badgeDataFixture.create({ key: 'badge-1' })),
382
432
  new Badge(badgeDataFixture.create({ key: 'badge-2' })),
383
433
  new Badge(badgeDataFixture.create({ key: 'badge-3' })),
@@ -386,12 +436,11 @@ describe(BadgeIndex.name, () => {
386
436
  ])
387
437
 
388
438
  const result = index.search()
389
- expect(result.totalPages).toBe(1)
439
+ expect(result.totalPageCount).toBe(1)
390
440
  })
391
441
 
392
442
  test(`should return the last page if a page is requested past the max`, () => {
393
- const index = new BadgeIndex()
394
- index.load([
443
+ const index = new BadgeIndex([
395
444
  new Badge(badgeDataFixture.create({ key: 'badge-1' })),
396
445
  new Badge(badgeDataFixture.create({ key: 'badge-2' })),
397
446
  new Badge(badgeDataFixture.create({ key: 'badge-3' })),
@@ -402,19 +451,18 @@ describe(BadgeIndex.name, () => {
402
451
  const result = index.search({ pageSize: 2, page: 10 })
403
452
  const keys = result.items.map(x => x.key)
404
453
  expect(keys).toStrictEqual(['badge-5'])
405
- expect(result.page).toBe(3)
454
+ expect(result.pageNumber).toBe(3)
406
455
  })
407
456
 
408
457
  test(`should return the first page if a page is requested lower than 1`, () => {
409
- const result = new BadgeIndex().search({ page: -10 })
410
- expect(result.page).toBe(1)
458
+ const result = new BadgeIndex([]).search({ page: -10 })
459
+ expect(result.pageNumber).toBe(1)
411
460
  })
412
461
  })
413
462
 
414
463
  describe('sort', () => {
415
464
  test(`should not modify sort if not specified`, () => {
416
- const index = new BadgeIndex()
417
- index.load([
465
+ const index = new BadgeIndex([
418
466
  new Badge(badgeDataFixture.create({ key: 'badge-1' })),
419
467
  new Badge(badgeDataFixture.create({ key: 'badge-2' })),
420
468
  new Badge(badgeDataFixture.create({ key: 'badge-3' })),
@@ -427,115 +475,118 @@ describe(BadgeIndex.name, () => {
427
475
  })
428
476
 
429
477
  test(`should not modify sort if order is canonical`, () => {
430
- const index = new BadgeIndex()
431
- index.load([
478
+ const index = new BadgeIndex([
432
479
  new Badge(badgeDataFixture.create({ key: 'badge-1' })),
433
480
  new Badge(badgeDataFixture.create({ key: 'badge-2' })),
434
481
  new Badge(badgeDataFixture.create({ key: 'badge-3' })),
435
482
  new Badge(badgeDataFixture.create({ key: 'badge-4' })),
436
483
  ])
437
484
 
438
- const result = index.search({ sort: { by: 'canonical' } })
485
+ const result = index.search({ sort: 'canonical.asc' })
439
486
  const keys = result.items.map(x => x.key)
440
487
  expect(keys).toStrictEqual(['badge-1', 'badge-2', 'badge-3', 'badge-4'])
441
488
  })
442
489
 
443
490
  test(`should reverse default sort with desc`, () => {
444
- const index = new BadgeIndex()
445
- index.load([
491
+ const index = new BadgeIndex([
446
492
  new Badge(badgeDataFixture.create({ key: 'badge-1' })),
447
493
  new Badge(badgeDataFixture.create({ key: 'badge-2' })),
448
494
  new Badge(badgeDataFixture.create({ key: 'badge-3' })),
449
495
  new Badge(badgeDataFixture.create({ key: 'badge-4' })),
450
496
  ])
451
497
 
452
- const result = index.search({ sort: { dir: 'desc' } })
498
+ const result = index.search({ sort: 'canonical.desc' })
453
499
  const keys = result.items.map(x => x.key)
454
500
  expect(keys).toStrictEqual(['badge-4', 'badge-3', 'badge-2', 'badge-1'])
455
501
  })
456
502
 
457
503
  test(`should sort by badge name`, () => {
458
- const index = new BadgeIndex()
459
- index.load([
504
+ const index = new BadgeIndex([
460
505
  new Badge(badgeDataFixture.create({ key: 'badge-1', name: [{ value: 'Abc' }] })),
461
506
  new Badge(badgeDataFixture.create({ key: 'badge-2', name: [{ value: 'XYZ' }] })),
462
507
  new Badge(badgeDataFixture.create({ key: 'badge-3', name: [{ value: 'AAB' }] })),
463
508
  ])
464
509
 
465
- const result = index.search({ sort: { by: 'badge-name' } })
510
+ const result = index.search({ sort: 'name.asc' })
466
511
  const keys = result.items.map(x => x.key)
467
512
  expect(keys).toStrictEqual(['badge-3', 'badge-1', 'badge-2'])
468
513
  })
469
514
 
515
+ test(`should sort by badge name, taking variant context into account`, () => {
516
+ const index = new BadgeIndex([
517
+ new Badge(badgeDataFixture.create({ key: 'badge-1', name: [{ value: 'A' }, { value: 'C', alignment: 'praetorian', sex: 'F' }] })),
518
+ new Badge(badgeDataFixture.create({ key: 'badge-2', name: [{ value: 'B' }] })),
519
+ new Badge(badgeDataFixture.create({ key: 'badge-3', name: [{ value: 'C' }, { value: 'A', alignment: 'praetorian' }] })),
520
+ ])
521
+
522
+ const result = index.search({ sort: 'name.asc', context: { morality: 'resistance', sex: 'F' } })
523
+ const keys = result.items.map(x => x.key)
524
+ expect(keys).toStrictEqual(['badge-3', 'badge-2', 'badge-1'])
525
+ })
526
+
470
527
  test(`should sort by badge name descending`, () => {
471
- const index = new BadgeIndex()
472
- index.load([
528
+ const index = new BadgeIndex([
473
529
  new Badge(badgeDataFixture.create({ key: 'badge-1', name: [{ value: 'Abc' }] })),
474
530
  new Badge(badgeDataFixture.create({ key: 'badge-2', name: [{ value: 'XYZ' }] })),
475
531
  new Badge(badgeDataFixture.create({ key: 'badge-3', name: [{ value: 'AAB' }] })),
476
532
  ])
477
533
 
478
- const result = index.search({ sort: { by: 'badge-name', dir: 'desc' } })
534
+ const result = index.search({ sort: 'name.desc' })
479
535
  const keys = result.items.map(x => x.key)
480
536
  expect(keys).toStrictEqual(['badge-2', 'badge-1', 'badge-3'])
481
537
  })
482
538
 
483
539
  test(`should use the default badge name when sorting by name`, () => {
484
- const index = new BadgeIndex()
485
- index.load([
540
+ const index = new BadgeIndex([
486
541
  new Badge(badgeDataFixture.create({ key: 'badge-1', name: [{ value: 'Abc' }] })),
487
542
  new Badge(badgeDataFixture.create({ key: 'badge-2', name: [{ value: 'XYZ' }, { sex: 'F', value: 'AAA' }] })),
488
543
  new Badge(badgeDataFixture.create({ key: 'badge-3', name: [{ value: 'AAB' }] })),
489
544
  ])
490
545
 
491
- const result = index.search({ sort: { by: 'badge-name' } })
546
+ const result = index.search({ sort: 'name.asc' })
492
547
  const keys = result.items.map(x => x.key)
493
548
  expect(keys).toStrictEqual(['badge-3', 'badge-1', 'badge-2'])
494
549
  })
495
550
 
496
551
  test(`should sort by zone name`, () => {
497
- const index = new BadgeIndex()
498
- index.load([
552
+ const index = new BadgeIndex([
499
553
  new Badge(badgeDataFixture.create({ key: 'badge-1', requirements: [{ location: { zoneKey: 'atlas-park' } }] })),
500
554
  new Badge(badgeDataFixture.create({ key: 'badge-2', requirements: [{ location: { zoneKey: 'perez-park' } }] })),
501
555
  new Badge(badgeDataFixture.create({ key: 'badge-3', requirements: [{ location: { zoneKey: 'abandoned-sewer-network' } }] })),
502
556
  ])
503
557
 
504
- const result = index.search({ sort: { by: 'zone-key' } })
558
+ const result = index.search({ sort: 'zone-key.asc' })
505
559
  const keys = result.items.map(x => x.key)
506
560
  expect(keys).toStrictEqual(['badge-3', 'badge-1', 'badge-2'])
507
561
  })
508
562
 
509
563
  test(`should sort by zone name descending`, () => {
510
- const index = new BadgeIndex()
511
- index.load([
564
+ const index = new BadgeIndex([
512
565
  new Badge(badgeDataFixture.create({ key: 'badge-1', requirements: [{ location: { zoneKey: 'atlas-park' } }] })),
513
566
  new Badge(badgeDataFixture.create({ key: 'badge-2', requirements: [{ location: { zoneKey: 'perez-park' } }] })),
514
567
  new Badge(badgeDataFixture.create({ key: 'badge-3', requirements: [{ location: { zoneKey: 'abandoned-sewer-network' } }] })),
515
568
  ])
516
569
 
517
- const result = index.search({ sort: { by: 'zone-key', dir: 'desc' } })
570
+ const result = index.search({ sort: 'zone-key.desc' })
518
571
  const keys = result.items.map(x => x.key)
519
572
  expect(keys).toStrictEqual(['badge-2', 'badge-1', 'badge-3'])
520
573
  })
521
574
 
522
575
  test(`should maintain canonical as secondary sort when sorting by zone name`, () => {
523
- const index = new BadgeIndex()
524
- index.load([
576
+ const index = new BadgeIndex([
525
577
  new Badge(badgeDataFixture.create({ key: 'badge-1', requirements: [{ location: { zoneKey: 'atlas-park' } }] })),
526
578
  new Badge(badgeDataFixture.create({ key: 'badge-2', requirements: [{ location: { zoneKey: 'perez-park' } }] })),
527
579
  new Badge(badgeDataFixture.create({ key: 'badge-3', requirements: [{ location: { zoneKey: 'atlas-park' } }] })),
528
580
  new Badge(badgeDataFixture.create({ key: 'badge-4', requirements: [{ location: { zoneKey: 'abandoned-sewer-network' } }] })),
529
581
  ])
530
582
 
531
- const result = index.search({ sort: { by: 'zone-key' } })
583
+ const result = index.search({ sort: 'zone-key.asc' })
532
584
  const keys = result.items.map(x => x.key)
533
585
  expect(keys).toStrictEqual(['badge-4', 'badge-1', 'badge-3', 'badge-2'])
534
586
  })
535
587
 
536
588
  test(`should sort undefined or multiple zone names to the end`, () => {
537
- const index = new BadgeIndex()
538
- index.load([
589
+ const index = new BadgeIndex([
539
590
  new Badge(badgeDataFixture.create({ key: 'badge-1', requirements: [{ location: { zoneKey: 'atlas-park' } }] })),
540
591
  new Badge(badgeDataFixture.create({ key: 'badge-2', requirements: [{ location: undefined }] })),
541
592
  new Badge(badgeDataFixture.create({ key: 'badge-3', requirements: [{ location: { zoneKey: 'perez-park' } }] })),
@@ -548,10 +599,55 @@ describe(BadgeIndex.name, () => {
548
599
  new Badge(badgeDataFixture.create({ key: 'badge-5', requirements: [{ location: { zoneKey: 'abandoned-sewer-network' } }] })),
549
600
  ])
550
601
 
551
- const result = index.search({ sort: { by: 'zone-key' } })
602
+ const result = index.search({ sort: 'zone-key.asc' })
552
603
  const keys = result.items.map(x => x.key)
553
604
  expect(keys).toStrictEqual(['badge-5', 'badge-1', 'badge-3', 'badge-2', 'badge-4'])
554
605
  })
606
+
607
+ test(`should sort by release date`, () => {
608
+ const index = new BadgeIndex([
609
+ new Badge(badgeDataFixture.create({ key: 'badge-1', releaseDate: '2025-02-03' })),
610
+ new Badge(badgeDataFixture.create({ key: 'badge-2', releaseDate: '2025-02-03' })),
611
+ new Badge(badgeDataFixture.create({ key: 'badge-3', releaseDate: '2020-01-01' })),
612
+ ])
613
+
614
+ const result = index.search({ sort: 'release-date.asc' })
615
+ const keys = result.items.map(x => x.key)
616
+ expect(keys).toStrictEqual(['badge-3', 'badge-1', 'badge-2'])
617
+ })
618
+
619
+ test(`should sort by release date descending`, () => {
620
+ const index = new BadgeIndex([
621
+ new Badge(badgeDataFixture.create({ key: 'badge-1', releaseDate: '2025-02-03' })),
622
+ new Badge(badgeDataFixture.create({ key: 'badge-2', releaseDate: '2020-01-01' })),
623
+ new Badge(badgeDataFixture.create({ key: 'badge-3', releaseDate: '2025-02-03' })),
624
+ ])
625
+
626
+ const result = index.search({ sort: 'release-date.desc' })
627
+ const keys = result.items.map(x => x.key)
628
+ expect(keys).toStrictEqual(['badge-1', 'badge-3', 'badge-2'])
629
+ })
630
+
631
+ test(`sort should apply before paging`, () => {
632
+ const index = new BadgeIndex([
633
+ new Badge(badgeDataFixture.create({ key: 'badge-a', name: 'A' })),
634
+ new Badge(badgeDataFixture.create({ key: 'badge-c', name: 'C' })),
635
+ new Badge(badgeDataFixture.create({ key: 'badge-e', name: 'E' })),
636
+ new Badge(badgeDataFixture.create({ key: 'badge-d', name: 'D' })),
637
+ new Badge(badgeDataFixture.create({ key: 'badge-b', name: 'B' })),
638
+ new Badge(badgeDataFixture.create({ key: 'badge-f', name: 'F' })),
639
+ ])
640
+
641
+ const page1 = index.search({ sort: 'name.asc', page: 1, pageSize: 2 })
642
+ const page2 = index.search({ sort: 'name.asc', page: 2, pageSize: 2 })
643
+ const page3 = index.search({ sort: 'name.asc', page: 3, pageSize: 2 })
644
+ const page1Keys = page1.items.map(x => x.key)
645
+ const page2Keys = page2.items.map(x => x.key)
646
+ const page3Keys = page3.items.map(x => x.key)
647
+ expect(page1Keys).toStrictEqual(['badge-a', 'badge-b'])
648
+ expect(page2Keys).toStrictEqual(['badge-c', 'badge-d'])
649
+ expect(page3Keys).toStrictEqual(['badge-e', 'badge-f'])
650
+ })
555
651
  })
556
652
  })
557
653
  })