@tailgatefantasysports/seasonservice 0.5.0 → 0.6.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/SeasonService.js +24 -11
- package/__tests__/SeasonService.test.js +48 -10
- package/__tests__/index.test.js +31 -1
- package/app.config.js +8 -3
- package/package.json +1 -1
package/SeasonService.js
CHANGED
|
@@ -71,23 +71,36 @@ module.exports = class SeasonService {
|
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
/**
|
|
74
|
-
* Get the
|
|
75
|
-
* @summary
|
|
76
|
-
*
|
|
74
|
+
* Get the last season that finished, including all of its types.
|
|
75
|
+
* @summary Walks backwards from `date` past every frame of the season `date` falls in, then
|
|
76
|
+
* collects all frames of the season before it, keyed by type.
|
|
77
|
+
*
|
|
78
|
+
* A season `date` sits inside has not finished, so it is never the answer. Skipping the
|
|
79
|
+
* whole season rather than just the current frame matters mid-season: from a regular-season
|
|
80
|
+
* date, stepping back one frame lands on that same season's preseason, which would report a
|
|
81
|
+
* partial in-progress season as the previous one.
|
|
82
|
+
*
|
|
77
83
|
* @param {object} date - date to begin search
|
|
78
|
-
* @returns {object}
|
|
84
|
+
* @returns {object|null} frames keyed by type, or null when no earlier season exists
|
|
79
85
|
*/
|
|
80
86
|
getPreviousFullSeason(date) {
|
|
81
|
-
|
|
87
|
+
const current = this.getSeason(date);
|
|
88
|
+
let frame = current || this._getSeasonDirection(date, -1);
|
|
82
89
|
if (!frame)
|
|
83
|
-
return null
|
|
90
|
+
return null;
|
|
91
|
+
|
|
92
|
+
if (current) {
|
|
93
|
+
while (frame && frame.season === current.season)
|
|
94
|
+
frame = this._getSeasonDirection(new Date(frame.start - 1), -1);
|
|
95
|
+
if (!frame)
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
84
98
|
|
|
85
99
|
const full = { };
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
newFrame = this.getPreviousSeason(new Date(newFrame.start - 1))
|
|
100
|
+
const season = frame.season;
|
|
101
|
+
while (frame && frame.season === season) {
|
|
102
|
+
full[frame.type] = frame;
|
|
103
|
+
frame = this._getSeasonDirection(new Date(frame.start - 1), -1);
|
|
91
104
|
}
|
|
92
105
|
|
|
93
106
|
return full;
|
|
@@ -127,21 +127,28 @@ describe('GetPreviousFullSeason', () => {
|
|
|
127
127
|
end: new Date('2022-04-01')
|
|
128
128
|
}
|
|
129
129
|
}],
|
|
130
|
+
// Inside season 3, so season 3 is the *current* season and cannot be the previous one.
|
|
131
|
+
// The answer is season 2, whole. This previously returned season 3's preseason and
|
|
132
|
+
// regular frames — a partial, in-progress season reported as a finished one.
|
|
130
133
|
['Exists - start inside frame', new Date('2022-02-02'), {
|
|
131
|
-
[types.preseason]: {
|
|
132
|
-
season: "3",
|
|
133
|
-
type: types.preseason,
|
|
134
|
-
start: new Date('2022-01-01'),
|
|
135
|
-
end: new Date('2022-02-01')
|
|
136
|
-
},
|
|
137
134
|
[types.regular]: {
|
|
138
|
-
season: "
|
|
135
|
+
season: "2",
|
|
139
136
|
type: types.regular,
|
|
140
|
-
start: new Date('
|
|
141
|
-
end: new Date('
|
|
137
|
+
start: new Date('2021-03-01'),
|
|
138
|
+
end: new Date('2021-04-01')
|
|
142
139
|
},
|
|
143
140
|
}],
|
|
144
|
-
['Doesn\'t exist', new Date('2019-01-01'), null]
|
|
141
|
+
['Doesn\'t exist', new Date('2019-01-01'), null],
|
|
142
|
+
// Mid-season, one frame back is the *same* season's preseason. Skipping only the
|
|
143
|
+
// current frame rather than the whole season would report season 3 as previous.
|
|
144
|
+
['Exists - mid regular season', new Date('2022-02-20'), {
|
|
145
|
+
[types.regular]: {
|
|
146
|
+
season: "2",
|
|
147
|
+
type: types.regular,
|
|
148
|
+
start: new Date('2021-03-01'),
|
|
149
|
+
end: new Date('2021-04-01')
|
|
150
|
+
},
|
|
151
|
+
}]
|
|
145
152
|
])('%s', (_tName, inp, expected) => {
|
|
146
153
|
// setup
|
|
147
154
|
// execute
|
|
@@ -150,4 +157,35 @@ describe('GetPreviousFullSeason', () => {
|
|
|
150
157
|
// assert
|
|
151
158
|
expect(season).toStrictEqual(expected);
|
|
152
159
|
});
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
describe('GetPreviousFullSeason — real calendar', () => {
|
|
163
|
+
// The configured singleton, not the bare class the synthetic fixtures above use.
|
|
164
|
+
const { SeasonService: configured } = require('../index');
|
|
165
|
+
|
|
166
|
+
// The case this method exists for: during the 2026 preseason the app asks what last season
|
|
167
|
+
// was, to show finished-season stats. It used to answer "2026 preseason" — the frame the
|
|
168
|
+
// date sits in — so the app showed preseason stats beside a regular-season fixture.
|
|
169
|
+
test.each([
|
|
170
|
+
['from the 2026 preseason', new Date('2026-08-23T00:00:00Z')],
|
|
171
|
+
['from mid 2026 regular season', new Date('2026-11-01T00:00:00Z')],
|
|
172
|
+
['from the 2026 off-season', new Date('2026-05-01T00:00:00Z')]
|
|
173
|
+
])('%s answers 2025, whole', (_tName, date) => {
|
|
174
|
+
const full = configured.getPreviousFullSeason(date);
|
|
175
|
+
expect(full).not.toBeNull();
|
|
176
|
+
|
|
177
|
+
const frames = Object.values(full);
|
|
178
|
+
expect(frames.every(f => f.season === '2025')).toBe(true);
|
|
179
|
+
expect(frames.map(f => f.type).sort()).toEqual(
|
|
180
|
+
[types.preseason, types.regular, types.postseason].sort());
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test('never reports the season the date sits inside', () => {
|
|
184
|
+
const inPreseason = new Date('2026-08-23T00:00:00Z');
|
|
185
|
+
const current = configured.getSeason(inPreseason);
|
|
186
|
+
const full = configured.getPreviousFullSeason(inPreseason);
|
|
187
|
+
|
|
188
|
+
expect(current.season).toBe('2026');
|
|
189
|
+
expect(Object.values(full).some(f => f.season === current.season)).toBe(false);
|
|
190
|
+
});
|
|
153
191
|
})
|
package/__tests__/index.test.js
CHANGED
|
@@ -7,7 +7,9 @@ describe('Service', () => {
|
|
|
7
7
|
['2020 Post', new Date('2021-01-20T00:00:00Z'), '2020', 2, new Date('2021-01-07T00:00:00Z'), new Date('2021-02-07T00:00:00Z')],
|
|
8
8
|
['2021 Beta', new Date('2021-06-20T00:00:00Z'), 'BETA', 'beta', new Date('2021-06-16T00:00:00Z'), new Date('2021-08-05T00:00:00Z')],
|
|
9
9
|
['2021 Regular', new Date('2021-09-10T00:00:00Z'), '2021', 1, new Date('2021-09-08T00:00:00Z'), new Date('2022-01-12T00:00:00Z')],
|
|
10
|
-
['2021 Post', new Date('2022-01-20T00:00:00Z'), '2021', 2, new Date('2022-01-12T00:00:00Z'), new Date('2022-02-15T00:00:00Z')]
|
|
10
|
+
['2021 Post', new Date('2022-01-20T00:00:00Z'), '2021', 2, new Date('2022-01-12T00:00:00Z'), new Date('2022-02-15T00:00:00Z')],
|
|
11
|
+
['2026 Preseason', new Date('2026-08-13T23:00:00Z'), '2026', 0, new Date('2026-08-12T00:00:00Z'), new Date('2026-09-02T00:00:00Z')],
|
|
12
|
+
['2026 Regular', new Date('2026-10-11T23:00:00Z'), '2026', 1, new Date('2026-09-09T00:00:00Z'), new Date('2027-01-13T00:00:00Z')]
|
|
11
13
|
])('%s', (_tName, date, season, type, start, end) => {
|
|
12
14
|
let frame = SeasonService.getSeason(date);
|
|
13
15
|
expect(frame.season).toBe(season);
|
|
@@ -15,6 +17,34 @@ describe('Service', () => {
|
|
|
15
17
|
expect(frame.start).toStrictEqual(start);
|
|
16
18
|
expect(frame.end).toStrictEqual(end);
|
|
17
19
|
});
|
|
20
|
+
|
|
21
|
+
// The Aug 6 Hall of Fame game sits outside the preseason frame on
|
|
22
|
+
// purpose — the game sync drops it, so week 1 is the Aug 13-15 slate.
|
|
23
|
+
test('2026 Hall of Fame game is outside the preseason frame', () => {
|
|
24
|
+
expect(SeasonService.getSeason(new Date('2026-08-06T23:00:00Z'))).toBeUndefined();
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// Each frame's weeks must line up with the provider's week numbering:
|
|
29
|
+
// dates below are the first and last day of each slate on the real 2026
|
|
30
|
+
// schedule, at 7pm ET.
|
|
31
|
+
describe('Get Week — 2026 slates match provider weeks', () => {
|
|
32
|
+
test.each([
|
|
33
|
+
['Preseason Week 1 open', new Date('2026-08-13T23:00:00Z'), 1],
|
|
34
|
+
['Preseason Week 1 close', new Date('2026-08-15T23:00:00Z'), 1],
|
|
35
|
+
['Preseason Week 2 open', new Date('2026-08-20T23:00:00Z'), 2],
|
|
36
|
+
['Preseason Week 2 close', new Date('2026-08-23T23:00:00Z'), 2],
|
|
37
|
+
['Preseason Week 3 open', new Date('2026-08-27T23:00:00Z'), 3],
|
|
38
|
+
['Preseason Week 3 close', new Date('2026-08-29T23:00:00Z'), 3],
|
|
39
|
+
['Regular Week 1 open', new Date('2026-09-09T23:00:00Z'), 1],
|
|
40
|
+
['Regular Week 1 close', new Date('2026-09-14T23:00:00Z'), 1],
|
|
41
|
+
['Regular Week 2 open', new Date('2026-09-17T23:00:00Z'), 2],
|
|
42
|
+
['Regular Week 17 open', new Date('2026-12-31T23:00:00Z'), 17],
|
|
43
|
+
['Regular Week 17 close', new Date('2027-01-04T23:00:00Z'), 17],
|
|
44
|
+
['Regular Week 18', new Date('2027-01-10T23:00:00Z'), 18]
|
|
45
|
+
])('%s', (_tName, date, week) => {
|
|
46
|
+
expect(SeasonService.getWeek(date).week).toBe(week);
|
|
47
|
+
});
|
|
18
48
|
});
|
|
19
49
|
});
|
|
20
50
|
|
package/app.config.js
CHANGED
|
@@ -89,12 +89,17 @@ module.exports = {
|
|
|
89
89
|
}
|
|
90
90
|
},
|
|
91
91
|
2026: {
|
|
92
|
+
// Frames start the Wednesday 00:00Z before their week 1 games so the
|
|
93
|
+
// service's week N matches the provider's week N. The Aug 6 Hall of
|
|
94
|
+
// Fame game is intentionally outside the preseason frame — the game
|
|
95
|
+
// sync filters it out (its "Hall of Fame Weekend" label has no week
|
|
96
|
+
// number), so preseason week 1 is the Aug 13-15 slate.
|
|
92
97
|
[types.preseason]: {
|
|
93
|
-
start: new Date('2026-08-
|
|
94
|
-
end: new Date('2026-09-
|
|
98
|
+
start: new Date('2026-08-12T00:00:00Z'),
|
|
99
|
+
end: new Date('2026-09-02T00:00:00Z')
|
|
95
100
|
},
|
|
96
101
|
[types.regular]: {
|
|
97
|
-
start: new Date('2026-09-
|
|
102
|
+
start: new Date('2026-09-09T00:00:00Z'),
|
|
98
103
|
end: new Date('2027-01-13T00:00:00Z'),
|
|
99
104
|
},
|
|
100
105
|
[types.postseason]: {
|