Skip to content

Commit

Permalink
Update replaceChromeGridTemplateAreas utility function to keep from b…
Browse files Browse the repository at this point in the history
…reaking captured cssText when grid-template styles are at the beginning of the style declarations.

Add unit tests to cover functinality and avoid regression for this fix
  • Loading branch information
jaj1014 committed Sep 20, 2024
1 parent 6321c05 commit 44ed8bd
Show file tree
Hide file tree
Showing 2 changed files with 144 additions and 19 deletions.
42 changes: 23 additions & 19 deletions packages/rrweb-snapshot/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export const nativeSetTimeout =
typeof window !== 'undefined'
? (getNative<typeof window.setTimeout>('setTimeout').bind(
window,
) as typeof window.setTimeout)
) )
: global.setTimeout;

/**
Expand Down Expand Up @@ -159,7 +159,7 @@ export function stringifyStylesheet(s: CSSStyleSheet): string | null {
}
}

function replaceChromeGridTemplateAreas(rule: CSSStyleRule): string {
export function replaceChromeGridTemplateAreas(rule: CSSStyleRule): string {
const hasGridTemplateInCSSText = rule.cssText.includes('grid-template:');
const hasGridTemplateAreaInStyleRules =
rule.style.getPropertyValue('grid-template-areas') !== '';
Expand All @@ -176,23 +176,27 @@ function replaceChromeGridTemplateAreas(rule: CSSStyleRule): string {
// e.g. https://bugs.chromium.org/p/chromium/issues/detail?id=1303968
// we remove the grid-template rule from the text... so everything from grid-template: to the next semicolon
// and then add each grid-template-x rule into the css text because Chrome isn't doing this correctly
const parts = rule.cssText
.split(';')
.filter((s) => !s.includes('grid-template:'))
.map((s) => s.trim());

const gridStyles: string[] = [];

for (let i = 0; i < rule.style.length; i++) {
const styleName = rule.style[i];
if (styleName.startsWith('grid-template')) {
gridStyles.push(
`${styleName}: ${rule.style.getPropertyValue(styleName)}`,
);
}
}
parts.splice(parts.length - 1, 0, gridStyles.join('; '));
return parts.join('; ');
const regex = /\{([^}]*)\}/;
const match = rule.cssText.match(regex);
const styleDeclarations = match !== null ? match[1].split('; ') : [];

styleDeclarations.forEach((declaration, i) => {
if (!declaration.includes('grid-template:')) return;

const gridStyles = [];

for (let i = 0; i < rule.style.length; i++) {
const styleName = rule.style[i];

if (styleName.startsWith('grid-template')) {
gridStyles.push(`${styleName}: ${rule.style.getPropertyValue(styleName)}`);
}
}

styleDeclarations[i] = gridStyles.join('; ');
});

return `${rule.selectorText} {${styleDeclarations.join('; ')}}`;
}
return rule.cssText;
}
Expand Down
121 changes: 121 additions & 0 deletions packages/rrweb-snapshot/test/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
extractFileExtension,
fixSafariColons,
isNodeMetaEqual,
replaceChromeGridTemplateAreas,
} from '../src/utils';
import type { serializedNodeWithId } from 'rrweb-snapshot';

Expand Down Expand Up @@ -280,4 +281,124 @@ describe('utils', () => {
expect(out3).toEqual('[data-aa\\:other] { color: red; }');
});
});

describe('replaceChromeGridTemplateAreas', () => {
it('does not alter corectly parsed grid template rules', () => {
const cssText = '#wrapper { display: grid; width: 100%; height: 100%; grid-template: repeat(2, 1fr); margin: 0px auto; }';
const mockCssRule = {
cssText,
selectorText: '#wrapper',
style: {
getPropertyValue (prop) {
return {
'grid-template-areas': ''
}[prop]
}
}
} as Partial<CSSStyleRule> as CSSStyleRule

expect(replaceChromeGridTemplateAreas(mockCssRule)).toEqual(cssText);
});

it('fixes incorrectly parsed grid template rules', () => {
const cssText1 = '#wrapper { grid-template-areas: "header header" "main main" "footer footer"; grid-template-rows: repeat(2, 1fr); grid-template-columns: repeat(2, 1fr); display: grid; margin: 0px auto; }';
const cssText2 = '.some-class { color: purple; grid-template: "TopNav TopNav" 65px "SideNav Content" 52px "SideNav Content" / 255px auto; column-gap: 32px; }';

const mockCssRule1 = {
cssText: cssText1,
selectorText: '#wrapper',
style: {
length: 3,
0: 'grid-template-areas',
1: 'grid-template-rows',
2: 'grid-template-columns',
items: (i: number): string => {
return [cssStyleDeclaration[i]].toString();
},
getPropertyValue: (key: string): string => {
if (key === 'grid-template-areas') {
return '"header header" "main main" "footer footer"';
}
if (key === 'grid-template-rows') {
return 'repeat(2, 1fr)';
}
if (key === 'grid-template-columns') {
return 'repeat(2, 1fr)';
}
return '';
},
} as Record<string | number, any>
} as Partial<CSSStyleRule> as CSSStyleRule

const mockCssRule2 = {
cssText: cssText2,
selectorText: '.some-class',
style: {
length: 3,
0: 'grid-template-areas',
1: 'grid-template-rows',
2: 'grid-template-columns',
items: (i: number): string => {
return [cssStyleDeclaration[i]].toString();
},
getPropertyValue: (key: string): string => {
if (key === 'grid-template-areas') {
return '"TopNav TopNav" "SideNav Content" "SideNav Content"';
}
if (key === 'grid-template-rows') {
return '65px 52px auto';
}
if (key === 'grid-template-columns') {
return '255px auto';
}
return '';
},
} as Record<string | number, any>
} as Partial<CSSStyleRule> as CSSStyleRule

expect(replaceChromeGridTemplateAreas(mockCssRule1)).toEqual(
'#wrapper { grid-template-areas: "header header" "main main" "footer footer"; grid-template-rows: repeat(2, 1fr); grid-template-columns: repeat(2, 1fr); display: grid; margin: 0px auto; }'
);
expect(replaceChromeGridTemplateAreas(mockCssRule2)).toEqual(
'.some-class { color: purple; column-gap: 32px; grid-template-areas: "TopNav TopNav" "SideNav Content" "SideNav Content"; grid-template-rows: 65px 52px auto; grid-template-columns: 255px auto; }'
);
});
});

// it('fixes incorrectly parsed grid template rules', () => {
// const cssText =
// '#wrapper { display: grid; grid-template: "header header" max-content / repeat(2, 1fr); margin: 0px auto; }';
// // to avoid using JSDom we can fake as much of the CSSStyleDeclaration as we need
const cssStyleDeclaration: Record<string | number, any> = {
length: 3,
0: 'grid-template-areas',
1: 'grid-template-rows',
2: 'grid-template-columns',
items: (i: number): string => {
return [cssStyleDeclaration[i]].toString();
},
getPropertyValue: (key: string): string => {
if (key === 'grid-template-areas') {
return '"header header" "main main" "footer footer"';
}
if (key === 'grid-template-rows') {
return 'repeat(2, 1fr)';
}
if (key === 'grid-template-columns') {
return 'repeat(2, 1fr)';
}
return '';
},
};

// const stringified = stringifyRule({
// cssText: cssText,
// selectorText: '#wrapper',
// style: cssStyleDeclaration as unknown as CSSStyleDeclaration,
// } as Partial<CSSStyleRule> as CSSStyleRule, null);

// expect(stringified).toEqual(
// '#wrapper { display: grid; margin: 0px auto; grid-template-areas: "header header" "main main" "footer footer"; grid-template-rows: repeat(2, 1fr); grid-template-columns: repeat(2, 1fr); }',
// );
// });
});

0 comments on commit 44ed8bd

Please sign in to comment.