Skip to content

Commit 428f6a5

Browse files
Rename Scoped V-Bind Styles
1 parent 379e713 commit 428f6a5

File tree

9 files changed

+855
-634
lines changed

9 files changed

+855
-634
lines changed

package-lock.json

Lines changed: 715 additions & 622 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "vue3-snapshot-serializer",
33
"type": "module",
4-
"version": "2.11.0",
4+
"version": "2.12.0",
55
"description": "Vitest snapshot serializer for Vue 3 components",
66
"main": "index.js",
77
"scripts": {
@@ -14,26 +14,26 @@
1414
"debug": "vitest --inspect-brk --no-file-parallelism -t \"Renders\" \"testingLibrary\""
1515
},
1616
"dependencies": {
17-
"cheerio": "^1.0.0",
17+
"cheerio": "^1.1.0",
1818
"htmlparser2": "^10.0.0",
19-
"js-beautify": "^1.15.3"
19+
"js-beautify": "^1.15.4"
2020
},
2121
"devDependencies": {
2222
"@eslint/js": "^9.28.0",
23-
"@stylistic/eslint-plugin": "^4.4.0",
23+
"@stylistic/eslint-plugin": "^4.4.1",
2424
"@testing-library/user-event": "^14.6.1",
2525
"@testing-library/vue": "^8.1.0",
2626
"@vitejs/plugin-vue": "^5.2.4",
27-
"@vitest/coverage-v8": "^3.1.4",
27+
"@vitest/coverage-v8": "^3.2.3",
2828
"@vue/test-utils": "^2.4.6",
2929
"eslint": "^9.28.0",
3030
"eslint-config-tjw-base": "^4.2.0",
3131
"eslint-config-tjw-jest": "^3.0.0",
3232
"eslint-config-tjw-jsdoc": "^2.0.1",
33-
"eslint-plugin-jest": "^28.12.0",
34-
"eslint-plugin-jsdoc": "^50.7.1",
35-
"happy-dom": "^17.5.6",
36-
"vitest": "^3.1.4"
33+
"eslint-plugin-jest": "^28.13.3",
34+
"eslint-plugin-jsdoc": "^50.8.0",
35+
"happy-dom": "^18.0.1",
36+
"vitest": "^3.2.3"
3737
},
3838
"repository": {
3939
"type": "git",
@@ -58,7 +58,7 @@
5858
},
5959
"homepage": "https://github.com/tjw-lint/vue3-snapshot-serializer#readme",
6060
"volta": {
61-
"node": "24.1.0",
62-
"npm": "11.4.1"
61+
"node": "24.2.0",
62+
"npm": "11.4.2"
6363
}
6464
}

src/cheerioManipulation.js

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,52 @@ const removeScopedStylesDataVIDAttributes = function ($) {
309309
}
310310
};
311311

312+
/**
313+
* This renames `style="--abcd1234-color: #F00;"` to
314+
* `style="--scoped-color: #F00;"`. This come from using
315+
* `background: v-bind(color);` in your scoped styles.
316+
*
317+
* @param {object} $ The markup as a cheerio object
318+
*/
319+
const renameScopedVBindCustomProperties = function ($) {
320+
if (globalThis.vueSnapshots?.renameScopedVBindCSS) {
321+
debugLogger({ function: 'cheerioManipulation.js:renameScopedVBindCustomProperties' });
322+
// String starts, there are exactly 8 characters using lowercase
323+
// hexidecimal, no other characters allowed, then the string ends.
324+
const scopeIdTester = /^[0-9a-f]{8}$/;
325+
$('[style]').each(function (index, element) {
326+
let inlineStyles = element.attribs.style;
327+
inlineStyles = inlineStyles
328+
.split(';')
329+
.map((inlineStyle) => {
330+
// Is a custom property definition
331+
if (inlineStyle.trim().startsWith('--')) {
332+
// '--abcd1234-background-color'
333+
let property = inlineStyle.split(':')[0].trim();
334+
// '#F00'
335+
const value = inlineStyle.split(':')[1].trim();
336+
// ['abcd1234', 'background', 'color']
337+
let propertyChunks = property.split('-').filter(Boolean);
338+
const isVbindScopedCustomProperty = (
339+
propertyChunks.length > 1 &&
340+
scopeIdTester.test(propertyChunks[0])
341+
);
342+
if (isVbindScopedCustomProperty) {
343+
// 'abcd1234' => 'scoped'
344+
propertyChunks[0] = 'scoped';
345+
// '--scoped-background-color: #F00'
346+
return '--' + propertyChunks.join('-') + ': ' + value;
347+
}
348+
return inlineStyle;
349+
}
350+
return inlineStyle;
351+
})
352+
.join(';');
353+
element.attribs.style = inlineStyles;
354+
});
355+
}
356+
};
357+
312358
/**
313359
* This removes the data-server-rendered="true" from your snapshots.
314360
*
@@ -529,6 +575,7 @@ export const cheerioManipulation = function (vueWrapper) {
529575
removeScopedStylesDataVIDAttributes($);
530576
clearAttributes($);
531577
clearInlineFunctions($);
578+
renameScopedVBindCustomProperties($);
532579
sortAttributes($);
533580
sortClasses($);
534581

src/loadOptions.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export const booleanDefaults = {
2727
removeIdTest: false,
2828
removeClassTest: false,
2929
removeComments: false,
30+
renameScopedVBindCSS: false,
3031
clearInlineFunctions: false
3132
};
3233
export const formattingBooleanDefaults = {
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<template>
2+
<div>
3+
<span
4+
class="example"
5+
style="--padding-size: 10px; padding: var(--padding-size);"
6+
>
7+
Text
8+
</span>
9+
</div>
10+
</template>
11+
12+
<script>
13+
export default {
14+
name: 'ScopedVBindStyles',
15+
data: function () {
16+
return {
17+
color: '#FF0000',
18+
pageWidth: '800px'
19+
};
20+
}
21+
};
22+
</script>
23+
24+
<style scoped>
25+
.example {
26+
background: v-bind(color);
27+
width: v-bind(pageWidth);
28+
padding: 10px;
29+
}
30+
</style>

tests/unit/src/cheerioManipulation.test.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import CheckboxesAndRadios from '@@/mockComponents/CheckboxesAndRadios.vue';
1010
import EmbeddedStyles from '@@/mockComponents/EmbeddedStyles.vue';
1111
import InlineFunctions from '@@/mockComponents/InlineFunctions.vue';
1212
import SeveralInputs from '@@/mockComponents/SeveralInputs.vue';
13+
import ScopedVBindStyles from '@@/mockComponents/ScopedVBindStyles.vue';
1314
import SortAttributes from '@@/mockComponents/SortAttributes.vue';
1415
import SortClasses from '@@/mockComponents/SortClasses.vue';
1516
import StringifyAttributes from '@@/mockComponents/StringifyAttributes.vue';
@@ -289,6 +290,50 @@ describe('Cheerio Manipulation', () => {
289290
});
290291
});
291292

293+
describe('ScopedVBindStyles.vue', () => {
294+
test('Scoped ID replaced in dynamic custom properties', async () => {
295+
const wrapper = await mount(ScopedVBindStyles);
296+
297+
expect(wrapper)
298+
.toMatchInlineSnapshot(`
299+
<div style="
300+
--9593b251-color: #FF0000;
301+
--9593b251-pageWidth: 800px;
302+
">
303+
<span
304+
class="example"
305+
style="
306+
--padding-size: 10px;
307+
padding: var(--padding-size);
308+
"
309+
>
310+
Text
311+
</span>
312+
</div>
313+
`);
314+
315+
globalThis.vueSnapshots.renameScopedVBindCSS = true;
316+
317+
expect(wrapper)
318+
.toMatchInlineSnapshot(`
319+
<div style="
320+
--scoped-color: #FF0000;
321+
--scoped-pageWidth: 800px;
322+
">
323+
<span
324+
class="example"
325+
style="
326+
--padding-size: 10px;
327+
padding: var(--padding-size);
328+
"
329+
>
330+
Text
331+
</span>
332+
</div>
333+
`);
334+
});
335+
});
336+
292337
describe('Add input values', () => {
293338
test('Adds values from VTU Wrapper into DOM', async () => {
294339
const wrapper = await mount(SeveralInputs);

tests/unit/src/loadOptions.test.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ describe('Load options', () => {
100100
removeDataVId: false,
101101
removeIdTest: true,
102102
removeServerRendered: false,
103+
renameScopedVBindCSS: true,
103104
sortAttributes: false,
104105
sortClasses: false,
105106
stringifyAttributes: false,
@@ -144,6 +145,7 @@ describe('Load options', () => {
144145
removeDataVId: false,
145146
removeIdTest: true,
146147
removeServerRendered: false,
148+
renameScopedVBindCSS: true,
147149
sortAttributes: false,
148150
sortClasses: false,
149151
stringifyAttributes: false,

types.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@
8989
* @property {boolean} [removeIdTest=false] Removes `id="test-whatever"` or `id="testWhatever"` from snapshots. **Warning:** You should never use ID's for test tokens, as they can also be used by JS and CSS, making them more brittle and their intent less clear. Use `data-test-id` instead.
9090
* @property {boolean} [removeClassTest=false] Removes all CSS classes that start with "test", like `class="test-whatever"`. **Warning:** Don't use this approach. Use `data-test` instead. It is better suited for this because it doesn't conflate CSS and test tokens.
9191
* @property {boolean} [removeComments=false] Removes all HTML comments from your snapshots. This is false by default, as sometimes these comments can infer important information about how your DOM was rendered. However, this is mostly just personal preference.
92+
* @property {boolean} [renameScopedVBindCSS=false] This renames `style="--abcd1234-color: #F00;"` to `style="--scoped-color: #F00;"`. This come from using `background: v-bind(color);` in your scoped styles.
9293
* @property {boolean} [clearInlineFunctions=false] Replaces `<div title="function () { return true; }"></div>` or `<div title="(x) => !x"></div>` with this placeholder `<div title="[function]"></div>`.
9394
* @property {STUBS} [stubs={}] Allows targeting specific DOM nodes in the snapshot to optionally replace their tag name or remove attributes and innerHTML.
9495
* @property {POSTPROCESSOR} [postProcessor] This is a custom function you can pass in. It will be handed a string of formatted markup and must return a string (not a promise). It runs right after the formatter.

vitest.config.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ export default defineConfig({
1515
resolve: {
1616
alias: {
1717
'@': fileURLToPath(new URL('./src', import.meta.url)),
18-
'@@': fileURLToPath(new URL('./tests', import.meta.url))
18+
'@@': fileURLToPath(new URL('./tests', import.meta.url)),
19+
vue: 'vue/dist/vue.esm-bundler.js',
20+
'@vue/runtime-dom': '@vue/runtime-dom/dist/runtime-dom.esm-bundler.js'
1921
}
2022
},
2123
test: {

0 commit comments

Comments
 (0)