diff --git a/src/snapshot.ts b/src/snapshot.ts index 42ceaf6e..bb4ed167 100644 --- a/src/snapshot.ts +++ b/src/snapshot.ts @@ -17,11 +17,36 @@ export function resetId() { _id = 1; } +const CSS_RULE = /(([#|\.]{0,1}[a-z0-9\[\]=:]+[\s|,]*)+)\s(\{[\s\S]?[^}]*})/; +const CSS_RULE_GLOBAL = /(([#|\.]{0,1}[a-z0-9\[\]=:]+[\s|,]*)+)\s(\{[\s\S]?[^}]*})/g; +const HOVER_SELECTOR = /([^\\]):hover/g; +export function addHoverClass(cssText: string): string { + const matches = cssText.match(CSS_RULE_GLOBAL) || []; + for (const match of matches) { + const [, selectorText = '', , rules = ''] = match.match(CSS_RULE) || []; + const selectors = selectorText + .split(',') + .map(selector => selector.trim()) + .map(selector => { + if (HOVER_SELECTOR.test(selector)) { + const newSelector = selector.replace(HOVER_SELECTOR, '$1.\\:hover'); + selector += `, ${newSelector}`; + } + return selector; + }); + cssText = cssText.replace(match, selectors.join(', ') + ' ' + rules); + } + return cssText; +} + function getCssRulesString(s: CSSStyleSheet): string | null { try { const rules = s.rules || s.cssRules; return rules - ? Array.from(rules).reduce((prev, cur) => (prev += cur.cssText), '') + ? Array.from(rules).reduce( + (prev, cur) => (prev += addHoverClass(cur.cssText)), + '', + ) : null; } catch (error) { return null; @@ -152,6 +177,9 @@ function serializeNode(n: Node, doc: Document): serializedNode | false { if (parentTagName === 'SCRIPT') { textContent = 'SCRIPT_PLACEHOLDER'; } + if (parentTagName === 'STYLE') { + textContent = addHoverClass(textContent || ''); + } return { type: NodeType.Text, textContent: textContent || '', diff --git a/test/__snapshots__/integration.ts.snap b/test/__snapshots__/integration.ts.snap index f2213003..4658b966 100644 --- a/test/__snapshots__/integration.ts.snap +++ b/test/__snapshots__/integration.ts.snap @@ -88,6 +88,29 @@ exports[`[html file]: form-fields.html 1`] = ` " `; +exports[`[html file]: hover.html 1`] = ` +" + + + + hover selector + + +
hover me
+" +`; + exports[`[html file]: iframe.html 1`] = ` " diff --git a/test/html/hover.html b/test/html/hover.html new file mode 100644 index 00000000..e6df2126 --- /dev/null +++ b/test/html/hover.html @@ -0,0 +1,31 @@ + + + + + + + + hover selector + + + + +
hover me
+ + + \ No newline at end of file diff --git a/test/snapshot.test.ts b/test/snapshot.test.ts index 0720f120..c5e12226 100644 --- a/test/snapshot.test.ts +++ b/test/snapshot.test.ts @@ -1,6 +1,6 @@ import 'mocha'; import { expect } from 'chai'; -import { absoluteToStylesheet } from '../src/snapshot'; +import { absoluteToStylesheet, addHoverClass } from '../src/snapshot'; describe('absolute url to stylesheet', () => { const href = 'http://localhost/css/style.css'; @@ -29,3 +29,43 @@ describe('absolute url to stylesheet', () => { ).to.equal(`url('http://localhost/a.jpg')`); }); }); + +describe('add hover class to hover selector related rules', () => { + it('will do nothing to css text without :hover', () => { + const cssText = 'body { color: white }'; + expect(addHoverClass(cssText)).to.equal(cssText); + }); + + it('can add hover class to css text', () => { + const cssText = '.a:hover { color: white }'; + expect(addHoverClass(cssText)).to.equal( + '.a:hover, .a.\\:hover { color: white }', + ); + }); + + it('can add hover class when there is multi selector', () => { + const cssText = '.a, .b:hover, .c { color: white }'; + expect(addHoverClass(cssText)).to.equal( + '.a, .b:hover, .b.\\:hover, .c { color: white }', + ); + }); + + it('can add hover class when :hover is not the end of selector', () => { + const cssText = 'div:hover::after { color: white }'; + expect(addHoverClass(cssText)).to.equal( + 'div:hover::after, div.\\:hover::after { color: white }', + ); + }); + + it('can add hover class when the selector has multi :hover', () => { + const cssText = 'a:hover b:hover { color: white }'; + expect(addHoverClass(cssText)).to.equal( + 'a:hover b:hover, a.\\:hover b.\\:hover { color: white }', + ); + }); + + it('will ignore :hover in css value', () => { + const cssText = '.a::after { content: ":hover" }'; + expect(addHoverClass(cssText)).to.equal(cssText); + }); +});