diff --git a/lib/Cartesian.svelte.d.ts b/lib/Cartesian.svelte.d.ts
index c938703..939307e 100644
--- a/lib/Cartesian.svelte.d.ts
+++ b/lib/Cartesian.svelte.d.ts
@@ -14,6 +14,17 @@ interface Props {
* @default false
*/
asChild?: boolean
+ /**
+ * Generate labels under every iteration.
+ *
+ * - **true**: same as `'short'`.
+ * - **short**: display comma-separated values, skip objects.
+ * - **long**: display line-separated key-value pairs, represent object values
+ * as their type name.
+ * - **long-with-objects**: same as `'long'` but with full object definitions.
+ * @default undefined
+ */
+ labels?: undefined | boolean | 'short' | 'long' | 'long-with-objects'
/**
* Disable built-in CSS.
* @default false
@@ -26,6 +37,10 @@ interface Props {
divAttributes?: RestProps
}
+/**
+ * A single component that helps render prop combinations
+ * (the "Cartesian Product") for visual regression testing.
+ */
export default class Cartesian extends SvelteComponent<
Props,
{},
@@ -38,5 +53,15 @@ export default class Cartesian extends SvelteComponent<
*/
innerProps: Record
}
+ label: {
+ /**
+ * The generated label. Hint: use `` to render provided newline characters.
+ */
+ label: string
+ /**
+ * A single combination of props.
+ */
+ innerProps: Record
+ }
}
> {}
diff --git a/lib/cartesian.js b/lib/cartesian.js
index 3a10fdc..37eb8cc 100644
--- a/lib/cartesian.js
+++ b/lib/cartesian.js
@@ -1,9 +1,13 @@
/**
- * Convert props with arrays of values into their
- * Cartesian Product: an array of prop combinations.
- * @param {{[key: string]: any[]}} obj
- * @returns {{ [key: string]: any }[]}
- */
+ * @typedef {{ [key: string]: any }} CartesianProp
+ */
+
+/**
+ * Convert props with arrays of values into their
+ * Cartesian Product: an array of prop combinations.
+ * @param {{[key: string]: any[]}} obj
+ * @returns {CartesianProp[]}
+ */
export function getCartesianProduct (obj) {
const entries = Object.entries(obj)
@@ -31,3 +35,52 @@ export function getCartesianProduct (obj) {
return result
}
+
+/**
+ * Creates a label to render for a given component combination.
+ * @param {CartesianProp} innerProps
+ * @param {{verbosity?: boolean | 'short' | 'long' | 'long-with-objects'}} [options={ verbosity: 'short' }]
+ */
+export function createLabel (
+ innerProps,
+ { verbosity } = { verbosity: 'short' }
+) {
+ const label = []
+ const shortVerbosity = verbosity === 'short' || verbosity === true
+ const joinCharacter = shortVerbosity ? ', ' : '\n'
+
+ for (const [key, value] of Object.entries(innerProps)) {
+ if (
+ shortVerbosity
+ && typeof value !== 'string'
+ && typeof value !== 'number'
+ && typeof value !== 'boolean'
+ ) {
+ // Skip symbols and objects for 'short' labels
+ continue
+ }
+
+ let refinedValue = value
+
+ // Long verbosity treatment
+ if (
+ verbosity === 'long'
+ && typeof value !== 'string'
+ && typeof value !== 'number'
+ ) {
+ refinedValue = typeof value
+ } else if (verbosity === 'long-with-objects' && typeof value === 'object') {
+ refinedValue = JSON.stringify(value, null, 1)
+ }
+
+ if (verbosity === 'long' || verbosity === 'long-with-objects') {
+ label.push(`${key}: ${refinedValue}`)
+ } else if (shortVerbosity && typeof value === 'boolean') {
+ label.push(`${key}=${value}`)
+ } else {
+ label.push(refinedValue)
+ }
+ }
+
+ return label.join(joinCharacter)
+}
diff --git a/package.json b/package.json
index 46c4332..4cef3c4 100644
--- a/package.json
+++ b/package.json
@@ -12,7 +12,7 @@
],
"scripts": {
"check": "svelte-check",
- "test:unit": "vitest"
+ "test": "vitest"
},
"author": "Enrico Sacchetti ",
"license": "MIT",
diff --git a/tests/cartesian.test.js b/tests/cartesian.test.js
index 591dc1c..8a57a40 100644
--- a/tests/cartesian.test.js
+++ b/tests/cartesian.test.js
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest'
-import { getCartesianProduct } from '../lib/cartesian'
+import { createLabel, getCartesianProduct } from '../lib/cartesian'
describe('getCartesianProduct', () => {
it('returns prop combinations', () => {
@@ -38,3 +38,64 @@ describe('getCartesianProduct', () => {
])
})
})
+
+describe('createLabel', () => {
+ it('returns short labels (default behaviour)', () => {
+ expect(createLabel({ variant: 'primary' }))
+ .toBe('primary')
+
+ expect(createLabel({ variant: 'primary' }, { verbosity: 'short' }))
+ .toBe('primary')
+
+ expect(createLabel({ variant: 'primary' }, { verbosity: true }))
+ .toBe('primary')
+
+ expect(createLabel({ variant: 'primary', size: 'md' }))
+ .toBe('primary, md')
+
+ expect(createLabel({ variant: 'primary', disabled: true }), 'handles booleans')
+ .toBe('primary, disabled=true')
+ })
+
+ it('returns long labels', () => {
+ expect(createLabel({ variant: 'primary' }, { verbosity: 'long' }))
+ .toBe('variant: primary')
+
+ expect(createLabel({
+ variant: 'primary',
+ size: 'md',
+ obj: { hello: 'world' }
+ }, { verbosity: 'long' }))
+ .toBe('variant: primary\nsize: md\nobj: object')
+ })
+
+ it('handles functions and symbols (short)', () => {
+ expect(createLabel({
+ variant: 'primary',
+ cb: (/** @type {Event} */ e) => {
+ e.preventDefault()
+ },
+ obj: { hello: 'world' },
+ sym: Symbol('foo')
+ }))
+ .toBe('primary')
+ })
+
+ it('returns object contents', () => {
+ expect(createLabel({
+ variant: 'primary',
+ cb: (/** @type {Event} */ e) => {
+ e.preventDefault()
+ },
+ obj: { hello: 'world' }
+ }, { verbosity: 'long-with-objects' }))
+ .toBe(
+ 'variant: primary\n\
+cb: (/** @type {Event} */ e) => {\n\
+ e.preventDefault()\n\
+ }\n\
+obj: {\n\
+ "hello": "world"\n\
+}')
+ })
+})