Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/FoxxMD/multi-scrobbler. Scrobble plays from multiple sources to multiple clients docs.multi-scrobbler.app
deezer docker jellyfin koito lastfm listenbrainz maloja mopidy mpris music music-assistant plex scrobble self-hosted spotify subsonic tautulli youtube-music
Something went wrong. Try again.
9.9 kB · 259 lines
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259import { assert, expect } from 'chai';import { describe, it } from 'mocha';import { compareNormalizedStrings, normalizeStr,} from "../../utils/StringUtils.ts";import { replaceInterpolatedValues } from "../../utils/DataUtils.ts";import { splitByFirstFound } from '../../../core/StringUtils.ts';import { noCasePropObj } from '../../utils/DataUtils.ts';import { isrcNoHyphens, isrcWithHyphens, REGEX_ISRC_HYPHENS, REGEX_ISRC_NO_HYPHENS } from '../../../core/PlayUtils.ts';
describe('String Comparisons', function () {
it('should ignore symbols', async function () { const result = compareNormalizedStrings('this string! is the. same', 'this string is the same'); assert.isAtLeast(result.highScore, 100); });
it('should ignore whitespace', async function () { const result = compareNormalizedStrings('this string is the same ', 'this string is the same'); assert.isAtLeast(result.highScore, 100); });
it('should ignore case', async function () { const result = compareNormalizedStrings('ThIs STRING iS THe SAMe', 'this string is the same'); assert.isAtLeast( result.highScore, 100); });
it('should normalize unicode', async function () { const tests = [ ['Enamorate Bailando','Enamórate Bailando'], ['Dina Ogon', 'Dina Ögon'], ['Nana', 'Nanã'], ['Nana', 'Nanä'] ]
for(const test of tests) { const result = compareNormalizedStrings(test[0], test[1]); assert.equal(100, result.highScore); } });
it('should not erase non-english characters', async function () { const tests = [ ['VAPERROR / t e l e p a t h テレパシー能力者 - 切っても切れない', 'vaperror t e l e p a t h テレパシー能力者 切っても切れない'], ['Мой мармеладный (Speed Up)', 'мои мармеладныи speed up'], ['Мой мармеладный (Я не права) [Из сериала "Ольга", 2 Сезон]', 'мои мармеладныи я не права из сериала ольга 2 сезон'] ]
for(const test of tests) { const result = normalizeStr(test[0], {keepSingleWhitespace: true}); assert.equal(result, test[1]); } });
it('should score small changes correctly', async function () { const tests = [ ['there is change', 'therr is change'], ['laser cannon', 'lazer cannon'], ['Monster', 'Manster'], ['What are you doing', 'hwat are you doing'] ]
for(const test of tests) { const result = compareNormalizedStrings(test[0], test[1]); assert.isAtLeast( result.highScore, 75, `Comparing: '${test[0]}' | '${test[1]}'`); } });
it('should score word bags correctly', async function () { const tests = [ ['there change is', 'there is change'], ['laser cannon', 'cannon laser'], ['One two three four', 'One four two threee'], ['you, doin hwat are', 'hwat are you doing'], ['My Track (feat. Tourist1, Artist2)', 'My Track (feat. Artist2, Tourist1)'] ]
for(const test of tests) { const result = compareNormalizedStrings(test[0], test[1]); assert.isAtLeast( result.highScore, 80, `Comparing: '${test[0]}' | '${test[1]}'`); } });
it('should not score very similar as identical', async function () { const tests = [ ['Another Brick in the Wall, Pt. 1', 'Another Brick in the Wall, Pt. 2'], ]
for(const test of tests) { const result = compareNormalizedStrings(test[0], test[1]); assert.isAtMost( result.highScore, 99, `Comparing: '${test[0]}' | '${test[1]}'`); } });
it('should handle strings with different lengths', async function () { const tests = [ ['The Amazing Bongo Hop', 'The Bongo Hop'], ]
for(const test of tests) { const result = compareNormalizedStrings(test[0], test[1]); assert.isAtMost( result.highScore, 58, `Comparing: '${test[0]}' | '${test[1]}'`); } });
it('should be string parameter order invariant', async function () { const longerString = 'Nidia Gongora TEST'; const shorterString = 'Nidia Gongora'
const result1 = compareNormalizedStrings(longerString, shorterString); const result2 = compareNormalizedStrings(shorterString, longerString);
assert.equal( result1.highScore, result2.highScore, `Comparing: '${longerString}' | '${shorterString}'`); });});
describe('String Splitting', function() {
it('should not split string with no delimiter', function() { const artistName = undefined; const artist = "Phil Collins"; const artistStrings = splitByFirstFound(artist, [','], [artistName]); expect(artistStrings).to.eql(['Phil Collins']) });});
it('Proxy object has case-insensitive keys', function() { const myObj: Record<string, any> = {FOO: 'bar'};
const iObj = noCasePropObj(myObj);
expect(iObj.foO).is.not.undefined; expect(iObj.foo).eq('bar');});
it('Proxy object handles spread', function() { const myObj = {FOO: 'bar', WIDget: 'cool'};
const iObj = noCasePropObj(myObj); const { foo, ...rest } = iObj;
expect(foo).is.not.undefined; expect(foo).eq('bar'); expect(rest.widget).is.not.undefined; expect(rest.widget).eq('cool');});
describe('Interpolation', function() {
it('interpolates values', function() { const replaced = replaceInterpolatedValues('My cool string has [[asecret]] in it', {asecret: 'foo'}); expect(replaced).not.includes('['); expect(replaced).not.includes(']'); expect(replaced).includes('foo'); });
it('interpolates values case-insensitive', function() { const replaced = replaceInterpolatedValues('My cool string has [[ASECREt]] in it', {aSeCrEt: 'foo'}); expect(replaced).includes('foo'); });
it('interpolates values after trimming', function() { const replaced = replaceInterpolatedValues('My cool string has [[ ASECREt ]] in it', {aSeCrEt: 'foo'}); expect(replaced).includes('foo'); });
it('leaves match in place if not interpolated', function() { const replaced = replaceInterpolatedValues('My cool string has [[asecret]] and [[coolsecret]] in it', {bar: 'foo', coolsecret: 'xxx'}); expect(replaced).includes('[[asecret]]'); expect(replaced).includes('xxx'); expect(replaced).not.includes('[[coolsecret]]'); });});
describe('ISRC Parsing', function() {
describe('Regex tests', function() {
it('isrc no-hypen regex matches isrc with no hyphens', function() { const isrcs = ['USRC17607839','GBWUL1805639','USUG11902962','QT65X2609829']; for(const i of isrcs) { expect(REGEX_ISRC_NO_HYPHENS.test(i)).is.true; } });
it('isrc no-hypen regex does not match isrc with hyphens or invalid', function() { const isrcs = ['US-RC1-76-07839','GB-WUL1-80-5639','US-UG1-19-02962','QTLASDSDASD','']; for(const i of isrcs) { expect(REGEX_ISRC_NO_HYPHENS.test(i)).is.false; } });
it('isrc hypen regex matches isrc with hyphens', function() { const isrcs = ['US-RC1-76-07839','GB-WUL-18-05639','US-UG1-19-02962']; for(const i of isrcs) { expect(REGEX_ISRC_HYPHENS.test(i), `${i} did not match`).is.true; } });
it('isrc hypen regex does not match isrc with no hyphens or invalid', function() { const isrcs = ['USRC17607839','GBWUL1805639','USUG11902962','QT65X2609829','QTLASDSDASD','']; for(const i of isrcs) { expect(REGEX_ISRC_HYPHENS.test(i)).is.false; } });
});
describe('ISRC Transform', function() {
describe('To non-hyphenated', function() { it('Transforms from hyphenated', function() { const isrcs = ['US-RC1-76-07839','GB-WUL-18-05639','US-UG1-19-02962']; for(const i of isrcs) { const res = isrcNoHyphens(i); expect(res).eq(i.replaceAll(/-/g, '')); } });
it('Returns already non-hyphenated', function() { const isrcs = ['USRC17607839','GBWUL1805639','USUG11902962','QT65X2609829']; for(const i of isrcs) { const res = isrcNoHyphens(i); expect(res).eq(i); } });
it('Fails on invalid', function() { const isrcs = ['GB-WUL1-80-5639','QTLASDSDASD','']; for(const i of isrcs) { expect(() => isrcNoHyphens(i)).to.throw(); } }); });
describe('To hyphenated', function() { it('Transforms from non-hyphenated', function() { const res = isrcWithHyphens('USRC17607839'); expect(res).eq('US-RC1-76-07839'); });
it('Returns already non-hyphenated', function() { const isrcs = ['US-RC1-76-07839','GB-WUL-18-05639','US-UG1-19-02962']; for(const i of isrcs) { const res = isrcWithHyphens(i); expect(res).eq(i); } });
it('Fails on invalid', function() { const isrcs = ['GB-WUL1-80-5639','QTLASDSDASD','']; for(const i of isrcs) { expect(() => isrcNoHyphens(i)).to.throw(); } }); }); });});