Fix magic-link verify URLs in email and related create-flow QA #70
@@ -15,7 +15,7 @@ import type { CoreValueDetailEntry } from "../../types";
|
|||||||
export interface CoreValueEditFieldsProps {
|
export interface CoreValueEditFieldsProps {
|
||||||
value: CoreValueDetailEntry;
|
value: CoreValueDetailEntry;
|
||||||
onChange: (_next: CoreValueDetailEntry) => void;
|
onChange: (_next: CoreValueDetailEntry) => void;
|
||||||
/** Disable meaning/signals. Create-flow core-values omits this; final-review locks until Customize. */
|
/** Disable meaning/signals. Facet and final-review callers omit this so fields stay editable on open. */
|
||||||
readOnly?: boolean;
|
readOnly?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,8 +10,8 @@
|
|||||||
*
|
*
|
||||||
* Card click opens the Figma create modal (node `20246-15829`) with three
|
* Card click opens the Figma create modal (node `20246-15829`) with three
|
||||||
* editable sections rendered by {@link CommunicationMethodEditFields}. The primary
|
* editable sections rendered by {@link CommunicationMethodEditFields}. The primary
|
||||||
* action is **Add Platform** for an unselected card; a selected card in view mode has
|
* action is **Add Platform** for an unselected card and **Save** for a selected
|
||||||
* no footer primary — **Remove** is available from the kebab (same behavior as legacy
|
* card. **Remove** is available from the kebab (same behavior as legacy
|
||||||
* footer remove via {@link removeMethodCardFromFacetSelection}).
|
* footer remove via {@link removeMethodCardFromFacetSelection}).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -216,7 +216,7 @@ export function CommunicationMethodsScreen() {
|
|||||||
methodId: pendingCardId,
|
methodId: pendingCardId,
|
||||||
meta: state.customMethodCardMetaById,
|
meta: state.customMethodCardMetaById,
|
||||||
fieldBlocksById: state.customMethodCardFieldBlocksById,
|
fieldBlocksById: state.customMethodCardFieldBlocksById,
|
||||||
modalEditUnlocked: false,
|
modalEditUnlocked: true,
|
||||||
draftFieldBlocks,
|
draftFieldBlocks,
|
||||||
customFacetDetailsMatchPreset,
|
customFacetDetailsMatchPreset,
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -217,7 +217,7 @@ export function ConflictManagementScreen() {
|
|||||||
methodId: pendingCardId,
|
methodId: pendingCardId,
|
||||||
meta: state.customMethodCardMetaById,
|
meta: state.customMethodCardMetaById,
|
||||||
fieldBlocksById: state.customMethodCardFieldBlocksById,
|
fieldBlocksById: state.customMethodCardFieldBlocksById,
|
||||||
modalEditUnlocked: false,
|
modalEditUnlocked: true,
|
||||||
draftFieldBlocks,
|
draftFieldBlocks,
|
||||||
customFacetDetailsMatchPreset,
|
customFacetDetailsMatchPreset,
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -214,7 +214,7 @@ export function MembershipMethodsScreen() {
|
|||||||
methodId: pendingCardId,
|
methodId: pendingCardId,
|
||||||
meta: state.customMethodCardMetaById,
|
meta: state.customMethodCardMetaById,
|
||||||
fieldBlocksById: state.customMethodCardFieldBlocksById,
|
fieldBlocksById: state.customMethodCardFieldBlocksById,
|
||||||
modalEditUnlocked: false,
|
modalEditUnlocked: true,
|
||||||
draftFieldBlocks,
|
draftFieldBlocks,
|
||||||
customFacetDetailsMatchPreset,
|
customFacetDetailsMatchPreset,
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -250,7 +250,7 @@ export function DecisionApproachesScreen() {
|
|||||||
methodId: pendingCardId,
|
methodId: pendingCardId,
|
||||||
meta: state.customMethodCardMetaById,
|
meta: state.customMethodCardMetaById,
|
||||||
fieldBlocksById: state.customMethodCardFieldBlocksById,
|
fieldBlocksById: state.customMethodCardFieldBlocksById,
|
||||||
modalEditUnlocked: false,
|
modalEditUnlocked: true,
|
||||||
draftFieldBlocks,
|
draftFieldBlocks,
|
||||||
customFacetDetailsMatchPreset,
|
customFacetDetailsMatchPreset,
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -80,7 +80,8 @@ const TextAreaContainer = forwardRef<HTMLTextAreaElement, TextAreaProps>(
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// State styles (embedded: Figma 20736-12668 – borderless, darker grey block, white text)
|
// Embedded (Figma 20736-12668): borderless grey block; default copy is
|
||||||
|
// tertiary, primary on focus so seeded modal fields stay muted until edit.
|
||||||
const getStateStyles = (): {
|
const getStateStyles = (): {
|
||||||
textarea: string;
|
textarea: string;
|
||||||
label: string;
|
label: string;
|
||||||
@@ -89,13 +90,13 @@ const TextAreaContainer = forwardRef<HTMLTextAreaElement, TextAreaProps>(
|
|||||||
if (disabled) {
|
if (disabled) {
|
||||||
return {
|
return {
|
||||||
textarea:
|
textarea:
|
||||||
"border-0 bg-[var(--color-surface-default-secondary)] text-[var(--color-content-default-primary)] cursor-not-allowed opacity-60",
|
"border-0 bg-[var(--color-surface-default-secondary)] text-[var(--color-content-default-tertiary,#b4b4b4)] cursor-not-allowed opacity-60",
|
||||||
label: "text-[var(--color-content-default-secondary)]",
|
label: "text-[var(--color-content-default-secondary)]",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
textarea:
|
textarea:
|
||||||
"border-0 bg-[var(--color-surface-default-secondary)] text-[var(--color-content-default-primary)] focus:outline-none focus:ring-2 focus:ring-[var(--color-border-default-tertiary)] focus:ring-inset",
|
"border-0 bg-[var(--color-surface-default-secondary)] text-[var(--color-content-default-tertiary,#b4b4b4)] placeholder:text-[var(--color-content-default-tertiary,#b4b4b4)] focus:text-[var(--color-content-default-primary)] focus:outline-none focus:ring-2 focus:ring-[var(--color-border-default-tertiary)] focus:ring-inset",
|
||||||
label: "text-[var(--color-content-default-secondary)]",
|
label: "text-[var(--color-content-default-secondary)]",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,8 @@ export interface TextAreaProps extends Omit<
|
|||||||
showHelpIcon?: boolean;
|
showHelpIcon?: boolean;
|
||||||
/**
|
/**
|
||||||
* Visual appearance. "embedded" matches Create modal sections (Figma 20736-12668):
|
* Visual appearance. "embedded" matches Create modal sections (Figma 20736-12668):
|
||||||
* borderless, darker grey background, white text. "default" is standard bordered input.
|
* borderless, darker grey background, tertiary text in default (primary on
|
||||||
|
* focus). "default" is the standard bordered input.
|
||||||
* @default "default"
|
* @default "default"
|
||||||
*/
|
*/
|
||||||
appearance?: TextAreaAppearanceValue;
|
appearance?: TextAreaAppearanceValue;
|
||||||
|
|||||||
@@ -29,6 +29,9 @@ import { isCustomMethodCardId } from "./isCustomMethodCardId";
|
|||||||
* so meta-only wizard cards show policy copy instead of empty section editors.
|
* so meta-only wizard cards show policy copy instead of empty section editors.
|
||||||
* Pass `customFacetDetailsMatchPreset: false` when the caller knows facet details
|
* Pass `customFacetDetailsMatchPreset: false` when the caller knows facet details
|
||||||
* were edited or cloned from a filled preset.
|
* were edited or cloned from a filled preset.
|
||||||
|
*
|
||||||
|
* Create-flow facet screens and final-review pass `modalEditUnlocked: true` so
|
||||||
|
* section fields stay editable on open (Customize is the kebab wizard).
|
||||||
*/
|
*/
|
||||||
export function usesWizardFieldBlocksModalBody(args: {
|
export function usesWizardFieldBlocksModalBody(args: {
|
||||||
methodId: string;
|
methodId: string;
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import { describe, it, expect, afterEach } from "vitest";
|
||||||
|
import {
|
||||||
|
renderWithProviders as render,
|
||||||
|
screen,
|
||||||
|
cleanup,
|
||||||
|
within,
|
||||||
|
} from "../utils/test-utils";
|
||||||
|
import { fireEvent } from "@testing-library/react";
|
||||||
|
import "@testing-library/jest-dom/vitest";
|
||||||
|
import { ConflictManagementScreen } from "../../app/(app)/create/screens/card/ConflictManagementScreen";
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
cleanup();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("ConflictManagementScreen", () => {
|
||||||
|
it("opens section fields editable without Customize", async () => {
|
||||||
|
render(<ConflictManagementScreen />);
|
||||||
|
fireEvent.click(
|
||||||
|
screen.getAllByRole("button", {
|
||||||
|
name: /Peer Mediation: Trained members/,
|
||||||
|
})[0],
|
||||||
|
);
|
||||||
|
const dialog = await screen.findByRole("dialog");
|
||||||
|
const fields = within(dialog).getAllByRole("textbox");
|
||||||
|
expect(fields.length).toBeGreaterThan(0);
|
||||||
|
for (const field of fields) {
|
||||||
|
expect(field).toBeEnabled();
|
||||||
|
}
|
||||||
|
fireEvent.click(
|
||||||
|
within(dialog).getByRole("button", { name: "More options" }),
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
screen.getByRole("menuitem", { name: "Customize" }),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import { describe, it, expect, afterEach } from "vitest";
|
||||||
|
import {
|
||||||
|
renderWithProviders as render,
|
||||||
|
screen,
|
||||||
|
cleanup,
|
||||||
|
within,
|
||||||
|
} from "../utils/test-utils";
|
||||||
|
import { fireEvent } from "@testing-library/react";
|
||||||
|
import "@testing-library/jest-dom/vitest";
|
||||||
|
import { MembershipMethodsScreen } from "../../app/(app)/create/screens/card/MembershipMethodsScreen";
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
cleanup();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("MembershipMethodsScreen", () => {
|
||||||
|
it("opens section fields editable without Customize", async () => {
|
||||||
|
render(<MembershipMethodsScreen />);
|
||||||
|
fireEvent.click(
|
||||||
|
screen.getAllByRole("button", {
|
||||||
|
name: /Open Access: Maximum inclusion/,
|
||||||
|
})[0],
|
||||||
|
);
|
||||||
|
const dialog = await screen.findByRole("dialog");
|
||||||
|
const fields = within(dialog).getAllByRole("textbox");
|
||||||
|
expect(fields.length).toBeGreaterThan(0);
|
||||||
|
for (const field of fields) {
|
||||||
|
expect(field).toBeEnabled();
|
||||||
|
}
|
||||||
|
fireEvent.click(
|
||||||
|
within(dialog).getByRole("button", { name: "More options" }),
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
screen.getByRole("menuitem", { name: "Customize" }),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -42,4 +42,17 @@ describe("TextArea appearance", () => {
|
|||||||
expect(textarea).toBeInTheDocument();
|
expect(textarea).toBeInTheDocument();
|
||||||
expect(textarea).toHaveClass("border-0");
|
expect(textarea).toHaveClass("border-0");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("uses tertiary text in the embedded default state and primary on focus", () => {
|
||||||
|
renderWithProviders(
|
||||||
|
<TextArea label="Notes" value="Some text" appearance="embedded" />,
|
||||||
|
);
|
||||||
|
const textarea = screen.getByRole("textbox", { name: /notes/i });
|
||||||
|
expect(textarea).toHaveClass(
|
||||||
|
"text-[var(--color-content-default-tertiary,#b4b4b4)]",
|
||||||
|
);
|
||||||
|
expect(textarea).toHaveClass(
|
||||||
|
"focus:text-[var(--color-content-default-primary)]",
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -126,6 +126,33 @@ describe("Create flow communication-methods page", () => {
|
|||||||
expect(screen.getByRole("menuitem", { name: "Customize" })).toBeInTheDocument();
|
expect(screen.getByRole("menuitem", { name: "Customize" })).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("meta-only custom policy opens with editable section fields", async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
render(
|
||||||
|
<CommunicationMethodsScreenWithState
|
||||||
|
initial={{
|
||||||
|
selectedCommunicationMethodIds: [CUSTOM_POLICY_ID],
|
||||||
|
customMethodCardMetaById: {
|
||||||
|
[CUSTOM_POLICY_ID]: { label: "My policy", supportText: "Desc" },
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
await user.click(
|
||||||
|
screen.getAllByRole("button", { name: /My policy: Desc/ })[0],
|
||||||
|
);
|
||||||
|
const dialog = await screen.findByRole("dialog");
|
||||||
|
const textboxes = within(dialog).getAllByRole("textbox");
|
||||||
|
expect(textboxes.length).toBeGreaterThan(0);
|
||||||
|
for (const field of textboxes) {
|
||||||
|
expect(field).not.toBeDisabled();
|
||||||
|
}
|
||||||
|
expect(
|
||||||
|
within(dialog).queryByText("No custom fields yet."),
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
test("renders without error", () => {
|
test("renders without error", () => {
|
||||||
render(<CommunicationMethodsScreen />);
|
render(<CommunicationMethodsScreen />);
|
||||||
|
|
||||||
|
|||||||
@@ -201,6 +201,29 @@ describe("Create flow decision-approaches page", () => {
|
|||||||
).toHaveTextContent("SELECTED");
|
).toHaveTextContent("SELECTED");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("opens approach fields editable without Customize", async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
render(<DecisionApproachesScreen />);
|
||||||
|
|
||||||
|
await user.click(
|
||||||
|
screen.getByRole("button", {
|
||||||
|
name: /Lazy Consensus: A decision is assumed approved/,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
const dialog = await screen.findByRole("dialog");
|
||||||
|
const textboxes = within(dialog).getAllByRole("textbox");
|
||||||
|
expect(textboxes.length).toBeGreaterThan(0);
|
||||||
|
for (const field of textboxes) {
|
||||||
|
expect(field).not.toBeDisabled();
|
||||||
|
}
|
||||||
|
await user.click(
|
||||||
|
within(dialog).getByRole("button", { name: "More options" }),
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
screen.getByRole("menuitem", { name: "Customize" }),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
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 />);
|
||||||
|
|||||||
Reference in New Issue
Block a user