diff --git a/tests/blog-carousel.test.mjs b/tests/blog-carousel.test.mjs index adb15233..e332f293 100644 --- a/tests/blog-carousel.test.mjs +++ b/tests/blog-carousel.test.mjs @@ -9,6 +9,17 @@ import { readDynamicStyleRules, } from './test-helpers/blog-dom.mjs'; +function assertNoInlineStyleAttributes(elements, messagePrefix = 'element') { + elements.forEach((element, index) => { + assert.ok(element, `${messagePrefix} at index ${index} should exist`); + assert.strictEqual( + element.hasAttribute('style'), + false, + `${messagePrefix} at index ${index} should not set inline style attributes`, + ); + }); +} + const CAROUSEL_HTML = `
@@ -68,6 +79,8 @@ describe('blog carousel', () => { const trackDynamicClasses = Array.from(track.classList).filter((className) => className.startsWith('bvds-')); assert.ok(trackDynamicClasses.length >= 1, 'track should receive dynamic style classes'); + assertNoInlineStyleAttributes([container, track, ...slides], 'carousel element'); + for (const slide of slides) { assert.ok(slide.classList.contains('splide__slide')); const dynamicClasses = Array.from(slide.classList).filter((className) => className.startsWith('bvds-')); @@ -151,6 +164,8 @@ describe('blog carousel', () => { assert.ok(!container.classList.contains('is-active')); assert.strictEqual(state.cleanup.length, 0); + assertNoInlineStyleAttributes([container, track, ...slides], 'carousel element'); + const previousIndex = state.index; nextButton.dispatchEvent(new env.window.MouseEvent('click', { bubbles: true })); assert.strictEqual(state.index, previousIndex, 'clicks after destroy should not change the index'); @@ -195,6 +210,8 @@ describe('progress bar', () => { progress.reset(); const cssRules = readDynamicStyleRules(env.document); assert.match(cssRules, /width:\s*0%/); + + assertNoInlineStyleAttributes([container, wrapper, fill], 'progress element'); }); test('autoStart runs animation and triggers completion callback', () => { @@ -204,6 +221,8 @@ describe('progress bar', () => { const fill = wrapper.querySelector('.splide__progress__bar'); assert.ok(Array.from(fill.classList).some((className) => className.startsWith('bvds-'))); + assertNoInlineStyleAttributes([container, wrapper, fill], 'progress element'); + env.raf.step(0); env.raf.step(300); assert.strictEqual(onComplete.callCount, 0); @@ -232,5 +251,7 @@ describe('progress bar', () => { progress.destroy(); assert.strictEqual(container.children.length, 0, 'destroy should remove wrapper from DOM'); + + assertNoInlineStyleAttributes([container], 'progress container'); }); }); diff --git a/tests/test-helpers/blog-dom.mjs b/tests/test-helpers/blog-dom.mjs index b7eefed0..ca203c6a 100644 --- a/tests/test-helpers/blog-dom.mjs +++ b/tests/test-helpers/blog-dom.mjs @@ -66,7 +66,43 @@ export function createAnimationFrameStub() { export function installInlineStyleGuard(document) { const originalCreateElement = document.createElement.bind(document); + const ElementConstructor = document.defaultView?.Element; const guardedElements = new WeakSet(); + const attributePatches = []; + + if (ElementConstructor?.prototype) { + const prototype = ElementConstructor.prototype; + + if (typeof prototype.setAttribute === 'function') { + const original = prototype.setAttribute; + prototype.setAttribute = function inlineStyleSafeSetAttribute(name, value) { + if (typeof name === 'string' && name.toLowerCase() === 'style') { + throw new Error( + 'Inline style attributes are disabled in blog widget tests. Use applyDynamicStyles or CSS class toggles instead.', + ); + } + return original.call(this, name, value); + }; + attributePatches.push(() => { + prototype.setAttribute = original; + }); + } + + if (typeof prototype.setAttributeNS === 'function') { + const original = prototype.setAttributeNS; + prototype.setAttributeNS = function inlineStyleSafeSetAttributeNS(namespace, name, value) { + if (typeof name === 'string' && name.toLowerCase() === 'style') { + throw new Error( + 'Inline style attributes are disabled in blog widget tests. Use applyDynamicStyles or CSS class toggles instead.', + ); + } + return original.call(this, namespace, name, value); + }; + attributePatches.push(() => { + prototype.setAttributeNS = original; + }); + } + } function guard(element) { if (!element || typeof element !== 'object') { @@ -110,6 +146,13 @@ export function installInlineStyleGuard(document) { guard, restore() { document.createElement = originalCreateElement; + attributePatches.forEach((restorePatch) => { + try { + restorePatch(); + } catch (error) { + // ignore failures to restore in tests + } + }); }, }; }