@pautena/react-design-system 0.7.2 → 0.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/index.js +4 -4
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/types/components/alerts/expandable-alert/expandable-alert.d.ts +4 -1
- package/dist/cjs/types/components/data-display/board/board.d.ts +1 -2
- package/dist/cjs/types/components/feedback/query-container/query-container.d.ts +7 -3
- package/dist/cjs/types/components/inputs/index.d.ts +1 -0
- package/dist/cjs/types/components/value-displays/group-value-card/group-value-card.mock.d.ts +3 -1
- package/dist/cjs/types/components/value-displays/value-base/value-edit.d.ts +7 -2
- package/dist/cjs/types/components/value-displays/value-content/value-content.d.ts +10 -1
- package/dist/esm/index.js +4 -4
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/types/components/alerts/expandable-alert/expandable-alert.d.ts +4 -1
- package/dist/esm/types/components/data-display/board/board.d.ts +1 -2
- package/dist/esm/types/components/feedback/query-container/query-container.d.ts +7 -3
- package/dist/esm/types/components/inputs/index.d.ts +1 -0
- package/dist/esm/types/components/value-displays/group-value-card/group-value-card.mock.d.ts +3 -1
- package/dist/esm/types/components/value-displays/value-base/value-edit.d.ts +7 -2
- package/dist/esm/types/components/value-displays/value-content/value-content.d.ts +10 -1
- package/dist/index.d.ts +26 -9
- package/package.json +2 -1
- package/src/components/alerts/expandable-alert/expandable-alert.stories.tsx +23 -1
- package/src/components/alerts/expandable-alert/expandable-alert.tsx +11 -4
- package/src/components/data-display/board/board.test.tsx +60 -43
- package/src/components/data-display/board/board.tsx +13 -16
- package/src/components/feedback/query-container/query-container.stories.tsx +19 -6
- package/src/components/feedback/query-container/query-container.test.tsx +41 -17
- package/src/components/feedback/query-container/query-container.tsx +17 -5
- package/src/components/inputs/index.ts +1 -0
- package/src/components/value-displays/group-value-card/group-value-card.mock.tsx +28 -8
- package/src/components/value-displays/group-value-card/group-value-card.stories.tsx +19 -2
- package/src/components/value-displays/group-value-card/group-value-card.test.tsx +16 -18
- package/src/components/value-displays/value-base/value-edit.test.tsx +88 -0
- package/src/components/value-displays/value-base/value-edit.tsx +28 -6
- package/src/components/value-displays/value-boolean/value-boolean.stories.tsx +9 -0
- package/src/components/value-displays/value-boolean/value-boolean.test.tsx +29 -15
- package/src/components/value-displays/value-boolean/value-boolean.tsx +18 -11
- package/src/components/value-displays/value-content/value-content.test.tsx +20 -6
- package/src/components/value-displays/value-content/value-content.tsx +24 -10
- package/src/components/value-displays/value-datetime/value-datetime.stories.tsx +11 -0
- package/src/components/value-displays/value-datetime/value-datetime.test.tsx +9 -9
- package/src/components/value-displays/value-datetime/value-datetime.tsx +14 -10
- package/src/components/value-displays/value-rating/value-rating.stories.tsx +10 -0
- package/src/components/value-displays/value-rating/value-rating.test.tsx +11 -11
- package/src/components/value-displays/value-rating/value-rating.tsx +10 -8
- package/src/components/value-displays/value-text/value-text.stories.tsx +9 -0
- package/src/components/value-displays/value-text/value-text.test.tsx +20 -9
- package/src/components/value-displays/value-text/value-text.tsx +23 -10
- package/src/generators/model-form/model-form.test.tsx +1 -1
- package/src/generators/model-router/model-router.test.tsx +3 -3
- package/src/layouts/header-layout/header-layout.stories.tsx +2 -2
- package/src/layouts/header-layout/header-layout.tsx +1 -7
|
@@ -6,47 +6,71 @@ import { expectProgressIndicator } from "~/tests/assertions";
|
|
|
6
6
|
|
|
7
7
|
describe("QueryContainer", () => {
|
|
8
8
|
const renderComponent = ({
|
|
9
|
-
|
|
9
|
+
fetching = false,
|
|
10
|
+
loading = false,
|
|
10
11
|
error = undefined,
|
|
11
12
|
success = undefined,
|
|
12
13
|
}: {
|
|
13
|
-
|
|
14
|
+
fetching?: boolean;
|
|
15
|
+
loading?: boolean;
|
|
14
16
|
error?: QueryContainerError;
|
|
15
17
|
success?: QueryContainerSuccess;
|
|
16
18
|
} = {}) => {
|
|
17
19
|
return render(
|
|
18
|
-
<QueryContainer
|
|
20
|
+
<QueryContainer fetching={fetching} loading={loading} error={error} success={success}>
|
|
19
21
|
<Typography>children content</Typography>
|
|
20
22
|
</QueryContainer>,
|
|
21
23
|
);
|
|
22
24
|
};
|
|
23
25
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
describe("fetching", () => {
|
|
27
|
+
it("should render a loading indicator", () => {
|
|
28
|
+
renderComponent({ fetching: true });
|
|
26
29
|
|
|
27
|
-
|
|
30
|
+
expectProgressIndicator();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("should render the children", () => {
|
|
34
|
+
renderComponent({ fetching: true });
|
|
35
|
+
|
|
36
|
+
expect(screen.getByText(/children content/i)).toBeVisible();
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe("loading", () => {
|
|
41
|
+
it("should render a loading indicator", () => {
|
|
42
|
+
renderComponent({ loading: true });
|
|
43
|
+
|
|
44
|
+
expectProgressIndicator();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("shouldn't render the children", () => {
|
|
48
|
+
renderComponent({ loading: true });
|
|
49
|
+
|
|
50
|
+
expect(screen.queryByText(/children content/i)).not.toBeInTheDocument();
|
|
51
|
+
});
|
|
28
52
|
});
|
|
29
53
|
|
|
30
54
|
describe("error", () => {
|
|
31
|
-
it("
|
|
55
|
+
it("should render an error alert", () => {
|
|
32
56
|
renderComponent({ error: { message: "Internal error" } });
|
|
33
57
|
|
|
34
58
|
expect(screen.getByRole("alert")).toHaveAttribute("aria-describedby", "error");
|
|
35
59
|
});
|
|
36
60
|
|
|
37
|
-
it("
|
|
61
|
+
it("should render the error message if error is defined", () => {
|
|
38
62
|
renderComponent({ error: { message: "Internal error" } });
|
|
39
63
|
|
|
40
64
|
expect(screen.getByText(/internal error/i)).toBeInTheDocument();
|
|
41
65
|
});
|
|
42
66
|
|
|
43
|
-
it("
|
|
67
|
+
it("should render the error title if error.name is defined", () => {
|
|
44
68
|
renderComponent({ error: { name: "Internal error", message: "There was an error" } });
|
|
45
69
|
|
|
46
70
|
expect(screen.getByRole("heading", { name: /internal error/i })).toBeInTheDocument();
|
|
47
71
|
});
|
|
48
72
|
|
|
49
|
-
it("
|
|
73
|
+
it("shouldn't render the error title if error is defined but the error.name not", () => {
|
|
50
74
|
renderComponent({ error: { message: "Internal error" } });
|
|
51
75
|
|
|
52
76
|
expect(screen.queryByRole("heading")).not.toBeInTheDocument();
|
|
@@ -54,19 +78,19 @@ describe("QueryContainer", () => {
|
|
|
54
78
|
});
|
|
55
79
|
|
|
56
80
|
describe("success", () => {
|
|
57
|
-
it("
|
|
81
|
+
it("should render a success alert", () => {
|
|
58
82
|
renderComponent({ success: { message: "Item added" } });
|
|
59
83
|
|
|
60
84
|
expect(screen.getByRole("alert")).toHaveAttribute("aria-describedby", "success");
|
|
61
85
|
});
|
|
62
86
|
|
|
63
|
-
it("
|
|
87
|
+
it("should render the success message if success is defined", () => {
|
|
64
88
|
renderComponent({ success: { message: "Item added" } });
|
|
65
89
|
|
|
66
90
|
expect(screen.getByText(/item added/i)).toBeInTheDocument();
|
|
67
91
|
});
|
|
68
92
|
|
|
69
|
-
it("
|
|
93
|
+
it("should render the success title if success.name is defined", () => {
|
|
70
94
|
renderComponent({
|
|
71
95
|
success: { name: "Item added", message: "The item has been added successfully" },
|
|
72
96
|
});
|
|
@@ -74,22 +98,22 @@ describe("QueryContainer", () => {
|
|
|
74
98
|
expect(screen.getByRole("heading", { name: /item added/i })).toBeInTheDocument();
|
|
75
99
|
});
|
|
76
100
|
|
|
77
|
-
it("
|
|
101
|
+
it("shouldn't render the error title if success is defined but the success.name not", () => {
|
|
78
102
|
renderComponent({ success: { message: "Item added" } });
|
|
79
103
|
|
|
80
104
|
expect(screen.queryByRole("heading")).not.toBeInTheDocument();
|
|
81
105
|
});
|
|
82
106
|
//TODO validate the severity of the alert
|
|
83
107
|
|
|
84
|
-
it("
|
|
108
|
+
it("should render the content", () => {
|
|
85
109
|
renderComponent({ success: { message: "Item added" } });
|
|
86
110
|
|
|
87
111
|
expect(screen.getByText(/children content/i)).toBeInTheDocument();
|
|
88
112
|
});
|
|
89
113
|
});
|
|
90
114
|
|
|
91
|
-
it("
|
|
92
|
-
renderComponent({
|
|
115
|
+
it("should render the children if isFetching is false and there is no defined error", () => {
|
|
116
|
+
renderComponent({ fetching: false, error: undefined });
|
|
93
117
|
|
|
94
118
|
expect(screen.getByText(/children content/i)).toBeInTheDocument();
|
|
95
119
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Alert, AlertTitle, Box } from "@mui/material";
|
|
1
|
+
import { Alert, AlertTitle, Box, LinearProgress } from "@mui/material";
|
|
2
2
|
import React from "react";
|
|
3
3
|
import { PropsWithChildren } from "react";
|
|
4
4
|
import { LoadingArea } from "../loading-area";
|
|
@@ -27,9 +27,14 @@ export interface QueryContainerSuccess {
|
|
|
27
27
|
|
|
28
28
|
export type QueryContainerProps = PropsWithChildren<{
|
|
29
29
|
/**
|
|
30
|
-
* There is a query in progress
|
|
30
|
+
* There is a query in progress and we have available data
|
|
31
31
|
*/
|
|
32
|
-
|
|
32
|
+
fetching: boolean;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* There is a query in progress and we don't have available data
|
|
36
|
+
*/
|
|
37
|
+
loading: boolean;
|
|
33
38
|
/**
|
|
34
39
|
* The query has returned an error
|
|
35
40
|
*/
|
|
@@ -43,8 +48,14 @@ export type QueryContainerProps = PropsWithChildren<{
|
|
|
43
48
|
/**
|
|
44
49
|
* Component to show different indicators based on the usual api query statuses
|
|
45
50
|
*/
|
|
46
|
-
export function QueryContainer({
|
|
47
|
-
|
|
51
|
+
export function QueryContainer({
|
|
52
|
+
fetching,
|
|
53
|
+
loading,
|
|
54
|
+
error,
|
|
55
|
+
success,
|
|
56
|
+
children,
|
|
57
|
+
}: QueryContainerProps) {
|
|
58
|
+
if (loading) {
|
|
48
59
|
return <LoadingArea />;
|
|
49
60
|
}
|
|
50
61
|
|
|
@@ -65,6 +76,7 @@ export function QueryContainer({ isFetching, error, success, children }: QueryCo
|
|
|
65
76
|
{success.message}
|
|
66
77
|
</Alert>
|
|
67
78
|
)}
|
|
79
|
+
{fetching && <LinearProgress sx={{ width: 1, mb: 1 }} />}
|
|
68
80
|
{children}
|
|
69
81
|
</Box>
|
|
70
82
|
);
|
|
@@ -4,37 +4,57 @@ import { ValueRating } from "../value-rating";
|
|
|
4
4
|
import { ValueText } from "../value-text";
|
|
5
5
|
import { GroupValueCard, GroupValueCardProps } from "./group-value-card";
|
|
6
6
|
import { ValueItem } from "../value-item";
|
|
7
|
+
import { ValueDatetime } from "../value-datetime";
|
|
7
8
|
|
|
8
|
-
export const GroupValueCardDummy = ({
|
|
9
|
+
export const GroupValueCardDummy = ({
|
|
10
|
+
dense,
|
|
11
|
+
editable,
|
|
12
|
+
...rest
|
|
13
|
+
}: GroupValueCardProps & { editable?: boolean }) => {
|
|
9
14
|
return (
|
|
10
15
|
<GroupValueCard {...rest} dense={dense}>
|
|
11
16
|
<ValueItem xs={12} sm={6} md={4}>
|
|
12
|
-
<ValueText
|
|
17
|
+
<ValueText
|
|
18
|
+
editable={editable}
|
|
19
|
+
dense={dense}
|
|
20
|
+
label="Hello world"
|
|
21
|
+
value="Lorem ipsum sit amet"
|
|
22
|
+
/>
|
|
13
23
|
</ValueItem>
|
|
14
24
|
<ValueItem xs={12} sm={6} md={2}>
|
|
15
|
-
<ValueBoolean dense={dense} label="Enabled" value />
|
|
25
|
+
<ValueBoolean editable={editable} dense={dense} label="Enabled" value />
|
|
16
26
|
</ValueItem>
|
|
17
27
|
<ValueItem xs={12} sm={6} md={3}>
|
|
18
|
-
<ValueText dense={dense} label="Quantity" value="1200" />
|
|
28
|
+
<ValueText editable={editable} dense={dense} label="Quantity" value="1200" />
|
|
19
29
|
</ValueItem>
|
|
20
30
|
<ValueItem xs={12} sm={6} md={3}>
|
|
21
|
-
<ValueText dense={dense} label="Currency" value="EUR" />
|
|
31
|
+
<ValueText editable={editable} dense={dense} label="Currency" value="EUR" />
|
|
22
32
|
</ValueItem>
|
|
23
33
|
<ValueItem xs={12} sm={6} md={6}>
|
|
24
34
|
<ValueText
|
|
35
|
+
editable={editable}
|
|
25
36
|
dense={dense}
|
|
26
37
|
label="I am Batman"
|
|
27
38
|
value=" Does it come in black? It's ends here. Hero can be anyone"
|
|
28
39
|
/>
|
|
29
40
|
</ValueItem>
|
|
30
41
|
<ValueItem xs={12} sm={6} md={3}>
|
|
31
|
-
<ValueText dense={dense} label="Status" value="Open" />
|
|
42
|
+
<ValueText editable={editable} dense={dense} label="Status" value="Open" />
|
|
32
43
|
</ValueItem>
|
|
33
44
|
<ValueItem xs={12} sm={6} md={3}>
|
|
34
|
-
<ValueText dense={dense} label="Level" value="2144" />
|
|
45
|
+
<ValueText editable={editable} dense={dense} label="Level" value="2144" />
|
|
35
46
|
</ValueItem>
|
|
36
47
|
<ValueItem xs={12} sm={6} md={3}>
|
|
37
|
-
<ValueRating dense={dense} label="Rating" value={3} />
|
|
48
|
+
<ValueRating editable={editable} dense={dense} label="Rating" value={3} />
|
|
49
|
+
</ValueItem>
|
|
50
|
+
<ValueItem xs={12} sm={6} md={3}>
|
|
51
|
+
<ValueDatetime
|
|
52
|
+
editable={editable}
|
|
53
|
+
dense={dense}
|
|
54
|
+
label="Created at"
|
|
55
|
+
value={new Date(2022, 5, 1)}
|
|
56
|
+
format="yyyy-MM-dd"
|
|
57
|
+
/>
|
|
38
58
|
</ValueItem>
|
|
39
59
|
</GroupValueCard>
|
|
40
60
|
);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Meta, StoryObj } from "@storybook/react";
|
|
2
2
|
import { GroupValueCard } from "./group-value-card";
|
|
3
|
-
import { withPadding } from "../../../storybook";
|
|
3
|
+
import { withPadding, withLocalizationProvider } from "../../../storybook";
|
|
4
4
|
import { GroupValueCardDummy } from "./group-value-card.mock";
|
|
5
5
|
import { ValueImage } from "../value-image";
|
|
6
6
|
import workInProgressImg from "../../../stories/assets/work-in-progress.jpg";
|
|
@@ -12,7 +12,7 @@ import { ValueItem } from "../value-item";
|
|
|
12
12
|
export default {
|
|
13
13
|
title: "Components/Value displays/GroupValueCard",
|
|
14
14
|
component: GroupValueCardDummy,
|
|
15
|
-
decorators: [withPadding(2)],
|
|
15
|
+
decorators: [withPadding(2), withLocalizationProvider],
|
|
16
16
|
parameters: {
|
|
17
17
|
layout: "fullscreen",
|
|
18
18
|
},
|
|
@@ -34,6 +34,23 @@ export const Dense: Story = {
|
|
|
34
34
|
},
|
|
35
35
|
};
|
|
36
36
|
|
|
37
|
+
export const EditableDense: Story = {
|
|
38
|
+
args: {
|
|
39
|
+
title: "Hello world",
|
|
40
|
+
subtitle: "Lorem ipsum sit amet",
|
|
41
|
+
dense: true,
|
|
42
|
+
editable: true,
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const Editable: Story = {
|
|
47
|
+
args: {
|
|
48
|
+
title: "Hello world",
|
|
49
|
+
subtitle: "Lorem ipsum sit amet",
|
|
50
|
+
editable: true,
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
|
|
37
54
|
export const WihtoutSubtitle: Story = {
|
|
38
55
|
args: {
|
|
39
56
|
title: "Hello world",
|
|
@@ -17,7 +17,7 @@ describe("GroupValueCard", () => {
|
|
|
17
17
|
it("would render a title", () => {
|
|
18
18
|
renderComponent({ title: "Hello world" });
|
|
19
19
|
|
|
20
|
-
expect(screen.getByRole("heading", { level: 1 })).
|
|
20
|
+
expect(screen.getByRole("heading", { level: 1 })).toBeVisible();
|
|
21
21
|
});
|
|
22
22
|
|
|
23
23
|
describe("subtitle", () => {
|
|
@@ -30,34 +30,32 @@ describe("GroupValueCard", () => {
|
|
|
30
30
|
it("would render a subtitle if it's provided", () => {
|
|
31
31
|
renderComponent({ subtitle: "Hi. I am batman" });
|
|
32
32
|
|
|
33
|
-
expect(
|
|
34
|
-
screen.getByRole("heading", { level: 2, name: /hi. i am batman/i }),
|
|
35
|
-
).toBeInTheDocument();
|
|
33
|
+
expect(screen.getByRole("heading", { level: 2, name: /hi. i am batman/i })).toBeVisible();
|
|
36
34
|
});
|
|
37
35
|
});
|
|
38
36
|
|
|
39
37
|
it("would render all values", () => {
|
|
40
38
|
renderComponent();
|
|
41
39
|
|
|
42
|
-
expect(screen.getByRole("label", { name: /hello world/i })).
|
|
43
|
-
expect(screen.getByText(/lorem ipsum sit amet/i)).
|
|
40
|
+
expect(screen.getByRole("label", { name: /hello world/i })).toBeVisible();
|
|
41
|
+
expect(screen.getByText(/lorem ipsum sit amet/i)).toBeVisible();
|
|
44
42
|
|
|
45
|
-
expect(screen.getByRole("label", { name: /enabled/i })).
|
|
46
|
-
expect(screen.
|
|
43
|
+
expect(screen.getByRole("label", { name: /enabled/i })).toBeVisible();
|
|
44
|
+
expect(screen.getByLabelText(/enabled/i)).toBeChecked();
|
|
47
45
|
|
|
48
|
-
expect(screen.getByRole("label", { name: /quantity/i })).
|
|
49
|
-
expect(screen.getByText(/1200/i)).
|
|
46
|
+
expect(screen.getByRole("label", { name: /quantity/i })).toBeVisible();
|
|
47
|
+
expect(screen.getByText(/1200/i)).toBeVisible();
|
|
50
48
|
|
|
51
|
-
expect(screen.getByRole("label", { name: /currency/i })).
|
|
52
|
-
expect(screen.getByText(/eur/i)).
|
|
49
|
+
expect(screen.getByRole("label", { name: /currency/i })).toBeVisible();
|
|
50
|
+
expect(screen.getByText(/eur/i)).toBeVisible();
|
|
53
51
|
|
|
54
|
-
expect(screen.getByRole("label", { name: /i am batman/i })).
|
|
55
|
-
expect(screen.getByText(/does it come in black/i)).
|
|
52
|
+
expect(screen.getByRole("label", { name: /i am batman/i })).toBeVisible();
|
|
53
|
+
expect(screen.getByText(/does it come in black/i)).toBeVisible();
|
|
56
54
|
|
|
57
|
-
expect(screen.getByRole("label", { name: /status/i })).
|
|
58
|
-
expect(screen.getByText(/open/i)).
|
|
55
|
+
expect(screen.getByRole("label", { name: /status/i })).toBeVisible();
|
|
56
|
+
expect(screen.getByText(/open/i)).toBeVisible();
|
|
59
57
|
|
|
60
|
-
expect(screen.getByRole("label", { name: /level/i })).
|
|
61
|
-
expect(screen.getByText(/2144/i)).
|
|
58
|
+
expect(screen.getByRole("label", { name: /level/i })).toBeVisible();
|
|
59
|
+
expect(screen.getByText(/2144/i)).toBeVisible();
|
|
62
60
|
});
|
|
63
61
|
});
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { render, screen } from "~/tests/testing-library";
|
|
2
|
+
import { ValueEditButton, ValueEditButtons } from "./value-edit";
|
|
3
|
+
import React from "react";
|
|
4
|
+
import userEvent from "@testing-library/user-event";
|
|
5
|
+
import { vi } from "vitest";
|
|
6
|
+
|
|
7
|
+
describe("ValueEditButton", () => {
|
|
8
|
+
const renderComponent = () => {
|
|
9
|
+
const onClick = vi.fn();
|
|
10
|
+
render(<ValueEditButton onClick={onClick} />);
|
|
11
|
+
|
|
12
|
+
return { onClick };
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
it("should render a edit icon", () => {
|
|
16
|
+
renderComponent();
|
|
17
|
+
|
|
18
|
+
expect(screen.getByTestId("EditIcon")).toBeVisible();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("should be accessible", () => {
|
|
22
|
+
renderComponent();
|
|
23
|
+
|
|
24
|
+
expect(screen.getByRole("button", { name: /edit button/i })).toBeVisible();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("should call onClick when the button is clicked", async () => {
|
|
28
|
+
const { onClick } = renderComponent();
|
|
29
|
+
|
|
30
|
+
await userEvent.click(screen.getByRole("button", { name: /edit button/i }));
|
|
31
|
+
|
|
32
|
+
expect(onClick).toHaveBeenCalledTimes(1);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe("ValueEditButtons", () => {
|
|
37
|
+
const renderComponent = () => {
|
|
38
|
+
const onClickCancel = vi.fn();
|
|
39
|
+
const onClickSubmit = vi.fn();
|
|
40
|
+
render(<ValueEditButtons onClickCancel={onClickCancel} onClickSubmit={onClickSubmit} />);
|
|
41
|
+
|
|
42
|
+
return { onClickCancel, onClickSubmit };
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
describe("cancel button", () => {
|
|
46
|
+
it("should render a cancel icon", () => {
|
|
47
|
+
renderComponent();
|
|
48
|
+
|
|
49
|
+
expect(screen.getByTestId("ClearIcon")).toBeVisible();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("should be accessible", () => {
|
|
53
|
+
renderComponent();
|
|
54
|
+
|
|
55
|
+
expect(screen.getByRole("button", { name: /cancel button/i })).toBeVisible();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("should call onClickCancel when the button is clicked", async () => {
|
|
59
|
+
const { onClickCancel } = renderComponent();
|
|
60
|
+
|
|
61
|
+
await userEvent.click(screen.getByRole("button", { name: /cancel button/i }));
|
|
62
|
+
|
|
63
|
+
expect(onClickCancel).toHaveBeenCalledTimes(1);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe("submit button", () => {
|
|
68
|
+
it("should render a submit icon", () => {
|
|
69
|
+
renderComponent();
|
|
70
|
+
|
|
71
|
+
expect(screen.getByTestId("CheckIcon")).toBeVisible();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("should be accessible", () => {
|
|
75
|
+
renderComponent();
|
|
76
|
+
|
|
77
|
+
expect(screen.getByRole("button", { name: /submit button/i })).toBeVisible();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("should call onClickCancel when the button is clicked", async () => {
|
|
81
|
+
const { onClickSubmit } = renderComponent();
|
|
82
|
+
|
|
83
|
+
await userEvent.click(screen.getByRole("button", { name: /submit button/i }));
|
|
84
|
+
|
|
85
|
+
expect(onClickSubmit).toHaveBeenCalledTimes(1);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
});
|
|
@@ -1,22 +1,24 @@
|
|
|
1
1
|
import React, { useState } from "react";
|
|
2
2
|
import CheckIcon from "@mui/icons-material/Check";
|
|
3
3
|
import ClearIcon from "@mui/icons-material/Clear";
|
|
4
|
-
import { Button, InputAdornment, SxProps, Theme } from "@mui/material";
|
|
4
|
+
import { Button, IconButton, InputAdornment, SxProps, Theme, useTheme } from "@mui/material";
|
|
5
|
+
import EditIcon from "@mui/icons-material/Edit";
|
|
5
6
|
|
|
6
7
|
export interface ValueEditButtonsProps {
|
|
7
8
|
onClickCancel: () => void;
|
|
8
|
-
|
|
9
|
+
onClickSubmit: () => void;
|
|
9
10
|
sx?: SxProps<Theme>;
|
|
10
11
|
}
|
|
11
12
|
|
|
12
|
-
export const ValueEditButtons = ({ onClickCancel,
|
|
13
|
+
export const ValueEditButtons = ({ onClickCancel, onClickSubmit, sx }: ValueEditButtonsProps) => {
|
|
13
14
|
return (
|
|
14
15
|
<InputAdornment position="end" sx={sx}>
|
|
15
16
|
<Button
|
|
16
17
|
variant="contained"
|
|
17
18
|
size="small"
|
|
18
19
|
color="error"
|
|
19
|
-
|
|
20
|
+
aria-label="cancel button"
|
|
21
|
+
startIcon={<ClearIcon sx={{ fontSize: 12 }} />}
|
|
20
22
|
onClick={onClickCancel}
|
|
21
23
|
sx={{ paddingRight: 0, minWidth: 0, marginRight: 1 }}
|
|
22
24
|
/>
|
|
@@ -24,8 +26,9 @@ export const ValueEditButtons = ({ onClickCancel, onSubmitEdit, sx }: ValueEditB
|
|
|
24
26
|
variant="contained"
|
|
25
27
|
size="small"
|
|
26
28
|
color="primary"
|
|
27
|
-
|
|
28
|
-
|
|
29
|
+
aria-label="submit button"
|
|
30
|
+
startIcon={<CheckIcon sx={{ fontSize: 12 }} />}
|
|
31
|
+
onClick={onClickSubmit}
|
|
29
32
|
sx={{ paddingRight: 0, minWidth: 0 }}
|
|
30
33
|
/>
|
|
31
34
|
</InputAdornment>
|
|
@@ -57,3 +60,22 @@ export const useEditableValueDisplay = <T,>(
|
|
|
57
60
|
|
|
58
61
|
return { isEditing, cancelEdit, editValue, setEditValue, startEdit, submitEdit };
|
|
59
62
|
};
|
|
63
|
+
|
|
64
|
+
export interface ValueEditButtonProps {
|
|
65
|
+
dense?: boolean;
|
|
66
|
+
onClick: () => void;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export const ValueEditButton = ({ dense, onClick }: ValueEditButtonProps) => {
|
|
70
|
+
const { typography } = useTheme();
|
|
71
|
+
return (
|
|
72
|
+
<IconButton
|
|
73
|
+
size="small"
|
|
74
|
+
onClick={onClick}
|
|
75
|
+
sx={{ ml: dense ? 0.5 : 1 }}
|
|
76
|
+
aria-label="edit button"
|
|
77
|
+
>
|
|
78
|
+
<EditIcon sx={{ fontSize: typography.pxToRem(dense ? 18 : 24) }} />
|
|
79
|
+
</IconButton>
|
|
80
|
+
);
|
|
81
|
+
};
|
|
@@ -31,6 +31,15 @@ export const Editable: Story = {
|
|
|
31
31
|
},
|
|
32
32
|
};
|
|
33
33
|
|
|
34
|
+
export const DenseEditable: Story = {
|
|
35
|
+
args: {
|
|
36
|
+
label: "Lorem ipsum",
|
|
37
|
+
value: false,
|
|
38
|
+
dense: true,
|
|
39
|
+
editable: true,
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
|
|
34
43
|
export const Dense: Story = {
|
|
35
44
|
args: {
|
|
36
45
|
label: "Lorem ipsum",
|
|
@@ -30,50 +30,64 @@ describe("ValueBoolean", () => {
|
|
|
30
30
|
it("would render the label", () => {
|
|
31
31
|
renderComponent({ value: true });
|
|
32
32
|
|
|
33
|
-
expect(screen.getByRole("label", { name: /hello world/i })).
|
|
33
|
+
expect(screen.getByRole("label", { name: /hello world/i })).toBeVisible();
|
|
34
34
|
});
|
|
35
35
|
|
|
36
36
|
it("would render a check if value is true", () => {
|
|
37
37
|
renderComponent({ value: true });
|
|
38
38
|
|
|
39
|
-
expect(screen.getByTestId("CheckIcon")).
|
|
39
|
+
expect(screen.getByTestId("CheckIcon")).toBeVisible();
|
|
40
40
|
});
|
|
41
41
|
|
|
42
42
|
it("would render a cross if value is false", () => {
|
|
43
43
|
renderComponent({ value: false });
|
|
44
44
|
|
|
45
|
-
expect(screen.getByTestId("CloseIcon")).
|
|
45
|
+
expect(screen.getByTestId("CloseIcon")).toBeVisible();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe("accessibility", () => {
|
|
49
|
+
it("should be checked if value is true", () => {
|
|
50
|
+
renderComponent({ value: true });
|
|
51
|
+
|
|
52
|
+
expect(screen.getByLabelText(/hello world/i)).toBeChecked();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("shouldn't be checked if value is false", () => {
|
|
56
|
+
renderComponent({ value: false });
|
|
57
|
+
|
|
58
|
+
expect(screen.getByLabelText(/hello world/i)).not.toBeChecked();
|
|
59
|
+
});
|
|
46
60
|
});
|
|
47
61
|
|
|
48
62
|
it("should render the placeholder if value is undefined", () => {
|
|
49
63
|
renderComponent({ value: undefined });
|
|
50
64
|
|
|
51
|
-
expect(screen.getByText(/-/i)).
|
|
65
|
+
expect(screen.getByText(/-/i)).toBeVisible();
|
|
52
66
|
});
|
|
53
67
|
|
|
54
68
|
it("should render the custom placeholder if value is undefined and placeholder has value", () => {
|
|
55
69
|
renderComponent({ value: undefined, placeholder: "_" });
|
|
56
70
|
|
|
57
|
-
expect(screen.getByText(/_/i)).
|
|
71
|
+
expect(screen.getByText(/_/i)).toBeVisible();
|
|
58
72
|
});
|
|
59
73
|
|
|
60
74
|
describe("editable", () => {
|
|
61
75
|
it("shouldn't render an option to edit if editable is false", () => {
|
|
62
76
|
renderComponent({ value: true, editable: false });
|
|
63
77
|
|
|
64
|
-
expect(screen.
|
|
78
|
+
expect(screen.queryByRole("button", { name: /edit/i })).not.toBeInTheDocument();
|
|
65
79
|
});
|
|
66
80
|
|
|
67
81
|
it("should render an option to edit if editable is true", () => {
|
|
68
82
|
renderComponent({ value: true, editable: true });
|
|
69
83
|
|
|
70
|
-
expect(screen.
|
|
84
|
+
expect(screen.getByRole("button", { name: /edit/i })).toBeVisible();
|
|
71
85
|
});
|
|
72
86
|
|
|
73
87
|
it("should render an input with the value if the edit button is clicked", async () => {
|
|
74
88
|
renderComponent({ value: true, editable: true });
|
|
75
89
|
|
|
76
|
-
await userEvent.click(screen.
|
|
90
|
+
await userEvent.click(screen.getByRole("button", { name: /edit/i }));
|
|
77
91
|
|
|
78
92
|
expect(screen.getByRole("checkbox")).toBeChecked();
|
|
79
93
|
});
|
|
@@ -86,9 +100,9 @@ describe("ValueBoolean", () => {
|
|
|
86
100
|
async (expectedValue: boolean, initialValue: boolean) => {
|
|
87
101
|
const { onEdit } = renderComponent({ value: initialValue, editable: true });
|
|
88
102
|
|
|
89
|
-
await userEvent.click(screen.
|
|
103
|
+
await userEvent.click(screen.getByRole("button", { name: /edit/i }));
|
|
90
104
|
await userEvent.click(screen.getByRole("checkbox"));
|
|
91
|
-
await userEvent.click(screen.
|
|
105
|
+
await userEvent.click(screen.getByRole("button", { name: /submit/i }));
|
|
92
106
|
|
|
93
107
|
expect(onEdit).toHaveBeenCalledTimes(1);
|
|
94
108
|
expect(onEdit).toHaveBeenCalledWith(expectedValue);
|
|
@@ -98,9 +112,9 @@ describe("ValueBoolean", () => {
|
|
|
98
112
|
it("should not call onEdit if the edition is cancelled", async () => {
|
|
99
113
|
const { onEdit } = renderComponent({ value: true, editable: true });
|
|
100
114
|
|
|
101
|
-
await userEvent.click(screen.
|
|
115
|
+
await userEvent.click(screen.getByRole("button", { name: /edit/i }));
|
|
102
116
|
await userEvent.click(screen.getByRole("checkbox"));
|
|
103
|
-
await userEvent.click(screen.
|
|
117
|
+
await userEvent.click(screen.getByRole("button", { name: /cancel/i }));
|
|
104
118
|
|
|
105
119
|
expect(onEdit).not.toHaveBeenCalled();
|
|
106
120
|
});
|
|
@@ -108,10 +122,10 @@ describe("ValueBoolean", () => {
|
|
|
108
122
|
it("should have the original value if is edited again after clear a change", async () => {
|
|
109
123
|
renderComponent({ value: true, editable: true });
|
|
110
124
|
|
|
111
|
-
await userEvent.click(screen.
|
|
125
|
+
await userEvent.click(screen.getByRole("button", { name: /edit/i }));
|
|
112
126
|
await userEvent.click(screen.getByRole("checkbox"));
|
|
113
|
-
await userEvent.click(screen.
|
|
114
|
-
await userEvent.click(screen.
|
|
127
|
+
await userEvent.click(screen.getByRole("button", { name: /cancel/i }));
|
|
128
|
+
await userEvent.click(screen.getByRole("button", { name: /edit/i }));
|
|
115
129
|
|
|
116
130
|
expect(screen.getByRole("checkbox")).toBeChecked();
|
|
117
131
|
});
|