QA pass: layout, create-flow, and About books #68
@@ -84,6 +84,7 @@ import { SignedInDraftHydration } from "./SignedInDraftHydration";
|
||||
import { CreateFlowPendingAvatarFlush } from "./components/CreateFlowPendingAvatarFlush";
|
||||
import Alert from "../../components/modals/Alert";
|
||||
import Create from "../../components/modals/Create";
|
||||
import { MODAL_FOOTER_START_SLOT_CLASS } from "../../components/modals/ModalFooter";
|
||||
import Share from "../../components/modals/Share";
|
||||
import {
|
||||
CreateFlowDraftSaveBannerProvider,
|
||||
@@ -637,14 +638,16 @@ function CreateFlowLayoutContent({
|
||||
nextButtonText={messages.create.topNav.leaveConfirmProceed}
|
||||
onNext={() => closeLeaveConfirm(true)}
|
||||
footerContent={
|
||||
<Button
|
||||
buttonType="ghost"
|
||||
palette="default"
|
||||
size="xsmall"
|
||||
onClick={() => closeLeaveConfirm(false)}
|
||||
>
|
||||
{messages.create.topNav.leaveConfirmCancel}
|
||||
</Button>
|
||||
<div className={MODAL_FOOTER_START_SLOT_CLASS}>
|
||||
<Button
|
||||
buttonType="ghost"
|
||||
palette="default"
|
||||
size="xsmall"
|
||||
onClick={() => closeLeaveConfirm(false)}
|
||||
>
|
||||
{messages.create.topNav.leaveConfirmCancel}
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
backdropVariant="blurredYellow"
|
||||
ariaLabel={messages.create.topNav.leaveConfirmTitle}
|
||||
|
||||
@@ -15,7 +15,7 @@ import type { CoreValueDetailEntry } from "../../types";
|
||||
export interface CoreValueEditFieldsProps {
|
||||
value: CoreValueDetailEntry;
|
||||
onChange: (_next: CoreValueDetailEntry) => void;
|
||||
/** View mode until the user taps **Customize**. */
|
||||
/** Disable meaning/signals. Create-flow core-values omits this; final-review locks until Customize. */
|
||||
readOnly?: boolean;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import ContentLockup from "../../../../components/type/ContentLockup";
|
||||
import { useMessages } from "../../../../contexts/MessagesContext";
|
||||
import { buildCoreValueChipOptionsFromDraft } from "../../../../../lib/create/coreValueChipOptionsFromDraft";
|
||||
import { useCreateFlow } from "../../context/CreateFlowContext";
|
||||
import { useDiscardCustomizeConfirm } from "../../hooks/useDiscardCustomizeConfirm";
|
||||
import { useAsyncConfirm } from "../../../../hooks/useAsyncConfirm";
|
||||
import type {
|
||||
CommunityStructureChipSnapshotRow,
|
||||
CoreValueDetailEntry,
|
||||
@@ -16,14 +16,7 @@ import type {
|
||||
import { CreateFlowHeaderLockup } from "../../components/CreateFlowHeaderLockup";
|
||||
import { CreateFlowTwoColumnSelectShell } from "../../components/CreateFlowTwoColumnSelectShell";
|
||||
import { CoreValueEditFields } from "../../components/methodEditFields";
|
||||
import MethodCardCustomizeModalHeader from "../../components/MethodCardCustomizeModalHeader";
|
||||
import { buildCustomRuleModalKebabMenu } from "../../components/customRuleModalKebabMenu";
|
||||
import {
|
||||
captureMethodCardCustomizeSnapshot,
|
||||
isMethodCardCustomizeSessionDirty,
|
||||
type MethodCardCustomizeSnapshot,
|
||||
type MethodCardHeaderDraft,
|
||||
} from "../../../../../lib/create/methodCardCustomizeSession";
|
||||
import {
|
||||
duplicateCoreValueChipInDraft,
|
||||
MAX_SELECTED_CORE_VALUES,
|
||||
@@ -37,8 +30,7 @@ const MAX_CORE_VALUES = MAX_SELECTED_CORE_VALUES;
|
||||
* Why three sessions, not two:
|
||||
*
|
||||
* - `pending` — preset chip just selected; modal opened to capture
|
||||
* meaning/signals. Dismiss = unselect the chip (keep it in the
|
||||
* preset row, just not selected).
|
||||
* meaning/signals. Close (X) confirms, then unselects the chip.
|
||||
* - `customPending` — brand-new custom chip just created via the Add
|
||||
* value flow; modal opened with empty fields. Dismiss = drop the
|
||||
* chip entirely (it was never confirmed via the Add Value button).
|
||||
@@ -101,13 +93,11 @@ export function CoreValuesSelectScreen() {
|
||||
[cv.values],
|
||||
);
|
||||
|
||||
const { confirmDiscard, confirmDirtyCustomizeCancel, confirmDialog } =
|
||||
useDiscardCustomizeConfirm();
|
||||
const { requestConfirm, confirmDialog } = useAsyncConfirm();
|
||||
const { markCreateFlowInteraction, updateState, replaceState, state } =
|
||||
useCreateFlow();
|
||||
|
||||
const coreCustomizeSnapshotRef =
|
||||
useRef<MethodCardCustomizeSnapshot<CoreValueDetailEntry> | null>(null);
|
||||
const initialDraftRef = useRef<CoreValueDetailEntry | null>(null);
|
||||
const pendingEphemeralCoreDuplicateRef = useRef<string | null>(null);
|
||||
|
||||
const [coreValueOptions, setCoreValueOptions] = useState<ChipOption[]>(() =>
|
||||
@@ -123,9 +113,6 @@ export function CoreValuesSelectScreen() {
|
||||
);
|
||||
const [modalSession, setModalSession] = useState<ModalSession | null>(null);
|
||||
const [draft, setDraft] = useState<CoreValueDetailEntry>(EMPTY_DETAIL);
|
||||
const [modalEditUnlocked, setModalEditUnlocked] = useState(false);
|
||||
const [customizeHeaderDraft, setCustomizeHeaderDraft] =
|
||||
useState<MethodCardHeaderDraft | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setCoreValueOptions(
|
||||
@@ -190,12 +177,11 @@ export function CoreValuesSelectScreen() {
|
||||
valueLabel: string,
|
||||
seedDetail?: CoreValueDetailEntry,
|
||||
) => {
|
||||
setDraft(seedDetail ?? getInitialTexts(chipId, valueLabel));
|
||||
const initial = seedDetail ?? getInitialTexts(chipId, valueLabel);
|
||||
initialDraftRef.current = { ...initial };
|
||||
setDraft(initial);
|
||||
setActiveModalChipId(chipId);
|
||||
setModalSession(session);
|
||||
setModalEditUnlocked(false);
|
||||
setCustomizeHeaderDraft(null);
|
||||
coreCustomizeSnapshotRef.current = null;
|
||||
markCreateFlowInteraction();
|
||||
},
|
||||
[getInitialTexts, markCreateFlowInteraction],
|
||||
@@ -209,87 +195,36 @@ export function CoreValuesSelectScreen() {
|
||||
[markCreateFlowInteraction],
|
||||
);
|
||||
|
||||
const resetCustomizeSession = useCallback(() => {
|
||||
coreCustomizeSnapshotRef.current = null;
|
||||
setModalEditUnlocked(false);
|
||||
setCustomizeHeaderDraft(null);
|
||||
}, []);
|
||||
|
||||
const finalizeModalDismiss = useCallback(() => {
|
||||
pendingEphemeralCoreDuplicateRef.current = null;
|
||||
resetCustomizeSession();
|
||||
initialDraftRef.current = null;
|
||||
setActiveModalChipId(null);
|
||||
setModalSession(null);
|
||||
}, [resetCustomizeSession]);
|
||||
}, []);
|
||||
|
||||
const handleCustomize = useCallback(() => {
|
||||
if (!activeModalChipId) return;
|
||||
const chipLabelNow =
|
||||
coreValueOptions.find((o) => o.id === activeModalChipId)?.label ?? "";
|
||||
if (!chipLabelNow) return;
|
||||
markCreateFlowInteraction();
|
||||
const headerDraft: MethodCardHeaderDraft = {
|
||||
title: chipLabelNow,
|
||||
description: "",
|
||||
};
|
||||
coreCustomizeSnapshotRef.current = captureMethodCardCustomizeSnapshot(
|
||||
draft,
|
||||
null,
|
||||
headerDraft,
|
||||
);
|
||||
setCustomizeHeaderDraft(headerDraft);
|
||||
setModalEditUnlocked(true);
|
||||
}, [activeModalChipId, coreValueOptions, draft, markCreateFlowInteraction]);
|
||||
|
||||
const handleCancelCustomize = useCallback(async () => {
|
||||
if (!modalEditUnlocked) return;
|
||||
const snap = coreCustomizeSnapshotRef.current;
|
||||
if (!snap) {
|
||||
resetCustomizeSession();
|
||||
return;
|
||||
const confirmLeaveWithoutSaving = useCallback(async () => {
|
||||
const isPendingAdd =
|
||||
modalSession === "pending" || modalSession === "customPending";
|
||||
const initial = initialDraftRef.current;
|
||||
const editingDirty =
|
||||
modalSession === "editing" &&
|
||||
initial != null &&
|
||||
(draft.meaning !== initial.meaning || draft.signals !== initial.signals);
|
||||
if (!isPendingAdd && !editingDirty) {
|
||||
return true;
|
||||
}
|
||||
if (
|
||||
!(await confirmDirtyCustomizeCancel(
|
||||
snap,
|
||||
draft,
|
||||
null,
|
||||
customizeHeaderDraft,
|
||||
))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
setDraft(structuredClone(snap.pendingDraft));
|
||||
resetCustomizeSession();
|
||||
}, [
|
||||
confirmDirtyCustomizeCancel,
|
||||
customizeHeaderDraft,
|
||||
draft,
|
||||
modalEditUnlocked,
|
||||
resetCustomizeSession,
|
||||
]);
|
||||
return requestConfirm({
|
||||
title: cv.detailModal.discardTitle,
|
||||
description: isPendingAdd
|
||||
? cv.detailModal.discardPendingDescription
|
||||
: cv.detailModal.discardEditsDescription,
|
||||
proceedText: cv.detailModal.discardProceed,
|
||||
cancelText: cv.detailModal.discardKeepEditing,
|
||||
});
|
||||
}, [cv.detailModal, draft, modalSession, requestConfirm]);
|
||||
|
||||
const syncLabelFromCustomizeHeaderToOptions = useCallback(() => {
|
||||
if (!activeModalChipId || !customizeHeaderDraft) return coreValueOptions;
|
||||
const trimmed = customizeHeaderDraft.title.trim();
|
||||
if (!trimmed) return coreValueOptions;
|
||||
return coreValueOptions.map((opt) =>
|
||||
opt.id === activeModalChipId ? { ...opt, label: trimmed } : opt,
|
||||
);
|
||||
}, [activeModalChipId, customizeHeaderDraft, coreValueOptions]);
|
||||
|
||||
const handleDuplicateCoreChip = useCallback(async () => {
|
||||
const handleDuplicateCoreChip = useCallback(() => {
|
||||
if (!activeModalChipId || !modalSession) return;
|
||||
if (
|
||||
!(await confirmDiscard(
|
||||
modalEditUnlocked,
|
||||
coreCustomizeSnapshotRef.current,
|
||||
draft,
|
||||
null,
|
||||
customizeHeaderDraft,
|
||||
))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
markCreateFlowInteraction();
|
||||
const priorEphemeral = pendingEphemeralCoreDuplicateRef.current;
|
||||
let outcome: ReturnType<typeof duplicateCoreValueChipInDraft> | null = null;
|
||||
@@ -312,7 +247,6 @@ export function CoreValuesSelectScreen() {
|
||||
if (!outcome) {
|
||||
return;
|
||||
}
|
||||
resetCustomizeSession();
|
||||
pendingEphemeralCoreDuplicateRef.current = outcome.newId;
|
||||
openModal(
|
||||
outcome.newId,
|
||||
@@ -322,30 +256,15 @@ export function CoreValuesSelectScreen() {
|
||||
);
|
||||
}, [
|
||||
activeModalChipId,
|
||||
confirmDiscard,
|
||||
customizeHeaderDraft,
|
||||
draft,
|
||||
markCreateFlowInteraction,
|
||||
modalEditUnlocked,
|
||||
modalKebabMenu.duplicateTitleSuffix,
|
||||
modalSession,
|
||||
openModal,
|
||||
replaceState,
|
||||
resetCustomizeSession,
|
||||
]);
|
||||
|
||||
const handleRemoveFromKebab = useCallback(async () => {
|
||||
if (
|
||||
!(await confirmDiscard(
|
||||
modalEditUnlocked,
|
||||
coreCustomizeSnapshotRef.current,
|
||||
draft,
|
||||
null,
|
||||
customizeHeaderDraft,
|
||||
))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const handleRemoveFromKebab = useCallback(() => {
|
||||
markCreateFlowInteraction();
|
||||
|
||||
const ep = pendingEphemeralCoreDuplicateRef.current;
|
||||
@@ -386,28 +305,16 @@ export function CoreValuesSelectScreen() {
|
||||
finalizeModalDismiss();
|
||||
}, [
|
||||
activeModalChipId,
|
||||
confirmDiscard,
|
||||
coreValueOptions,
|
||||
customizeHeaderDraft,
|
||||
draft,
|
||||
finalizeModalDismiss,
|
||||
markCreateFlowInteraction,
|
||||
modalEditUnlocked,
|
||||
modalSession,
|
||||
persistCoreValues,
|
||||
replaceState,
|
||||
]);
|
||||
|
||||
const handleModalDismiss = useCallback(async () => {
|
||||
if (
|
||||
!(await confirmDiscard(
|
||||
modalEditUnlocked,
|
||||
coreCustomizeSnapshotRef.current,
|
||||
draft,
|
||||
null,
|
||||
customizeHeaderDraft,
|
||||
))
|
||||
) {
|
||||
if (!(await confirmLeaveWithoutSaving())) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -436,93 +343,39 @@ export function CoreValuesSelectScreen() {
|
||||
finalizeModalDismiss();
|
||||
}, [
|
||||
activeModalChipId,
|
||||
confirmDiscard,
|
||||
confirmLeaveWithoutSaving,
|
||||
coreValueOptions,
|
||||
customizeHeaderDraft,
|
||||
draft,
|
||||
finalizeModalDismiss,
|
||||
modalEditUnlocked,
|
||||
modalSession,
|
||||
persistCoreValues,
|
||||
replaceState,
|
||||
]);
|
||||
|
||||
const coreCustomizeSaveDisabled = useMemo(() => {
|
||||
if (!modalEditUnlocked) return false;
|
||||
const snap = coreCustomizeSnapshotRef.current;
|
||||
if (!snap) return true;
|
||||
return !isMethodCardCustomizeSessionDirty(
|
||||
snap,
|
||||
draft,
|
||||
null,
|
||||
customizeHeaderDraft,
|
||||
);
|
||||
}, [customizeHeaderDraft, draft, modalEditUnlocked]);
|
||||
|
||||
const handleModalConfirm = useCallback(() => {
|
||||
if (!activeModalChipId || !modalSession) return;
|
||||
|
||||
if (modalEditUnlocked && customizeHeaderDraft) {
|
||||
if (coreCustomizeSaveDisabled) {
|
||||
return;
|
||||
}
|
||||
markCreateFlowInteraction();
|
||||
pendingEphemeralCoreDuplicateRef.current = null;
|
||||
const nextOpts = syncLabelFromCustomizeHeaderToOptions();
|
||||
persistCoreValues(nextOpts);
|
||||
updateState({
|
||||
coreValueDetailsByChipId: {
|
||||
...(state.coreValueDetailsByChipId ?? {}),
|
||||
[activeModalChipId]: draft,
|
||||
},
|
||||
});
|
||||
resetCustomizeSession();
|
||||
return;
|
||||
}
|
||||
|
||||
if (modalSession === "pending" || modalSession === "customPending") {
|
||||
markCreateFlowInteraction();
|
||||
pendingEphemeralCoreDuplicateRef.current = null;
|
||||
updateState({
|
||||
coreValueDetailsByChipId: {
|
||||
...(state.coreValueDetailsByChipId ?? {}),
|
||||
[activeModalChipId]: draft,
|
||||
},
|
||||
});
|
||||
resetCustomizeSession();
|
||||
setActiveModalChipId(null);
|
||||
setModalSession(null);
|
||||
}
|
||||
markCreateFlowInteraction();
|
||||
pendingEphemeralCoreDuplicateRef.current = null;
|
||||
updateState({
|
||||
coreValueDetailsByChipId: {
|
||||
...(state.coreValueDetailsByChipId ?? {}),
|
||||
[activeModalChipId]: draft,
|
||||
},
|
||||
});
|
||||
finalizeModalDismiss();
|
||||
}, [
|
||||
activeModalChipId,
|
||||
coreCustomizeSaveDisabled,
|
||||
customizeHeaderDraft,
|
||||
draft,
|
||||
finalizeModalDismiss,
|
||||
markCreateFlowInteraction,
|
||||
modalEditUnlocked,
|
||||
modalSession,
|
||||
persistCoreValues,
|
||||
resetCustomizeSession,
|
||||
state.coreValueDetailsByChipId,
|
||||
syncLabelFromCustomizeHeaderToOptions,
|
||||
updateState,
|
||||
]);
|
||||
|
||||
const modalChipLabel =
|
||||
coreValueOptions.find((o) => o.id === activeModalChipId)?.label ?? "";
|
||||
|
||||
const modalFieldsLocked =
|
||||
!modalEditUnlocked &&
|
||||
Boolean(
|
||||
modalSession === "pending" ||
|
||||
modalSession === "customPending" ||
|
||||
modalSession === "editing",
|
||||
);
|
||||
|
||||
const showFooterPrimary =
|
||||
modalEditUnlocked ||
|
||||
modalSession === "pending" ||
|
||||
modalSession === "customPending";
|
||||
const showFooterPrimary = Boolean(modalSession);
|
||||
|
||||
const kebabMenuItems = useMemo(() => {
|
||||
if (!modalSession || !activeModalChipId) return [];
|
||||
@@ -530,8 +383,6 @@ export function CoreValuesSelectScreen() {
|
||||
(o) => o.state === "selected",
|
||||
).length;
|
||||
return buildCustomRuleModalKebabMenu(modalKebabMenu, {
|
||||
showCustomize: !modalEditUnlocked,
|
||||
onCustomize: handleCustomize,
|
||||
onDuplicate:
|
||||
modalSession !== "editing" || selectedCount >= MAX_CORE_VALUES
|
||||
? undefined
|
||||
@@ -542,10 +393,8 @@ export function CoreValuesSelectScreen() {
|
||||
}, [
|
||||
activeModalChipId,
|
||||
coreValueOptions,
|
||||
handleCustomize,
|
||||
handleDuplicateCoreChip,
|
||||
handleRemoveFromKebab,
|
||||
modalEditUnlocked,
|
||||
modalKebabMenu,
|
||||
modalSession,
|
||||
]);
|
||||
@@ -675,41 +524,22 @@ export function CoreValuesSelectScreen() {
|
||||
onClose={handleModalDismiss}
|
||||
backdropVariant="blurredYellow"
|
||||
headerContent={
|
||||
modalEditUnlocked && customizeHeaderDraft ? (
|
||||
<MethodCardCustomizeModalHeader
|
||||
titleLabel={detailModal.customizeValueNameLabel}
|
||||
descriptionLabel=""
|
||||
titleValue={customizeHeaderDraft.title}
|
||||
descriptionValue=""
|
||||
onTitleChange={(title) =>
|
||||
setCustomizeHeaderDraft((prev) =>
|
||||
prev ? { ...prev, title } : null,
|
||||
)
|
||||
}
|
||||
onDescriptionChange={() => {}}
|
||||
showDescription={false}
|
||||
<div className="bg-[var(--color-surface-default-primary)] px-[24px] py-[12px] shrink-0">
|
||||
<ContentLockup
|
||||
title={modalChipLabel}
|
||||
description={detailModal.subtitle}
|
||||
variant="modal"
|
||||
alignment="left"
|
||||
/>
|
||||
) : (
|
||||
<div className="bg-[var(--color-surface-default-primary)] px-[24px] py-[12px] shrink-0">
|
||||
<ContentLockup
|
||||
title={modalChipLabel}
|
||||
description={detailModal.subtitle}
|
||||
variant="modal"
|
||||
alignment="left"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
</div>
|
||||
}
|
||||
showBackButton={modalEditUnlocked}
|
||||
onBack={handleCancelCustomize}
|
||||
backButtonText={modalKebabMenu.cancelCustomize}
|
||||
showBackButton={false}
|
||||
showNextButton={showFooterPrimary}
|
||||
nextButtonDisabled={
|
||||
modalEditUnlocked && coreCustomizeSaveDisabled
|
||||
}
|
||||
onNext={handleModalConfirm}
|
||||
nextButtonText={
|
||||
modalEditUnlocked ? modalKebabMenu.saveEdits : detailModal.addValueButton
|
||||
modalSession === "editing"
|
||||
? modalKebabMenu.saveEdits
|
||||
: detailModal.addValueButton
|
||||
}
|
||||
kebabTriggerAriaLabel={modalKebabMenu.triggerAriaLabel}
|
||||
kebabMenuAriaLabel={modalKebabMenu.menuAriaLabel}
|
||||
@@ -719,7 +549,6 @@ export function CoreValuesSelectScreen() {
|
||||
ariaLabel={modalChipLabel || "Core value details"}
|
||||
>
|
||||
<CoreValueEditFields
|
||||
readOnly={modalFieldsLocked}
|
||||
value={draft}
|
||||
onChange={handleDraftChange}
|
||||
/>
|
||||
|
||||
@@ -23,3 +23,7 @@ export interface ModalFooterProps {
|
||||
footerContent?: React.ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
/** Left footer slot for confirm-dialog cancel; matches Back (`left` / `top` 16 / 12). */
|
||||
export const MODAL_FOOTER_START_SLOT_CLASS =
|
||||
"absolute left-[16px] top-[12px] flex min-h-[40px] items-center";
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export { default } from "./ModalFooter.container";
|
||||
export type { ModalFooterProps } from "./ModalFooter.types";
|
||||
export { MODAL_FOOTER_START_SLOT_CLASS } from "./ModalFooter.types";
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { useCallback, useRef, useState } from "react";
|
||||
import Button from "../components/buttons/Button";
|
||||
import Create from "../components/modals/Create";
|
||||
import { MODAL_FOOTER_START_SLOT_CLASS } from "../components/modals/ModalFooter";
|
||||
import type { CreateModalBackdropVariant } from "../components/modals/Create/CreateModalFrame.view";
|
||||
|
||||
export type AsyncConfirmOptions = {
|
||||
@@ -58,14 +59,16 @@ export function useAsyncConfirm() {
|
||||
nextButtonText={options.proceedText}
|
||||
onNext={() => close(true)}
|
||||
footerContent={
|
||||
<Button
|
||||
buttonType="ghost"
|
||||
palette="default"
|
||||
size="xsmall"
|
||||
onClick={() => close(false)}
|
||||
>
|
||||
{options.cancelText}
|
||||
</Button>
|
||||
<div className={MODAL_FOOTER_START_SLOT_CLASS}>
|
||||
<Button
|
||||
buttonType="ghost"
|
||||
palette="default"
|
||||
size="xsmall"
|
||||
onClick={() => close(false)}
|
||||
>
|
||||
{options.cancelText}
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
backdropVariant={options.backdropVariant ?? "blurredYellow"}
|
||||
ariaLabel={options.ariaLabel ?? options.title}
|
||||
|
||||
@@ -13,7 +13,12 @@
|
||||
"meaningLabel": "What does this value mean to your group?",
|
||||
"signalsLabel": "Signals of Violation",
|
||||
"addValueButton": "Add Value",
|
||||
"customizeValueNameLabel": "Value name"
|
||||
"customizeValueNameLabel": "Value name",
|
||||
"discardTitle": "Discard unsaved changes?",
|
||||
"discardPendingDescription": "This value will not be added.",
|
||||
"discardEditsDescription": "Your edits will not be saved.",
|
||||
"discardProceed": "Discard",
|
||||
"discardKeepEditing": "Keep editing"
|
||||
},
|
||||
"values": [
|
||||
{
|
||||
|
||||
@@ -263,7 +263,14 @@ describe("CommunicationMethodsScreen — Add Platform persistence", () => {
|
||||
fireEvent.change(textboxes[2], { target: { value: "Edited principle" } });
|
||||
|
||||
fireEvent.keyDown(document, { key: "Escape" });
|
||||
await screen.findByRole("button", { name: "Keep editing" });
|
||||
const keepEditing = await screen.findByRole("button", {
|
||||
name: "Keep editing",
|
||||
});
|
||||
expect(keepEditing.parentElement).toHaveClass(
|
||||
"absolute",
|
||||
"left-[16px]",
|
||||
"top-[12px]",
|
||||
);
|
||||
await declineDiscardCustomizeEdits();
|
||||
|
||||
expect(screen.getByRole("dialog")).toBeInTheDocument();
|
||||
|
||||
@@ -9,6 +9,10 @@ describe("CoreValuesSelectScreen", () => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
async function discardPendingValue() {
|
||||
fireEvent.click(await screen.findByRole("button", { name: "Discard" }));
|
||||
}
|
||||
|
||||
it("opens core value detail modal when a preset chip is clicked", async () => {
|
||||
renderWithProviders(<CoreValuesSelectScreen />);
|
||||
fireEvent.click(screen.getByText("Accessibility"));
|
||||
@@ -16,18 +20,41 @@ describe("CoreValuesSelectScreen", () => {
|
||||
expect(
|
||||
within(dialog).getByRole("button", { name: "Add Value" }),
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.queryByRole("menuitem", { name: "Customize" }),
|
||||
).not.toBeInTheDocument();
|
||||
fireEvent.click(within(dialog).getByRole("button", { name: "More options" }));
|
||||
expect(
|
||||
screen.queryByRole("menuitem", { name: "Customize" }),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("closes modal and reverts pending selection when Escape is pressed", async () => {
|
||||
it("asks to discard when closing a pending value, then unselects on Discard", async () => {
|
||||
renderWithProviders(<CoreValuesSelectScreen />);
|
||||
fireEvent.click(screen.getByText("Accessibility"));
|
||||
await screen.findByRole("dialog");
|
||||
fireEvent.keyDown(document, { key: "Escape" });
|
||||
expect(
|
||||
await screen.findByRole("button", { name: "Keep editing" }),
|
||||
).toBeInTheDocument();
|
||||
await discardPendingValue();
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps the pending value modal open when Keep editing is chosen", async () => {
|
||||
renderWithProviders(<CoreValuesSelectScreen />);
|
||||
fireEvent.click(screen.getByText("Accessibility"));
|
||||
await screen.findByRole("dialog");
|
||||
fireEvent.keyDown(document, { key: "Escape" });
|
||||
fireEvent.click(await screen.findByRole("button", { name: "Keep editing" }));
|
||||
const dialog = await screen.findByRole("dialog");
|
||||
expect(
|
||||
within(dialog).getByRole("button", { name: "Add Value" }),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("saves details when Add Value is clicked", async () => {
|
||||
renderWithProviders(<CoreValuesSelectScreen />);
|
||||
fireEvent.click(screen.getByText("Accessibility"));
|
||||
@@ -40,6 +67,17 @@ describe("CoreValuesSelectScreen", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("opens meaning and signals fields editable without Customize", async () => {
|
||||
renderWithProviders(<CoreValuesSelectScreen />);
|
||||
fireEvent.click(screen.getByText("Accessibility"));
|
||||
const dialog = await screen.findByRole("dialog");
|
||||
const fields = within(dialog).getAllByRole("textbox");
|
||||
expect(fields.length).toBeGreaterThanOrEqual(2);
|
||||
for (const field of fields) {
|
||||
expect(field).toBeEnabled();
|
||||
}
|
||||
});
|
||||
|
||||
// The "Add value" → custom-chip → modal flow uses a `customPending`
|
||||
// session: dismissing the modal must drop the brand-new chip entirely
|
||||
// (not just unselect it), because the user never confirmed it via
|
||||
@@ -80,6 +118,7 @@ describe("CoreValuesSelectScreen", () => {
|
||||
expect(countCustomChips(CUSTOM_LABEL)).toBe(1);
|
||||
|
||||
fireEvent.keyDown(document, { key: "Escape" });
|
||||
fireEvent.click(await screen.findByRole("button", { name: "Discard" }));
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user