[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
0

Configure Feed

Select the types of activity you want to include in your feed.

feat(transforms): Allow for config with stage type

As long as it conforms to user stage type. Preserve backwards compatibility

FoxxMD (Nov 20, 2025, 5:36 PM UTC) 128c4894 4cab02e8

+272 -211
+23 -13
src/backend/common/infrastructure/Transform.ts
··· 8 8 export type SearchAndReplaceTerm = string | ConditionalSearchAndReplaceTerm; 9 9 export type ExternalMetadataTerm = true | undefined | { when: WhenConditionsConfig }; 10 10 11 - export type PlayTransformParts<T> = PlayTransformStage<T[]> & { when?: WhenConditionsConfig }; 11 + export type PlayTransformParts<T, Y = MaybeStageTyped> = Extract<PlayTransformStage<T[]>, Y> & { when?: WhenConditionsConfig }; 12 12 export type PlayTransformUserParts<T> = PlayTransformUserStage<T[]> & { when?: WhenConditionsConfig }; 13 - export type PlayTransformPartsArray<T> = PlayTransformParts<T>[]; 14 - export type PlayTransformPartsConfig<T> = PlayTransformPartsArray<T> | PlayTransformParts<T>; 13 + export type PlayTransformPartsArray<T, Y = MaybeStageTyped> = PlayTransformParts<T, Y>[]; 14 + 15 + /** Represents the weakly-defined user config. May be an array of parts or one parts object */ 16 + export type PlayTransformPartsConfig<T, Y = MaybeStageTyped> = PlayTransformPartsArray<T, Y> | PlayTransformParts<T, Y>; 15 17 16 18 export interface PlayTransformPartsAtomic<T> { 17 19 title?: T ··· 21 23 22 24 export type StageType = 'spotify' | 'listenbrainz' | 'native' | 'user'; 23 25 export const STAGE_TYPES: StageType[] = ['spotify','listenbrainz','native','user'] 26 + 27 + export interface StageTyped { 28 + type: StageType 29 + } 30 + 31 + export interface NotStageTyped { 32 + type?: never 33 + } 34 + 35 + export type MaybeStageTyped = StageTyped | NotStageTyped; 24 36 25 37 export interface PlayTransformStageTyped<T> extends PlayTransformPartsAtomic<T> { 26 - type: 'spotify' | 'listenbrainz' | 'native' | 'user' 38 + type: StageType 27 39 } 28 40 29 41 export interface PlayTransformMetadataStage extends PlayTransformStageTyped<ExternalMetadataTerm[]> { ··· 35 47 export interface PlayTransformUserStage<T> extends PlayTransformStageTyped<T> { 36 48 type: 'user' 37 49 } 50 + export type UntypedPlayTransformUserStage<T> = Omit<PlayTransformUserStage<T>, 'type'> & {type?: never}; 38 51 39 - export type PlayTransformStage<T> = PlayTransformMetadataStage | PlayTransformUserStage<T> 52 + export type PlayTransformStage<T> = PlayTransformMetadataStage | PlayTransformUserStage<T> | UntypedPlayTransformUserStage<T>; 40 53 41 54 /** Represents the plain json user-configured structure (input) */ 42 55 export interface PlayTransformHooksConfig<T> { ··· 50 63 51 64 /** Represents the final, strongly-typed transform configuration used during runtime */ 52 65 export interface PlayTransformHooks<T> extends PlayTransformHooksConfig<T> { 53 - preCompare?: PlayTransformPartsArray<T> 66 + preCompare?: PlayTransformPartsArray<T, StageTyped> 54 67 compare?: { 55 - candidate?: PlayTransformPartsArray<T> 56 - existing?: PlayTransformPartsArray<T> 68 + candidate?: PlayTransformPartsArray<T, StageTyped> 69 + existing?: PlayTransformPartsArray<T, StageTyped> 57 70 } 58 - postCompare?: PlayTransformPartsArray<T> 71 + postCompare?: PlayTransformPartsArray<T, StageTyped> 59 72 } 60 73 61 74 export type PlayTransformRules = PlayTransformHooks<ConditionalSearchAndReplaceRegExp> ··· 70 83 export type PlayTransformOptions = PlayTransformConfig & { log?: boolean | 'all' } 71 84 export type WhenParts<T> = PlayTransformPartsAtomic<T>; 72 85 export type WhenConditions<T> = WhenParts<T>[]; 73 - export type WhenConditionsConfig = WhenConditions<string>; 74 - 75 - 76 - //export type PlayTransform 86 + export type WhenConditionsConfig = WhenConditions<string>;
+249 -198
src/backend/tests/component/component.test.ts
··· 4 4 import { after, before, describe, it } from 'mocha'; 5 5 import AbstractComponent from "../../common/AbstractComponent.js"; 6 6 7 - import { TRANSFORM_HOOK } from "../../common/infrastructure/Transform.js"; 7 + import { ConditionalSearchAndReplaceRegExp, TRANSFORM_HOOK } from "../../common/infrastructure/Transform.js"; 8 8 9 9 import { isConditionalSearchAndReplace } from "../../utils/PlayTransformUtils.js"; 10 10 import { asPlays, generatePlay, normalizePlays } from "../utils/PlayTestUtils.js"; 11 11 import { WebhookPayload } from "../../common/infrastructure/config/health/webhooks.js"; 12 + import { findCauseByMessage } from "../../utils/ErrorUtils.js"; 12 13 13 14 chai.use(asPromised); 14 15 ··· 29 30 30 31 describe('Play Transforms', function () { 31 32 32 - beforeEach(function() { 33 - component.config = {}; 34 - component.transformRules = {}; 33 + beforeEach(function () { 34 + component.config = {}; 35 + component.transformRules = {}; 35 36 }); 37 + describe('Transform Config Parsing', function () { 36 38 37 - describe('Transform Config Parsing', function() { 38 - 39 - it('Sets transform rules as empty object if config is not present', function() { 39 + it('Sets transform rules as empty object if config is not present', function () { 40 40 component.buildTransformRules(); 41 41 expect(component.transformRules).exist; 42 42 expect(Object.keys(component.transformRules).length).eq(0); 43 43 }); 44 44 45 - it('Converts single object hook into hook array', function() { 45 + it('Converts single object hook into hook array', function () { 46 46 component.config = { 47 47 options: { 48 48 playTransform: { ··· 61 61 expect(component.transformRules.preCompare).to.have.nested.property('0.title'); 62 62 }); 63 63 64 - it('Accepts hook array', function() { 65 - component.config = { 66 - options: { 67 - playTransform: { 68 - preCompare: [ 69 - { 70 - type: "user", 64 + describe('Stage Parsing', function () { 65 + 66 + it(`Throws an error if stage is an unexpected value`, function () { 67 + component.config = { 68 + options: { 69 + playTransform: { 70 + preCompare: { 71 + // @ts-expect-error 72 + type: "test", 73 + // @ts-expect-error 71 74 title: ['something'] 72 - }, 73 - { 74 - type: "user", 75 - title: ['something else'] 76 75 } 77 - ] 76 + } 78 77 } 79 78 } 80 - } 81 79 82 - component.buildTransformRules(); 80 + // https://github.com/chaijs/chai/issues/655#issuecomment-204386414 81 + expect(() => component.buildTransformRules()).to.throw(Error).that.satisfies((e) => { 82 + return findCauseByMessage(e, `Stage has invalid 'type'`); 83 + }); 84 + }); 83 85 84 - expect(component.transformRules.preCompare).to.be.an('array'); 85 - expect(component.transformRules.preCompare).to.be.length(2); 86 - expect(component.transformRules.preCompare).to.have.nested.property('0.title'); 87 - expect(component.transformRules.preCompare).to.have.nested.property('1.title') 88 86 }); 89 87 90 - it('Converts transform config into real S&P data', function() { 91 - component.config = { 92 - options: { 93 - playTransform: { 94 - preCompare: { 95 - type: "user", 96 - title: ['something'] 88 + describe('User Stage Parsing', function () { 89 + 90 + it(`Assumes user 'type' if no type is present`, function () { 91 + component.config = { 92 + options: { 93 + playTransform: { 94 + preCompare: { 95 + title: ['something'] 96 + } 97 97 } 98 98 } 99 99 } 100 - } 101 100 102 - component.buildTransformRules(); 101 + component.buildTransformRules(); 103 102 104 - expect(component.transformRules.preCompare![0]).to.exist; 105 - expect(component.transformRules.preCompare![0].title).to.exist; 106 - expect(Array.isArray(component.transformRules.preCompare![0].title)).is.true; 107 - expect( isConditionalSearchAndReplace(component.transformRules.preCompare![0].title![0])).is.true 108 - }); 103 + expect(component.transformRules.preCompare).to.be.an('array'); 104 + expect(component.transformRules.preCompare).to.be.length(1); 105 + expect(component.transformRules.preCompare).to.have.nested.property('0.type'); 106 + expect(component.transformRules.preCompare[0].type).eq('user'); 107 + }); 109 108 110 - it('Converts transform config into real S&P data with default being empty string', function() { 111 - component.config = { 112 - options: { 113 - playTransform: { 114 - preCompare: { 115 - type: "user", 116 - title: ['something'] 109 + it(`Allows user 'type'`, function () { 110 + component.config = { 111 + options: { 112 + playTransform: { 113 + preCompare: { 114 + type: "user", 115 + title: ['something'] 116 + } 117 117 } 118 118 } 119 119 } 120 - } 121 120 122 - component.buildTransformRules(); 121 + component.buildTransformRules(); 123 122 124 - expect(component.transformRules.preCompare![0]).to.exist; 125 - expect(component.transformRules.preCompare![0].title).to.exist; 126 - expect(Array.isArray(component.transformRules.preCompare![0].title)).is.true; 127 - expect( isConditionalSearchAndReplace(component.transformRules.preCompare![0].title![0])).is.true 128 - expect( component.transformRules.preCompare![0].title![0].search).is.eq('something'); 129 - expect( component.transformRules.preCompare![0].title![0].replace).is.eq(''); 130 - }); 123 + expect(component.transformRules.preCompare).to.be.an('array'); 124 + expect(component.transformRules.preCompare).to.be.length(1); 125 + expect(component.transformRules.preCompare).to.have.nested.property('0.type'); 126 + expect(component.transformRules.preCompare[0].type).eq('user'); 127 + }); 131 128 132 - it('Respects transform config when it is already S&P data', function() { 133 - component.config = { 134 - options: { 135 - playTransform: { 136 - preCompare: { 137 - type: "user", 138 - title: [ 129 + it('Accepts hook array', function () { 130 + component.config = { 131 + options: { 132 + playTransform: { 133 + preCompare: [ 139 134 { 140 - 141 - search: 'nothing', 142 - replace: 'anything' 135 + title: ['something'] 136 + }, 137 + { 138 + title: ['something else'] 143 139 } 144 140 ] 145 141 } 146 142 } 147 143 } 148 - } 149 144 150 - component.buildTransformRules(); 145 + component.buildTransformRules(); 151 146 152 - expect(component.transformRules.preCompare![0]).to.exist; 153 - expect(component.transformRules.preCompare![0].title).to.exist; 154 - expect(Array.isArray(component.transformRules.preCompare![0].title)).is.true; 155 - expect( isConditionalSearchAndReplace(component.transformRules.preCompare![0].title![0])).is.true 156 - expect( component.transformRules.preCompare![0].title![0].search).is.eq('nothing'); 157 - expect( component.transformRules.preCompare![0].title![0].replace).is.eq('anything'); 147 + expect(component.transformRules.preCompare).to.be.an('array'); 148 + expect(component.transformRules.preCompare).to.be.length(2); 149 + expect(component.transformRules.preCompare).to.have.nested.property('0.title'); 150 + expect(component.transformRules.preCompare).to.have.nested.property('1.title') 151 + }); 152 + 153 + it('Converts transform config into real S&P data', function () { 154 + component.config = { 155 + options: { 156 + playTransform: { 157 + preCompare: { 158 + title: ['something'] 159 + } 160 + } 161 + } 162 + } 163 + 164 + component.buildTransformRules(); 165 + 166 + expect(component.transformRules.preCompare![0]).to.exist; 167 + expect(component.transformRules.preCompare![0].title).to.exist; 168 + expect(Array.isArray(component.transformRules.preCompare![0].title)).is.true; 169 + expect(isConditionalSearchAndReplace(component.transformRules.preCompare![0].title![0])).is.true 170 + }); 171 + 172 + it('Converts transform config into real S&P data with default being empty string', function () { 173 + component.config = { 174 + options: { 175 + playTransform: { 176 + preCompare: { 177 + title: ['something'] 178 + } 179 + } 180 + } 181 + } 182 + 183 + component.buildTransformRules(); 184 + 185 + expect(component.transformRules.preCompare![0]).to.exist; 186 + expect(component.transformRules.preCompare![0].title).to.exist; 187 + expect(Array.isArray(component.transformRules.preCompare![0].title)).is.true; 188 + expect(isConditionalSearchAndReplace(component.transformRules.preCompare![0].title![0])).is.true 189 + const title = component.transformRules.preCompare![0].title![0] as ConditionalSearchAndReplaceRegExp; 190 + expect(title.search).is.eq('something'); 191 + expect(title.replace).is.eq(''); 192 + }); 193 + 194 + it('Respects transform config when it is already S&P data', function () { 195 + component.config = { 196 + options: { 197 + playTransform: { 198 + preCompare: { 199 + title: [ 200 + { 201 + 202 + search: 'nothing', 203 + replace: 'anything' 204 + } 205 + ] 206 + } 207 + } 208 + } 209 + } 210 + 211 + component.buildTransformRules(); 212 + 213 + expect(component.transformRules.preCompare![0]).to.exist; 214 + expect(component.transformRules.preCompare![0].title).to.exist; 215 + expect(Array.isArray(component.transformRules.preCompare![0].title)).is.true; 216 + expect(isConditionalSearchAndReplace(component.transformRules.preCompare![0].title![0])).is.true 217 + const title = component.transformRules.preCompare![0].title![0] as ConditionalSearchAndReplaceRegExp; 218 + expect(title.search).is.eq('nothing'); 219 + expect(title.replace).is.eq('anything'); 220 + }); 158 221 }); 159 - }); 160 222 161 - describe('Play Transforming', function() { 223 + describe('Play Transforming', function () { 162 224 163 - it('Returns original play if no hooks are defined', function () { 164 - component.buildTransformRules(); 225 + it('Returns original play if no hooks are defined', function () { 226 + component.buildTransformRules(); 165 227 166 - const play = generatePlay(); 167 - const transformed = component.transformPlay(play, TRANSFORM_HOOK.preCompare); 168 - expect(JSON.stringify(play)).equal(JSON.stringify(transformed)); 169 - }); 228 + const play = generatePlay(); 229 + const transformed = component.transformPlay(play, TRANSFORM_HOOK.preCompare); 230 + expect(JSON.stringify(play)).equal(JSON.stringify(transformed)); 231 + }); 170 232 171 - it('Transforms when hook is present', function () { 172 - component.config = { 173 - options: { 174 - playTransform: { 175 - preCompare: { 176 - type: "user", 177 - title: ['something'] 233 + it('Transforms when hook is present', function () { 234 + component.config = { 235 + options: { 236 + playTransform: { 237 + preCompare: { 238 + title: ['something'] 239 + } 178 240 } 179 241 } 180 242 } 181 - } 182 - component.buildTransformRules(); 243 + component.buildTransformRules(); 183 244 184 - const play = generatePlay({track: 'My coolsomething track'}); 185 - const transformed = component.transformPlay(play, TRANSFORM_HOOK.preCompare); 186 - expect(transformed.data.track).equal('My cool track'); 187 - }); 245 + const play = generatePlay({ track: 'My coolsomething track' }); 246 + const transformed = component.transformPlay(play, TRANSFORM_HOOK.preCompare); 247 + expect(transformed.data.track).equal('My cool track'); 248 + }); 188 249 189 - it('Transforms consecutively when hook is present with multiple values', function () { 190 - component.config = { 191 - options: { 192 - playTransform: { 193 - preCompare: { 194 - type: "user", 195 - title: ['something', 'cool'] 250 + it('Transforms consecutively when hook is present with multiple values', function () { 251 + component.config = { 252 + options: { 253 + playTransform: { 254 + preCompare: { 255 + title: ['something', 'cool'] 256 + } 196 257 } 197 258 } 198 259 } 199 - } 200 - component.buildTransformRules(); 260 + component.buildTransformRules(); 201 261 202 - const play = generatePlay({track: 'My coolsomething track'}); 203 - const transformed = component.transformPlay(play, TRANSFORM_HOOK.preCompare); 204 - expect(transformed.data.track).equal('My track'); 205 - }); 262 + const play = generatePlay({ track: 'My coolsomething track' }); 263 + const transformed = component.transformPlay(play, TRANSFORM_HOOK.preCompare); 264 + expect(transformed.data.track).equal('My track'); 265 + }); 206 266 207 - it('Transforms using parsed regex', function () { 208 - component.config = { 209 - options: { 210 - playTransform: { 211 - preCompare: { 212 - type: "user", 213 - title: [ 214 - { 215 - search: '/(cool )(some)(thing)/i', 216 - replace: '$1$3' 267 + it('Transforms using parsed regex', function () { 268 + component.config = { 269 + options: { 270 + playTransform: { 271 + preCompare: { 272 + title: [ 273 + { 274 + search: '/(cool )(some)(thing)/i', 275 + replace: '$1$3' 276 + } 277 + ] 217 278 } 218 - ] 219 279 } 220 280 } 221 281 } 222 - } 223 - component.buildTransformRules(); 282 + component.buildTransformRules(); 224 283 225 - const play = generatePlay({track: 'My cool something track'}); 226 - const transformed = component.transformPlay(play, TRANSFORM_HOOK.preCompare); 227 - expect(transformed.data.track).equal('My cool thing track'); 228 - }); 284 + const play = generatePlay({ track: 'My cool something track' }); 285 + const transformed = component.transformPlay(play, TRANSFORM_HOOK.preCompare); 286 + expect(transformed.data.track).equal('My cool thing track'); 287 + }); 229 288 230 289 231 - it('Transforms using parsed regex to get primary artist from delimited artist string', function () { 232 - component.config = { 233 - options: { 234 - playTransform: { 235 - preCompare: { 236 - type: "user", 237 - artists: [ 238 - { 239 - search: '/(.*?)(\\s*\\/\\s*)(.*$)/i', 240 - replace: '$1' 241 - } 242 - ] 290 + it('Transforms using parsed regex to get primary artist from delimited artist string', function () { 291 + component.config = { 292 + options: { 293 + playTransform: { 294 + preCompare: { 295 + artists: [ 296 + { 297 + search: '/(.*?)(\\s*\\/\\s*)(.*$)/i', 298 + replace: '$1' 299 + } 300 + ] 301 + } 243 302 } 244 303 } 245 304 } 246 - } 247 - component.buildTransformRules(); 305 + component.buildTransformRules(); 248 306 249 - const play = generatePlay({artists: ['My Artist One / My Artist Two / Another Guy']}); 250 - const transformed = component.transformPlay(play, TRANSFORM_HOOK.preCompare); 251 - expect(transformed.data.artists).length(1) 252 - expect(transformed.data.artists[0]).equal('My Artist One'); 253 - }); 307 + const play = generatePlay({ artists: ['My Artist One / My Artist Two / Another Guy'] }); 308 + const transformed = component.transformPlay(play, TRANSFORM_HOOK.preCompare); 309 + expect(transformed.data.artists).length(1) 310 + expect(transformed.data.artists[0]).equal('My Artist One'); 311 + }); 254 312 255 - it('Removes title when transform replaces with empty string', function () { 256 - component.config = { 257 - options: { 258 - playTransform: { 259 - preCompare: { 260 - type: "user", 261 - title: ['something'] 313 + it('Removes title when transform replaces with empty string', function () { 314 + component.config = { 315 + options: { 316 + playTransform: { 317 + preCompare: { 318 + title: ['something'] 319 + } 262 320 } 263 321 } 264 322 } 265 - } 266 - component.buildTransformRules(); 323 + component.buildTransformRules(); 267 324 268 - const play = generatePlay({track: 'something'}); 269 - const transformed = component.transformPlay(play, TRANSFORM_HOOK.preCompare); 270 - expect(transformed.data.track).is.undefined; 271 - }); 325 + const play = generatePlay({ track: 'something' }); 326 + const transformed = component.transformPlay(play, TRANSFORM_HOOK.preCompare); 327 + expect(transformed.data.track).is.undefined; 328 + }); 272 329 273 - it('Removes album when transform replaces with empty string', function () { 274 - component.config = { 275 - options: { 276 - playTransform: { 277 - preCompare: { 278 - type: "user", 279 - album: ['something'] 330 + it('Removes album when transform replaces with empty string', function () { 331 + component.config = { 332 + options: { 333 + playTransform: { 334 + preCompare: { 335 + album: ['something'] 336 + } 280 337 } 281 338 } 282 339 } 283 - } 284 - component.buildTransformRules(); 340 + component.buildTransformRules(); 285 341 286 - const play = generatePlay({album: 'something'}); 287 - const transformed = component.transformPlay(play, TRANSFORM_HOOK.preCompare); 288 - expect(transformed.data.album).is.undefined; 289 - }); 342 + const play = generatePlay({ album: 'something' }); 343 + const transformed = component.transformPlay(play, TRANSFORM_HOOK.preCompare); 344 + expect(transformed.data.album).is.undefined; 345 + }); 290 346 291 - it('Removes an artist when transform replaces with empty string', function () { 292 - component.config = { 293 - options: { 294 - playTransform: { 295 - preCompare: { 296 - type: "user", 297 - artists: ['something'] 347 + it('Removes an artist when transform replaces with empty string', function () { 348 + component.config = { 349 + options: { 350 + playTransform: { 351 + preCompare: { 352 + artists: ['something'] 353 + } 298 354 } 299 355 } 300 356 } 301 - } 302 - component.buildTransformRules(); 357 + component.buildTransformRules(); 303 358 304 - const play = generatePlay({artists: ['something', 'big']}); 305 - const transformed = component.transformPlay(play, TRANSFORM_HOOK.preCompare); 306 - expect(transformed.data.artists!.length).is.eq(1) 307 - expect(transformed.data.artists![0]).is.eq('big') 359 + const play = generatePlay({ artists: ['something', 'big'] }); 360 + const transformed = component.transformPlay(play, TRANSFORM_HOOK.preCompare); 361 + expect(transformed.data.artists!.length).is.eq(1) 362 + expect(transformed.data.artists![0]).is.eq('big') 363 + }); 364 + 308 365 }); 309 366 }); 310 367 311 - describe('Conditional Transforming', function() { 368 + describe('Conditional Transforming', function () { 312 369 313 370 describe('On Hook', function () { 314 371 it('Does not run hook if when conditions do not match', function () { ··· 316 373 options: { 317 374 playTransform: { 318 375 preCompare: { 319 - type: "user", 320 376 when: [ 321 377 { 322 378 album: "Has This" ··· 329 385 } 330 386 component.buildTransformRules(); 331 387 332 - const play = generatePlay({artists: ['something', 'big'], album: 'It Has No Match'}); 388 + const play = generatePlay({ artists: ['something', 'big'], album: 'It Has No Match' }); 333 389 const transformed = component.transformPlay(play, TRANSFORM_HOOK.preCompare); 334 390 expect(transformed.data.artists!.length).is.eq(2) 335 391 expect(transformed.data.artists![0]).is.eq('something') ··· 340 396 options: { 341 397 playTransform: { 342 398 preCompare: { 343 - type: "user", 344 399 when: [ 345 400 { 346 401 album: "Has This" ··· 353 408 } 354 409 component.buildTransformRules(); 355 410 356 - const play = generatePlay({artists: ['something', 'big'], album: 'It Has This Match'}); 411 + const play = generatePlay({ artists: ['something', 'big'], album: 'It Has This Match' }); 357 412 const transformed = component.transformPlay(play, TRANSFORM_HOOK.preCompare); 358 413 expect(transformed.data.artists!.length).is.eq(1) 359 414 expect(transformed.data.artists![0]).is.eq('big') 360 415 }); 361 416 }); 362 417 363 - describe('On Search-And-Replace', function() { 418 + describe('On Search-And-Replace', function () { 364 419 it('Does not run hook if when conditions do not match', function () { 365 420 component.config = { 366 421 options: { 367 422 playTransform: { 368 423 preCompare: { 369 - type: "user", 370 424 artists: [ 371 425 { 372 426 search: "something", ··· 384 438 } 385 439 component.buildTransformRules(); 386 440 387 - const play = generatePlay({artists: ['something', 'big'], album: 'It Has No Match'}); 441 + const play = generatePlay({ artists: ['something', 'big'], album: 'It Has No Match' }); 388 442 const transformed = component.transformPlay(play, TRANSFORM_HOOK.preCompare); 389 443 expect(transformed.data.artists!.length).is.eq(2) 390 444 expect(transformed.data.artists![0]).is.eq('something') ··· 395 449 options: { 396 450 playTransform: { 397 451 preCompare: { 398 - type: "user", 399 452 artists: [ 400 453 { 401 454 search: "something", ··· 413 466 } 414 467 component.buildTransformRules(); 415 468 416 - const play = generatePlay({artists: ['something', 'big'], album: 'It Has This Match'}); 469 + const play = generatePlay({ artists: ['something', 'big'], album: 'It Has This Match' }); 417 470 const transformed = component.transformPlay(play, TRANSFORM_HOOK.preCompare); 418 471 expect(transformed.data.artists!.length).is.eq(1) 419 472 expect(transformed.data.artists![0]).is.eq('big') ··· 422 475 423 476 }); 424 477 425 - describe('Multiple hook transforms', function() { 478 + describe('Multiple hook transforms', function () { 426 479 427 - it('Accumulates transforms', function() { 480 + it('Accumulates transforms', function () { 428 481 component.config = { 429 482 options: { 430 483 playTransform: { 431 484 preCompare: [ 432 485 { 433 - type: "user", 434 486 title: [ 435 487 { 436 488 search: "something", ··· 439 491 ] 440 492 }, 441 493 { 442 - type: "user", 443 494 title: [ 444 495 { 445 496 search: "another else", ··· 453 504 } 454 505 455 506 component.buildTransformRules(); 456 - const play = generatePlay({track: 'My cool something track'}); 507 + const play = generatePlay({ track: 'My cool something track' }); 457 508 const transformed = component.transformPlay(play, TRANSFORM_HOOK.preCompare); 458 509 expect(transformed.data.track).equal('My cool final thing track'); 459 510 });