diff --git a/content.js b/content.js index 4816eb2..4a5b4b3 100644 --- a/content.js +++ b/content.js @@ -2385,13 +2385,22 @@ class YTMBlockController { console.log('[YTM Block Debug] normalized song:', normalizedInput); console.log('[YTM Block Debug] blockedSongs array:', this.blockedSongs); - for (const blocked of this.blockedSongs) { - const normalizedBlocked = this.normalizeSongTitle(blocked); + // Cache normalized versions of blocked songs to avoid redundant normalization per iteration + if (!this._normalizedBlockedSongsCache || this._normalizedBlockedSongsSource !== this.blockedSongs) { + this._normalizedBlockedSongsSource = this.blockedSongs; + this._normalizedBlockedSongsCache = this.blockedSongs.map((blocked) => ({ + raw: blocked, + normalized: this.normalizeSongTitle(blocked) + })); + } + + for (const item of this._normalizedBlockedSongsCache) { + const normalizedBlocked = item.normalized; if (normalizedInput === normalizedBlocked || normalizedInput.includes(normalizedBlocked) || normalizedBlocked.includes(normalizedInput)) { - console.log('[YTM Block Debug] shouldBlockSong result:', blocked); - return blocked; + console.log('[YTM Block Debug] shouldBlockSong result:', item.raw); + return item.raw; } } console.log('[YTM Block Debug] shouldBlockSong result: null'); diff --git a/tests/shouldBlockSong.test.js b/tests/shouldBlockSong.test.js new file mode 100644 index 0000000..6b8cd20 --- /dev/null +++ b/tests/shouldBlockSong.test.js @@ -0,0 +1,59 @@ +const test = require('node:test'); +const assert = require('node:assert'); +const { performance } = require('node:perf_hooks'); +const { YTMBlockController } = require('../content.js'); + +test('YTMBlockController - shouldBlockSong correctness', async (t) => { + const controller = new YTMBlockController(); + + await t.test('returns null when songTitle is empty or blockedSongs is empty', () => { + controller.blockedSongs = []; + assert.strictEqual(controller.shouldBlockSong('Some Song'), null); + + controller.blockedSongs = ['Some Song']; + assert.strictEqual(controller.shouldBlockSong(''), null); + assert.strictEqual(controller.shouldBlockSong(null), null); + }); + + await t.test('matches exact, substring, or superstring blocked songs', () => { + controller.blockedSongs = ['Radioactive (Official Video)', 'Believer']; + + // Exact match normalized + assert.strictEqual(controller.shouldBlockSong('Radioactive'), 'Radioactive (Official Video)'); + assert.strictEqual(controller.shouldBlockSong('Believer'), 'Believer'); + + // Substring match + assert.strictEqual(controller.shouldBlockSong('Believer (Remix)'), 'Believer'); + + // Non-match + assert.strictEqual(controller.shouldBlockSong('Demons'), null); + }); +}); + +test('YTMBlockController - shouldBlockSong performance benchmark', async (t) => { + const controller = new YTMBlockController(); + + await t.test('benchmark performance of song blocking evaluation', () => { + const blockedSongs = []; + for (let i = 0; i < 100; i++) { + blockedSongs.push(`Blocked Song Title Number ${i} (Official Audio) [Remastered 2023]`); + } + controller.blockedSongs = blockedSongs; + + const testSong = 'Unblocked Song Title (Live at Wembley Stadium 2020) [Deluxe Edition]'; + + // Warmup JIT + for (let i = 0; i < 100; i++) { + controller.shouldBlockSong(testSong); + } + + const iterations = 5000; + const start = performance.now(); + for (let i = 0; i < iterations; i++) { + controller.shouldBlockSong(testSong); + } + const duration = performance.now() - start; + + console.log(`[Benchmark] ${iterations} shouldBlockSong calls duration: ${duration.toFixed(2)}ms`); + }); +});