@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.
Files changed (148) hide show
  1. package/config/jest/reactEasyCropMock.js +15 -0
  2. package/config/jest/reactTimezoneMock.js +13 -0
  3. package/package.json +221 -219
  4. package/public/img/welcome.svg +127 -0
  5. package/src/__tests__/components/Browser/Browser.test.tsx +27 -51
  6. package/src/__tests__/components/CategoryCell/CategoryCell.test.tsx +10 -5
  7. package/src/__tests__/components/ElementsTooltip/ElementsTooltip.test.tsx +27 -14
  8. package/src/__tests__/components/HeadingsPreviewModal/ErrorsBanner/ErrorItem/ErrorItem.test.tsx +2 -0
  9. package/src/__tests__/components/HeadingsPreviewModal/HeadingsPreviewModal.utils.test.tsx +138 -1
  10. package/src/__tests__/components/ImageDragAndDrop/CropStep/CropStep.test.tsx +84 -0
  11. package/src/__tests__/components/ImageDragAndDrop/ImageDragAndDrop.test.tsx +173 -0
  12. package/src/__tests__/components/KeywordsPreviewModal/KeywordsPreviewModal.test.tsx +3 -4
  13. package/src/__tests__/components/ProfileImage/ProfileImage.test.tsx +120 -0
  14. package/src/__tests__/components/ResizePanel/ResizePanel.test.tsx +8 -0
  15. package/src/__tests__/components/UserRolesAndSites/RoleItem/RoleItem.test.tsx +190 -0
  16. package/src/__tests__/components/UserRolesAndSites/UserRolesAndSites.test.tsx +471 -0
  17. package/src/__tests__/modules/FramePreview/HeadingsOverlay/HeadingsOverlay.test.tsx +15 -2
  18. package/src/__tests__/modules/Sites/Sites.test.tsx +68 -224
  19. package/src/__tests__/modules/Sites/SitesList/ListView/BulkHeader/BulkHeader.test.tsx +21 -17
  20. package/src/__tests__/modules/Sites/SitesList/SitesList.test.tsx +65 -565
  21. package/src/__tests__/modules/Sites/SitesList/WelcomeModal/DataStep/DataStep.test.tsx +109 -0
  22. package/src/__tests__/modules/Sites/SitesList/WelcomeModal/FinalStep/FinalStep.test.tsx +157 -0
  23. package/src/__tests__/modules/Sites/SitesList/WelcomeModal/ImageStep/CropView/CropView.test.tsx +51 -0
  24. package/src/__tests__/modules/Sites/SitesList/WelcomeModal/ImageStep/ImageStep.test.tsx +70 -0
  25. package/src/__tests__/modules/Sites/SitesList/WelcomeModal/ImageStep/UploadView/UploadView.test.tsx +92 -0
  26. package/src/__tests__/modules/Sites/SitesList/WelcomeModal/TimezoneStep/TimezoneStep.test.tsx +94 -0
  27. package/src/__tests__/modules/Sites/SitesList/WelcomeModal/WelcomeModal.test.tsx +78 -0
  28. package/src/__tests__/modules/Sites/SitesList/WelcomeModal/WelcomeStep/WelcomeStep.test.tsx +39 -0
  29. package/src/__tests__/modules/Sites/SitesList/WelcomeModal/utils.test.ts +55 -0
  30. package/src/api/sites.tsx +4 -4
  31. package/src/components/Avatar/index.tsx +26 -5
  32. package/src/components/Avatar/style.tsx +20 -10
  33. package/src/components/Browser/index.tsx +7 -1
  34. package/src/components/ConfigPanel/index.tsx +11 -7
  35. package/src/components/ElementsTooltip/index.tsx +96 -34
  36. package/src/components/ElementsTooltip/style.tsx +12 -1
  37. package/src/components/Fields/FileField/index.tsx +16 -18
  38. package/src/components/Fields/HeadingField/index.tsx +1 -1
  39. package/src/components/Fields/ImageField/index.tsx +9 -38
  40. package/src/components/Fields/ImageField/style.tsx +12 -1
  41. package/src/components/Fields/ToggleField/index.tsx +1 -1
  42. package/src/components/Fields/Wysiwyg/index.tsx +25 -20
  43. package/src/components/FileGallery/GalleryPanel/index.tsx +15 -7
  44. package/src/components/FileGallery/index.tsx +33 -28
  45. package/src/components/Gallery/GalleryPanel/index.tsx +5 -16
  46. package/src/components/Gallery/index.tsx +0 -2
  47. package/src/components/HeadingsPreviewModal/ErrorsBanner/ErrorItem/index.tsx +11 -2
  48. package/src/components/HeadingsPreviewModal/ErrorsBanner/index.tsx +21 -3
  49. package/src/components/HeadingsPreviewModal/ErrorsBanner/style.tsx +2 -2
  50. package/src/components/HeadingsPreviewModal/index.tsx +13 -3
  51. package/src/components/HeadingsPreviewModal/style.tsx +18 -0
  52. package/src/components/HeadingsPreviewModal/utils.tsx +31 -3
  53. package/src/components/Image/index.tsx +2 -2
  54. package/src/components/ImageDragAndDrop/CropStep/index.tsx +95 -0
  55. package/src/components/ImageDragAndDrop/CropStep/style.tsx +101 -0
  56. package/src/{modules/MediaGallery → components}/ImageDragAndDrop/index.tsx +103 -40
  57. package/src/{modules/MediaGallery → components}/ImageDragAndDrop/style.tsx +14 -2
  58. package/src/components/KeywordsPreviewModal/atoms.tsx +2 -2
  59. package/src/components/KeywordsPreviewModal/index.tsx +6 -6
  60. package/src/components/KeywordsPreviewModal/utils.tsx +2 -2
  61. package/src/components/ProfileImage/index.tsx +55 -0
  62. package/src/components/ProfileImage/style.tsx +58 -0
  63. package/src/components/ResizePanel/ResizeHandle/index.tsx +44 -6
  64. package/src/components/ResizePanel/ResizeHandle/style.tsx +7 -0
  65. package/src/components/ResizePanel/index.tsx +25 -4
  66. package/src/components/Tabs/style.tsx +1 -1
  67. package/src/components/Tag/index.tsx +0 -1
  68. package/src/components/UserRolesAndSites/RoleItem/index.tsx +42 -0
  69. package/src/components/UserRolesAndSites/RoleItem/style.tsx +29 -0
  70. package/src/components/UserRolesAndSites/index.tsx +102 -0
  71. package/src/components/UserRolesAndSites/style.tsx +67 -0
  72. package/src/components/index.tsx +6 -0
  73. package/src/constants/index.ts +13 -1
  74. package/src/containers/App/actions.tsx +8 -1
  75. package/src/containers/Sites/actions.tsx +26 -0
  76. package/src/containers/Sites/constants.tsx +1 -0
  77. package/src/containers/Sites/interfaces.tsx +6 -0
  78. package/src/containers/Sites/reducer.tsx +5 -1
  79. package/src/containers/Users/reducer.tsx +6 -5
  80. package/src/guards/routeLeaving/index.tsx +9 -11
  81. package/src/helpers/images.tsx +50 -3
  82. package/src/helpers/index.tsx +2 -1
  83. package/src/hooks/forms.tsx +45 -48
  84. package/src/hooks/index.tsx +2 -1
  85. package/src/hooks/modals.tsx +4 -3
  86. package/src/hooks/window.ts +50 -2
  87. package/src/modules/ActivityLog/ItemLogUser/UserItem/index.tsx +1 -1
  88. package/src/modules/App/Routing/Logout/index.tsx +3 -5
  89. package/src/modules/App/Routing/NavMenu/NavItem/index.tsx +73 -52
  90. package/src/modules/App/Routing/NavMenu/NavItem/style.tsx +21 -7
  91. package/src/modules/App/Routing/NavMenu/index.tsx +59 -54
  92. package/src/modules/App/Routing/NavMenu/style.tsx +13 -11
  93. package/src/modules/CreatePass/index.tsx +1 -1
  94. package/src/modules/FileDrive/FileDragAndDrop/index.tsx +11 -8
  95. package/src/modules/FileDrive/FileModal/index.tsx +8 -9
  96. package/src/modules/FileDrive/index.tsx +1 -18
  97. package/src/modules/Forms/FormEditor/index.tsx +1 -1
  98. package/src/modules/FramePreview/HeadingsOverlay/index.tsx +22 -11
  99. package/src/modules/FramePreview/HeadingsOverlay/style.tsx +1 -1
  100. package/src/modules/MediaGallery/ImageModal/index.tsx +1 -5
  101. package/src/modules/MediaGallery/index.tsx +1 -3
  102. package/src/modules/Settings/Globals/constants.tsx +942 -106
  103. package/src/modules/Sites/SitesList/AllSitesHeader/index.tsx +33 -0
  104. package/src/modules/Sites/SitesList/AllSitesHeader/style.tsx +35 -0
  105. package/src/modules/Sites/SitesList/GridView/GridHeaderFilter/index.tsx +5 -5
  106. package/src/modules/Sites/SitesList/GridView/GridSiteItem/index.tsx +23 -119
  107. package/src/modules/Sites/SitesList/ListView/BulkHeader/TableHeader/index.tsx +4 -4
  108. package/src/modules/Sites/SitesList/ListView/BulkHeader/index.tsx +4 -3
  109. package/src/modules/Sites/SitesList/ListView/ListSiteItem/index.tsx +23 -120
  110. package/src/modules/Sites/SitesList/{RecentSiteItem → RecentSites/RecentSiteItem}/index.tsx +4 -5
  111. package/src/modules/Sites/SitesList/RecentSites/index.tsx +49 -0
  112. package/src/modules/Sites/SitesList/RecentSites/style.tsx +92 -0
  113. package/src/modules/Sites/SitesList/SiteModal/index.tsx +8 -7
  114. package/src/modules/Sites/SitesList/WelcomeModal/DataStep/index.tsx +72 -0
  115. package/src/modules/Sites/SitesList/WelcomeModal/DataStep/style.tsx +59 -0
  116. package/src/modules/Sites/SitesList/WelcomeModal/FinalStep/constants.tsx +78 -0
  117. package/src/modules/Sites/SitesList/WelcomeModal/FinalStep/index.tsx +78 -0
  118. package/src/modules/Sites/SitesList/WelcomeModal/FinalStep/style.tsx +141 -0
  119. package/src/modules/Sites/SitesList/WelcomeModal/ImageStep/CropView/index.tsx +93 -0
  120. package/src/modules/Sites/SitesList/WelcomeModal/ImageStep/CropView/style.tsx +77 -0
  121. package/src/modules/Sites/SitesList/WelcomeModal/ImageStep/UploadView/index.tsx +100 -0
  122. package/src/modules/Sites/SitesList/WelcomeModal/ImageStep/UploadView/style.tsx +94 -0
  123. package/src/modules/Sites/SitesList/WelcomeModal/ImageStep/index.tsx +44 -0
  124. package/src/modules/Sites/SitesList/WelcomeModal/ImageStep/style.tsx +31 -0
  125. package/src/modules/Sites/SitesList/WelcomeModal/TimezoneStep/index.tsx +51 -0
  126. package/src/modules/Sites/SitesList/WelcomeModal/TimezoneStep/style.tsx +52 -0
  127. package/src/modules/Sites/SitesList/WelcomeModal/WelcomeStep/index.tsx +40 -0
  128. package/src/modules/Sites/SitesList/WelcomeModal/WelcomeStep/style.tsx +53 -0
  129. package/src/modules/Sites/SitesList/WelcomeModal/index.tsx +215 -0
  130. package/src/modules/Sites/SitesList/WelcomeModal/style.tsx +12 -0
  131. package/src/modules/Sites/SitesList/WelcomeModal/utils.ts +26 -0
  132. package/src/modules/Sites/SitesList/atoms.tsx +4 -4
  133. package/src/modules/Sites/SitesList/hooks.tsx +149 -16
  134. package/src/modules/Sites/SitesList/index.tsx +127 -125
  135. package/src/modules/Sites/SitesList/style.tsx +1 -117
  136. package/src/modules/Sites/SitesList/utils.tsx +9 -2
  137. package/src/modules/Sites/index.tsx +19 -8
  138. package/src/modules/Users/Profile/index.tsx +169 -31
  139. package/src/modules/Users/Profile/style.tsx +81 -1
  140. package/src/modules/Users/Roles/RoleItem/index.tsx +2 -2
  141. package/src/modules/Users/UserCreate/SiteItem/index.tsx +11 -14
  142. package/src/modules/Users/UserForm/atoms.tsx +3 -3
  143. package/src/modules/Users/UserForm/index.tsx +25 -29
  144. package/src/modules/Users/UserForm/style.tsx +15 -2
  145. package/src/modules/Users/UserList/UserItem/index.tsx +4 -4
  146. package/src/routes/index.tsx +1 -0
  147. package/src/types/index.tsx +2 -0
  148. /package/src/modules/Sites/SitesList/{RecentSiteItem → RecentSites/RecentSiteItem}/style.tsx +0 -0
@@ -0,0 +1,190 @@
1
+ import RoleItem from "@ax/components/UserRolesAndSites/RoleItem";
2
+ import { parseTheme } from "@ax/helpers";
3
+ import globalTheme from "@ax/themes/theme.json";
4
+ import type { IRole } from "@ax/types";
5
+
6
+ import { cleanup, render, screen } from "@testing-library/react";
7
+ import { ThemeProvider } from "styled-components";
8
+
9
+ afterEach(cleanup);
10
+
11
+ const mockRole = (overrides?: Partial<IRole>): IRole => ({
12
+ id: 1,
13
+ name: "Admin",
14
+ hex: "#5057FF",
15
+ description: "Admin role",
16
+ permissions: {
17
+ totalPermissions: "10",
18
+ sitePermissions: [],
19
+ globalPermissions: [],
20
+ },
21
+ active: true,
22
+ users: null,
23
+ editable: true,
24
+ ...overrides,
25
+ });
26
+
27
+ describe("RoleItem Component", () => {
28
+ describe("Rendering", () => {
29
+ it("should render title", () => {
30
+ const props = {
31
+ title: "Global Permissions",
32
+ description: "You can access to Global data",
33
+ roles: [],
34
+ };
35
+
36
+ render(
37
+ <ThemeProvider theme={parseTheme(globalTheme)}>
38
+ <RoleItem {...props} />
39
+ </ThemeProvider>,
40
+ );
41
+
42
+ expect(screen.getByText("Global Permissions")).toBeTruthy();
43
+ });
44
+
45
+ it("should render description", () => {
46
+ const props = {
47
+ title: "Global Permissions",
48
+ description: "You can access to Global data",
49
+ roles: [],
50
+ };
51
+
52
+ render(
53
+ <ThemeProvider theme={parseTheme(globalTheme)}>
54
+ <RoleItem {...props} />
55
+ </ThemeProvider>,
56
+ );
57
+
58
+ expect(screen.getByText("You can access to Global data")).toBeTruthy();
59
+ });
60
+
61
+ it("should render with multiple roles", () => {
62
+ const roles = [
63
+ mockRole({ id: 1, name: "Editor", hex: "#5057FF" }),
64
+ mockRole({ id: 2, name: "Viewer", hex: "#FF0000" }),
65
+ ];
66
+ const props = {
67
+ title: "Site Permissions",
68
+ description: "You have the following roles",
69
+ roles,
70
+ };
71
+
72
+ render(
73
+ <ThemeProvider theme={parseTheme(globalTheme)}>
74
+ <RoleItem {...props} />
75
+ </ThemeProvider>,
76
+ );
77
+
78
+ expect(screen.getByText("Site Permissions")).toBeTruthy();
79
+ expect(screen.getByText("You have the following roles")).toBeTruthy();
80
+ });
81
+
82
+ it("should render with empty roles array", () => {
83
+ const props = {
84
+ title: "Empty Roles",
85
+ description: "No roles assigned",
86
+ roles: [],
87
+ };
88
+
89
+ render(
90
+ <ThemeProvider theme={parseTheme(globalTheme)}>
91
+ <RoleItem {...props} />
92
+ </ThemeProvider>,
93
+ );
94
+
95
+ expect(screen.getByText("Empty Roles")).toBeTruthy();
96
+ expect(screen.getByText("No roles assigned")).toBeTruthy();
97
+ });
98
+ });
99
+
100
+ describe("Super Admin Mode", () => {
101
+ it("should render Super-admin label when isSuperAdmin is true", () => {
102
+ const props = {
103
+ title: "You are a Super admin",
104
+ description: "You have full access to all features",
105
+ roles: [],
106
+ isSuperAdmin: true,
107
+ };
108
+
109
+ render(
110
+ <ThemeProvider theme={parseTheme(globalTheme)}>
111
+ <RoleItem {...props} />
112
+ </ThemeProvider>,
113
+ );
114
+
115
+ expect(screen.getByText("You are a Super admin")).toBeTruthy();
116
+ });
117
+
118
+ it("should not render regular roles when isSuperAdmin is true", () => {
119
+ const roles = [mockRole({ id: 1, name: "Editor" })];
120
+ const props = {
121
+ title: "You are a Super admin",
122
+ description: "You have full access to all features",
123
+ roles,
124
+ isSuperAdmin: true,
125
+ };
126
+
127
+ render(
128
+ <ThemeProvider theme={parseTheme(globalTheme)}>
129
+ <RoleItem {...props} />
130
+ </ThemeProvider>,
131
+ );
132
+
133
+ expect(screen.getByText("You are a Super admin")).toBeTruthy();
134
+ });
135
+ });
136
+
137
+ describe("Edge Cases", () => {
138
+ it("should handle single role", () => {
139
+ const roles = [mockRole({ id: 1, name: "Admin", hex: "#5057FF" })];
140
+ const props = {
141
+ title: "Your Role",
142
+ description: "Single role assignment",
143
+ roles,
144
+ };
145
+
146
+ render(
147
+ <ThemeProvider theme={parseTheme(globalTheme)}>
148
+ <RoleItem {...props} />
149
+ </ThemeProvider>,
150
+ );
151
+
152
+ expect(screen.getByText("Your Role")).toBeTruthy();
153
+ });
154
+
155
+ it("should handle many roles", () => {
156
+ const roles = Array.from({ length: 10 }, (_, i) => mockRole({ id: i + 1, name: `Role ${i + 1}` }));
157
+ const props = {
158
+ title: "Many Roles",
159
+ description: "Multiple role assignment",
160
+ roles,
161
+ };
162
+
163
+ render(
164
+ <ThemeProvider theme={parseTheme(globalTheme)}>
165
+ <RoleItem {...props} />
166
+ </ThemeProvider>,
167
+ );
168
+
169
+ expect(screen.getByText("Many Roles")).toBeTruthy();
170
+ });
171
+
172
+ it("should default isSuperAdmin to false when not provided", () => {
173
+ const roles = [mockRole({ id: 1, name: "Editor" })];
174
+ const props = {
175
+ title: "Editor Access",
176
+ description: "You are an editor",
177
+ roles,
178
+ // isSuperAdmin not provided
179
+ };
180
+
181
+ render(
182
+ <ThemeProvider theme={parseTheme(globalTheme)}>
183
+ <RoleItem {...props} />
184
+ </ThemeProvider>,
185
+ );
186
+
187
+ expect(screen.getByText("Editor Access")).toBeTruthy();
188
+ });
189
+ });
190
+ });
@@ -0,0 +1,471 @@
1
+ import UserRolesAndSites from "@ax/components/UserRolesAndSites";
2
+ import { parseTheme } from "@ax/helpers";
3
+ import globalTheme from "@ax/themes/theme.json";
4
+ import type { IRole, ISite, ISiteRoles } from "@ax/types";
5
+
6
+ import { cleanup, render, screen } from "@testing-library/react";
7
+ import { ThemeProvider } from "styled-components";
8
+
9
+ afterEach(cleanup);
10
+
11
+ const mockRole = (overrides?: Partial<IRole>): IRole => ({
12
+ id: 1,
13
+ name: "Admin",
14
+ hex: "#5057FF",
15
+ description: "Admin role",
16
+ permissions: {
17
+ totalPermissions: "10",
18
+ sitePermissions: [],
19
+ globalPermissions: [],
20
+ },
21
+ active: true,
22
+ users: null,
23
+ editable: true,
24
+ ...overrides,
25
+ });
26
+
27
+ const mockSite = (overrides?: Partial<ISite>): ISite => ({
28
+ author: "Test Author",
29
+ id: 1,
30
+ name: "Test Site",
31
+ published: "2024-01-01",
32
+ isPublished: true,
33
+ updated: false,
34
+ theme: "default",
35
+ defaultLanguage: null,
36
+ path: "/test",
37
+ timezone: "UTC",
38
+ favicon: "",
39
+ smallAvatar: "",
40
+ bigAvatar: "",
41
+ thumbnail: "",
42
+ domain: null,
43
+ navigationModules: {},
44
+ pages: [],
45
+ ...overrides,
46
+ });
47
+
48
+ const mockSiteRole = (siteId: number | string, roles: number[]): ISiteRoles => ({
49
+ siteId,
50
+ roles,
51
+ });
52
+
53
+ describe("UserRolesAndSites Component", () => {
54
+ describe("Super Admin", () => {
55
+ it("should render super admin message when isSuperAdmin is true", () => {
56
+ const props = {
57
+ isSuperAdmin: true,
58
+ userRoles: [],
59
+ sites: [],
60
+ roles: [],
61
+ };
62
+
63
+ render(
64
+ <ThemeProvider theme={parseTheme(globalTheme)}>
65
+ <UserRolesAndSites {...props} />
66
+ </ThemeProvider>,
67
+ );
68
+
69
+ const superAdminSection = screen.getByTestId("final-step-super-admin");
70
+ expect(superAdminSection).toBeTruthy();
71
+ expect(superAdminSection.textContent).toContain("You are a Super admin");
72
+ });
73
+ });
74
+
75
+ describe("Global Roles", () => {
76
+ it("should render global roles section when user has global role", () => {
77
+ const role = mockRole({ id: 1, name: "Editor" });
78
+ const props = {
79
+ isSuperAdmin: false,
80
+ userRoles: [mockSiteRole("global", [1])],
81
+ sites: [],
82
+ roles: [role],
83
+ };
84
+
85
+ render(
86
+ <ThemeProvider theme={parseTheme(globalTheme)}>
87
+ <UserRolesAndSites {...props} />
88
+ </ThemeProvider>,
89
+ );
90
+
91
+ const globalRoleSection = screen.getByTestId("final-step-global-role");
92
+ expect(globalRoleSection).toBeTruthy();
93
+ expect(globalRoleSection.textContent).toContain("Global data permissions");
94
+ });
95
+
96
+ it("should not render global roles section when user has no global role", () => {
97
+ const props = {
98
+ isSuperAdmin: false,
99
+ userRoles: [],
100
+ sites: [],
101
+ roles: [],
102
+ };
103
+
104
+ render(
105
+ <ThemeProvider theme={parseTheme(globalTheme)}>
106
+ <UserRolesAndSites {...props} />
107
+ </ThemeProvider>,
108
+ );
109
+
110
+ const globalRoleSection = screen.queryByTestId("final-step-global-role");
111
+ expect(globalRoleSection).toBeFalsy();
112
+ });
113
+ });
114
+
115
+ describe("Site-Specific Roles", () => {
116
+ it("should render sites content when user has site-specific roles", () => {
117
+ const role = mockRole({ id: 1, name: "Editor" });
118
+ const site = mockSite({ id: 1, name: "Test Site" });
119
+ const props = {
120
+ isSuperAdmin: false,
121
+ userRoles: [mockSiteRole(1, [1])],
122
+ sites: [site],
123
+ roles: [role],
124
+ };
125
+
126
+ render(
127
+ <ThemeProvider theme={parseTheme(globalTheme)}>
128
+ <UserRolesAndSites {...props} />
129
+ </ThemeProvider>,
130
+ );
131
+
132
+ const sitesContent = screen.getByTestId("final-step-sites-content");
133
+ expect(sitesContent).toBeTruthy();
134
+ expect(sitesContent.textContent).toContain("Test Site");
135
+ });
136
+
137
+ it("should display multiple site cards with role colors", () => {
138
+ const roles = [
139
+ mockRole({ id: 1, name: "Editor", hex: "#5057FF" }),
140
+ mockRole({ id: 2, name: "Viewer", hex: "#FF0000" }),
141
+ ];
142
+ const sites = [mockSite({ id: 1, name: "Site One" }), mockSite({ id: 2, name: "Site Two" })];
143
+ const props = {
144
+ isSuperAdmin: false,
145
+ userRoles: [mockSiteRole(1, [1, 2]), mockSiteRole(2, [1])],
146
+ sites,
147
+ roles,
148
+ };
149
+
150
+ render(
151
+ <ThemeProvider theme={parseTheme(globalTheme)}>
152
+ <UserRolesAndSites {...props} />
153
+ </ThemeProvider>,
154
+ );
155
+
156
+ expect(screen.getByText("Site One")).toBeTruthy();
157
+ expect(screen.getByText("Site Two")).toBeTruthy();
158
+ });
159
+
160
+ it("should not render sites when user is super admin", () => {
161
+ const role = mockRole({ id: 1, name: "Editor" });
162
+ const site = mockSite({ id: 1, name: "Test Site" });
163
+ const props = {
164
+ isSuperAdmin: true,
165
+ userRoles: [mockSiteRole(1, [1])],
166
+ sites: [site],
167
+ roles: [role],
168
+ };
169
+
170
+ render(
171
+ <ThemeProvider theme={parseTheme(globalTheme)}>
172
+ <UserRolesAndSites {...props} />
173
+ </ThemeProvider>,
174
+ );
175
+
176
+ const sitesContent = screen.queryByTestId("final-step-sites-content");
177
+ expect(sitesContent).toBeFalsy();
178
+ });
179
+
180
+ it("should filter out sites that do not exist in the sites list", () => {
181
+ const role = mockRole({ id: 1, name: "Editor" });
182
+ const site = mockSite({ id: 1, name: "Existing Site" });
183
+ const props = {
184
+ isSuperAdmin: false,
185
+ userRoles: [mockSiteRole(1, [1]), mockSiteRole(999, [1])],
186
+ sites: [site],
187
+ roles: [role],
188
+ };
189
+
190
+ render(
191
+ <ThemeProvider theme={parseTheme(globalTheme)}>
192
+ <UserRolesAndSites {...props} />
193
+ </ThemeProvider>,
194
+ );
195
+
196
+ expect(screen.getByText("Existing Site")).toBeTruthy();
197
+ });
198
+ });
199
+
200
+ describe("All Sites Access", () => {
201
+ it("should render 'Access to All Sites' section when user has all sites role", () => {
202
+ const role = mockRole({ id: 1, name: "Editor" });
203
+ const props = {
204
+ isSuperAdmin: false,
205
+ userRoles: [mockSiteRole("all", [1])],
206
+ sites: [],
207
+ roles: [role],
208
+ };
209
+
210
+ render(
211
+ <ThemeProvider theme={parseTheme(globalTheme)}>
212
+ <UserRolesAndSites {...props} />
213
+ </ThemeProvider>,
214
+ );
215
+
216
+ const sitesContent = screen.getByTestId("final-step-sites-content");
217
+ expect(sitesContent).toBeTruthy();
218
+ expect(sitesContent.textContent).toContain("Access to All Sites");
219
+ });
220
+
221
+ it("should not show Access to Sites header when showRows is true or allRoles exist", () => {
222
+ const role = mockRole({ id: 1, name: "Editor" });
223
+ const props = {
224
+ isSuperAdmin: false,
225
+ userRoles: [mockSiteRole("all", [1])],
226
+ sites: [],
227
+ roles: [role],
228
+ showRows: true,
229
+ };
230
+
231
+ render(
232
+ <ThemeProvider theme={parseTheme(globalTheme)}>
233
+ <UserRolesAndSites {...props} />
234
+ </ThemeProvider>,
235
+ );
236
+
237
+ const sitesContent = screen.getByTestId("final-step-sites-content");
238
+ expect(sitesContent?.textContent).not.toContain("Access to Sites");
239
+ });
240
+ });
241
+
242
+ describe("ShowRows Behavior", () => {
243
+ it("should show header when showRows is false and no allRoles", () => {
244
+ const role = mockRole({ id: 1, name: "Editor" });
245
+ const site = mockSite({ id: 1, name: "Test Site" });
246
+ const props = {
247
+ isSuperAdmin: false,
248
+ userRoles: [mockSiteRole(1, [1])],
249
+ sites: [site],
250
+ roles: [role],
251
+ showRows: false,
252
+ };
253
+
254
+ render(
255
+ <ThemeProvider theme={parseTheme(globalTheme)}>
256
+ <UserRolesAndSites {...props} />
257
+ </ThemeProvider>,
258
+ );
259
+
260
+ const sitesContent = screen.getByTestId("final-step-sites-content");
261
+ expect(sitesContent.textContent).toContain("Access to Sites");
262
+ });
263
+
264
+ it("should use default showRows logic when prop is not provided", () => {
265
+ const role = mockRole({ id: 1, name: "Editor" });
266
+ const site = mockSite({ id: 1, name: "Test Site" });
267
+ const props = {
268
+ isSuperAdmin: false,
269
+ userRoles: [mockSiteRole(1, [1])],
270
+ sites: [site],
271
+ roles: [role],
272
+ };
273
+
274
+ render(
275
+ <ThemeProvider theme={parseTheme(globalTheme)}>
276
+ <UserRolesAndSites {...props} />
277
+ </ThemeProvider>,
278
+ );
279
+
280
+ const sitesContent = screen.getByTestId("final-step-sites-content");
281
+ expect(sitesContent).toBeTruthy();
282
+ });
283
+
284
+ it("should show header when superAdmin is false and no global role but has site roles", () => {
285
+ const role = mockRole({ id: 1, name: "Editor" });
286
+ const site = mockSite({ id: 1, name: "Test Site" });
287
+ const props = {
288
+ isSuperAdmin: false,
289
+ userRoles: [mockSiteRole(1, [1])],
290
+ sites: [site],
291
+ roles: [role],
292
+ };
293
+
294
+ render(
295
+ <ThemeProvider theme={parseTheme(globalTheme)}>
296
+ <UserRolesAndSites {...props} />
297
+ </ThemeProvider>,
298
+ );
299
+
300
+ const sitesContent = screen.getByTestId("final-step-sites-content");
301
+ expect(sitesContent.textContent).toContain("Access to Sites");
302
+ });
303
+
304
+ it("should not show header when showRows is true", () => {
305
+ const role = mockRole({ id: 1, name: "Editor" });
306
+ const site = mockSite({ id: 1, name: "Test Site" });
307
+ const props = {
308
+ isSuperAdmin: false,
309
+ userRoles: [mockSiteRole(1, [1])],
310
+ sites: [site],
311
+ roles: [role],
312
+ showRows: true,
313
+ };
314
+
315
+ render(
316
+ <ThemeProvider theme={parseTheme(globalTheme)}>
317
+ <UserRolesAndSites {...props} />
318
+ </ThemeProvider>,
319
+ );
320
+
321
+ const sitesContent = screen.getByTestId("final-step-sites-content");
322
+ expect(sitesContent.textContent).not.toContain("Access to Sites");
323
+ });
324
+ });
325
+
326
+ describe("Role Rendering with ElementsTooltip", () => {
327
+ it("should render role names in site cards", () => {
328
+ const roles = [
329
+ mockRole({ id: 1, name: "Editor", hex: "#5057FF" }),
330
+ mockRole({ id: 2, name: "Viewer", hex: "#FF0000" }),
331
+ ];
332
+ const site = mockSite({ id: 1, name: "Test Site" });
333
+ const props = {
334
+ isSuperAdmin: false,
335
+ userRoles: [mockSiteRole(1, [1, 2])],
336
+ sites: [site],
337
+ roles,
338
+ };
339
+
340
+ render(
341
+ <ThemeProvider theme={parseTheme(globalTheme)}>
342
+ <UserRolesAndSites {...props} />
343
+ </ThemeProvider>,
344
+ );
345
+
346
+ expect(screen.getByText("Test Site")).toBeTruthy();
347
+ });
348
+
349
+ it("should handle empty roles array in site card", () => {
350
+ const site = mockSite({ id: 1, name: "Test Site" });
351
+ const props = {
352
+ isSuperAdmin: false,
353
+ userRoles: [mockSiteRole(1, [])],
354
+ sites: [site],
355
+ roles: [],
356
+ };
357
+
358
+ render(
359
+ <ThemeProvider theme={parseTheme(globalTheme)}>
360
+ <UserRolesAndSites {...props} />
361
+ </ThemeProvider>,
362
+ );
363
+
364
+ expect(screen.getByText("Test Site")).toBeTruthy();
365
+ });
366
+
367
+ it("should map role ids to role names and colors correctly", () => {
368
+ const roles = [
369
+ mockRole({ id: 1, name: "Editor", hex: "#5057FF" }),
370
+ mockRole({ id: 2, name: "Viewer", hex: "#FF0000" }),
371
+ mockRole({ id: 3, name: "Admin", hex: "#00FF00" }),
372
+ ];
373
+ const site = mockSite({ id: 1, name: "Test Site" });
374
+ const props = {
375
+ isSuperAdmin: false,
376
+ userRoles: [mockSiteRole(1, [1, 3])],
377
+ sites: [site],
378
+ roles,
379
+ };
380
+
381
+ render(
382
+ <ThemeProvider theme={parseTheme(globalTheme)}>
383
+ <UserRolesAndSites {...props} />
384
+ </ThemeProvider>,
385
+ );
386
+
387
+ expect(screen.getByText("Test Site")).toBeTruthy();
388
+ });
389
+ });
390
+
391
+ describe("Edge Cases", () => {
392
+ it("should handle user with no roles at all", () => {
393
+ const props = {
394
+ isSuperAdmin: false,
395
+ userRoles: [],
396
+ sites: [],
397
+ roles: [],
398
+ };
399
+
400
+ render(
401
+ <ThemeProvider theme={parseTheme(globalTheme)}>
402
+ <UserRolesAndSites {...props} />
403
+ </ThemeProvider>,
404
+ );
405
+
406
+ expect(screen.queryByTestId("final-step-sites-content")).toBeFalsy();
407
+ });
408
+
409
+ it("should handle multiple roles per site", () => {
410
+ const roles = [
411
+ mockRole({ id: 1, name: "Editor" }),
412
+ mockRole({ id: 2, name: "Viewer" }),
413
+ mockRole({ id: 3, name: "Manager" }),
414
+ ];
415
+ const site = mockSite({ id: 1, name: "Test Site" });
416
+ const props = {
417
+ isSuperAdmin: false,
418
+ userRoles: [mockSiteRole(1, [1, 2, 3])],
419
+ sites: [site],
420
+ roles,
421
+ };
422
+
423
+ render(
424
+ <ThemeProvider theme={parseTheme(globalTheme)}>
425
+ <UserRolesAndSites {...props} />
426
+ </ThemeProvider>,
427
+ );
428
+
429
+ expect(screen.getByText("Test Site")).toBeTruthy();
430
+ });
431
+
432
+ it("should handle global and all sites roles together", () => {
433
+ const role = mockRole({ id: 1, name: "Editor" });
434
+ const site = mockSite({ id: 1, name: "Test Site" });
435
+ const props = {
436
+ isSuperAdmin: false,
437
+ userRoles: [mockSiteRole("global", [1]), mockSiteRole("all", [1])],
438
+ sites: [site],
439
+ roles: [role],
440
+ };
441
+
442
+ render(
443
+ <ThemeProvider theme={parseTheme(globalTheme)}>
444
+ <UserRolesAndSites {...props} />
445
+ </ThemeProvider>,
446
+ );
447
+
448
+ expect(screen.getByTestId("final-step-global-role")).toBeTruthy();
449
+ expect(screen.getByTestId("final-step-sites-content")).toBeTruthy();
450
+ });
451
+
452
+ it("should not render sites content when super admin has site-specific roles", () => {
453
+ const role = mockRole({ id: 1, name: "Editor" });
454
+ const site = mockSite({ id: 1, name: "Test Site" });
455
+ const props = {
456
+ isSuperAdmin: true,
457
+ userRoles: [mockSiteRole(1, [1])],
458
+ sites: [site],
459
+ roles: [role],
460
+ };
461
+
462
+ render(
463
+ <ThemeProvider theme={parseTheme(globalTheme)}>
464
+ <UserRolesAndSites {...props} />
465
+ </ThemeProvider>,
466
+ );
467
+
468
+ expect(screen.queryByTestId("final-step-sites-content")).toBeFalsy();
469
+ });
470
+ });
471
+ });
@@ -10,10 +10,23 @@ const renderWithTheme = (ui: React.ReactElement) =>
10
10
  render(<ThemeProvider theme={parseTheme(globalTheme)}>{ui}</ThemeProvider>);
11
11
 
12
12
  beforeEach(() => {
13
+ let rafId = 0;
14
+ let processingRaf = false;
15
+
13
16
  jest.spyOn(window, "requestAnimationFrame").mockImplementation((cb) => {
14
- cb(0);
15
- return 0;
17
+ // Avoid infinite recursion by checking if we're already processing
18
+ if (!processingRaf) {
19
+ processingRaf = true;
20
+ try {
21
+ cb(performance.now());
22
+ } finally {
23
+ processingRaf = false;
24
+ }
25
+ }
26
+ return ++rafId;
16
27
  });
28
+
29
+ jest.spyOn(window, "cancelAnimationFrame").mockImplementation(() => {});
17
30
  });
18
31
 
19
32
  afterEach(() => {