Close method-card Save like first add, keep key-resource chips on the approach they were chosen for, and leave expanded See-all stacks in catalog order.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -35,10 +35,6 @@ import {
|
|||||||
decisionApproachPresetFor,
|
decisionApproachPresetFor,
|
||||||
membershipPresetFor,
|
membershipPresetFor,
|
||||||
} from "../../../../lib/create/finalReviewChipPresets";
|
} from "../../../../lib/create/finalReviewChipPresets";
|
||||||
import {
|
|
||||||
applyDecisionApproachKeyResources,
|
|
||||||
selectedKeyResourceLabelsFromCheckedIds,
|
|
||||||
} from "../../../../lib/create/decisionApproachKeyResources";
|
|
||||||
import { isCustomMethodCardId } from "../../../../lib/create/isCustomMethodCardId";
|
import { isCustomMethodCardId } from "../../../../lib/create/isCustomMethodCardId";
|
||||||
import { usesWizardFieldBlocksModalBody } from "../../../../lib/create/usesWizardFieldBlocksModalBody";
|
import { usesWizardFieldBlocksModalBody } from "../../../../lib/create/usesWizardFieldBlocksModalBody";
|
||||||
import type { CustomMethodCardFieldBlock } from "../../../../lib/create/customMethodCardFieldBlocks";
|
import type { CustomMethodCardFieldBlock } from "../../../../lib/create/customMethodCardFieldBlocks";
|
||||||
@@ -1270,7 +1266,6 @@ function facetSeedSignature(
|
|||||||
return JSON.stringify({
|
return JSON.stringify({
|
||||||
meta: state.customMethodCardMetaById?.[id] ?? null,
|
meta: state.customMethodCardMetaById?.[id] ?? null,
|
||||||
details: state.decisionApproachDetailsById?.[id] ?? null,
|
details: state.decisionApproachDetailsById?.[id] ?? null,
|
||||||
keyResources: state.selectedDecisionKeyResourceIds ?? null,
|
|
||||||
blocks: state.customMethodCardFieldBlocksById?.[id] ?? null,
|
blocks: state.customMethodCardFieldBlocksById?.[id] ?? null,
|
||||||
});
|
});
|
||||||
case "conflictManagement":
|
case "conflictManagement":
|
||||||
@@ -1335,18 +1330,12 @@ function seedDraftForTarget(
|
|||||||
const saved =
|
const saved =
|
||||||
state.decisionApproachDetailsById?.[target.overrideKey] ??
|
state.decisionApproachDetailsById?.[target.overrideKey] ??
|
||||||
decisionApproachPresetFor(target.overrideKey);
|
decisionApproachPresetFor(target.overrideKey);
|
||||||
const withKeys = applyDecisionApproachKeyResources(
|
|
||||||
saved,
|
|
||||||
selectedKeyResourceLabelsFromCheckedIds(
|
|
||||||
state.selectedDecisionKeyResourceIds ?? [],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return {
|
return {
|
||||||
groupKey: "decisionApproaches",
|
groupKey: "decisionApproaches",
|
||||||
value: {
|
value: {
|
||||||
...withKeys,
|
...saved,
|
||||||
applicableScope: [...withKeys.applicableScope],
|
applicableScope: [...saved.applicableScope],
|
||||||
selectedApplicableScope: [...withKeys.selectedApplicableScope],
|
selectedApplicableScope: [...saved.selectedApplicableScope],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import {
|
import { mergeCompactCardIdsWithPinnedSelected } from "../../../../lib/create/methodCardDisplayOrder";
|
||||||
mergeCompactCardIdsWithPinnedSelected,
|
|
||||||
orderRankedMethodsWithPinnedSelection,
|
|
||||||
} from "../../../../lib/create/methodCardDisplayOrder";
|
|
||||||
import {
|
import {
|
||||||
deriveCompactCards,
|
deriveCompactCards,
|
||||||
rankMethodsByScore,
|
rankMethodsByScore,
|
||||||
@@ -15,10 +12,10 @@ import {
|
|||||||
type MethodEntry = { id: string; label: string; supportText: string };
|
type MethodEntry = { id: string; label: string; supportText: string };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Applies score ranking, compact-slot rules, then surfaces selected ids first in
|
* Applies score ranking and compact-slot rules. Expanded CardStack order stays
|
||||||
* `selected*Ids` order (most-recent add at index 0 via
|
* the ranked catalog (selected cards keep their place). Compact slots still
|
||||||
* {@link moveFacetSelectionIdToFront}). Selection-first applies whenever the facet
|
* pin selected ids first so a pick outside the unpinned top-N remains visible
|
||||||
* has any selection — not only after footer Confirm (`methodSectionsPinCommitted`).
|
* when the stack is collapsed.
|
||||||
*/
|
*/
|
||||||
export function useMethodCardDeckOrdering(
|
export function useMethodCardDeckOrdering(
|
||||||
section: RecommendationSection,
|
section: RecommendationSection,
|
||||||
@@ -35,16 +32,7 @@ export function useMethodCardDeckOrdering(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const selectionShowcaseActive = selectedIds.length > 0;
|
const selectionShowcaseActive = selectedIds.length > 0;
|
||||||
|
const displayMethods = rankedMethods;
|
||||||
const displayMethods = useMemo(
|
|
||||||
() =>
|
|
||||||
orderRankedMethodsWithPinnedSelection(
|
|
||||||
rankedMethods,
|
|
||||||
selectedIds,
|
|
||||||
selectionShowcaseActive,
|
|
||||||
),
|
|
||||||
[rankedMethods, selectedIds, selectionShowcaseActive],
|
|
||||||
);
|
|
||||||
|
|
||||||
const { compactCardIds: baseCompactCardIds, recommendedIds } = useMemo(
|
const { compactCardIds: baseCompactCardIds, recommendedIds } = useMemo(
|
||||||
() =>
|
() =>
|
||||||
@@ -60,13 +48,13 @@ export function useMethodCardDeckOrdering(
|
|||||||
const compactCardIds = useMemo(
|
const compactCardIds = useMemo(
|
||||||
() =>
|
() =>
|
||||||
mergeCompactCardIdsWithPinnedSelected(
|
mergeCompactCardIdsWithPinnedSelected(
|
||||||
displayMethods.map((m) => m.id),
|
rankedMethods.map((m) => m.id),
|
||||||
baseCompactCardIds,
|
baseCompactCardIds,
|
||||||
selectedIds,
|
selectedIds,
|
||||||
selectionShowcaseActive,
|
selectionShowcaseActive,
|
||||||
5,
|
5,
|
||||||
),
|
),
|
||||||
[displayMethods, baseCompactCardIds, selectedIds, selectionShowcaseActive],
|
[rankedMethods, baseCompactCardIds, selectedIds, selectionShowcaseActive],
|
||||||
);
|
);
|
||||||
|
|
||||||
const sampleCards = useMemo(
|
const sampleCards = useMemo(
|
||||||
|
|||||||
@@ -643,16 +643,9 @@ export function CommunicationMethodsScreen() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (pendingDraft) {
|
pendingEphemeralDuplicateIdRef.current = null;
|
||||||
customizeSnapshotRef.current = captureMethodCardCustomizeSnapshot(
|
customizeSnapshotRef.current = null;
|
||||||
pendingDraft,
|
void handleCreateModalClose();
|
||||||
persistWizardBlocks ? draftFieldBlocks : null,
|
|
||||||
customizeSnapshotRef.current?.headerDraft ?? {
|
|
||||||
title: "",
|
|
||||||
description: "",
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -740,6 +733,7 @@ export function CommunicationMethodsScreen() {
|
|||||||
title={modalConfig.title}
|
title={modalConfig.title}
|
||||||
description={modalConfig.description}
|
description={modalConfig.description}
|
||||||
nextButtonText={modalConfig.nextButtonText}
|
nextButtonText={modalConfig.nextButtonText}
|
||||||
|
showBackButton={false}
|
||||||
showNextButton={showMethodModalPrimary}
|
showNextButton={showMethodModalPrimary}
|
||||||
backdropVariant="blurredYellow"
|
backdropVariant="blurredYellow"
|
||||||
kebabTriggerAriaLabel={modalKebabMenu.triggerAriaLabel}
|
kebabTriggerAriaLabel={modalKebabMenu.triggerAriaLabel}
|
||||||
|
|||||||
@@ -644,16 +644,9 @@ export function ConflictManagementScreen() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (pendingDraft) {
|
pendingEphemeralDuplicateIdRef.current = null;
|
||||||
customizeSnapshotRef.current = captureMethodCardCustomizeSnapshot(
|
customizeSnapshotRef.current = null;
|
||||||
pendingDraft,
|
void handleCreateModalClose();
|
||||||
persistWizardBlocks ? draftFieldBlocks : null,
|
|
||||||
customizeSnapshotRef.current?.headerDraft ?? {
|
|
||||||
title: "",
|
|
||||||
description: "",
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -741,6 +734,7 @@ export function ConflictManagementScreen() {
|
|||||||
title={modalConfig.title}
|
title={modalConfig.title}
|
||||||
description={modalConfig.description}
|
description={modalConfig.description}
|
||||||
nextButtonText={modalConfig.nextButtonText}
|
nextButtonText={modalConfig.nextButtonText}
|
||||||
|
showBackButton={false}
|
||||||
showNextButton={showMethodModalPrimary}
|
showNextButton={showMethodModalPrimary}
|
||||||
backdropVariant="blurredYellow"
|
backdropVariant="blurredYellow"
|
||||||
kebabTriggerAriaLabel={modalKebabMenu.triggerAriaLabel}
|
kebabTriggerAriaLabel={modalKebabMenu.triggerAriaLabel}
|
||||||
|
|||||||
@@ -637,16 +637,9 @@ export function MembershipMethodsScreen() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (pendingDraft) {
|
pendingEphemeralDuplicateIdRef.current = null;
|
||||||
customizeSnapshotRef.current = captureMethodCardCustomizeSnapshot(
|
customizeSnapshotRef.current = null;
|
||||||
pendingDraft,
|
void handleCreateModalClose();
|
||||||
persistWizardBlocks ? draftFieldBlocks : null,
|
|
||||||
customizeSnapshotRef.current?.headerDraft ?? {
|
|
||||||
title: "",
|
|
||||||
description: "",
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -734,6 +727,7 @@ export function MembershipMethodsScreen() {
|
|||||||
title={modalConfig.title}
|
title={modalConfig.title}
|
||||||
description={modalConfig.description}
|
description={modalConfig.description}
|
||||||
nextButtonText={modalConfig.nextButtonText}
|
nextButtonText={modalConfig.nextButtonText}
|
||||||
|
showBackButton={false}
|
||||||
showNextButton={showMethodModalPrimary}
|
showNextButton={showMethodModalPrimary}
|
||||||
backdropVariant="blurredYellow"
|
backdropVariant="blurredYellow"
|
||||||
kebabTriggerAriaLabel={modalKebabMenu.triggerAriaLabel}
|
kebabTriggerAriaLabel={modalKebabMenu.triggerAriaLabel}
|
||||||
|
|||||||
@@ -35,10 +35,8 @@ import { uploadCreateFlowFile } from "../../../../../lib/create/uploadToServer";
|
|||||||
import { decisionApproachPresetFor } from "../../../../../lib/create/finalReviewChipPresets";
|
import { decisionApproachPresetFor } from "../../../../../lib/create/finalReviewChipPresets";
|
||||||
import {
|
import {
|
||||||
applyDecisionApproachKeyResources,
|
applyDecisionApproachKeyResources,
|
||||||
decisionApproachKeyResourceIdsFromLabels,
|
decisionApproachKeyResourceCheckboxIds,
|
||||||
selectedKeyResourceLabelsFromCheckedIds,
|
selectedKeyResourceLabelsFromCheckedIds,
|
||||||
stringArraysEqual,
|
|
||||||
syncDecisionApproachKeyResourceDetails,
|
|
||||||
} from "../../../../../lib/create/decisionApproachKeyResources";
|
} from "../../../../../lib/create/decisionApproachKeyResources";
|
||||||
import type { CustomMethodCardFieldBlock } from "../../../../../lib/create/customMethodCardFieldBlocks";
|
import type { CustomMethodCardFieldBlock } from "../../../../../lib/create/customMethodCardFieldBlocks";
|
||||||
import { mergePresetMethodsWithCustom } from "../../../../../lib/create/mergePresetMethodsWithCustom";
|
import { mergePresetMethodsWithCustom } from "../../../../../lib/create/mergePresetMethodsWithCustom";
|
||||||
@@ -94,11 +92,12 @@ export function DecisionApproachesScreen() {
|
|||||||
>(null);
|
>(null);
|
||||||
|
|
||||||
const selectedIds = state.selectedDecisionApproachIds ?? [];
|
const selectedIds = state.selectedDecisionApproachIds ?? [];
|
||||||
const messageBoxCheckedIds = state.selectedDecisionKeyResourceIds ?? [];
|
const messageBoxCheckedIds = decisionApproachKeyResourceCheckboxIds({
|
||||||
const selectedKeyResourceLabels = useMemo(
|
detailsById: state.decisionApproachDetailsById,
|
||||||
() => selectedKeyResourceLabelsFromCheckedIds(messageBoxCheckedIds),
|
selectedApproachIds: selectedIds,
|
||||||
[messageBoxCheckedIds],
|
reminderIds: state.selectedDecisionKeyResourceIds,
|
||||||
);
|
openDraft: pendingDraft,
|
||||||
|
});
|
||||||
|
|
||||||
const messageBoxItems: InfoMessageBoxItem[] = useMemo(
|
const messageBoxItems: InfoMessageBoxItem[] = useMemo(
|
||||||
() =>
|
() =>
|
||||||
@@ -155,23 +154,16 @@ export function DecisionApproachesScreen() {
|
|||||||
setPendingDraft(
|
setPendingDraft(
|
||||||
applyDecisionApproachKeyResources(pendingDraft, nextLabels),
|
applyDecisionApproachKeyResources(pendingDraft, nextLabels),
|
||||||
);
|
);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
updateState({
|
updateState({
|
||||||
selectedDecisionKeyResourceIds: nextCheckedIds,
|
selectedDecisionKeyResourceIds: nextCheckedIds,
|
||||||
decisionApproachDetailsById: syncDecisionApproachKeyResourceDetails(
|
|
||||||
state.decisionApproachDetailsById,
|
|
||||||
selectedIds,
|
|
||||||
nextLabels,
|
|
||||||
decisionApproachPresetFor,
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
markCreateFlowInteraction,
|
markCreateFlowInteraction,
|
||||||
messageBoxCheckedIds,
|
messageBoxCheckedIds,
|
||||||
pendingDraft,
|
pendingDraft,
|
||||||
selectedIds,
|
|
||||||
state.decisionApproachDetailsById,
|
|
||||||
updateState,
|
updateState,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@@ -179,19 +171,12 @@ export function DecisionApproachesScreen() {
|
|||||||
const seedDraft = useCallback(
|
const seedDraft = useCallback(
|
||||||
(id: string): DecisionApproachDetailEntry => {
|
(id: string): DecisionApproachDetailEntry => {
|
||||||
const saved = state.decisionApproachDetailsById?.[id];
|
const saved = state.decisionApproachDetailsById?.[id];
|
||||||
const base = saved
|
if (saved) {
|
||||||
? {
|
return structuredClone(saved);
|
||||||
...saved,
|
}
|
||||||
applicableScope: [...saved.applicableScope],
|
return decisionApproachPresetFor(id);
|
||||||
selectedApplicableScope: [...saved.selectedApplicableScope],
|
|
||||||
}
|
|
||||||
: decisionApproachPresetFor(id);
|
|
||||||
return applyDecisionApproachKeyResources(
|
|
||||||
base,
|
|
||||||
selectedKeyResourceLabels,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
[selectedKeyResourceLabels, state.decisionApproachDetailsById],
|
[state.decisionApproachDetailsById],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleCardSelect = useCallback(
|
const handleCardSelect = useCallback(
|
||||||
@@ -237,31 +222,8 @@ export function DecisionApproachesScreen() {
|
|||||||
(next: DecisionApproachDetailEntry) => {
|
(next: DecisionApproachDetailEntry) => {
|
||||||
markCreateFlowInteraction();
|
markCreateFlowInteraction();
|
||||||
setPendingDraft(next);
|
setPendingDraft(next);
|
||||||
const nextCheckedIds = decisionApproachKeyResourceIdsFromLabels(
|
|
||||||
next.selectedApplicableScope,
|
|
||||||
);
|
|
||||||
if (stringArraysEqual(nextCheckedIds, messageBoxCheckedIds)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const nextLabels =
|
|
||||||
selectedKeyResourceLabelsFromCheckedIds(nextCheckedIds);
|
|
||||||
updateState({
|
|
||||||
selectedDecisionKeyResourceIds: nextCheckedIds,
|
|
||||||
decisionApproachDetailsById: syncDecisionApproachKeyResourceDetails(
|
|
||||||
state.decisionApproachDetailsById,
|
|
||||||
selectedIds,
|
|
||||||
nextLabels,
|
|
||||||
decisionApproachPresetFor,
|
|
||||||
),
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
[
|
[markCreateFlowInteraction],
|
||||||
markCreateFlowInteraction,
|
|
||||||
messageBoxCheckedIds,
|
|
||||||
selectedIds,
|
|
||||||
state.decisionApproachDetailsById,
|
|
||||||
updateState,
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const isSelectedCardModal =
|
const isSelectedCardModal =
|
||||||
@@ -652,10 +614,7 @@ export function DecisionApproachesScreen() {
|
|||||||
},
|
},
|
||||||
decisionApproachDetailsById: {
|
decisionApproachDetailsById: {
|
||||||
...(state.decisionApproachDetailsById ?? {}),
|
...(state.decisionApproachDetailsById ?? {}),
|
||||||
[id]: applyDecisionApproachKeyResources(
|
[id]: decisionApproachPresetFor(id),
|
||||||
decisionApproachPresetFor(id),
|
|
||||||
selectedKeyResourceLabels,
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
customMethodCardFieldBlocksById: {
|
customMethodCardFieldBlocksById: {
|
||||||
...(state.customMethodCardFieldBlocksById ?? {}),
|
...(state.customMethodCardFieldBlocksById ?? {}),
|
||||||
@@ -667,7 +626,6 @@ export function DecisionApproachesScreen() {
|
|||||||
markCreateFlowInteraction,
|
markCreateFlowInteraction,
|
||||||
pendingDraft,
|
pendingDraft,
|
||||||
selectedIds,
|
selectedIds,
|
||||||
selectedKeyResourceLabels,
|
|
||||||
state.customMethodCardFieldBlocksById,
|
state.customMethodCardFieldBlocksById,
|
||||||
state.customMethodCardMetaById,
|
state.customMethodCardMetaById,
|
||||||
state.decisionApproachDetailsById,
|
state.decisionApproachDetailsById,
|
||||||
@@ -687,31 +645,30 @@ export function DecisionApproachesScreen() {
|
|||||||
modalUsesWizardFieldBlocksBody && draftFieldBlocks !== null;
|
modalUsesWizardFieldBlocksBody && draftFieldBlocks !== null;
|
||||||
|
|
||||||
if (selectedIds.includes(pendingCardId)) {
|
if (selectedIds.includes(pendingCardId)) {
|
||||||
if (persistWizardBlocks) {
|
replaceState((prev) => {
|
||||||
updateState({
|
if (persistWizardBlocks) {
|
||||||
customMethodCardFieldBlocksById: {
|
return {
|
||||||
...(state.customMethodCardFieldBlocksById ?? {}),
|
...prev,
|
||||||
[pendingCardId]: structuredClone(draftFieldBlocks ?? []),
|
customMethodCardFieldBlocksById: {
|
||||||
},
|
...(prev.customMethodCardFieldBlocksById ?? {}),
|
||||||
});
|
[pendingCardId]: structuredClone(draftFieldBlocks ?? []),
|
||||||
} else if (pendingDraft) {
|
},
|
||||||
updateState({
|
};
|
||||||
|
}
|
||||||
|
if (!pendingDraft) {
|
||||||
|
return prev;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...prev,
|
||||||
decisionApproachDetailsById: {
|
decisionApproachDetailsById: {
|
||||||
...(state.decisionApproachDetailsById ?? {}),
|
...(prev.decisionApproachDetailsById ?? {}),
|
||||||
[pendingCardId]: pendingDraft,
|
[pendingCardId]: structuredClone(pendingDraft),
|
||||||
},
|
},
|
||||||
});
|
};
|
||||||
}
|
});
|
||||||
if (pendingDraft) {
|
pendingEphemeralDuplicateIdRef.current = null;
|
||||||
customizeSnapshotRef.current = captureMethodCardCustomizeSnapshot(
|
customizeSnapshotRef.current = null;
|
||||||
pendingDraft,
|
void handleCreateModalClose();
|
||||||
persistWizardBlocks ? draftFieldBlocks : null,
|
|
||||||
customizeSnapshotRef.current?.headerDraft ?? {
|
|
||||||
title: "",
|
|
||||||
description: "",
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -719,24 +676,25 @@ export function DecisionApproachesScreen() {
|
|||||||
void handleCreateModalClose();
|
void handleCreateModalClose();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
updateState({
|
replaceState((prev) => ({
|
||||||
|
...prev,
|
||||||
selectedDecisionApproachIds: moveFacetSelectionIdToFront(
|
selectedDecisionApproachIds: moveFacetSelectionIdToFront(
|
||||||
selectedIds,
|
prev.selectedDecisionApproachIds ?? [],
|
||||||
pendingCardId,
|
pendingCardId,
|
||||||
),
|
),
|
||||||
decisionApproachDetailsById: {
|
decisionApproachDetailsById: {
|
||||||
...(state.decisionApproachDetailsById ?? {}),
|
...(prev.decisionApproachDetailsById ?? {}),
|
||||||
[pendingCardId]: pendingDraft,
|
[pendingCardId]: structuredClone(pendingDraft),
|
||||||
},
|
},
|
||||||
...(persistWizardBlocks
|
...(persistWizardBlocks
|
||||||
? {
|
? {
|
||||||
customMethodCardFieldBlocksById: {
|
customMethodCardFieldBlocksById: {
|
||||||
...(state.customMethodCardFieldBlocksById ?? {}),
|
...(prev.customMethodCardFieldBlocksById ?? {}),
|
||||||
[pendingCardId]: structuredClone(draftFieldBlocks ?? []),
|
[pendingCardId]: structuredClone(draftFieldBlocks ?? []),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
});
|
}));
|
||||||
pendingEphemeralDuplicateIdRef.current = null;
|
pendingEphemeralDuplicateIdRef.current = null;
|
||||||
customizeSnapshotRef.current = null;
|
customizeSnapshotRef.current = null;
|
||||||
void handleCreateModalClose();
|
void handleCreateModalClose();
|
||||||
@@ -747,9 +705,8 @@ export function DecisionApproachesScreen() {
|
|||||||
modalUsesWizardFieldBlocksBody,
|
modalUsesWizardFieldBlocksBody,
|
||||||
pendingCardId,
|
pendingCardId,
|
||||||
pendingDraft,
|
pendingDraft,
|
||||||
|
replaceState,
|
||||||
selectedIds,
|
selectedIds,
|
||||||
state,
|
|
||||||
updateState,
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const modalConfig = pendingCardId
|
const modalConfig = pendingCardId
|
||||||
@@ -840,6 +797,7 @@ export function DecisionApproachesScreen() {
|
|||||||
title={modalConfig.title}
|
title={modalConfig.title}
|
||||||
description={modalConfig.description}
|
description={modalConfig.description}
|
||||||
nextButtonText={modalConfig.nextButtonText}
|
nextButtonText={modalConfig.nextButtonText}
|
||||||
|
showBackButton={false}
|
||||||
showNextButton={showMethodModalPrimary}
|
showNextButton={showMethodModalPrimary}
|
||||||
backdropVariant="blurredYellow"
|
backdropVariant="blurredYellow"
|
||||||
kebabTriggerAriaLabel={modalKebabMenu.triggerAriaLabel}
|
kebabTriggerAriaLabel={modalKebabMenu.triggerAriaLabel}
|
||||||
|
|||||||
@@ -59,6 +59,34 @@ export function decisionApproachKeyResourceIdsFromLabels(
|
|||||||
.map((item) => item.id);
|
.map((item) => item.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sidebar checkboxes follow the open approach while its modal is up.
|
||||||
|
* With no modal they show key resources assigned to any selected approach,
|
||||||
|
* plus extra reminder checks that are not painted onto chips.
|
||||||
|
*/
|
||||||
|
export function decisionApproachKeyResourceCheckboxIds(params: {
|
||||||
|
detailsById: Record<string, DecisionApproachDetailEntry> | undefined;
|
||||||
|
selectedApproachIds: readonly string[];
|
||||||
|
reminderIds?: readonly string[];
|
||||||
|
openDraft?: DecisionApproachDetailEntry | null;
|
||||||
|
}): string[] {
|
||||||
|
if (params.openDraft) {
|
||||||
|
return decisionApproachKeyResourceIdsFromLabels(
|
||||||
|
params.openDraft.selectedApplicableScope,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const assignedLabels: string[] = [];
|
||||||
|
for (const id of params.selectedApproachIds) {
|
||||||
|
const entry = params.detailsById?.[id];
|
||||||
|
if (!entry) continue;
|
||||||
|
assignedLabels.push(...entry.selectedApplicableScope);
|
||||||
|
}
|
||||||
|
return uniquePreserveOrder([
|
||||||
|
...decisionApproachKeyResourceIdsFromLabels(assignedLabels),
|
||||||
|
...(params.reminderIds ?? []),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
export function stringArraysEqual(
|
export function stringArraysEqual(
|
||||||
a: readonly string[],
|
a: readonly string[],
|
||||||
b: readonly string[],
|
b: readonly string[],
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* Reorders facet-ranked method presets so explicitly confirmed selections pin
|
* Compact CardStack slots pin confirmed selections to the front while the
|
||||||
* to the top while the remainder keeps score-based ranking (recommended before
|
* remainder keeps score-based ranking. Expanded order stays the ranked catalog.
|
||||||
* default).
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/** Selected ids first (selection array order); then tail in `ranked` order. */
|
/** Selected ids first (selection array order); then tail in `ranked` order. */
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* Canonical ordering for method-card facet `selected*Ids` when the user adds a card:
|
* Canonical ordering for method-card facet `selected*Ids` when the user adds a card:
|
||||||
* most recently confirmed id is index 0 so stack / compact layouts stay consistent
|
* most recently confirmed id is index 0 so compact layouts stay consistent
|
||||||
* with {@link orderRankedMethodsWithPinnedSelection}.
|
* with {@link mergeCompactCardIdsWithPinnedSelected}.
|
||||||
*/
|
*/
|
||||||
export function moveFacetSelectionIdToFront(
|
export function moveFacetSelectionIdToFront(
|
||||||
prev: readonly string[],
|
prev: readonly string[],
|
||||||
|
|||||||
@@ -161,6 +161,44 @@ describe("CommunicationMethodsScreen — Add Platform persistence", () => {
|
|||||||
expect(textareas[2].value).toBe("Saved coc");
|
expect(textareas[2].value).toBe("Saved coc");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("persists edits and closes when Save is clicked on an already-selected platform", async () => {
|
||||||
|
let latest: CreateFlowState = {};
|
||||||
|
render(
|
||||||
|
<ScreenWithStateProbe
|
||||||
|
onState={(s) => {
|
||||||
|
latest = s;
|
||||||
|
}}
|
||||||
|
initial={{
|
||||||
|
selectedCommunicationMethodIds: ["signal"],
|
||||||
|
communicationMethodDetailsById: {
|
||||||
|
signal: {
|
||||||
|
corePrinciple: "Saved principle",
|
||||||
|
logisticsAdmin: "Saved logistics",
|
||||||
|
codeOfConduct: "Saved coc",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
fireEvent.click(
|
||||||
|
screen.getAllByRole("button", { name: /Signal: Encrypted messaging/ })[0],
|
||||||
|
);
|
||||||
|
const dialog = await screen.findByRole("dialog");
|
||||||
|
const textareas = within(dialog).getAllByRole(
|
||||||
|
"textbox",
|
||||||
|
) as HTMLTextAreaElement[];
|
||||||
|
fireEvent.change(textareas[0], { target: { value: "Edited principle" } });
|
||||||
|
fireEvent.click(within(dialog).getByRole("button", { name: "Save" }));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
latest.communicationMethodDetailsById?.signal?.corePrinciple,
|
||||||
|
).toBe("Edited principle");
|
||||||
|
});
|
||||||
|
|
||||||
it("keeps catalog section editors when a title override exists and there are no custom fields", async () => {
|
it("keeps catalog section editors when a title override exists and there are no custom fields", async () => {
|
||||||
const details = communicationPresetFor("video-meetings");
|
const details = communicationPresetFor("video-meetings");
|
||||||
const noFieldsHint =
|
const noFieldsHint =
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {
|
|||||||
screen,
|
screen,
|
||||||
cleanup,
|
cleanup,
|
||||||
within,
|
within,
|
||||||
|
waitFor,
|
||||||
} from "../utils/test-utils";
|
} from "../utils/test-utils";
|
||||||
import userEvent from "@testing-library/user-event";
|
import userEvent from "@testing-library/user-event";
|
||||||
import { describe, test, expect, afterEach } from "vitest";
|
import { describe, test, expect, afterEach } from "vitest";
|
||||||
@@ -161,6 +162,45 @@ describe("Create flow decision-approaches page", () => {
|
|||||||
).toBeInTheDocument();
|
).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("selecting an approach in the expanded list does not move it to the top", async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
render(<DecisionApproachesScreen />);
|
||||||
|
|
||||||
|
await user.click(
|
||||||
|
screen.getByRole("button", { name: "See all decision approaches" }),
|
||||||
|
);
|
||||||
|
|
||||||
|
const cardLabels = () =>
|
||||||
|
screen
|
||||||
|
.getAllByRole("button")
|
||||||
|
.map((el) => el.getAttribute("aria-label") || "")
|
||||||
|
.filter((label) => label.includes(": "));
|
||||||
|
|
||||||
|
const labelsBefore = cardLabels();
|
||||||
|
expect(labelsBefore[0]).toMatch(/^Lazy Consensus:/);
|
||||||
|
expect(labelsBefore.some((label) => label.startsWith("Sociocracy:"))).toBe(
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
|
await user.click(screen.getByRole("button", { name: /^Sociocracy:/ }));
|
||||||
|
await user.click(
|
||||||
|
within(await screen.findByRole("dialog")).getByRole("button", {
|
||||||
|
name: "Add Approach",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen.getByRole("button", { name: "Show less" }),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(cardLabels()).toEqual(labelsBefore);
|
||||||
|
expect(
|
||||||
|
screen.getByRole("button", { name: /^Sociocracy:/ }),
|
||||||
|
).toHaveTextContent("SELECTED");
|
||||||
|
});
|
||||||
|
|
||||||
test("clicking a card opens the create modal and confirming selects it", async () => {
|
test("clicking a card opens the create modal and confirming selects it", async () => {
|
||||||
const user = userEvent.setup();
|
const user = userEvent.setup();
|
||||||
render(<DecisionApproachesScreen />);
|
render(<DecisionApproachesScreen />);
|
||||||
@@ -212,6 +252,33 @@ describe("Create flow decision-approaches page", () => {
|
|||||||
expect(screen.getByRole("menuitem", { name: "Remove" })).toBeInTheDocument();
|
expect(screen.getByRole("menuitem", { name: "Remove" })).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("Save on a selected approach persists the edit and closes", async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
render(<DecisionApproachesScreen />);
|
||||||
|
|
||||||
|
const card = screen.getByRole("button", {
|
||||||
|
name: /Lazy Consensus: A decision is assumed approved/,
|
||||||
|
});
|
||||||
|
await user.click(card);
|
||||||
|
const dialog = await screen.findByRole("dialog");
|
||||||
|
await user.click(
|
||||||
|
within(dialog).getByRole("button", { name: "Add Approach" }),
|
||||||
|
);
|
||||||
|
|
||||||
|
await user.click(card);
|
||||||
|
const dialogAgain = await screen.findByRole("dialog");
|
||||||
|
const principleField = within(dialogAgain).getByRole("textbox", {
|
||||||
|
name: /core principle/i,
|
||||||
|
});
|
||||||
|
await user.clear(principleField);
|
||||||
|
await user.type(principleField, "Edited principle");
|
||||||
|
await user.click(within(dialogAgain).getByRole("button", { name: "Save" }));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test("Remove from the kebab deselects the approach", async () => {
|
test("Remove from the kebab deselects the approach", async () => {
|
||||||
const user = userEvent.setup();
|
const user = userEvent.setup();
|
||||||
render(<DecisionApproachesScreen />);
|
render(<DecisionApproachesScreen />);
|
||||||
@@ -303,7 +370,7 @@ describe("Create flow decision-approaches page", () => {
|
|||||||
).toBeInTheDocument();
|
).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("checking a key-resource box selects that chip on every approach", async () => {
|
test("checking a key-resource box does not highlight that chip on an unselected approach", async () => {
|
||||||
const user = userEvent.setup();
|
const user = userEvent.setup();
|
||||||
render(<DecisionApproachesScreen />);
|
render(<DecisionApproachesScreen />);
|
||||||
|
|
||||||
@@ -319,10 +386,35 @@ describe("Create flow decision-approaches page", () => {
|
|||||||
const lazyDialog = await screen.findByRole("dialog");
|
const lazyDialog = await screen.findByRole("dialog");
|
||||||
expect(
|
expect(
|
||||||
within(lazyDialog).getByRole("button", {
|
within(lazyDialog).getByRole("button", {
|
||||||
name: "Deselect Steward finances",
|
name: "Select Steward finances",
|
||||||
}),
|
}),
|
||||||
).toBeInTheDocument();
|
).toBeInTheDocument();
|
||||||
await user.click(within(lazyDialog).getByRole("button", { name: "Close dialog" }));
|
});
|
||||||
|
|
||||||
|
test("applicable-scope chips stay on the approach they were chosen for", async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
render(<DecisionApproachesScreen />);
|
||||||
|
|
||||||
|
await user.click(
|
||||||
|
screen.getByRole("button", {
|
||||||
|
name: /Lazy Consensus: A decision is assumed approved/,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
const lazyDialog = await screen.findByRole("dialog");
|
||||||
|
await user.click(
|
||||||
|
within(lazyDialog).getByRole("button", {
|
||||||
|
name: "Select Steward finances",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
screen.getByRole("checkbox", { name: "Steward finances" }),
|
||||||
|
).toBeChecked();
|
||||||
|
await user.click(
|
||||||
|
within(lazyDialog).getByRole("button", { name: "Add Approach" }),
|
||||||
|
);
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
await user.click(
|
await user.click(
|
||||||
screen.getByRole("button", {
|
screen.getByRole("button", {
|
||||||
@@ -332,12 +424,45 @@ describe("Create flow decision-approaches page", () => {
|
|||||||
const doocracyDialog = await screen.findByRole("dialog");
|
const doocracyDialog = await screen.findByRole("dialog");
|
||||||
expect(
|
expect(
|
||||||
within(doocracyDialog).getByRole("button", {
|
within(doocracyDialog).getByRole("button", {
|
||||||
|
name: "Select Steward finances",
|
||||||
|
}),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
within(doocracyDialog).queryByRole("button", {
|
||||||
|
name: "Deselect Steward finances",
|
||||||
|
}),
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByRole("checkbox", { name: "Steward finances" }),
|
||||||
|
).not.toBeChecked();
|
||||||
|
await user.click(
|
||||||
|
within(doocracyDialog).getByRole("button", { name: "Close dialog" }),
|
||||||
|
);
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen.getByRole("checkbox", { name: "Steward finances" }),
|
||||||
|
).toBeChecked();
|
||||||
|
|
||||||
|
await user.click(
|
||||||
|
screen.getByRole("button", {
|
||||||
|
name: /Lazy Consensus: A decision is assumed approved/,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
const lazyAgain = await screen.findByRole("dialog");
|
||||||
|
expect(
|
||||||
|
within(lazyAgain).getByRole("button", {
|
||||||
name: "Deselect Steward finances",
|
name: "Deselect Steward finances",
|
||||||
}),
|
}),
|
||||||
).toBeInTheDocument();
|
).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByRole("checkbox", { name: "Steward finances" }),
|
||||||
|
).toBeChecked();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("choosing a key-resource chip checks the matching sidebar box", async () => {
|
test("checking a key-resource box with a modal open selects that chip on the open approach only", async () => {
|
||||||
const user = userEvent.setup();
|
const user = userEvent.setup();
|
||||||
render(<DecisionApproachesScreen />);
|
render(<DecisionApproachesScreen />);
|
||||||
|
|
||||||
@@ -346,17 +471,148 @@ describe("Create flow decision-approaches page", () => {
|
|||||||
name: /Lazy Consensus: A decision is assumed approved/,
|
name: /Lazy Consensus: A decision is assumed approved/,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
const dialog = await screen.findByRole("dialog");
|
const lazyDialog = await screen.findByRole("dialog");
|
||||||
await user.click(
|
await user.click(
|
||||||
within(dialog).getByRole("button", {
|
within(lazyDialog).getByRole("button", { name: "Add Approach" }),
|
||||||
name: "Select Discipline and member termination",
|
);
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
await user.click(
|
||||||
|
screen.getByRole("button", {
|
||||||
|
name: /Do-ocracy: Decisions are made by those who take initiative/,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
const doocracyDialog = await screen.findByRole("dialog");
|
||||||
|
await user.click(
|
||||||
|
screen.getByRole("checkbox", { name: "Steward finances" }),
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
within(doocracyDialog).getByRole("button", {
|
||||||
|
name: "Deselect Steward finances",
|
||||||
|
}),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
await user.click(
|
||||||
|
within(doocracyDialog).getByRole("button", { name: "Close dialog" }),
|
||||||
|
);
|
||||||
|
await user.click(await screen.findByRole("button", { name: "Discard" }));
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
await user.click(
|
||||||
|
screen.getByRole("button", {
|
||||||
|
name: /Lazy Consensus: A decision is assumed approved/,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
const lazyAgain = await screen.findByRole("dialog");
|
||||||
|
expect(
|
||||||
|
within(lazyAgain).getByRole("button", {
|
||||||
|
name: "Select Steward finances",
|
||||||
|
}),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("highlighting a key-resource chip on a second approach does not rewrite the first", async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
render(<DecisionApproachesScreen />);
|
||||||
|
|
||||||
|
await user.click(
|
||||||
|
screen.getByRole("button", {
|
||||||
|
name: /Lazy Consensus: A decision is assumed approved/,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
const lazyDialog = await screen.findByRole("dialog");
|
||||||
|
await user.click(
|
||||||
|
within(lazyDialog).getByRole("button", {
|
||||||
|
name: "Select Steward finances",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
screen.getByRole("checkbox", { name: "Steward finances" }),
|
||||||
|
).toBeChecked();
|
||||||
|
await user.click(
|
||||||
|
within(lazyDialog).getByRole("button", { name: "Add Approach" }),
|
||||||
|
);
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
await user.click(
|
||||||
|
screen.getByRole("button", {
|
||||||
|
name: /Do-ocracy: Decisions are made by those who take initiative/,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
const doocracyDialog = await screen.findByRole("dialog");
|
||||||
|
await user.click(
|
||||||
|
within(doocracyDialog).getByRole("button", {
|
||||||
|
name: "Select Project level decisions",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
screen.getByRole("checkbox", { name: "Project level decisions" }),
|
||||||
|
).toBeChecked();
|
||||||
|
expect(
|
||||||
|
screen.getByRole("checkbox", { name: "Steward finances" }),
|
||||||
|
).not.toBeChecked();
|
||||||
|
expect(
|
||||||
|
within(doocracyDialog).getByRole("button", {
|
||||||
|
name: "Select Steward finances",
|
||||||
|
}),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
await user.click(
|
||||||
|
within(doocracyDialog).getByRole("button", { name: "Add Approach" }),
|
||||||
|
);
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
screen.getByRole("checkbox", {
|
screen.getByRole("checkbox", { name: "Steward finances" }),
|
||||||
name: "Discipline and member termination",
|
|
||||||
}),
|
|
||||||
).toBeChecked();
|
).toBeChecked();
|
||||||
|
expect(
|
||||||
|
screen.getByRole("checkbox", { name: "Project level decisions" }),
|
||||||
|
).toBeChecked();
|
||||||
|
|
||||||
|
await user.click(
|
||||||
|
screen.getByRole("button", {
|
||||||
|
name: /Lazy Consensus: A decision is assumed approved/,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
const lazyAgain = await screen.findByRole("dialog");
|
||||||
|
expect(
|
||||||
|
within(lazyAgain).getByRole("button", {
|
||||||
|
name: "Deselect Steward finances",
|
||||||
|
}),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
within(lazyAgain).getByRole("button", {
|
||||||
|
name: "Select Project level decisions",
|
||||||
|
}),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
await user.click(
|
||||||
|
within(lazyAgain).getByRole("button", { name: "Close dialog" }),
|
||||||
|
);
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
await user.click(
|
||||||
|
screen.getByRole("button", {
|
||||||
|
name: /Do-ocracy: Decisions are made by those who take initiative/,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
const doocracyAgain = await screen.findByRole("dialog");
|
||||||
|
expect(
|
||||||
|
within(doocracyAgain).getByRole("button", {
|
||||||
|
name: "Deselect Project level decisions",
|
||||||
|
}),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
within(doocracyAgain).getByRole("button", {
|
||||||
|
name: "Select Steward finances",
|
||||||
|
}),
|
||||||
|
).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
import {
|
import {
|
||||||
applyDecisionApproachKeyResources,
|
applyDecisionApproachKeyResources,
|
||||||
|
decisionApproachKeyResourceCheckboxIds,
|
||||||
decisionApproachKeyResourceIdsFromLabels,
|
decisionApproachKeyResourceIdsFromLabels,
|
||||||
decisionApproachScopeForPublish,
|
decisionApproachScopeForPublish,
|
||||||
decisionApproachKeyResourceItems,
|
decisionApproachKeyResourceItems,
|
||||||
@@ -96,6 +97,37 @@ describe("decisionApproachKeyResources", () => {
|
|||||||
).toEqual(["amend", "project"]);
|
).toEqual(["amend", "project"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("derives sidebar checks from the open draft or selected approaches", () => {
|
||||||
|
const detailsById = {
|
||||||
|
"lazy-consensus": {
|
||||||
|
...emptyEntry(),
|
||||||
|
selectedApplicableScope: ["Steward finances"],
|
||||||
|
},
|
||||||
|
"do-ocracy": {
|
||||||
|
...emptyEntry(),
|
||||||
|
selectedApplicableScope: ["Project level decisions"],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
expect(
|
||||||
|
decisionApproachKeyResourceCheckboxIds({
|
||||||
|
detailsById,
|
||||||
|
selectedApproachIds: ["lazy-consensus", "do-ocracy"],
|
||||||
|
reminderIds: ["discipline"],
|
||||||
|
}),
|
||||||
|
).toEqual(["finances", "project", "discipline"]);
|
||||||
|
expect(
|
||||||
|
decisionApproachKeyResourceCheckboxIds({
|
||||||
|
detailsById,
|
||||||
|
selectedApproachIds: ["lazy-consensus", "do-ocracy"],
|
||||||
|
reminderIds: ["discipline"],
|
||||||
|
openDraft: {
|
||||||
|
...emptyEntry(),
|
||||||
|
selectedApplicableScope: ["Amend your CommunityRule"],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toEqual(["amend"]);
|
||||||
|
});
|
||||||
|
|
||||||
it("syncs existing and selected approach details", () => {
|
it("syncs existing and selected approach details", () => {
|
||||||
const next = syncDecisionApproachKeyResourceDetails(
|
const next = syncDecisionApproachKeyResourceDetails(
|
||||||
{ "lazy-consensus": emptyEntry() },
|
{ "lazy-consensus": emptyEntry() },
|
||||||
|
|||||||
Reference in New Issue
Block a user