@griddo/ax 11.14.2-rc.0 → 11.14.2
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/config/jest/reactEasyCropMock.js +15 -0
- package/config/jest/reactTimezoneMock.js +13 -0
- package/package.json +221 -219
- package/public/img/welcome.svg +127 -0
- package/src/__tests__/components/Browser/Browser.test.tsx +27 -51
- package/src/__tests__/components/CategoryCell/CategoryCell.test.tsx +10 -5
- package/src/__tests__/components/ElementsTooltip/ElementsTooltip.test.tsx +27 -14
- package/src/__tests__/components/HeadingsPreviewModal/ErrorsBanner/ErrorItem/ErrorItem.test.tsx +2 -0
- package/src/__tests__/components/HeadingsPreviewModal/HeadingsPreviewModal.utils.test.tsx +138 -1
- package/src/__tests__/components/ImageDragAndDrop/CropStep/CropStep.test.tsx +84 -0
- package/src/__tests__/components/ImageDragAndDrop/ImageDragAndDrop.test.tsx +173 -0
- package/src/__tests__/components/KeywordsPreviewModal/KeywordsPreviewModal.test.tsx +3 -4
- package/src/__tests__/components/ProfileImage/ProfileImage.test.tsx +120 -0
- package/src/__tests__/components/ResizePanel/ResizePanel.test.tsx +8 -0
- package/src/__tests__/components/UserRolesAndSites/RoleItem/RoleItem.test.tsx +190 -0
- package/src/__tests__/components/UserRolesAndSites/UserRolesAndSites.test.tsx +471 -0
- package/src/__tests__/modules/FramePreview/HeadingsOverlay/HeadingsOverlay.test.tsx +15 -2
- package/src/__tests__/modules/Sites/Sites.test.tsx +68 -224
- package/src/__tests__/modules/Sites/SitesList/ListView/BulkHeader/BulkHeader.test.tsx +21 -17
- package/src/__tests__/modules/Sites/SitesList/SitesList.test.tsx +65 -565
- package/src/__tests__/modules/Sites/SitesList/WelcomeModal/DataStep/DataStep.test.tsx +109 -0
- package/src/__tests__/modules/Sites/SitesList/WelcomeModal/FinalStep/FinalStep.test.tsx +157 -0
- package/src/__tests__/modules/Sites/SitesList/WelcomeModal/ImageStep/CropView/CropView.test.tsx +51 -0
- package/src/__tests__/modules/Sites/SitesList/WelcomeModal/ImageStep/ImageStep.test.tsx +70 -0
- package/src/__tests__/modules/Sites/SitesList/WelcomeModal/ImageStep/UploadView/UploadView.test.tsx +92 -0
- package/src/__tests__/modules/Sites/SitesList/WelcomeModal/TimezoneStep/TimezoneStep.test.tsx +94 -0
- package/src/__tests__/modules/Sites/SitesList/WelcomeModal/WelcomeModal.test.tsx +78 -0
- package/src/__tests__/modules/Sites/SitesList/WelcomeModal/WelcomeStep/WelcomeStep.test.tsx +39 -0
- package/src/__tests__/modules/Sites/SitesList/WelcomeModal/utils.test.ts +55 -0
- package/src/api/sites.tsx +4 -4
- package/src/components/Avatar/index.tsx +26 -5
- package/src/components/Avatar/style.tsx +20 -10
- package/src/components/Browser/index.tsx +7 -1
- package/src/components/ConfigPanel/index.tsx +11 -7
- package/src/components/ElementsTooltip/index.tsx +96 -34
- package/src/components/ElementsTooltip/style.tsx +12 -1
- package/src/components/Fields/FileField/index.tsx +16 -18
- package/src/components/Fields/HeadingField/index.tsx +1 -1
- package/src/components/Fields/ImageField/index.tsx +9 -38
- package/src/components/Fields/ImageField/style.tsx +12 -1
- package/src/components/Fields/ToggleField/index.tsx +1 -1
- package/src/components/Fields/Wysiwyg/index.tsx +25 -20
- package/src/components/FileGallery/GalleryPanel/index.tsx +15 -7
- package/src/components/FileGallery/index.tsx +33 -28
- package/src/components/Gallery/GalleryPanel/index.tsx +5 -16
- package/src/components/Gallery/index.tsx +0 -2
- package/src/components/HeadingsPreviewModal/ErrorsBanner/ErrorItem/index.tsx +11 -2
- package/src/components/HeadingsPreviewModal/ErrorsBanner/index.tsx +21 -3
- package/src/components/HeadingsPreviewModal/ErrorsBanner/style.tsx +2 -2
- package/src/components/HeadingsPreviewModal/index.tsx +13 -3
- package/src/components/HeadingsPreviewModal/style.tsx +18 -0
- package/src/components/HeadingsPreviewModal/utils.tsx +31 -3
- package/src/components/Image/index.tsx +2 -2
- package/src/components/ImageDragAndDrop/CropStep/index.tsx +95 -0
- package/src/components/ImageDragAndDrop/CropStep/style.tsx +101 -0
- package/src/{modules/MediaGallery → components}/ImageDragAndDrop/index.tsx +103 -40
- package/src/{modules/MediaGallery → components}/ImageDragAndDrop/style.tsx +14 -2
- package/src/components/KeywordsPreviewModal/atoms.tsx +2 -2
- package/src/components/KeywordsPreviewModal/index.tsx +6 -6
- package/src/components/KeywordsPreviewModal/utils.tsx +2 -2
- package/src/components/ProfileImage/index.tsx +55 -0
- package/src/components/ProfileImage/style.tsx +58 -0
- package/src/components/ResizePanel/ResizeHandle/index.tsx +44 -6
- package/src/components/ResizePanel/ResizeHandle/style.tsx +7 -0
- package/src/components/ResizePanel/index.tsx +25 -4
- package/src/components/Tabs/style.tsx +1 -1
- package/src/components/Tag/index.tsx +0 -1
- package/src/components/UserRolesAndSites/RoleItem/index.tsx +42 -0
- package/src/components/UserRolesAndSites/RoleItem/style.tsx +29 -0
- package/src/components/UserRolesAndSites/index.tsx +102 -0
- package/src/components/UserRolesAndSites/style.tsx +67 -0
- package/src/components/index.tsx +6 -0
- package/src/constants/index.ts +13 -1
- package/src/containers/App/actions.tsx +8 -1
- package/src/containers/Sites/actions.tsx +26 -0
- package/src/containers/Sites/constants.tsx +1 -0
- package/src/containers/Sites/interfaces.tsx +6 -0
- package/src/containers/Sites/reducer.tsx +5 -1
- package/src/containers/Users/reducer.tsx +6 -5
- package/src/guards/routeLeaving/index.tsx +9 -11
- package/src/helpers/images.tsx +50 -3
- package/src/helpers/index.tsx +2 -1
- package/src/hooks/forms.tsx +45 -48
- package/src/hooks/index.tsx +2 -1
- package/src/hooks/modals.tsx +4 -3
- package/src/hooks/window.ts +50 -2
- package/src/modules/ActivityLog/ItemLogUser/UserItem/index.tsx +1 -1
- package/src/modules/App/Routing/Logout/index.tsx +3 -5
- package/src/modules/App/Routing/NavMenu/NavItem/index.tsx +73 -52
- package/src/modules/App/Routing/NavMenu/NavItem/style.tsx +21 -7
- package/src/modules/App/Routing/NavMenu/index.tsx +59 -54
- package/src/modules/App/Routing/NavMenu/style.tsx +13 -11
- package/src/modules/CreatePass/index.tsx +1 -1
- package/src/modules/FileDrive/FileDragAndDrop/index.tsx +11 -8
- package/src/modules/FileDrive/FileModal/index.tsx +8 -9
- package/src/modules/FileDrive/index.tsx +1 -18
- package/src/modules/Forms/FormEditor/index.tsx +1 -1
- package/src/modules/FramePreview/HeadingsOverlay/index.tsx +22 -11
- package/src/modules/FramePreview/HeadingsOverlay/style.tsx +1 -1
- package/src/modules/MediaGallery/ImageModal/index.tsx +1 -5
- package/src/modules/MediaGallery/index.tsx +1 -3
- package/src/modules/Settings/Globals/constants.tsx +942 -106
- package/src/modules/Sites/SitesList/AllSitesHeader/index.tsx +33 -0
- package/src/modules/Sites/SitesList/AllSitesHeader/style.tsx +35 -0
- package/src/modules/Sites/SitesList/GridView/GridHeaderFilter/index.tsx +5 -5
- package/src/modules/Sites/SitesList/GridView/GridSiteItem/index.tsx +23 -119
- package/src/modules/Sites/SitesList/ListView/BulkHeader/TableHeader/index.tsx +4 -4
- package/src/modules/Sites/SitesList/ListView/BulkHeader/index.tsx +4 -3
- package/src/modules/Sites/SitesList/ListView/ListSiteItem/index.tsx +23 -120
- package/src/modules/Sites/SitesList/{RecentSiteItem → RecentSites/RecentSiteItem}/index.tsx +4 -5
- package/src/modules/Sites/SitesList/RecentSites/index.tsx +49 -0
- package/src/modules/Sites/SitesList/RecentSites/style.tsx +92 -0
- package/src/modules/Sites/SitesList/SiteModal/index.tsx +8 -7
- package/src/modules/Sites/SitesList/WelcomeModal/DataStep/index.tsx +72 -0
- package/src/modules/Sites/SitesList/WelcomeModal/DataStep/style.tsx +59 -0
- package/src/modules/Sites/SitesList/WelcomeModal/FinalStep/constants.tsx +78 -0
- package/src/modules/Sites/SitesList/WelcomeModal/FinalStep/index.tsx +78 -0
- package/src/modules/Sites/SitesList/WelcomeModal/FinalStep/style.tsx +141 -0
- package/src/modules/Sites/SitesList/WelcomeModal/ImageStep/CropView/index.tsx +93 -0
- package/src/modules/Sites/SitesList/WelcomeModal/ImageStep/CropView/style.tsx +77 -0
- package/src/modules/Sites/SitesList/WelcomeModal/ImageStep/UploadView/index.tsx +100 -0
- package/src/modules/Sites/SitesList/WelcomeModal/ImageStep/UploadView/style.tsx +94 -0
- package/src/modules/Sites/SitesList/WelcomeModal/ImageStep/index.tsx +44 -0
- package/src/modules/Sites/SitesList/WelcomeModal/ImageStep/style.tsx +31 -0
- package/src/modules/Sites/SitesList/WelcomeModal/TimezoneStep/index.tsx +51 -0
- package/src/modules/Sites/SitesList/WelcomeModal/TimezoneStep/style.tsx +52 -0
- package/src/modules/Sites/SitesList/WelcomeModal/WelcomeStep/index.tsx +40 -0
- package/src/modules/Sites/SitesList/WelcomeModal/WelcomeStep/style.tsx +53 -0
- package/src/modules/Sites/SitesList/WelcomeModal/index.tsx +215 -0
- package/src/modules/Sites/SitesList/WelcomeModal/style.tsx +12 -0
- package/src/modules/Sites/SitesList/WelcomeModal/utils.ts +26 -0
- package/src/modules/Sites/SitesList/atoms.tsx +4 -4
- package/src/modules/Sites/SitesList/hooks.tsx +149 -16
- package/src/modules/Sites/SitesList/index.tsx +127 -125
- package/src/modules/Sites/SitesList/style.tsx +1 -117
- package/src/modules/Sites/SitesList/utils.tsx +9 -2
- package/src/modules/Sites/index.tsx +19 -8
- package/src/modules/Users/Profile/index.tsx +169 -31
- package/src/modules/Users/Profile/style.tsx +81 -1
- package/src/modules/Users/Roles/RoleItem/index.tsx +2 -2
- package/src/modules/Users/UserCreate/SiteItem/index.tsx +11 -14
- package/src/modules/Users/UserForm/atoms.tsx +3 -3
- package/src/modules/Users/UserForm/index.tsx +25 -29
- package/src/modules/Users/UserForm/style.tsx +15 -2
- package/src/modules/Users/UserList/UserItem/index.tsx +4 -4
- package/src/routes/index.tsx +1 -0
- package/src/types/index.tsx +2 -0
- /package/src/modules/Sites/SitesList/{RecentSiteItem → RecentSites/RecentSiteItem}/style.tsx +0 -0
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
extractUniqueHeadingTypes,
|
|
5
5
|
filterHeadings,
|
|
6
6
|
getHeadColor,
|
|
7
|
+
hasHiddenHeadings,
|
|
7
8
|
} from "@ax/components/HeadingsPreviewModal/utils";
|
|
8
9
|
|
|
9
10
|
const flatNode = (
|
|
@@ -11,7 +12,8 @@ const flatNode = (
|
|
|
11
12
|
text: string,
|
|
12
13
|
level: HeadingNode["level"],
|
|
13
14
|
children: HeadingNode[] = [],
|
|
14
|
-
|
|
15
|
+
isHidden = false,
|
|
16
|
+
): HeadingNode => ({ tag, text, level, isHidden, children });
|
|
15
17
|
|
|
16
18
|
describe("analyzeHeadings", () => {
|
|
17
19
|
it("should return no errors for empty headings", () => {
|
|
@@ -111,6 +113,83 @@ describe("filterHeadings", () => {
|
|
|
111
113
|
const result = filterHeadings(headings, "h3");
|
|
112
114
|
expect(result).toHaveLength(0);
|
|
113
115
|
});
|
|
116
|
+
|
|
117
|
+
describe("with includeHidden parameter", () => {
|
|
118
|
+
it("should include hidden headings by default", () => {
|
|
119
|
+
const headingsWithHidden = [
|
|
120
|
+
flatNode("h1", "Title", 1, [flatNode("h2", "Visible", 2), flatNode("h2", "Hidden", 2, [], true)]),
|
|
121
|
+
];
|
|
122
|
+
const result = filterHeadings(headingsWithHidden, "all");
|
|
123
|
+
expect(result).toEqual(headingsWithHidden);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("should explicitly include hidden headings when includeHidden is true", () => {
|
|
127
|
+
const headingsWithHidden = [
|
|
128
|
+
flatNode("h1", "Title", 1, [flatNode("h2", "Visible", 2), flatNode("h2", "Hidden", 2, [], true)]),
|
|
129
|
+
];
|
|
130
|
+
const result = filterHeadings(headingsWithHidden, "all", true);
|
|
131
|
+
expect(result).toEqual(headingsWithHidden);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("should filter out hidden headings when includeHidden is false with filter 'all'", () => {
|
|
135
|
+
const headingsWithHidden = [
|
|
136
|
+
flatNode("h1", "Title", 1, [flatNode("h2", "Visible A", 2), flatNode("h2", "Hidden", 2, [], true), flatNode("h2", "Visible B", 2)]),
|
|
137
|
+
];
|
|
138
|
+
const result = filterHeadings(headingsWithHidden, "all", false);
|
|
139
|
+
expect(result).toHaveLength(1);
|
|
140
|
+
expect(result[0].tag).toBe("h1");
|
|
141
|
+
expect(result[0].children).toHaveLength(2);
|
|
142
|
+
expect(result[0].children.every((h) => !h.isHidden)).toBe(true);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("should filter out hidden headings when flattening with specific type", () => {
|
|
146
|
+
const headingsWithHidden = [
|
|
147
|
+
flatNode("h1", "Title", 1, [
|
|
148
|
+
flatNode("h2", "Visible A", 2),
|
|
149
|
+
flatNode("h2", "Hidden", 2, [], true),
|
|
150
|
+
flatNode("h2", "Visible B", 2),
|
|
151
|
+
]),
|
|
152
|
+
];
|
|
153
|
+
const result = filterHeadings(headingsWithHidden, "h2", false);
|
|
154
|
+
expect(result).toHaveLength(2);
|
|
155
|
+
expect(result.every((h) => h.tag === "h2")).toBe(true);
|
|
156
|
+
expect(result.every((h) => !h.isHidden)).toBe(true);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it("should return empty array when all matching headings are hidden", () => {
|
|
160
|
+
const headingsWithHidden = [
|
|
161
|
+
flatNode("h1", "Title", 1, [flatNode("h2", "Hidden A", 2, [], true), flatNode("h2", "Hidden B", 2, [], true)]),
|
|
162
|
+
];
|
|
163
|
+
const result = filterHeadings(headingsWithHidden, "h2", false);
|
|
164
|
+
expect(result).toHaveLength(0);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("should maintain hierarchy when filtering hidden with type 'all'", () => {
|
|
168
|
+
const headingsWithHidden = [
|
|
169
|
+
flatNode("h1", "Visible", 1, [
|
|
170
|
+
flatNode("h2", "Visible", 2, [flatNode("h3", "Hidden", 3, [], true)]),
|
|
171
|
+
flatNode("h2", "Hidden", 2, [], true),
|
|
172
|
+
]),
|
|
173
|
+
];
|
|
174
|
+
const result = filterHeadings(headingsWithHidden, "all", false);
|
|
175
|
+
expect(result).toHaveLength(1);
|
|
176
|
+
expect(result[0].tag).toBe("h1");
|
|
177
|
+
expect(result[0].children).toHaveLength(1);
|
|
178
|
+
expect(result[0].children[0].tag).toBe("h2");
|
|
179
|
+
expect(result[0].children[0].children).toHaveLength(0);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it("should filter hidden headings for specific type that has both visible and hidden", () => {
|
|
183
|
+
const headingsWithHidden = [
|
|
184
|
+
flatNode("h1", "Title", 1, [], true),
|
|
185
|
+
flatNode("h2", "Visible", 2),
|
|
186
|
+
flatNode("h2", "Hidden", 2, [], true),
|
|
187
|
+
];
|
|
188
|
+
const result = filterHeadings(headingsWithHidden, "h2", false);
|
|
189
|
+
expect(result).toHaveLength(1);
|
|
190
|
+
expect(result[0].text).toBe("Visible");
|
|
191
|
+
});
|
|
192
|
+
});
|
|
114
193
|
});
|
|
115
194
|
|
|
116
195
|
describe("extractUniqueHeadingTypes", () => {
|
|
@@ -148,3 +227,61 @@ describe("getHeadColor", () => {
|
|
|
148
227
|
expect(getHeadColor("unknown")).toBe("rgb(255, 255, 255)");
|
|
149
228
|
});
|
|
150
229
|
});
|
|
230
|
+
|
|
231
|
+
describe("hasHiddenHeadings", () => {
|
|
232
|
+
it("should return false when there are no hidden headings", () => {
|
|
233
|
+
const headings = [flatNode("h1", "Title", 1, [flatNode("h2", "Subtitle", 2)])];
|
|
234
|
+
expect(hasHiddenHeadings(headings)).toBe(false);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it("should return true when there are hidden headings with filter 'all'", () => {
|
|
238
|
+
const headings = [flatNode("h1", "Title", 1, [flatNode("h2", "Subtitle", 2, [], true)])];
|
|
239
|
+
expect(hasHiddenHeadings(headings, "all")).toBe(true);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it("should return true when a direct child heading is hidden", () => {
|
|
243
|
+
const headings = [flatNode("h1", "Title", 1, [], true)];
|
|
244
|
+
expect(hasHiddenHeadings(headings)).toBe(true);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it("should return true when a nested heading is hidden and filter matches", () => {
|
|
248
|
+
const headings = [flatNode("h1", "Title", 1, [flatNode("h2", "Subtitle", 2, [], true)])];
|
|
249
|
+
expect(hasHiddenHeadings(headings, "h2")).toBe(true);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it("should return false when hidden headings do not match the filter type", () => {
|
|
253
|
+
const headings = [flatNode("h1", "Title", 1, [flatNode("h2", "Subtitle", 2, [], true)])];
|
|
254
|
+
expect(hasHiddenHeadings(headings, "h1")).toBe(false);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it("should return true when filter type matches hidden heading", () => {
|
|
258
|
+
const headings = [flatNode("h1", "Title", 1, [], true), flatNode("h2", "Subtitle", 2)];
|
|
259
|
+
expect(hasHiddenHeadings(headings, "h1")).toBe(true);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it("should return false when no headings match the filter", () => {
|
|
263
|
+
const headings = [flatNode("h1", "Title", 1), flatNode("h2", "Subtitle", 2)];
|
|
264
|
+
expect(hasHiddenHeadings(headings, "h3")).toBe(false);
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it("should return true when multiple levels have hidden headings of the same type", () => {
|
|
268
|
+
const headings = [
|
|
269
|
+
flatNode("h2", "Subtitle A", 2, [], true),
|
|
270
|
+
flatNode("h2", "Subtitle B", 2, [flatNode("h3", "Nested", 3, [], true)]),
|
|
271
|
+
];
|
|
272
|
+
expect(hasHiddenHeadings(headings, "h2")).toBe(true);
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it("should handle deeply nested hidden headings", () => {
|
|
276
|
+
const headings = [
|
|
277
|
+
flatNode("h1", "Title", 1, [
|
|
278
|
+
flatNode("h2", "Subtitle", 2, [flatNode("h3", "Deep", 3, [flatNode("h4", "Deeper", 4, [], true)])]),
|
|
279
|
+
]),
|
|
280
|
+
];
|
|
281
|
+
expect(hasHiddenHeadings(headings, "h4")).toBe(true);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it("should return false for empty headings array", () => {
|
|
285
|
+
expect(hasHiddenHeadings([], "all")).toBe(false);
|
|
286
|
+
});
|
|
287
|
+
});
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import CropStep from "@ax/components/ImageDragAndDrop/CropStep";
|
|
2
|
+
import { parseTheme } from "@ax/helpers";
|
|
3
|
+
import globalTheme from "@ax/themes/theme.json";
|
|
4
|
+
|
|
5
|
+
import { cleanup, fireEvent, render, screen } from "@testing-library/react";
|
|
6
|
+
import { ThemeProvider } from "styled-components";
|
|
7
|
+
|
|
8
|
+
afterEach(cleanup);
|
|
9
|
+
|
|
10
|
+
const renderCropStep = (props = {}) =>
|
|
11
|
+
render(
|
|
12
|
+
<ThemeProvider theme={parseTheme(globalTheme)}>
|
|
13
|
+
<CropStep imageSrc="data:image/jpeg;base64,abc" onConfirm={jest.fn()} onCancel={jest.fn()} {...props} />
|
|
14
|
+
</ThemeProvider>,
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
describe("CropStep rendering", () => {
|
|
18
|
+
it("should render the cropper", () => {
|
|
19
|
+
renderCropStep();
|
|
20
|
+
expect(screen.getByTestId("cropper-mock")).toBeTruthy();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("should render zoom controls", () => {
|
|
24
|
+
renderCropStep();
|
|
25
|
+
expect(screen.getByLabelText("Zoom in")).toBeTruthy();
|
|
26
|
+
expect(screen.getByLabelText("Zoom out")).toBeTruthy();
|
|
27
|
+
expect(screen.getByLabelText("Zoom")).toBeTruthy();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("should render Done and Upload new image buttons", () => {
|
|
31
|
+
renderCropStep();
|
|
32
|
+
expect(screen.getByText("Done")).toBeTruthy();
|
|
33
|
+
expect(screen.getByText("Upload new image")).toBeTruthy();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("should render zoom slider with correct range and step", () => {
|
|
37
|
+
renderCropStep();
|
|
38
|
+
const slider = screen.getByLabelText("Zoom") as HTMLInputElement;
|
|
39
|
+
expect(slider).toBeTruthy();
|
|
40
|
+
expect(slider.min).toBe("1");
|
|
41
|
+
expect(slider.max).toBe("3");
|
|
42
|
+
expect(slider.step).toBe("0.1");
|
|
43
|
+
expect(slider.value).toBe("1");
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe("CropStep events", () => {
|
|
48
|
+
it("should call onConfirm with cropped area when Done is clicked", () => {
|
|
49
|
+
const onConfirm = jest.fn();
|
|
50
|
+
renderCropStep({ onConfirm });
|
|
51
|
+
fireEvent.click(screen.getByText("Done"));
|
|
52
|
+
expect(onConfirm).toHaveBeenCalledWith({ x: 0, y: 0, width: 100, height: 100 });
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("should call onCancel when Upload new image is clicked", () => {
|
|
56
|
+
const onCancel = jest.fn();
|
|
57
|
+
renderCropStep({ onCancel });
|
|
58
|
+
fireEvent.click(screen.getByText("Upload new image"));
|
|
59
|
+
expect(onCancel).toHaveBeenCalledTimes(1);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("should update zoom value when slider changes", () => {
|
|
63
|
+
renderCropStep();
|
|
64
|
+
const slider = screen.getByLabelText("Zoom") as HTMLInputElement;
|
|
65
|
+
fireEvent.change(slider, { target: { value: "2" } });
|
|
66
|
+
expect(slider.value).toBe("2");
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("should increase zoom when zoom in button is clicked", () => {
|
|
70
|
+
renderCropStep();
|
|
71
|
+
const slider = screen.getByLabelText("Zoom") as HTMLInputElement;
|
|
72
|
+
fireEvent.click(screen.getByLabelText("Zoom in"));
|
|
73
|
+
expect(slider.value).toBe("1.1");
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("should decrease zoom when zoom out button is clicked", () => {
|
|
77
|
+
renderCropStep();
|
|
78
|
+
const slider = screen.getByLabelText("Zoom") as HTMLInputElement;
|
|
79
|
+
expect(slider.value).toBe("1");
|
|
80
|
+
fireEvent.click(screen.getByLabelText("Zoom out"));
|
|
81
|
+
// Zoom can't go below MIN_ZOOM (1), so it should stay at 1
|
|
82
|
+
expect(slider.value).toBe("1");
|
|
83
|
+
});
|
|
84
|
+
});
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { Provider } from "react-redux";
|
|
2
|
+
|
|
3
|
+
import ImageDragAndDrop from "@ax/components/ImageDragAndDrop";
|
|
4
|
+
import { VALID_IMAGE_FORMATS } from "@ax/constants";
|
|
5
|
+
import { parseTheme } from "@ax/helpers";
|
|
6
|
+
import globalTheme from "@ax/themes/theme.json";
|
|
7
|
+
|
|
8
|
+
import { cleanup, fireEvent, render, screen } from "@testing-library/react";
|
|
9
|
+
import configureStore from "redux-mock-store";
|
|
10
|
+
import thunk from "redux-thunk";
|
|
11
|
+
import { ThemeProvider } from "styled-components";
|
|
12
|
+
|
|
13
|
+
jest.mock("@ax/containers/Gallery", () => ({
|
|
14
|
+
galleryActions: {
|
|
15
|
+
uploadError: jest.fn(() => ({ type: "UPLOAD_ERROR" })),
|
|
16
|
+
resetError: jest.fn(() => ({ type: "RESET_ERROR" })),
|
|
17
|
+
uploadImage: jest.fn(() => async () => null),
|
|
18
|
+
replaceImage: jest.fn(() => async () => null),
|
|
19
|
+
setUploadSuccess: jest.fn(() => ({ type: "SET_UPLOAD_SUCCESS" })),
|
|
20
|
+
},
|
|
21
|
+
}));
|
|
22
|
+
|
|
23
|
+
afterEach(cleanup);
|
|
24
|
+
|
|
25
|
+
const mockStore = configureStore([thunk]);
|
|
26
|
+
|
|
27
|
+
const initialStore = {
|
|
28
|
+
gallery: {
|
|
29
|
+
isUploading: false,
|
|
30
|
+
isSuccess: false,
|
|
31
|
+
isError: false,
|
|
32
|
+
errorMsg: "",
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const renderImageDragAndDrop = (props = {}, storeData = initialStore) => {
|
|
37
|
+
const store = mockStore(storeData);
|
|
38
|
+
return render(
|
|
39
|
+
<Provider store={store}>
|
|
40
|
+
<ThemeProvider theme={parseTheme(globalTheme)}>
|
|
41
|
+
<ImageDragAndDrop siteID="global" handleUpload={jest.fn()} {...props} />
|
|
42
|
+
</ThemeProvider>
|
|
43
|
+
</Provider>,
|
|
44
|
+
);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
describe("ImageDragAndDrop rendering", () => {
|
|
48
|
+
it("should render file-drag-and-drop-wrapper testid", () => {
|
|
49
|
+
renderImageDragAndDrop();
|
|
50
|
+
expect(screen.getByTestId("file-drag-and-drop-wrapper")).toBeTruthy();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("should render Drag your image here text", () => {
|
|
54
|
+
renderImageDragAndDrop();
|
|
55
|
+
expect(screen.getByText("Drag your image here")).toBeTruthy();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("should render Select images button", () => {
|
|
59
|
+
renderImageDragAndDrop();
|
|
60
|
+
expect(screen.getByText("Select images")).toBeTruthy();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("should render valid formats text", () => {
|
|
64
|
+
renderImageDragAndDrop();
|
|
65
|
+
const validFormatsTexts = screen.getAllByText((_, element) => {
|
|
66
|
+
return VALID_IMAGE_FORMATS.every((format) => element?.textContent?.includes(format));
|
|
67
|
+
});
|
|
68
|
+
expect(validFormatsTexts.length).toBeGreaterThan(0);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("should render valid formats in subtitle", () => {
|
|
72
|
+
renderImageDragAndDrop();
|
|
73
|
+
const subtitles = screen.getAllByText(/Max. size: 50MB/);
|
|
74
|
+
expect(subtitles.length).toBeGreaterThan(0);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("should render file input element", () => {
|
|
78
|
+
renderImageDragAndDrop();
|
|
79
|
+
const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
|
|
80
|
+
expect(fileInput).toBeTruthy();
|
|
81
|
+
const expectedAccept = VALID_IMAGE_FORMATS.map((format) => `.${format}`).join(",");
|
|
82
|
+
expect(fileInput.accept).toBe(expectedAccept);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("should render placeholder when isAllowedToUpload is false", () => {
|
|
86
|
+
renderImageDragAndDrop({ isAllowedToUpload: false });
|
|
87
|
+
expect(screen.getByText("Select an image to see details")).toBeTruthy();
|
|
88
|
+
expect(screen.queryByText("Drag your image here")).toBeFalsy();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("should render Uploading text when isUploading is true", () => {
|
|
92
|
+
renderImageDragAndDrop({}, { gallery: { isUploading: true, isSuccess: false, isError: false, errorMsg: "" } });
|
|
93
|
+
expect(screen.getByText("Uploading...")).toBeTruthy();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("should render Image loaded text when isSuccess is true", () => {
|
|
97
|
+
renderImageDragAndDrop({}, { gallery: { isUploading: false, isSuccess: true, isError: false, errorMsg: "" } });
|
|
98
|
+
expect(screen.getByText("Image loaded!")).toBeTruthy();
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("should render error message when isError is true", () => {
|
|
102
|
+
renderImageDragAndDrop(
|
|
103
|
+
{},
|
|
104
|
+
{ gallery: { isUploading: false, isSuccess: false, isError: true, errorMsg: "Upload failed" } },
|
|
105
|
+
);
|
|
106
|
+
expect(screen.getByText("Error uploading image")).toBeTruthy();
|
|
107
|
+
expect(screen.getByText("Upload failed")).toBeTruthy();
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe("ImageDragAndDrop crop flow", () => {
|
|
112
|
+
it("should NOT render CropStep initially when withCrop is true", () => {
|
|
113
|
+
renderImageDragAndDrop({ withCrop: true });
|
|
114
|
+
expect(screen.queryByTestId("cropper-mock")).toBeFalsy();
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("should support withCrop prop - renders drag area for crop workflow", () => {
|
|
118
|
+
renderImageDragAndDrop({ withCrop: true });
|
|
119
|
+
// Verify component is rendered and ready for crop workflow
|
|
120
|
+
const dragArea = screen.getByTestId("file-drag-and-drop-wrapper");
|
|
121
|
+
expect(dragArea).toBeTruthy();
|
|
122
|
+
// CropStep will appear after a valid file is selected and read as data URL
|
|
123
|
+
expect(screen.queryByTestId("cropper-mock")).toBeFalsy();
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
describe("ImageDragAndDrop file validation", () => {
|
|
128
|
+
it("should accept multiple files when maxImages is not set", () => {
|
|
129
|
+
renderImageDragAndDrop();
|
|
130
|
+
const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
|
|
131
|
+
expect(fileInput.multiple).toBe(true);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("should not allow multiple files when maxImages is 1", () => {
|
|
135
|
+
renderImageDragAndDrop({ maxImages: 1 });
|
|
136
|
+
const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
|
|
137
|
+
expect(fileInput.multiple).toBe(false);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("should allow multiple files when maxImages is greater than 1", () => {
|
|
141
|
+
renderImageDragAndDrop({ maxImages: 5 });
|
|
142
|
+
const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
|
|
143
|
+
expect(fileInput.multiple).toBe(true);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
describe("ImageDragAndDrop TRY AGAIN", () => {
|
|
148
|
+
it("should render TRY AGAIN button", () => {
|
|
149
|
+
renderImageDragAndDrop();
|
|
150
|
+
expect(screen.getByText("TRY AGAIN")).toBeTruthy();
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("should be clickable", () => {
|
|
154
|
+
renderImageDragAndDrop();
|
|
155
|
+
const tryAgainButton = screen.getByText("TRY AGAIN");
|
|
156
|
+
fireEvent.click(tryAgainButton);
|
|
157
|
+
expect(tryAgainButton).toBeTruthy();
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
describe("ImageDragAndDrop inverse theme", () => {
|
|
162
|
+
it("should render with normal button style by default", () => {
|
|
163
|
+
renderImageDragAndDrop();
|
|
164
|
+
const selectButton = screen.getByText("Select images").closest("button");
|
|
165
|
+
expect(selectButton?.className).not.toContain("inverse");
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it("should render with inverse button style when inverse prop is true", () => {
|
|
169
|
+
renderImageDragAndDrop({ inverse: true });
|
|
170
|
+
const selectButton = screen.getByText("Select images").closest("button");
|
|
171
|
+
expect(selectButton?.className).toContain("inverse");
|
|
172
|
+
});
|
|
173
|
+
});
|
|
@@ -71,8 +71,7 @@ describe("KeywordsPreviewModal component rendering", () => {
|
|
|
71
71
|
renderComponent({ ...defaultProps, isOpen: true, keywordsFilter: ["seo"] });
|
|
72
72
|
|
|
73
73
|
expect(screen.getByText("Show keyword:")).toBeTruthy();
|
|
74
|
-
|
|
75
|
-
expect(seoElements.length).toBeGreaterThan(0);
|
|
74
|
+
expect(screen.getByText("seo")).toBeTruthy();
|
|
76
75
|
});
|
|
77
76
|
|
|
78
77
|
it("should not render the filter section when keywordsFilter is empty", () => {
|
|
@@ -88,8 +87,8 @@ describe("KeywordsPreviewModal component events", () => {
|
|
|
88
87
|
|
|
89
88
|
renderComponent({ ...defaultProps, isOpen: true, toggleModal: toggleModalMock });
|
|
90
89
|
|
|
91
|
-
const
|
|
92
|
-
fireEvent.click(
|
|
90
|
+
const closeButton = screen.getByTestId("icon-action-component");
|
|
91
|
+
fireEvent.click(closeButton);
|
|
93
92
|
expect(toggleModalMock).toBeCalled();
|
|
94
93
|
});
|
|
95
94
|
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
import ProfileImage from "@ax/components/ProfileImage";
|
|
4
|
+
import { parseTheme } from "@ax/helpers";
|
|
5
|
+
import globalTheme from "@ax/themes/theme.json";
|
|
6
|
+
|
|
7
|
+
import { cleanup, fireEvent, render, screen } from "@testing-library/react";
|
|
8
|
+
import { ThemeProvider } from "styled-components";
|
|
9
|
+
|
|
10
|
+
jest.mock(
|
|
11
|
+
"@ax/components/ImageDragAndDrop",
|
|
12
|
+
() => (props: any) =>
|
|
13
|
+
React.createElement(
|
|
14
|
+
"div",
|
|
15
|
+
{
|
|
16
|
+
"data-testid": "image-drag-and-drop-mock",
|
|
17
|
+
onClick: () => props.handleUpload([{ id: 1, url: "http://example.com/image.jpg" }]),
|
|
18
|
+
},
|
|
19
|
+
React.createElement("button", { type: "button" }, "Upload Mock"),
|
|
20
|
+
),
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
afterEach(cleanup);
|
|
24
|
+
|
|
25
|
+
const renderProfileImage = (props = {}) =>
|
|
26
|
+
render(
|
|
27
|
+
<ThemeProvider theme={parseTheme(globalTheme)}>
|
|
28
|
+
<ProfileImage handleImage={jest.fn()} size={96} {...props} />
|
|
29
|
+
</ThemeProvider>,
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
describe("ProfileImage rendering", () => {
|
|
33
|
+
it("should render profile-image-wrapper testid", () => {
|
|
34
|
+
renderProfileImage();
|
|
35
|
+
expect(screen.getByTestId("profile-image-wrapper")).toBeTruthy();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("should render placeholder icon when no imageUrl is provided", () => {
|
|
39
|
+
renderProfileImage({ imageUrl: undefined });
|
|
40
|
+
const wrapper = screen.getByTestId("profile-image-wrapper");
|
|
41
|
+
expect(wrapper.querySelector("svg")).toBeTruthy();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("should render image when imageUrl is provided", () => {
|
|
45
|
+
renderProfileImage({ imageUrl: "http://example.com/profile.jpg" });
|
|
46
|
+
const wrapper = screen.getByTestId("profile-image-wrapper");
|
|
47
|
+
const img = wrapper.querySelector("img");
|
|
48
|
+
expect(img).toBeTruthy();
|
|
49
|
+
expect(img?.getAttribute("src")).toContain("profile.jpg");
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("should render Edit Avatar text in overlay", () => {
|
|
53
|
+
renderProfileImage();
|
|
54
|
+
expect(screen.getByText("Edit Avatar")).toBeTruthy();
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe("ProfileImage modal", () => {
|
|
59
|
+
it("should open modal when profile-image-wrapper is clicked", () => {
|
|
60
|
+
renderProfileImage();
|
|
61
|
+
const wrapper = screen.getByTestId("profile-image-wrapper");
|
|
62
|
+
fireEvent.click(wrapper);
|
|
63
|
+
expect(screen.getByText("Upload Media")).toBeTruthy();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("should close modal when profile-image-wrapper is clicked again", () => {
|
|
67
|
+
renderProfileImage();
|
|
68
|
+
const wrapper = screen.getByTestId("profile-image-wrapper");
|
|
69
|
+
|
|
70
|
+
fireEvent.click(wrapper);
|
|
71
|
+
expect(screen.getByText("Upload Media")).toBeTruthy();
|
|
72
|
+
|
|
73
|
+
fireEvent.click(wrapper);
|
|
74
|
+
expect(screen.queryByText("Upload Media")).toBeFalsy();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("should render ImageDragAndDrop inside modal when open", () => {
|
|
78
|
+
renderProfileImage();
|
|
79
|
+
const wrapper = screen.getByTestId("profile-image-wrapper");
|
|
80
|
+
fireEvent.click(wrapper);
|
|
81
|
+
|
|
82
|
+
expect(screen.getByText("Upload Media")).toBeTruthy();
|
|
83
|
+
expect(screen.getByTestId("image-drag-and-drop-mock")).toBeTruthy();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("should not render ImageDragAndDrop when modal is closed", () => {
|
|
87
|
+
renderProfileImage();
|
|
88
|
+
expect(screen.queryByTestId("image-drag-and-drop-mock")).toBeFalsy();
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe("ProfileImage upload", () => {
|
|
93
|
+
it("should call handleImage when image is uploaded", () => {
|
|
94
|
+
const handleImage = jest.fn();
|
|
95
|
+
renderProfileImage({ handleImage });
|
|
96
|
+
|
|
97
|
+
const wrapper = screen.getByTestId("profile-image-wrapper");
|
|
98
|
+
fireEvent.click(wrapper);
|
|
99
|
+
|
|
100
|
+
const uploadButton = screen.getByText("Upload Mock");
|
|
101
|
+
fireEvent.click(uploadButton);
|
|
102
|
+
|
|
103
|
+
expect(handleImage).toHaveBeenCalledWith({ id: 1, url: "http://example.com/image.jpg" });
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("should close modal after successful upload", () => {
|
|
107
|
+
const handleImage = jest.fn();
|
|
108
|
+
renderProfileImage({ handleImage });
|
|
109
|
+
|
|
110
|
+
const wrapper = screen.getByTestId("profile-image-wrapper");
|
|
111
|
+
fireEvent.click(wrapper);
|
|
112
|
+
|
|
113
|
+
expect(screen.getByText("Upload Media")).toBeTruthy();
|
|
114
|
+
|
|
115
|
+
const uploadButton = screen.getByText("Upload Mock");
|
|
116
|
+
fireEvent.click(uploadButton);
|
|
117
|
+
|
|
118
|
+
expect(screen.queryByText("Upload Media")).toBeFalsy();
|
|
119
|
+
});
|
|
120
|
+
});
|
|
@@ -10,6 +10,14 @@ import globalTheme from "@ax/themes/theme.json";
|
|
|
10
10
|
|
|
11
11
|
afterEach(cleanup);
|
|
12
12
|
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
global.ResizeObserver = jest.fn().mockImplementation(() => ({
|
|
15
|
+
observe: jest.fn(),
|
|
16
|
+
unobserve: jest.fn(),
|
|
17
|
+
disconnect: jest.fn(),
|
|
18
|
+
}));
|
|
19
|
+
});
|
|
20
|
+
|
|
13
21
|
jest.mock("react", () => {
|
|
14
22
|
const originReact = jest.requireActual("react");
|
|
15
23
|
const mUseRef = jest.fn();
|