@gogitcms/design-system 0.16.0-next.0 → 0.16.0-next.10
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/package.json +1 -1
- package/src/__tests__/ApplyChangesModal.test.tsx +48 -0
- package/src/__tests__/BranchDeletedModal.test.tsx +42 -0
- package/src/__tests__/ContentBrowser.fieldfocus.test.tsx +109 -0
- package/src/__tests__/ContentBrowser.forms.test.tsx +157 -0
- package/src/__tests__/ContentBrowser.history.test.tsx +385 -0
- package/src/__tests__/ContentBrowser.historycollab.test.tsx +433 -0
- package/src/__tests__/ContentBrowser.slotmedia.test.tsx +150 -0
- package/src/__tests__/FormsBrowser.test.tsx +7 -2
- package/src/__tests__/MediaField.test.tsx +18 -0
- package/src/__tests__/ProtectedBranchModal.test.tsx +68 -0
- package/src/components/ApplyChangesModal.tsx +70 -21
- package/src/components/BranchDeletedModal.tsx +111 -0
- package/src/components/ChangeDetail.tsx +94 -11
- package/src/components/CollabField.tsx +71 -0
- package/src/components/ContentBrowser.tsx +440 -71
- package/src/components/DocumentHistory.tsx +408 -0
- package/src/components/FormsBrowser.tsx +3 -5
- package/src/components/MediaField.tsx +62 -0
- package/src/components/MediaPreview.tsx +14 -1
- package/src/components/MediaPreview.web.tsx +18 -3
- package/src/components/Notifications.tsx +146 -28
- package/src/components/ProtectedBranchModal.tsx +136 -0
- package/src/components/primitives.tsx +46 -1
- package/src/history.ts +82 -0
- package/src/icons.ts +1 -0
- package/src/index.ts +13 -3
- package/src/media.ts +15 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gogitcms/design-system",
|
|
3
|
-
"version": "0.16.0-next.
|
|
3
|
+
"version": "0.16.0-next.10",
|
|
4
4
|
"main": "src/index.ts",
|
|
5
5
|
"types": "src/index.ts",
|
|
6
6
|
"// exports": "The root entry is the react-native source the SPAs, desktop app and mobile app consume. ./web is the plain-DOM build for server-rendered surfaces (the Astro docs site) that don't run react-native-web, and ./css ships the tokens as custom properties. The trailing wildcard keeps deep paths resolvable — plugin bundling and the Vite aliases reach into src/ directly.",
|
|
@@ -146,3 +146,51 @@ test("failure surfaces the error and a dismiss button", () => {
|
|
|
146
146
|
expect(screen.getByTestId("merge-error")).toHaveTextContent("main moved while merging");
|
|
147
147
|
expect(screen.getByTestId("apply-dismiss")).toBeInTheDocument();
|
|
148
148
|
});
|
|
149
|
+
|
|
150
|
+
test("with a review reason the confirmation step promises a review, not a merge commit", () => {
|
|
151
|
+
render(
|
|
152
|
+
wrap(
|
|
153
|
+
<ApplyChangesModal
|
|
154
|
+
{...branches}
|
|
155
|
+
reviewReason="main is protected, so this opens a change request for review."
|
|
156
|
+
onConfirm={() => {}}
|
|
157
|
+
onClose={() => {}}
|
|
158
|
+
/>,
|
|
159
|
+
),
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
const confirm = screen.getByTestId("apply-confirm");
|
|
163
|
+
// The run will stop before the push, so the copy must not describe one.
|
|
164
|
+
expect(confirm).not.toHaveTextContent("pushes a merge commit");
|
|
165
|
+
expect(confirm).not.toHaveTextContent("is then deleted");
|
|
166
|
+
expect(screen.getByTestId("apply-confirm-review")).toHaveTextContent("opens a pull request for review");
|
|
167
|
+
expect(confirm).toHaveTextContent("Nothing is pushed to origin/main until it is approved");
|
|
168
|
+
expect(confirm).toHaveTextContent("main is protected");
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test("needs_review without code conflicts explains the review instead of listing files", () => {
|
|
172
|
+
const onCreateChangeRequest = jest.fn();
|
|
173
|
+
render(
|
|
174
|
+
wrap(
|
|
175
|
+
<ApplyChangesModal
|
|
176
|
+
{...branches}
|
|
177
|
+
reviewReason="main is protected, so this opens a change request for review."
|
|
178
|
+
progress={{ ...base, status: "needs_review", step: "merging", escalationFiles: [] }}
|
|
179
|
+
onCreateChangeRequest={onCreateChangeRequest}
|
|
180
|
+
onClose={() => {}}
|
|
181
|
+
/>,
|
|
182
|
+
),
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
const panel = screen.getByTestId("merge-needs-review");
|
|
186
|
+
// No conflict: it is not called one, and no empty file list is drawn.
|
|
187
|
+
expect(panel).not.toHaveTextContent("Code conflicts need a developer");
|
|
188
|
+
expect(panel).toHaveTextContent("This merge needs a review");
|
|
189
|
+
expect(screen.getByTestId("merge-needs-review-reason")).toHaveTextContent("nothing was pushed to main");
|
|
190
|
+
expect(screen.getByTestId("merge-needs-review-reason")).toHaveTextContent("main is protected");
|
|
191
|
+
|
|
192
|
+
// The same escalation flow: an explanation, then a change request.
|
|
193
|
+
fireEvent.change(screen.getByTestId("change-request-explanation"), { target: { value: "Spring copy" } });
|
|
194
|
+
fireEvent.click(screen.getByTestId("create-change-request-submit"));
|
|
195
|
+
expect(onCreateChangeRequest).toHaveBeenCalledWith("Spring copy");
|
|
196
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { render, screen, fireEvent } from "@testing-library/react";
|
|
3
|
+
import { ThemeProvider } from "../ThemeProvider";
|
|
4
|
+
import { BranchDeletedModal } from "../components/BranchDeletedModal";
|
|
5
|
+
|
|
6
|
+
const wrap = (ui: React.ReactElement) => <ThemeProvider>{ui}</ThemeProvider>;
|
|
7
|
+
|
|
8
|
+
const others = [
|
|
9
|
+
{ id: "b-main", name: "main" },
|
|
10
|
+
{ id: "b-draft", name: "draft/spring" },
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
test("names the deleted branch and switches to the one picked", () => {
|
|
14
|
+
const onSwitch = jest.fn();
|
|
15
|
+
render(wrap(
|
|
16
|
+
<BranchDeletedModal branch="feat/pricing" repository="acme/site" branches={others} onSwitch={onSwitch} />,
|
|
17
|
+
));
|
|
18
|
+
|
|
19
|
+
expect(screen.getByTestId("branch-deleted-title")).toHaveTextContent("feat/pricing was deleted");
|
|
20
|
+
expect(screen.getByText(/removed with it/)).toBeInTheDocument();
|
|
21
|
+
|
|
22
|
+
fireEvent.click(screen.getByTestId("branch-deleted-switch-b-draft"));
|
|
23
|
+
expect(onSwitch).toHaveBeenCalledWith(others[1]);
|
|
24
|
+
// The repository exit is not offered unless the host provides it.
|
|
25
|
+
expect(screen.queryByTestId("branch-deleted-choose-repository")).toBeNull();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("a stale link (no branch name) still explains and offers the others", () => {
|
|
29
|
+
render(wrap(<BranchDeletedModal branches={others} onSwitch={() => {}} />));
|
|
30
|
+
expect(screen.getByTestId("branch-deleted-title")).toHaveTextContent("This branch no longer exists");
|
|
31
|
+
expect(screen.getByTestId("branch-deleted-switch-b-main")).toBeInTheDocument();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("with nothing to switch to, the repository picker is the exit", () => {
|
|
35
|
+
const onChoose = jest.fn();
|
|
36
|
+
render(wrap(
|
|
37
|
+
<BranchDeletedModal branch="main" repository="acme/site" branches={[]} onSwitch={() => {}} onChooseRepository={onChoose} />,
|
|
38
|
+
));
|
|
39
|
+
expect(screen.getByTestId("branch-deleted-none")).toHaveTextContent("No other branch of acme/site");
|
|
40
|
+
fireEvent.click(screen.getByTestId("branch-deleted-choose-repository"));
|
|
41
|
+
expect(onChoose).toHaveBeenCalled();
|
|
42
|
+
});
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// Which field the author is in, reported as a dotted path — what a preview
|
|
2
|
+
// pane uses to ring the matching part of the page. Read off the DOM's focus
|
|
3
|
+
// events and the `data-cms-field` attribute every control's wrapper carries,
|
|
4
|
+
// so it works for every kind of control and for a lone author with no live
|
|
5
|
+
// session.
|
|
6
|
+
import React from "react";
|
|
7
|
+
import { render, screen, fireEvent, act } from "@testing-library/react";
|
|
8
|
+
import { ThemeProvider } from "../ThemeProvider";
|
|
9
|
+
import { ContentBrowser, type CmsEntry, type CmsNavSection, type EntryField } from "../components/ContentBrowser";
|
|
10
|
+
|
|
11
|
+
jest.mock("../ThemeProvider", () => {
|
|
12
|
+
const actual = jest.requireActual("../ThemeProvider");
|
|
13
|
+
return { ...actual, useResponsive: () => ({ width: 1300, height: 900, isDesktop: true, isMobile: false }) };
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const wrap = (ui: React.ReactElement) => <ThemeProvider>{ui}</ThemeProvider>;
|
|
17
|
+
|
|
18
|
+
const sections: CmsNavSection[] = [
|
|
19
|
+
{ title: "Content", items: [{ key: "pages", label: "Pages", icon: "newspaper" }] },
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
const fields: EntryField[] = [
|
|
23
|
+
{ name: "title", label: "Title", type: "string", value: "Home", required: true },
|
|
24
|
+
{
|
|
25
|
+
name: "blocks",
|
|
26
|
+
label: "Blocks",
|
|
27
|
+
type: "array",
|
|
28
|
+
component: "mixedList",
|
|
29
|
+
value: [{ _variant: "hero", heading: "Hi", stats: [{ label: "a", value: "1" }] }],
|
|
30
|
+
variants: [
|
|
31
|
+
{
|
|
32
|
+
name: "hero",
|
|
33
|
+
fields: [
|
|
34
|
+
{ name: "heading", label: "Heading", type: "string", value: null },
|
|
35
|
+
{
|
|
36
|
+
name: "stats",
|
|
37
|
+
label: "Stats",
|
|
38
|
+
type: "array",
|
|
39
|
+
of: "object",
|
|
40
|
+
value: null,
|
|
41
|
+
fields: [
|
|
42
|
+
{ name: "label", label: "Label", type: "string", value: null },
|
|
43
|
+
{ name: "value", label: "Value", type: "string", value: null },
|
|
44
|
+
],
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
},
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
const entry: CmsEntry = { id: "doc-1", path: "content/pages/home.json", title: "Home", body: "", fields };
|
|
53
|
+
|
|
54
|
+
function renderBrowser(onFieldFocus = jest.fn()) {
|
|
55
|
+
render(
|
|
56
|
+
wrap(
|
|
57
|
+
<ContentBrowser
|
|
58
|
+
workspace={{ name: "acme/site", initials: "AC", branch: "main", changed: 0 }}
|
|
59
|
+
sections={sections}
|
|
60
|
+
activeNavKey="pages"
|
|
61
|
+
onSelectNav={() => {}}
|
|
62
|
+
entries={[entry]}
|
|
63
|
+
userInitials="ED"
|
|
64
|
+
onSaveEntry={jest.fn()}
|
|
65
|
+
onFieldFocus={onFieldFocus}
|
|
66
|
+
/>,
|
|
67
|
+
),
|
|
68
|
+
);
|
|
69
|
+
return onFieldFocus;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// The blur report is deferred a tick so a move between fields never passes
|
|
73
|
+
// through null; flush it.
|
|
74
|
+
const settle = () => act(() => new Promise((r) => setTimeout(r, 5)));
|
|
75
|
+
|
|
76
|
+
describe("onFieldFocus", () => {
|
|
77
|
+
it("reports a top-level field by name and null when it blurs", async () => {
|
|
78
|
+
const onFieldFocus = renderBrowser();
|
|
79
|
+
const title = screen.getByDisplayValue("Home");
|
|
80
|
+
fireEvent.focusIn(title);
|
|
81
|
+
expect(onFieldFocus).toHaveBeenLastCalledWith("doc-1", "title");
|
|
82
|
+
fireEvent.focusOut(title);
|
|
83
|
+
await settle();
|
|
84
|
+
expect(onFieldFocus).toHaveBeenLastCalledWith("doc-1", null);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("composes the path of a field inside a mixed-list item and a nested list", async () => {
|
|
88
|
+
const onFieldFocus = renderBrowser();
|
|
89
|
+
const heading = screen.getByDisplayValue("Hi");
|
|
90
|
+
fireEvent.focusIn(heading);
|
|
91
|
+
expect(onFieldFocus).toHaveBeenLastCalledWith("doc-1", "blocks.0.heading");
|
|
92
|
+
|
|
93
|
+
const statLabel = screen.getByDisplayValue("a");
|
|
94
|
+
// Straight from one field to the next: no null in between.
|
|
95
|
+
fireEvent.focusOut(heading);
|
|
96
|
+
fireEvent.focusIn(statLabel);
|
|
97
|
+
await settle();
|
|
98
|
+
expect(onFieldFocus).not.toHaveBeenCalledWith("doc-1", null);
|
|
99
|
+
expect(onFieldFocus).toHaveBeenLastCalledWith("doc-1", "blocks.0.stats.0.label");
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("annotates every control's wrapper with its path", () => {
|
|
103
|
+
renderBrowser();
|
|
104
|
+
const paths = Array.from(document.querySelectorAll("[data-cms-field]")).map((el) => el.getAttribute("data-cms-field"));
|
|
105
|
+
expect(paths).toEqual(
|
|
106
|
+
expect.arrayContaining(["title", "blocks", "blocks.0", "blocks.0.heading", "blocks.0.stats", "blocks.0.stats.0", "blocks.0.stats.0.label", "blocks.0.stats.0.value"]),
|
|
107
|
+
);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { render, screen, fireEvent, within } from "@testing-library/react";
|
|
3
|
+
import { ThemeProvider } from "../ThemeProvider";
|
|
4
|
+
import { ContentBrowser, type CmsEntry, type CmsNavSection } from "../components/ContentBrowser";
|
|
5
|
+
import { FORMS_NAV_KEY, formsNavKey, type FormInfo, type FormsApi } from "../forms";
|
|
6
|
+
|
|
7
|
+
// Force the desktop layout (jsdom reports width 0 → mobile otherwise).
|
|
8
|
+
jest.mock("../ThemeProvider", () => {
|
|
9
|
+
const actual = jest.requireActual("../ThemeProvider");
|
|
10
|
+
return { ...actual, useResponsive: () => ({ width: 1300, height: 900, isDesktop: true, isMobile: false }) };
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const wrap = (ui: React.ReactElement) => <ThemeProvider>{ui}</ThemeProvider>;
|
|
14
|
+
|
|
15
|
+
const contact: FormInfo = {
|
|
16
|
+
name: "contact",
|
|
17
|
+
label: "Contact us",
|
|
18
|
+
fields: [{ name: "email", type: "string" }],
|
|
19
|
+
fieldCount: 1,
|
|
20
|
+
submissionCount: 2,
|
|
21
|
+
versioned: true,
|
|
22
|
+
canDelete: true,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const newsletter: FormInfo = {
|
|
26
|
+
name: "newsletter",
|
|
27
|
+
label: "Newsletter",
|
|
28
|
+
fields: [{ name: "email", type: "string" }],
|
|
29
|
+
fieldCount: 1,
|
|
30
|
+
submissionCount: 308,
|
|
31
|
+
versioned: false,
|
|
32
|
+
canDelete: false,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
function makeApi(over: Partial<FormsApi> = {}): FormsApi {
|
|
36
|
+
return {
|
|
37
|
+
forms: [contact, newsletter],
|
|
38
|
+
list: jest.fn(async () => []),
|
|
39
|
+
count: jest.fn(async () => 0),
|
|
40
|
+
...over,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const sections: CmsNavSection[] = [
|
|
45
|
+
{ title: "Content", items: [{ key: "posts", label: "Posts", icon: "newspaper" }] },
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
const entries: CmsEntry[] = [
|
|
49
|
+
{ id: "a", path: "content/posts/a.md", title: "Alpha", body: "" },
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
function renderForms(activeNavKey: string, onSelectNav = jest.fn()) {
|
|
53
|
+
render(
|
|
54
|
+
wrap(
|
|
55
|
+
<ContentBrowser
|
|
56
|
+
workspace={{ name: "acme/site", initials: "AC", branch: "main", changed: 0 }}
|
|
57
|
+
sections={sections}
|
|
58
|
+
activeNavKey={activeNavKey}
|
|
59
|
+
onSelectNav={onSelectNav}
|
|
60
|
+
entries={entries}
|
|
61
|
+
userInitials="ED"
|
|
62
|
+
forms={makeApi()}
|
|
63
|
+
/>,
|
|
64
|
+
),
|
|
65
|
+
);
|
|
66
|
+
return onSelectNav;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// The forms list is a content column, like the documents of a collection or the
|
|
70
|
+
// sets under Media — not the whole area after the sidebar. jsdom computes no
|
|
71
|
+
// layout, so this asserts the mechanism: a fixed width rather than a flex.
|
|
72
|
+
test("the forms list sits in a content column, not the full content area", () => {
|
|
73
|
+
renderForms(FORMS_NAV_KEY);
|
|
74
|
+
|
|
75
|
+
const pane = screen.getByTestId("pane-forms");
|
|
76
|
+
expect(pane).toHaveStyle({ width: "340px" });
|
|
77
|
+
// A flexing pane is the bug: it would fill everything after the sidebar.
|
|
78
|
+
expect(pane).not.toHaveStyle({ flexGrow: 1 });
|
|
79
|
+
|
|
80
|
+
// Headed like every other content pane, and with the seam that resizes it.
|
|
81
|
+
expect(screen.getByText("Forms")).toBeInTheDocument();
|
|
82
|
+
expect(screen.getByTestId("resize-forms")).toBeInTheDocument();
|
|
83
|
+
|
|
84
|
+
// And the area beside it says what to do, the way Media and Changes do.
|
|
85
|
+
expect(screen.getByTestId("forms-empty")).toBeInTheDocument();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test("the column lists the forms and opens one through the nav key", () => {
|
|
89
|
+
const onSelectNav = renderForms(FORMS_NAV_KEY);
|
|
90
|
+
|
|
91
|
+
expect(screen.getByText("Contact us")).toBeInTheDocument();
|
|
92
|
+
expect(screen.getByText("Newsletter")).toBeInTheDocument();
|
|
93
|
+
|
|
94
|
+
fireEvent.click(screen.getByTestId("form-row-contact"));
|
|
95
|
+
expect(onSelectNav).toHaveBeenCalledWith(formsNavKey("contact"));
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// The other half of the rule: submissions are two levels deeper than a content
|
|
99
|
+
// column can express, so opening a form does hand the whole area to
|
|
100
|
+
// FormsBrowser. Pinned so the fix above is not later applied to both states.
|
|
101
|
+
test("an open form takes the whole content area", async () => {
|
|
102
|
+
renderForms(formsNavKey("contact"));
|
|
103
|
+
// Let the submissions load settle before asserting, so the empty inbox — not
|
|
104
|
+
// a pending fetch — is what is on screen.
|
|
105
|
+
await screen.findByText("No submissions yet.");
|
|
106
|
+
|
|
107
|
+
const pane = screen.getByTestId("pane-forms");
|
|
108
|
+
expect(pane).not.toHaveStyle({ width: "340px" });
|
|
109
|
+
expect(screen.queryByTestId("forms-empty")).not.toBeInTheDocument();
|
|
110
|
+
expect(screen.queryByTestId("resize-forms")).not.toBeInTheDocument();
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// Media, Forms and Changes head their content pane with PaneTitle; a collection
|
|
114
|
+
// heads the same pane with its own header. They have to be the same heading, or
|
|
115
|
+
// the title moves and changes size as you switch between them.
|
|
116
|
+
test("the forms heading matches a collection's, not a smaller indented one", () => {
|
|
117
|
+
const { unmount } = render(
|
|
118
|
+
wrap(
|
|
119
|
+
<ContentBrowser
|
|
120
|
+
workspace={{ name: "acme/site", initials: "AC", branch: "main", changed: 0 }}
|
|
121
|
+
sections={sections}
|
|
122
|
+
activeNavKey={FORMS_NAV_KEY}
|
|
123
|
+
onSelectNav={() => {}}
|
|
124
|
+
entries={entries}
|
|
125
|
+
userInitials="ED"
|
|
126
|
+
forms={makeApi()}
|
|
127
|
+
/>,
|
|
128
|
+
),
|
|
129
|
+
);
|
|
130
|
+
// Scoped to the pane: "Forms" is also the nav item that got us here.
|
|
131
|
+
const formsHeading = within(screen.getByTestId("pane-forms")).getByText("Forms");
|
|
132
|
+
// The heading sits directly in Pane's header row — the row with the pane's
|
|
133
|
+
// height and the rule under it. A padded wrapper of its own in between is
|
|
134
|
+
// what used to push this title a step right of every collection's.
|
|
135
|
+
expect(formsHeading.parentElement).toHaveStyle({ height: "48px", paddingLeft: "16px" });
|
|
136
|
+
const formsStyle = window.getComputedStyle(formsHeading);
|
|
137
|
+
unmount();
|
|
138
|
+
|
|
139
|
+
render(
|
|
140
|
+
wrap(
|
|
141
|
+
<ContentBrowser
|
|
142
|
+
workspace={{ name: "acme/site", initials: "AC", branch: "main", changed: 0 }}
|
|
143
|
+
sections={sections}
|
|
144
|
+
activeNavKey="posts"
|
|
145
|
+
onSelectNav={() => {}}
|
|
146
|
+
entries={entries}
|
|
147
|
+
userInitials="ED"
|
|
148
|
+
/>,
|
|
149
|
+
),
|
|
150
|
+
);
|
|
151
|
+
const collectionHeading = within(screen.getByTestId("pane-entries")).getByText("Posts");
|
|
152
|
+
const collectionStyle = window.getComputedStyle(collectionHeading);
|
|
153
|
+
|
|
154
|
+
expect(formsStyle.fontSize).toBe(collectionStyle.fontSize);
|
|
155
|
+
expect(formsStyle.fontWeight).toBe(collectionStyle.fontWeight);
|
|
156
|
+
expect(formsStyle.flexGrow).toBe(collectionStyle.flexGrow);
|
|
157
|
+
});
|