Round-3 review follow-ups on #2038. `buildHeaderSettingsPatch` treated the mere presence of stored headers as a change, so saving an id or URL edit re-sent the modal's settings snapshot — taken when it opened — and could overwrite a metadata or OAuth change made in the settings form since. It now compares the submitted headers against the stored ones and omits `settings` when they match, which is what makes the PUT route preserve the node. Order counts as a difference: it is what the form round-trips and what the user sees. Row labels carried the key alone, so two rows sharing a key — mid-edit, or a duplicate a server persisted — gave both rows' controls identical accessible names, the thing the labelling exists to prevent. The row number is now always part of the label rather than a blank-key fallback. Signed-off-by: cliffhall <cliff@futurescale.com>
60 lines
1.8 KiB
TypeScript
60 lines
1.8 KiB
TypeScript
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
import { expect, fn, userEvent, within } from "storybook/test";
|
|
import { KeyValueRows } from "./KeyValueRows";
|
|
|
|
const meta: Meta<typeof KeyValueRows> = {
|
|
title: "Elements/KeyValueRows",
|
|
component: KeyValueRows,
|
|
args: {
|
|
entityLabel: "header",
|
|
onChange: fn(),
|
|
onRemove: fn(),
|
|
},
|
|
};
|
|
|
|
export default meta;
|
|
type Story = StoryObj<typeof KeyValueRows>;
|
|
|
|
// Each row's controls carry a row-scoped accessible name, so a screen reader
|
|
// can tell one row's key box from another's.
|
|
export const Populated: Story = {
|
|
args: {
|
|
items: [
|
|
{ key: "Cookie", value: "branch=feature-x" },
|
|
{ key: "X-Env", value: "dev" },
|
|
],
|
|
},
|
|
play: async ({ args, canvasElement }) => {
|
|
const canvas = within(canvasElement);
|
|
const value = await canvas.findByRole("textbox", {
|
|
name: "header value, Cookie, row 1",
|
|
});
|
|
await expect(value).toHaveValue("branch=feature-x");
|
|
|
|
await userEvent.click(
|
|
canvas.getByRole("button", { name: "Remove header, X-Env, row 2" }),
|
|
);
|
|
await expect(args.onRemove).toHaveBeenCalledWith(1);
|
|
},
|
|
};
|
|
|
|
// A row whose key is still blank is named by position alone rather than
|
|
// announcing nothing.
|
|
export const BlankKeyRow: Story = {
|
|
args: { items: [{ key: "", value: "" }] },
|
|
play: async ({ canvasElement }) => {
|
|
const canvas = within(canvasElement);
|
|
await expect(
|
|
await canvas.findByRole("textbox", { name: "header name, row 1" }),
|
|
).toBeInTheDocument();
|
|
},
|
|
};
|
|
|
|
// An empty list renders nothing — callers draw their own empty-state hint.
|
|
export const Empty: Story = {
|
|
args: { items: [] },
|
|
play: async ({ canvasElement }) => {
|
|
const canvas = within(canvasElement);
|
|
await expect(canvas.queryAllByRole("textbox")).toHaveLength(0);
|
|
},
|
|
};
|