mtg-playerinfo 1.4.0 → 1.4.2
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/README.md +16 -7
- package/package.json +6 -7
- package/src/fetchers/melee.js +7 -13
- package/src/fetchers/mtgElo.js +2 -2
- package/src/fetchers/topdeck.js +9 -2
- package/src/fetchers/unityLeague.js +2 -2
- package/src/fetchers/untapped.js +2 -2
- package/src/utils/socialMediaExtractor.js +49 -0
- package/test/helpers.js +32 -0
- package/test/melee.test.js +54 -17
- package/test/mtgElo.test.js +62 -23
- package/test/playerInfoManager.test.js +365 -0
- package/test/socialMediaExtractor.test.js +31 -0
- package/test/topdeck.test.js +69 -33
- package/test/unityLeague.test.js +59 -18
- package/test/untapped.test.js +75 -64
- package/test/edgeCases.test.js +0 -128
- package/test/meleeEdgeCases.test.js +0 -53
- package/test/mtgEloEdgeCases.test.js +0 -92
- package/test/unityLeagueEdgeCases.test.js +0 -123
- package/test/verboseLogging.test.js +0 -213
- package/test/winRatePrecision.test.js +0 -25
package/test/untapped.test.js
CHANGED
|
@@ -1,15 +1,10 @@
|
|
|
1
1
|
const test = require('node:test')
|
|
2
2
|
const assert = require('node:assert/strict')
|
|
3
|
-
const fs = require('node:fs')
|
|
4
|
-
const path = require('node:path')
|
|
5
|
-
|
|
6
3
|
const UntappedFetcher = require('../src/fetchers/untapped')
|
|
4
|
+
const httpClient = require('../src/utils/httpClient')
|
|
5
|
+
const { readFixture, withMutedConsole } = require('./helpers')
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
return fs.readFileSync(path.join(__dirname, 'data', name), 'utf8')
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
test('UntappedFetcher: parses most recent match and extracts MTGA rank', () => {
|
|
7
|
+
test('UntappedFetcher: parseMatches extracts MTGA rank from fixture', () => {
|
|
13
8
|
const fetcher = new UntappedFetcher()
|
|
14
9
|
const fixtureJson = readFixture('untapped.json')
|
|
15
10
|
const matches = JSON.parse(fixtureJson)
|
|
@@ -19,83 +14,99 @@ test('UntappedFetcher: parses most recent match and extracts MTGA rank', () => {
|
|
|
19
14
|
|
|
20
15
|
assert.strictEqual(result.source, 'Untapped.gg')
|
|
21
16
|
assert.strictEqual(result.url, url)
|
|
22
|
-
|
|
23
17
|
assert.strictEqual(typeof result.mtga_rank, 'object')
|
|
24
18
|
|
|
25
|
-
|
|
26
|
-
if (result.mtga_rank.constructed
|
|
27
|
-
assert.match(result.mtga_rank.constructed,
|
|
19
|
+
const rankRegex = /^(Bronze|Silver|Gold|Platinum|Diamond|Mythic)(\s\d+|\s#\d+|\s\d+(\.\d+)?%)$/
|
|
20
|
+
if (result.mtga_rank.constructed) {
|
|
21
|
+
assert.match(result.mtga_rank.constructed, rankRegex)
|
|
28
22
|
}
|
|
29
|
-
if (result.mtga_rank.limited
|
|
30
|
-
assert.match(result.mtga_rank.limited,
|
|
23
|
+
if (result.mtga_rank.limited) {
|
|
24
|
+
assert.match(result.mtga_rank.limited, rankRegex)
|
|
31
25
|
}
|
|
32
26
|
})
|
|
33
27
|
|
|
34
|
-
test('UntappedFetcher:
|
|
28
|
+
test('UntappedFetcher: formats Mythic rank correctly', () => {
|
|
35
29
|
const fetcher = new UntappedFetcher()
|
|
36
|
-
const
|
|
30
|
+
const testCases = [
|
|
37
31
|
{
|
|
38
|
-
|
|
39
|
-
friendly_ranking_class_after:
|
|
40
|
-
|
|
41
|
-
match_start: 1000
|
|
32
|
+
label: 'leaderboard place',
|
|
33
|
+
match: { super_format: 2, friendly_ranking_class_after: 'Mythic', friendly_mythic_leaderboard_place_after: 123, match_start: 1000 },
|
|
34
|
+
expected: 'Mythic #123'
|
|
42
35
|
},
|
|
43
36
|
{
|
|
44
|
-
|
|
45
|
-
friendly_ranking_class_after:
|
|
46
|
-
|
|
47
|
-
match_start: 2000
|
|
37
|
+
label: 'percentile',
|
|
38
|
+
match: { super_format: 1, friendly_ranking_class_after: 'Mythic', friendly_mythic_percentile_after: 98.789, match_start: 2000 },
|
|
39
|
+
expected: 'Mythic 98%'
|
|
48
40
|
}
|
|
49
41
|
]
|
|
50
42
|
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
43
|
+
for (const { label, match, expected } of testCases) {
|
|
44
|
+
const result = fetcher.parseMatches([match], 'url')
|
|
45
|
+
const rank = match.super_format === 2 ? result.mtga_rank.constructed : result.mtga_rank.limited
|
|
46
|
+
assert.strictEqual(rank, expected, `Failed for ${label}`)
|
|
47
|
+
}
|
|
56
48
|
})
|
|
57
49
|
|
|
58
|
-
test('UntappedFetcher:
|
|
50
|
+
test('UntappedFetcher: handles missing or incomplete rank data', () => {
|
|
59
51
|
const fetcher = new UntappedFetcher()
|
|
60
|
-
const
|
|
61
|
-
{
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
friendly_ranking_tier_after: null,
|
|
65
|
-
friendly_mythic_leaderboard_place_after: 123,
|
|
66
|
-
friendly_mythic_percentile_after: 101.5,
|
|
67
|
-
match_start: 1000
|
|
68
|
-
},
|
|
69
|
-
{
|
|
70
|
-
super_format: 1,
|
|
71
|
-
friendly_ranking_class_after: 'Mythic',
|
|
72
|
-
friendly_ranking_tier_after: null,
|
|
73
|
-
friendly_mythic_leaderboard_place_after: null,
|
|
74
|
-
friendly_mythic_percentile_after: 98.789,
|
|
75
|
-
match_start: 2000
|
|
76
|
-
}
|
|
52
|
+
const cases = [
|
|
53
|
+
{ label: 'null class', match: { super_format: 2, friendly_ranking_class_after: null } },
|
|
54
|
+
{ label: 'Mythic no data', match: { super_format: 2, friendly_ranking_class_after: 'Mythic', friendly_mythic_leaderboard_place_after: null, friendly_mythic_percentile_after: null } },
|
|
55
|
+
{ label: 'missing tier', match: { super_format: 2, friendly_ranking_class_after: 'Gold', friendly_ranking_tier_after: null } }
|
|
77
56
|
]
|
|
78
57
|
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
assert.strictEqual(result.mtga_rank.limited, 'Mythic 98%')
|
|
58
|
+
for (const { label, match } of cases) {
|
|
59
|
+
const result = fetcher.parseMatches([match], 'url')
|
|
60
|
+
assert.ok(!result.mtga_rank.constructed, `Should not have rank for ${label}`)
|
|
61
|
+
}
|
|
84
62
|
})
|
|
85
63
|
|
|
86
|
-
test('UntappedFetcher:
|
|
87
|
-
const
|
|
88
|
-
const
|
|
89
|
-
|
|
64
|
+
test('UntappedFetcher: findMostRecentByFormat picks latest match', () => {
|
|
65
|
+
const fetcher = new UntappedFetcher()
|
|
66
|
+
const testMatches = [
|
|
67
|
+
{ super_format: 2, friendly_ranking_class_after: 'Gold', friendly_ranking_tier_after: 4, match_start: 1000 },
|
|
68
|
+
{ super_format: 2, friendly_ranking_class_after: 'Gold', friendly_ranking_tier_after: 1, match_start: 5000 },
|
|
69
|
+
{ super_format: 2, friendly_ranking_class_after: 'Gold', friendly_ranking_tier_after: 2, match_start: 3000 }
|
|
70
|
+
]
|
|
90
71
|
|
|
91
|
-
const
|
|
92
|
-
assert.strictEqual(
|
|
93
|
-
|
|
94
|
-
assert.strictEqual(parts[1], playerCode)
|
|
72
|
+
const result = fetcher.parseMatches(testMatches, 'url')
|
|
73
|
+
assert.strictEqual(result.mtga_rank.constructed, 'Gold 1')
|
|
74
|
+
})
|
|
95
75
|
|
|
96
|
-
|
|
97
|
-
|
|
76
|
+
test('UntappedFetcher: fetchById handles various ID and response scenarios', async (t) => {
|
|
77
|
+
const fetcher = new UntappedFetcher()
|
|
98
78
|
|
|
99
|
-
|
|
100
|
-
|
|
79
|
+
await t.test('invalid ID format', async () => {
|
|
80
|
+
await withMutedConsole(async () => {
|
|
81
|
+
const result = await fetcher.fetchById('invalid-id')
|
|
82
|
+
assert.strictEqual(result, null)
|
|
83
|
+
})
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
await t.test('network error', async () => {
|
|
87
|
+
const mockRequest = t.mock.method(httpClient, 'request', () => { throw new Error('Network Error') })
|
|
88
|
+
await withMutedConsole(async () => {
|
|
89
|
+
const result = await fetcher.fetchById('user/code')
|
|
90
|
+
assert.strictEqual(result, null)
|
|
91
|
+
})
|
|
92
|
+
mockRequest.mock.restore()
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
await t.test('empty matches array', async () => {
|
|
96
|
+
const mockRequest = t.mock.method(httpClient, 'request', async () => ({ data: [] }))
|
|
97
|
+
await withMutedConsole(async () => {
|
|
98
|
+
const result = await fetcher.fetchById('user/code')
|
|
99
|
+
assert.strictEqual(result, null)
|
|
100
|
+
})
|
|
101
|
+
mockRequest.mock.restore()
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
await t.test('non-array data', async () => {
|
|
105
|
+
const mockRequest = t.mock.method(httpClient, 'request', async () => ({ data: {} }))
|
|
106
|
+
await withMutedConsole(async () => {
|
|
107
|
+
const result = await fetcher.fetchById('user/code')
|
|
108
|
+
assert.strictEqual(result, null)
|
|
109
|
+
})
|
|
110
|
+
mockRequest.mock.restore()
|
|
111
|
+
})
|
|
101
112
|
})
|
package/test/edgeCases.test.js
DELETED
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
const test = require('node:test')
|
|
2
|
-
const assert = require('node:assert/strict')
|
|
3
|
-
const PlayerInfoManager = require('../src')
|
|
4
|
-
|
|
5
|
-
test('PlayerInfoManager: getPlayerInfo handles all null results gracefully', async () => {
|
|
6
|
-
const manager = new PlayerInfoManager()
|
|
7
|
-
|
|
8
|
-
manager.fetchers.unity.fetchById = async () => null
|
|
9
|
-
manager.fetchers.mtgelo.fetchById = async () => null
|
|
10
|
-
manager.fetchers.melee.fetchById = async () => null
|
|
11
|
-
manager.fetchers.topdeck.fetchById = async () => null
|
|
12
|
-
|
|
13
|
-
const result = await manager.getPlayerInfo({
|
|
14
|
-
unityId: '123',
|
|
15
|
-
mtgeloId: '456',
|
|
16
|
-
meleeUser: 'test',
|
|
17
|
-
topdeckHandle: 'test'
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
assert.deepEqual(result.general, {}, 'General should be empty object')
|
|
21
|
-
assert.deepEqual(result.sources, {}, 'Sources should be empty object')
|
|
22
|
-
assert.ok(!result.general['win rate'], 'Should not have win rate with no valid results')
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
test('PlayerInfoManager: getPlayerInfo handles partial null results', async () => {
|
|
26
|
-
const manager = new PlayerInfoManager()
|
|
27
|
-
|
|
28
|
-
manager.fetchers.unity.fetchById = async () => ({
|
|
29
|
-
source: 'Unity League',
|
|
30
|
-
url: 'http://unity.test',
|
|
31
|
-
name: 'Test Player'
|
|
32
|
-
})
|
|
33
|
-
manager.fetchers.mtgelo.fetchById = async () => null
|
|
34
|
-
manager.fetchers.melee.fetchById = async () => null
|
|
35
|
-
manager.fetchers.topdeck.fetchById = async () => null
|
|
36
|
-
|
|
37
|
-
const result = await manager.getPlayerInfo({
|
|
38
|
-
unityId: '123',
|
|
39
|
-
mtgeloId: '456',
|
|
40
|
-
meleeUser: 'test',
|
|
41
|
-
topdeckHandle: 'test'
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
assert.equal(result.general.name, 'Test Player', 'Should have data from Unity League')
|
|
45
|
-
assert.equal(Object.keys(result.sources).length, 1, 'Should have only one source')
|
|
46
|
-
assert.ok(result.sources['Unity League'], 'Should have Unity League in sources')
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
test('PlayerInfoManager: mergeData handles records without draws (W-L format)', async () => {
|
|
50
|
-
const manager = new PlayerInfoManager()
|
|
51
|
-
|
|
52
|
-
const results = [
|
|
53
|
-
{
|
|
54
|
-
source: 'Test Source',
|
|
55
|
-
url: 'http://test.com',
|
|
56
|
-
name: 'Test Player',
|
|
57
|
-
record: '10-5'
|
|
58
|
-
}
|
|
59
|
-
]
|
|
60
|
-
|
|
61
|
-
const merged = manager.mergeData(results, false)
|
|
62
|
-
|
|
63
|
-
assert.equal(merged.general['win rate'], '66.67%', 'Should calculate win rate correctly for W-L format')
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
test('PlayerInfoManager: mergeData handles invalid win rate strings gracefully', async () => {
|
|
67
|
-
const manager = new PlayerInfoManager()
|
|
68
|
-
|
|
69
|
-
const results = [
|
|
70
|
-
{
|
|
71
|
-
source: 'Test1',
|
|
72
|
-
url: 'http://test1.com',
|
|
73
|
-
name: 'Test',
|
|
74
|
-
winRate: 'invalid%'
|
|
75
|
-
},
|
|
76
|
-
{
|
|
77
|
-
source: 'Test2',
|
|
78
|
-
url: 'http://test2.com',
|
|
79
|
-
name: 'Test',
|
|
80
|
-
'win rate': 'N/A'
|
|
81
|
-
},
|
|
82
|
-
{
|
|
83
|
-
source: 'Test3',
|
|
84
|
-
url: 'http://test3.com',
|
|
85
|
-
name: 'Test',
|
|
86
|
-
record: '10-5-0'
|
|
87
|
-
}
|
|
88
|
-
]
|
|
89
|
-
|
|
90
|
-
const merged = manager.mergeData(results, false)
|
|
91
|
-
|
|
92
|
-
// Should not crash, should calculate from record only
|
|
93
|
-
assert.ok(merged, 'Should not crash on invalid win rate strings')
|
|
94
|
-
assert.equal(merged.general['win rate'], '66.67%', 'Should calculate from valid record')
|
|
95
|
-
})
|
|
96
|
-
|
|
97
|
-
test('PlayerInfoManager: mergeData returns no win rate when insufficient data', async () => {
|
|
98
|
-
const manager = new PlayerInfoManager()
|
|
99
|
-
|
|
100
|
-
const results = [
|
|
101
|
-
{
|
|
102
|
-
source: 'Test',
|
|
103
|
-
url: 'http://test.com',
|
|
104
|
-
name: 'Test Player'
|
|
105
|
-
}
|
|
106
|
-
]
|
|
107
|
-
|
|
108
|
-
const merged = manager.mergeData(results, false)
|
|
109
|
-
|
|
110
|
-
assert.ok(!merged.general['win rate'], 'Should not calculate win rate without data')
|
|
111
|
-
})
|
|
112
|
-
|
|
113
|
-
test('PlayerInfoManager: mergeData handles record with all zeros', async () => {
|
|
114
|
-
const manager = new PlayerInfoManager()
|
|
115
|
-
|
|
116
|
-
const results = [
|
|
117
|
-
{
|
|
118
|
-
source: 'Test',
|
|
119
|
-
url: 'http://test.com',
|
|
120
|
-
name: 'Test Player',
|
|
121
|
-
record: '0-0-0'
|
|
122
|
-
}
|
|
123
|
-
]
|
|
124
|
-
|
|
125
|
-
const merged = manager.mergeData(results, false)
|
|
126
|
-
|
|
127
|
-
assert.ok(!merged.general['win rate'], 'Should not calculate win rate when no games played')
|
|
128
|
-
})
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
const test = require('node:test')
|
|
2
|
-
const assert = require('node:assert/strict')
|
|
3
|
-
const MeleeFetcher = require('../src/fetchers/melee')
|
|
4
|
-
|
|
5
|
-
test('MeleeFetcher: parseHtml uses username when name not found in HTML', () => {
|
|
6
|
-
const fetcher = new MeleeFetcher()
|
|
7
|
-
const html = '<html><body></body></html>'
|
|
8
|
-
|
|
9
|
-
const result = fetcher.parseHtml(html, 'http://test.com', 'fallbackname')
|
|
10
|
-
|
|
11
|
-
assert.equal(result.name, 'fallbackname', 'Should use username as fallback')
|
|
12
|
-
assert.equal(result.source, 'Melee')
|
|
13
|
-
assert.equal(result.url, 'http://test.com')
|
|
14
|
-
})
|
|
15
|
-
|
|
16
|
-
test('MeleeFetcher: parseHtml handles invalid social link URLs gracefully', () => {
|
|
17
|
-
const fetcher = new MeleeFetcher()
|
|
18
|
-
const html = `
|
|
19
|
-
<html>
|
|
20
|
-
<body>
|
|
21
|
-
<span style="font-size: xx-large">Test Name</span>
|
|
22
|
-
<a class="social-link" href="not-a-valid-url">Invalid</a>
|
|
23
|
-
<a class="social-link" href="https://twitter.com/validuser">Valid</a>
|
|
24
|
-
</body>
|
|
25
|
-
</html>
|
|
26
|
-
`
|
|
27
|
-
|
|
28
|
-
const result = fetcher.parseHtml(html, 'http://test.com', 'testuser')
|
|
29
|
-
|
|
30
|
-
assert.equal(result.name, 'Test Name')
|
|
31
|
-
assert.equal(result.twitter, 'validuser', 'Should parse valid URL')
|
|
32
|
-
// Should not crash on invalid URL
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
test('MeleeFetcher: parseHtml extracts multiple social links correctly', () => {
|
|
36
|
-
const fetcher = new MeleeFetcher()
|
|
37
|
-
const html = `
|
|
38
|
-
<html>
|
|
39
|
-
<body>
|
|
40
|
-
<span style="font-size: xx-large">Test Player</span>
|
|
41
|
-
<a class="social-link" href="https://twitter.com/testtwitter">Twitter</a>
|
|
42
|
-
<a class="social-link" href="https://www.facebook.com/testfacebook">Facebook</a>
|
|
43
|
-
<a class="social-link" href="https://twitch.tv/testtwitch">Twitch</a>
|
|
44
|
-
</body>
|
|
45
|
-
</html>
|
|
46
|
-
`
|
|
47
|
-
|
|
48
|
-
const result = fetcher.parseHtml(html, 'http://test.com', 'testuser')
|
|
49
|
-
|
|
50
|
-
assert.equal(result.twitter, 'testtwitter')
|
|
51
|
-
assert.equal(result.facebook, 'testfacebook')
|
|
52
|
-
assert.equal(result.twitch, 'testtwitch')
|
|
53
|
-
})
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
const test = require('node:test')
|
|
2
|
-
const assert = require('node:assert/strict')
|
|
3
|
-
const MtgEloFetcher = require('../src/fetchers/mtgElo')
|
|
4
|
-
|
|
5
|
-
test('MtgEloFetcher: parseHtml returns null when no name found', () => {
|
|
6
|
-
const fetcher = new MtgEloFetcher()
|
|
7
|
-
const html = '<html><body></body></html>'
|
|
8
|
-
|
|
9
|
-
const result = fetcher.parseHtml(html, 'http://test.com', 'test-id')
|
|
10
|
-
|
|
11
|
-
assert.equal(result, null, 'Should return null when name cannot be extracted')
|
|
12
|
-
})
|
|
13
|
-
|
|
14
|
-
test('MtgEloFetcher: parseHtml handles invalid JSON in astro-island gracefully', () => {
|
|
15
|
-
const fetcher = new MtgEloFetcher()
|
|
16
|
-
const html = `
|
|
17
|
-
<html>
|
|
18
|
-
<astro-island component-url="Profile" props='invalid json'></astro-island>
|
|
19
|
-
<div class="text-[22pt]">Fallback, Name</div>
|
|
20
|
-
<div class="text-[18pt]">Current rating: <span class="font-bold">1500</span></div>
|
|
21
|
-
<div class="text-[18pt]">Record: 10-5-2</div>
|
|
22
|
-
</html>
|
|
23
|
-
`
|
|
24
|
-
|
|
25
|
-
const result = fetcher.parseHtml(html, 'http://test.com', 'test-id')
|
|
26
|
-
|
|
27
|
-
assert.equal(result.name, 'Name Fallback', 'Should use fallback parsing with flipped name')
|
|
28
|
-
assert.equal(result.current_rating, '1500')
|
|
29
|
-
assert.equal(result.record, '10-5-2')
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
test('MtgEloFetcher: parseHtml uses fallback selectors when astro-island missing', () => {
|
|
33
|
-
const fetcher = new MtgEloFetcher()
|
|
34
|
-
const html = `
|
|
35
|
-
<html>
|
|
36
|
-
<div class="text-[22pt]">Smith, John</div>
|
|
37
|
-
<div class="text-[18pt]">Current rating: <span class="font-bold">1600</span></div>
|
|
38
|
-
<div class="text-[18pt]">Record: 20-10-5</div>
|
|
39
|
-
</html>
|
|
40
|
-
`
|
|
41
|
-
|
|
42
|
-
const result = fetcher.parseHtml(html, 'http://test.com', 'test-id')
|
|
43
|
-
|
|
44
|
-
assert.equal(result.name, 'John Smith', 'Should flip comma-separated name')
|
|
45
|
-
assert.equal(result.current_rating, '1600')
|
|
46
|
-
assert.equal(result.record, '20-10-5')
|
|
47
|
-
assert.equal(result['win rate'], '57.14%', 'Should calculate win rate from record')
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
test('MtgEloFetcher: parseHtml calculates win rate correctly with draws', () => {
|
|
51
|
-
const fetcher = new MtgEloFetcher()
|
|
52
|
-
const html = `
|
|
53
|
-
<html>
|
|
54
|
-
<div class="text-[22pt]">Test Player</div>
|
|
55
|
-
<div class="text-[18pt]">Record: 15-10-5</div>
|
|
56
|
-
</html>
|
|
57
|
-
`
|
|
58
|
-
|
|
59
|
-
const result = fetcher.parseHtml(html, 'http://test.com', 'test-id')
|
|
60
|
-
|
|
61
|
-
assert.equal(result.record, '15-10-5')
|
|
62
|
-
assert.equal(result['win rate'], '50.00%')
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
test('MtgEloFetcher: parseHtml handles record without draws', () => {
|
|
66
|
-
const fetcher = new MtgEloFetcher()
|
|
67
|
-
const html = `
|
|
68
|
-
<html>
|
|
69
|
-
<div class="text-[22pt]">Test Player</div>
|
|
70
|
-
<div class="text-[18pt]">Record: 12-8</div>
|
|
71
|
-
</html>
|
|
72
|
-
`
|
|
73
|
-
|
|
74
|
-
const result = fetcher.parseHtml(html, 'http://test.com', 'test-id')
|
|
75
|
-
|
|
76
|
-
assert.equal(result.record, '12-8')
|
|
77
|
-
assert.equal(result['win rate'], '60.00%')
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
test('MtgEloFetcher: parseHtml handles name without comma', () => {
|
|
81
|
-
const fetcher = new MtgEloFetcher()
|
|
82
|
-
const html = `
|
|
83
|
-
<html>
|
|
84
|
-
<div class="text-[22pt]">SingleName</div>
|
|
85
|
-
<div class="text-[18pt]">Record: 5-5-0</div>
|
|
86
|
-
</html>
|
|
87
|
-
`
|
|
88
|
-
|
|
89
|
-
const result = fetcher.parseHtml(html, 'http://test.com', 'test-id')
|
|
90
|
-
|
|
91
|
-
assert.equal(result.name, 'SingleName', 'Should handle single name without comma')
|
|
92
|
-
})
|
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
const test = require('node:test')
|
|
2
|
-
const assert = require('node:assert/strict')
|
|
3
|
-
const UnityLeagueFetcher = require('../src/fetchers/unityLeague')
|
|
4
|
-
|
|
5
|
-
test('UnityLeagueFetcher: parseHtml ignores non-player_profile images', () => {
|
|
6
|
-
const fetcher = new UnityLeagueFetcher()
|
|
7
|
-
const html = `
|
|
8
|
-
<html>
|
|
9
|
-
<h1 class="d-inline">Test Player</h1>
|
|
10
|
-
<div class="card-body">
|
|
11
|
-
<img class="img-fluid" src="https://unityleague.gg/media/default_avatar.jpg" />
|
|
12
|
-
</div>
|
|
13
|
-
</html>
|
|
14
|
-
`
|
|
15
|
-
|
|
16
|
-
const result = fetcher.parseHtml(html, 'http://test.com')
|
|
17
|
-
|
|
18
|
-
assert.equal(result.photo, null, 'Should not use non-player_profile images')
|
|
19
|
-
assert.equal(result.name, 'Test Player')
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
test('UnityLeagueFetcher: parseHtml handles missing country data gracefully', () => {
|
|
23
|
-
const fetcher = new UnityLeagueFetcher()
|
|
24
|
-
const html = `
|
|
25
|
-
<html>
|
|
26
|
-
<h1 class="d-inline">Test Player</h1>
|
|
27
|
-
<dt class="small text-muted">Country:</dt>
|
|
28
|
-
<dd></dd>
|
|
29
|
-
</html>
|
|
30
|
-
`
|
|
31
|
-
|
|
32
|
-
const result = fetcher.parseHtml(html, 'http://test.com')
|
|
33
|
-
|
|
34
|
-
assert.ok(result, 'Should return result even with missing country')
|
|
35
|
-
assert.equal(result.name, 'Test Player')
|
|
36
|
-
assert.equal(result.country, '', 'Country should be empty string')
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
test('UnityLeagueFetcher: parseHtml handles country text without flag icon', () => {
|
|
40
|
-
const fetcher = new UnityLeagueFetcher()
|
|
41
|
-
const html = `
|
|
42
|
-
<html>
|
|
43
|
-
<h1 class="d-inline">Test Player</h1>
|
|
44
|
-
<dt class="small text-muted">Country:</dt>
|
|
45
|
-
<dd>Germany</dd>
|
|
46
|
-
</html>
|
|
47
|
-
`
|
|
48
|
-
|
|
49
|
-
const result = fetcher.parseHtml(html, 'http://test.com')
|
|
50
|
-
|
|
51
|
-
assert.equal(result.country, 'Germany', 'Should use text when no flag icon')
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
test('UnityLeagueFetcher: parseHtml handles missing bio element', () => {
|
|
55
|
-
const fetcher = new UnityLeagueFetcher()
|
|
56
|
-
const html = `
|
|
57
|
-
<html>
|
|
58
|
-
<h1 class="d-inline">Test Player</h1>
|
|
59
|
-
</html>
|
|
60
|
-
`
|
|
61
|
-
|
|
62
|
-
const result = fetcher.parseHtml(html, 'http://test.com')
|
|
63
|
-
|
|
64
|
-
assert.ok(!result.bio, 'Bio should be undefined when element missing')
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
test('UnityLeagueFetcher: parseHtml extracts country from header flag', () => {
|
|
68
|
-
const fetcher = new UnityLeagueFetcher()
|
|
69
|
-
const html = `
|
|
70
|
-
<html>
|
|
71
|
-
<h1 class="d-inline">Test Player</h1>
|
|
72
|
-
<div class="card-body">
|
|
73
|
-
<i class="fi fi-us"></i>
|
|
74
|
-
</div>
|
|
75
|
-
</html>
|
|
76
|
-
`
|
|
77
|
-
|
|
78
|
-
const result = fetcher.parseHtml(html, 'http://test.com')
|
|
79
|
-
|
|
80
|
-
assert.equal(result.country, 'us', 'Should extract country code from header flag')
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
test('UnityLeagueFetcher: parseHtml handles missing ranking table', () => {
|
|
84
|
-
const fetcher = new UnityLeagueFetcher()
|
|
85
|
-
const html = `
|
|
86
|
-
<html>
|
|
87
|
-
<h1 class="d-inline">Test Player</h1>
|
|
88
|
-
</html>
|
|
89
|
-
`
|
|
90
|
-
|
|
91
|
-
const result = fetcher.parseHtml(html, 'http://test.com')
|
|
92
|
-
|
|
93
|
-
assert.ok(result, 'Should handle missing ranking table')
|
|
94
|
-
assert.ok(!result['rank germany'], 'Should not have rank data')
|
|
95
|
-
})
|
|
96
|
-
|
|
97
|
-
test('UnityLeagueFetcher: parseHtml extracts ranking data with special characters', () => {
|
|
98
|
-
const fetcher = new UnityLeagueFetcher()
|
|
99
|
-
const html = `
|
|
100
|
-
<html>
|
|
101
|
-
<h1 class="d-inline">Test Player</h1>
|
|
102
|
-
<table class="table-sm">
|
|
103
|
-
<thead>
|
|
104
|
-
<tr>
|
|
105
|
-
<th>Germany</th>
|
|
106
|
-
<th>Europe</th>
|
|
107
|
-
</tr>
|
|
108
|
-
</thead>
|
|
109
|
-
<tbody>
|
|
110
|
-
<tr>
|
|
111
|
-
<td>#42</td>
|
|
112
|
-
<td>1st</td>
|
|
113
|
-
</tr>
|
|
114
|
-
</tbody>
|
|
115
|
-
</table>
|
|
116
|
-
</html>
|
|
117
|
-
`
|
|
118
|
-
|
|
119
|
-
const result = fetcher.parseHtml(html, 'http://test.com')
|
|
120
|
-
|
|
121
|
-
assert.equal(result['rank germany'], '42', 'Should extract numeric value from #42')
|
|
122
|
-
assert.equal(result['rank europe'], '1', 'Should extract numeric value from 1st')
|
|
123
|
-
})
|