Skip to content

Commit 1c71c45

Browse files
lkostrowskiclaude
andcommitted
Add unit tests and stories, fix parseEventData to throw on invalid action
Address review comments: - parseEventData: throw on unrecognized event action instead of silently defaulting to update (refactored to early returns) - Add unit tests for parseEventData, getDataKey, getMetadataTitle - Add unit tests for translations/utils (getParsedTranslationInputData, getTranslationFields, mapAttributeValuesToTranslationFields, getAttributeValueTranslationsInputData) - Add unit tests for getGiftCardTagsAddRemoveData (exported for testing) - Add Storybook stories for GiftCardTagInput component Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent d7cdad9 commit 1c71c45

7 files changed

Lines changed: 510 additions & 1112 deletions

File tree

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import { type ChangeEvent } from "@dashboard/hooks/useForm";
2+
3+
import { EventDataAction, EventDataField } from "./types";
4+
import { getDataKey, getMetadataTitle, parseEventData } from "./utils";
5+
6+
describe("parseEventData", () => {
7+
it("parses name field update event", () => {
8+
// Arrange
9+
const event: ChangeEvent<string> = {
10+
target: { name: "name:2", value: "my-key" },
11+
};
12+
13+
// Act
14+
const result = parseEventData(event);
15+
16+
// Assert
17+
expect(result).toEqual({
18+
action: EventDataAction.update,
19+
field: EventDataField.name,
20+
fieldIndex: 2,
21+
value: "my-key",
22+
});
23+
});
24+
25+
it("parses value field update event", () => {
26+
// Arrange
27+
const event: ChangeEvent<string> = {
28+
target: { name: "value:5", value: "my-value" },
29+
};
30+
31+
// Act
32+
const result = parseEventData(event);
33+
34+
// Assert
35+
expect(result).toEqual({
36+
action: EventDataAction.update,
37+
field: EventDataField.value,
38+
fieldIndex: 5,
39+
value: "my-value",
40+
});
41+
});
42+
43+
it("parses add action event", () => {
44+
// Arrange
45+
const event: ChangeEvent<string> = {
46+
target: { name: "add", value: "" },
47+
};
48+
49+
// Act
50+
const result = parseEventData(event);
51+
52+
// Assert
53+
expect(result).toEqual({
54+
action: EventDataAction.add,
55+
field: null,
56+
fieldIndex: null,
57+
value: "",
58+
});
59+
});
60+
61+
it("parses delete action event", () => {
62+
// Arrange
63+
const event: ChangeEvent<string> = {
64+
target: { name: "delete", value: "3" },
65+
};
66+
67+
// Act
68+
const result = parseEventData(event);
69+
70+
// Assert
71+
expect(result).toEqual({
72+
action: EventDataAction.delete,
73+
field: null,
74+
fieldIndex: 3,
75+
value: "",
76+
});
77+
});
78+
79+
it("throws on invalid event action", () => {
80+
// Arrange
81+
const event: ChangeEvent<string> = {
82+
target: { name: "unknown-action", value: "" },
83+
};
84+
85+
// Act & Assert
86+
expect(() => parseEventData(event)).toThrow('Invalid metadata event action: "unknown-action"');
87+
});
88+
});
89+
90+
describe("getDataKey", () => {
91+
it("returns 'privateMetadata' when isPrivate is true", () => {
92+
expect(getDataKey(true)).toBe("privateMetadata");
93+
});
94+
95+
it("returns 'metadata' when isPrivate is false", () => {
96+
expect(getDataKey(false)).toBe("metadata");
97+
});
98+
});
99+
100+
describe("getMetadataTitle", () => {
101+
it("returns private metadata message descriptor when isPrivate is true", () => {
102+
expect(getMetadataTitle(true)).toEqual({
103+
id: "ETHnjq",
104+
defaultMessage: "Private Metadata",
105+
description: "header",
106+
});
107+
});
108+
109+
it("returns metadata message descriptor when isPrivate is false", () => {
110+
expect(getMetadataTitle(false)).toEqual({
111+
id: "VcI+Zh",
112+
defaultMessage: "Metadata",
113+
description: "header",
114+
});
115+
});
116+
});

src/components/Metadata/utils.ts

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,40 +7,43 @@ export const nameInputPrefix = EventDataField.name;
77
export const valueInputPrefix = EventDataField.value;
88

99
export function parseEventData(event: ChangeEvent<string>): EventData {
10-
let action: EventDataAction = EventDataAction.update;
11-
let field: EventDataField | null = null;
12-
let fieldIndex: number | null = null;
13-
let value: string = "";
14-
1510
if (event.target.name.includes(EventDataField.name)) {
16-
action = EventDataAction.update;
17-
field = EventDataField.name;
18-
fieldIndex = parseInt(event.target.name.split(nameSeparator)[1], 10);
19-
value = event.target.value;
11+
return {
12+
action: EventDataAction.update,
13+
field: EventDataField.name,
14+
fieldIndex: parseInt(event.target.name.split(nameSeparator)[1], 10),
15+
value: event.target.value,
16+
};
2017
}
2118

2219
if (event.target.name.includes(EventDataField.value)) {
23-
action = EventDataAction.update;
24-
field = EventDataField.value;
25-
fieldIndex = parseInt(event.target.name.split(nameSeparator)[1], 10);
26-
value = event.target.value;
20+
return {
21+
action: EventDataAction.update,
22+
field: EventDataField.value,
23+
fieldIndex: parseInt(event.target.name.split(nameSeparator)[1], 10),
24+
value: event.target.value,
25+
};
2726
}
2827

2928
if (event.target.name === EventDataAction.add) {
30-
action = EventDataAction.add;
29+
return {
30+
action: EventDataAction.add,
31+
field: null,
32+
fieldIndex: null,
33+
value: "",
34+
};
3135
}
3236

3337
if (event.target.name === EventDataAction.delete) {
34-
action = EventDataAction.delete;
35-
fieldIndex = parseInt(event.target.value, 10);
38+
return {
39+
action: EventDataAction.delete,
40+
field: null,
41+
fieldIndex: parseInt(event.target.value, 10),
42+
value: "",
43+
};
3644
}
3745

38-
return {
39-
action,
40-
field,
41-
fieldIndex,
42-
value,
43-
};
46+
throw new Error(`Invalid metadata event action: "${event.target.name}"`);
4447
}
4548

4649
export function getDataKey(isPrivate: boolean) {
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { getGiftCardTagsAddRemoveData } from "./GiftCardUpdateFormProvider";
2+
3+
describe("getGiftCardTagsAddRemoveData", () => {
4+
it("returns added and removed tags", () => {
5+
// Arrange
6+
const initTags = ["vip", "holiday"];
7+
const changedTags = ["holiday", "promo"];
8+
9+
// Act
10+
const result = getGiftCardTagsAddRemoveData(initTags, changedTags);
11+
12+
// Assert
13+
expect(result).toEqual({
14+
addTags: ["promo"],
15+
removeTags: ["vip"],
16+
});
17+
});
18+
19+
it("returns empty arrays when no changes", () => {
20+
// Arrange
21+
const tags = ["vip", "holiday"];
22+
23+
// Act
24+
const result = getGiftCardTagsAddRemoveData(tags, tags);
25+
26+
// Assert
27+
expect(result).toEqual({
28+
addTags: [],
29+
removeTags: [],
30+
});
31+
});
32+
33+
it("returns all as added when init is empty", () => {
34+
// Arrange & Act
35+
const result = getGiftCardTagsAddRemoveData([], ["vip", "promo"]);
36+
37+
// Assert
38+
expect(result).toEqual({
39+
addTags: ["vip", "promo"],
40+
removeTags: [],
41+
});
42+
});
43+
44+
it("returns all as removed when changed is empty", () => {
45+
// Arrange & Act
46+
const result = getGiftCardTagsAddRemoveData(["vip", "promo"], []);
47+
48+
// Assert
49+
expect(result).toEqual({
50+
addTags: [],
51+
removeTags: ["vip", "promo"],
52+
});
53+
});
54+
});

src/giftCards/GiftCardUpdate/providers/GiftCardUpdateFormProvider/GiftCardUpdateFormProvider.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ export const GiftCardUpdateFormContext = createContext<GiftCardUpdateFormConsume
5454
null,
5555
);
5656

57-
const getGiftCardTagsAddRemoveData = (initTags: string[], changedTags: string[]) => {
57+
export const getGiftCardTagsAddRemoveData = (initTags: string[], changedTags: string[]) => {
5858
const removed = difference(initTags, changedTags);
5959
const added = difference(changedTags, initTags);
6060

@@ -63,6 +63,7 @@ const getGiftCardTagsAddRemoveData = (initTags: string[], changedTags: string[])
6363
removeTags: removed,
6464
};
6565
};
66+
6667
const GiftCardUpdateFormProvider = ({ children }: GiftCardUpdateFormProviderProps) => {
6768
const notify = useNotifier();
6869
const intl = useIntl();
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import type { Meta, StoryObj } from "@storybook/react-vite";
2+
import { fn } from "storybook/test";
3+
4+
import GiftCardTagInput from "./GiftCardTagInput";
5+
6+
const meta: Meta<typeof GiftCardTagInput> = {
7+
title: "GiftCards/GiftCardTagInput",
8+
component: GiftCardTagInput,
9+
args: {
10+
name: "tags",
11+
onChange: fn(),
12+
values: [],
13+
},
14+
};
15+
16+
export default meta;
17+
type Story = StoryObj<typeof GiftCardTagInput>;
18+
19+
export const Default: Story = {};
20+
21+
export const WithValues: Story = {
22+
args: {
23+
values: [
24+
{ label: "VIP", value: "vip" },
25+
{ label: "Holiday", value: "holiday" },
26+
],
27+
},
28+
};
29+
30+
export const WithError: Story = {
31+
args: {
32+
error: { code: "INVALID" as never, field: "tags" },
33+
},
34+
};
35+
36+
export const Required: Story = {
37+
args: {
38+
optional: false,
39+
},
40+
};
41+
42+
export const Loading: Story = {
43+
args: {
44+
loading: true,
45+
},
46+
};

0 commit comments

Comments
 (0)