Skip to content

Commit 749472d

Browse files
committed
chore(checkbox): updating test format for readability
1 parent 8d541b1 commit 749472d

File tree

2 files changed

+121
-709
lines changed

2 files changed

+121
-709
lines changed

.storybook/decorators/utilities.js

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -581,3 +581,82 @@ export const renderContent = (content = [], {
581581
export const getRandomId = (prefix = "spectrum") => {
582582
return `${prefix}-${Math.random().toString(36).substring(2, 7)}`;
583583
};
584+
585+
/**
586+
* For a given array of test or state descriptor objects, generate all non-empty combinations of the provided items.
587+
*
588+
* rules and behavior:
589+
* - each input item must include a unique `testHeading` string.
590+
* - items may include `not: string[]` where each string is a `testHeading` that
591+
* cannot co-exist with the item in the same combination.
592+
* - combinations that include any forbidden pairs (as declared via `not`) are
593+
* excluded from the output.
594+
* - output objects merge all boolean flags from members of the combination
595+
* (e.g., `isChecked`, `isDisabled`, etc.).
596+
* - the `not` property is never merged into the output; only behavioral flags
597+
* and the concatenated `testHeading` are emitted.
598+
*
599+
* implementation notes:
600+
* - combinations are enumerated via a bitmask from 1..(2^n - 1), thereby
601+
* excluding the empty set by design.
602+
* - constraint checking is done using the human-readable `testHeading` values
603+
* so authors can declare `not: ["Heading A", "Heading B"]` without having
604+
* to repeat flag keys.
605+
*
606+
* @typedef {Object} StateItem
607+
* @property {string} testHeading human-readable name for the state.
608+
* @property {string[]} [not] list of `testHeading` values that cannot co-exist.
609+
* @property {boolean} [isChecked]
610+
* @property {boolean} [isIndeterminate]
611+
* @property {boolean} [isEmphasized]
612+
* @property {boolean} [isReadOnly]
613+
* @property {boolean} [isDisabled]
614+
* @property {boolean} [isHovered]
615+
* @property {boolean} [isActive]
616+
* @property {boolean} [isFocused]
617+
*
618+
* @param {StateItem[]} items array of state descriptor objects.
619+
* @returns {Array<Record<string, unknown>>} all valid merged combinations.
620+
*/
621+
export const getAllCombinations = (items) => {
622+
// store all valid combinations
623+
const combos = [];
624+
const count = items.length;
625+
626+
// use a bitmask to generate the power set (excluding empty set)
627+
for (let mask = 1; mask < (1 << count); mask++) {
628+
// materialize the current combination from selected bits
629+
const combo = [];
630+
for (let i = 0; i < count; i++) {
631+
if (mask & (1 << i)) combo.push(items[i]);
632+
}
633+
634+
// build a set of headings present in this combination for fast lookup
635+
const headings = new Set(combo.map((s) => s.testHeading));
636+
637+
// if any item declares a `not` array that includes any other heading in the
638+
// combination, then the combination is invalid and must be skipped
639+
const hasForbiddenPair = combo.some((item) => {
640+
const blocked = Array.isArray(item.not) ? item.not : [];
641+
return blocked.some((h) => headings.has(h));
642+
});
643+
if (hasForbiddenPair) continue;
644+
645+
// merge flags from items in the combination, skipping meta fields
646+
const flags = combo.reduce((acc, cur) => {
647+
for (const [key, value] of Object.entries(cur)) {
648+
if (key === "testHeading" || key === "not") continue;
649+
acc[key] = value;
650+
}
651+
return acc;
652+
}, {});
653+
654+
// emit merged flags and a concatenated, readable heading
655+
combos.push({
656+
...flags,
657+
testHeading: combo.map((s) => s.testHeading).join(" + "),
658+
});
659+
}
660+
661+
return combos;
662+
};

0 commit comments

Comments
 (0)