@griddo/ax 11.11.8-rc.1 → 11.12.0
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/componentsMock.js +5 -7
- package/package.json +2 -2
- package/src/__tests__/components/Browser/Browser.test.tsx +87 -438
- package/src/__tests__/components/ConfigPanel/ConfigPanel.test.tsx +3 -1
- package/src/__tests__/components/Fields/Button/Button.test.tsx +27 -29
- package/src/__tests__/components/ResizePanel/ResizePanel.test.tsx +1 -1
- package/src/components/Browser/index.tsx +149 -294
- package/src/components/Browser/style.tsx +6 -75
- package/src/components/Button/index.tsx +1 -2
- package/src/components/ConfigPanel/Form/ConnectedField/PageConnectedField/Field/index.tsx +4 -2
- package/src/components/Fields/AsyncSelect/style.tsx +0 -13
- package/src/components/Fields/FieldGroup/index.tsx +2 -5
- package/src/components/Fields/FieldGroup/style.tsx +7 -32
- package/src/components/Fields/HeadingField/index.tsx +2 -2
- package/src/components/Fields/HiddenField/style.tsx +1 -1
- package/src/components/Fields/NumberField/index.tsx +16 -15
- package/src/components/Fields/NumberField/style.tsx +0 -2
- package/src/components/Fields/ReferenceField/index.tsx +1 -1
- package/src/components/Fields/Select/index.tsx +1 -5
- package/src/components/Fields/Select/style.tsx +0 -56
- package/src/components/Fields/SummaryButton/index.tsx +9 -18
- package/src/components/Fields/SummaryButton/style.tsx +2 -1
- package/src/components/Fields/TagsField/index.tsx +9 -8
- package/src/components/Fields/UrlField/index.tsx +27 -26
- package/src/components/Fields/index.tsx +0 -2
- package/src/components/FloatingPanel/index.tsx +2 -5
- package/src/components/FloatingPanel/style.tsx +1 -2
- package/src/components/IconAction/index.tsx +1 -1
- package/src/components/MainWrapper/AppBar/index.tsx +1 -8
- package/src/components/MainWrapper/index.tsx +1 -7
- package/src/components/Notification/index.tsx +2 -2
- package/src/components/PageFinder/index.tsx +1 -1
- package/src/components/ResizePanel/index.tsx +3 -4
- package/src/components/ResizePanel/style.tsx +1 -1
- package/src/components/SearchField/style.tsx +2 -2
- package/src/components/SideModal/index.tsx +1 -2
- package/src/components/Tabs/index.tsx +4 -13
- package/src/components/Tabs/style.tsx +8 -7
- package/src/components/Toast/index.tsx +2 -4
- package/src/components/Tooltip/index.tsx +3 -4
- package/src/components/index.tsx +0 -8
- package/src/forms/fields.tsx +68 -70
- package/src/hooks/forms.tsx +1 -22
- package/src/hooks/index.tsx +3 -13
- package/src/hooks/modals.tsx +15 -103
- package/src/hooks/users.tsx +8 -25
- package/src/modules/Forms/atoms.tsx +2 -2
- package/src/modules/FramePreview/index.tsx +16 -55
- package/src/modules/FramePreview/style.tsx +2 -34
- package/src/modules/GlobalEditor/Editor/index.tsx +3 -37
- package/src/modules/GlobalEditor/PageBrowser/index.tsx +2 -19
- package/src/modules/GlobalEditor/Preview/index.tsx +2 -0
- package/src/modules/GlobalEditor/Preview/style.tsx +1 -1
- package/src/modules/GlobalEditor/index.tsx +57 -119
- package/src/modules/PageEditor/Editor/index.tsx +2 -33
- package/src/modules/PageEditor/PageBrowser/index.tsx +2 -20
- package/src/modules/PageEditor/Preview/index.tsx +2 -0
- package/src/modules/PageEditor/Preview/style.tsx +1 -1
- package/src/modules/PageEditor/atoms.tsx +1 -1
- package/src/modules/PageEditor/index.tsx +66 -130
- package/src/modules/PublicPreview/index.tsx +2 -5
- package/src/schemas/pages/GlobalPage.ts +70 -87
- package/src/schemas/pages/Page.ts +70 -87
- package/src/types/index.tsx +0 -12
- package/src/__tests__/components/Browser/Browser.utils.test.ts +0 -55
- package/src/__tests__/components/HeadingsPreviewModal/ErrorsBanner/ErrorItem/ErrorItem.test.tsx +0 -158
- package/src/__tests__/components/HeadingsPreviewModal/ErrorsBanner/ErrorsBanner.test.tsx +0 -90
- package/src/__tests__/components/HeadingsPreviewModal/HeadingsPreviewModal.test.tsx +0 -178
- package/src/__tests__/components/HeadingsPreviewModal/HeadingsPreviewModal.utils.test.tsx +0 -150
- package/src/__tests__/components/KeywordsPreviewModal/KeywordItem/KeywordItem.test.tsx +0 -91
- package/src/__tests__/components/KeywordsPreviewModal/KeywordsPreviewModal.test.tsx +0 -122
- package/src/__tests__/components/KeywordsPreviewModal/KeywordsPreviewModal.utils.test.ts +0 -15
- package/src/__tests__/components/KeywordsPreviewModal/atoms.test.tsx +0 -101
- package/src/__tests__/modules/FramePreview/FramePreview.test.tsx +0 -318
- package/src/__tests__/modules/FramePreview/FramePreview.utils.test.ts +0 -242
- package/src/__tests__/modules/FramePreview/HeadingsOverlay/HeadingsOverlay.test.tsx +0 -185
- package/src/components/Browser/utils.tsx +0 -13
- package/src/components/Fields/SEOPreview/index.tsx +0 -36
- package/src/components/Fields/SEOPreview/style.tsx +0 -24
- package/src/components/FloatingNote/index.tsx +0 -35
- package/src/components/FloatingNote/style.tsx +0 -26
- package/src/components/HeadingsPreviewModal/ErrorsBanner/ErrorItem/index.tsx +0 -85
- package/src/components/HeadingsPreviewModal/ErrorsBanner/ErrorItem/style.tsx +0 -80
- package/src/components/HeadingsPreviewModal/ErrorsBanner/index.tsx +0 -57
- package/src/components/HeadingsPreviewModal/ErrorsBanner/style.tsx +0 -82
- package/src/components/HeadingsPreviewModal/HeadingItem/index.tsx +0 -71
- package/src/components/HeadingsPreviewModal/HeadingItem/style.tsx +0 -77
- package/src/components/HeadingsPreviewModal/index.tsx +0 -146
- package/src/components/HeadingsPreviewModal/style.tsx +0 -82
- package/src/components/HeadingsPreviewModal/utils.tsx +0 -257
- package/src/components/KeywordsPreviewModal/KeywordItem/index.tsx +0 -46
- package/src/components/KeywordsPreviewModal/KeywordItem/style.tsx +0 -64
- package/src/components/KeywordsPreviewModal/atoms.tsx +0 -96
- package/src/components/KeywordsPreviewModal/index.tsx +0 -99
- package/src/components/KeywordsPreviewModal/style.tsx +0 -87
- package/src/components/KeywordsPreviewModal/utils.tsx +0 -22
- package/src/modules/FramePreview/HeadingsOverlay/index.tsx +0 -113
- package/src/modules/FramePreview/HeadingsOverlay/style.tsx +0 -24
- package/src/modules/FramePreview/utils.tsx +0 -140
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
|
|
3
|
-
import { ThemeProvider } from "styled-components";
|
|
4
|
-
import { render, cleanup, screen, fireEvent } from "@testing-library/react";
|
|
5
|
-
import { parseTheme } from "@ax/helpers";
|
|
6
|
-
import "@testing-library/jest-dom";
|
|
7
|
-
|
|
8
|
-
import type { HeadingNode } from "@ax/types";
|
|
9
|
-
import HeadingsPreviewModal from "@ax/components/HeadingsPreviewModal";
|
|
10
|
-
import * as headingsUtils from "@ax/components/HeadingsPreviewModal/utils";
|
|
11
|
-
import globalTheme from "@ax/themes/theme.json";
|
|
12
|
-
|
|
13
|
-
jest.mock("@ax/components/HeadingsPreviewModal/utils", () => ({
|
|
14
|
-
...jest.requireActual("@ax/components/HeadingsPreviewModal/utils"),
|
|
15
|
-
parseHeadingsTree: jest.fn().mockReturnValue([]),
|
|
16
|
-
}));
|
|
17
|
-
|
|
18
|
-
afterEach(cleanup);
|
|
19
|
-
|
|
20
|
-
const createHeadingNode = (
|
|
21
|
-
tag: HeadingNode["tag"],
|
|
22
|
-
text: string,
|
|
23
|
-
level: HeadingNode["level"],
|
|
24
|
-
children: HeadingNode[] = [],
|
|
25
|
-
): HeadingNode => ({
|
|
26
|
-
tag,
|
|
27
|
-
text,
|
|
28
|
-
level,
|
|
29
|
-
isHidden: false,
|
|
30
|
-
children,
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
const mockHeadings: HeadingNode[] = [
|
|
34
|
-
createHeadingNode("h1", "Main Title", 1, [createHeadingNode("h2", "Subtitle", 2)]),
|
|
35
|
-
];
|
|
36
|
-
|
|
37
|
-
const browserRef = { current: document.createElement("div") } as React.RefObject<HTMLDivElement>;
|
|
38
|
-
|
|
39
|
-
const defaultProps = {
|
|
40
|
-
isOpen: false,
|
|
41
|
-
browserRef: { current: null } as React.RefObject<HTMLDivElement>,
|
|
42
|
-
toggleModal: jest.fn(),
|
|
43
|
-
headingsFilter: "all" as const,
|
|
44
|
-
setHeadingsFilter: jest.fn(),
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
const renderComponent = (props = defaultProps) =>
|
|
48
|
-
render(
|
|
49
|
-
<ThemeProvider theme={parseTheme(globalTheme)}>
|
|
50
|
-
<HeadingsPreviewModal {...props} />
|
|
51
|
-
</ThemeProvider>,
|
|
52
|
-
);
|
|
53
|
-
|
|
54
|
-
describe("HeadingsPreviewModal component rendering", () => {
|
|
55
|
-
it("should not render the headings content when closed", () => {
|
|
56
|
-
renderComponent();
|
|
57
|
-
|
|
58
|
-
expect(screen.queryByText("Show headings:")).toBeNull();
|
|
59
|
-
expect(screen.queryByTestId("wrapper")).toBeNull();
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it("should render the empty state when open with no headings", () => {
|
|
63
|
-
renderComponent({ ...defaultProps, isOpen: true });
|
|
64
|
-
|
|
65
|
-
const floatingPanel = screen.getByTestId("floating-panel");
|
|
66
|
-
expect(floatingPanel).toBeTruthy();
|
|
67
|
-
|
|
68
|
-
const emptyState = screen.getByTestId("wrapper");
|
|
69
|
-
expect(emptyState).toBeTruthy();
|
|
70
|
-
expect(emptyState).toHaveTextContent("There are no headings available to display");
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
it("should render heading items when open with headings", () => {
|
|
74
|
-
(headingsUtils.parseHeadingsTree as jest.Mock).mockReturnValueOnce(mockHeadings);
|
|
75
|
-
|
|
76
|
-
renderComponent({ ...defaultProps, isOpen: true, browserRef });
|
|
77
|
-
|
|
78
|
-
const floatingPanel = screen.getByTestId("floating-panel");
|
|
79
|
-
expect(floatingPanel).toBeTruthy();
|
|
80
|
-
|
|
81
|
-
const allButtons = screen.getAllByRole("button");
|
|
82
|
-
const headingItems = allButtons.filter((el) => el.hasAttribute("data-heading-id"));
|
|
83
|
-
expect(headingItems.length).toBeGreaterThan(0);
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
it("should render filter buttons when open with headings", () => {
|
|
87
|
-
(headingsUtils.parseHeadingsTree as jest.Mock).mockReturnValueOnce(mockHeadings);
|
|
88
|
-
|
|
89
|
-
renderComponent({ ...defaultProps, isOpen: true, browserRef });
|
|
90
|
-
|
|
91
|
-
const filterText = screen.getByText("Show headings:");
|
|
92
|
-
expect(filterText).toBeTruthy();
|
|
93
|
-
|
|
94
|
-
const allFilter = screen.getByText("all");
|
|
95
|
-
expect(allFilter).toBeTruthy();
|
|
96
|
-
|
|
97
|
-
const h1Texts = screen.getAllByText("h1");
|
|
98
|
-
const h1FilterButton = h1Texts.find((el) => el.tagName === "BUTTON");
|
|
99
|
-
expect(h1FilterButton).toBeTruthy();
|
|
100
|
-
|
|
101
|
-
const h2Texts = screen.getAllByText("h2");
|
|
102
|
-
const h2FilterButton = h2Texts.find((el) => el.tagName === "BUTTON");
|
|
103
|
-
expect(h2FilterButton).toBeTruthy();
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it("should render the errors banner when there are SEO errors", () => {
|
|
107
|
-
const headingsWithoutH1: HeadingNode[] = [createHeadingNode("h2", "Subtitle", 2)];
|
|
108
|
-
(headingsUtils.parseHeadingsTree as jest.Mock).mockReturnValueOnce(headingsWithoutH1);
|
|
109
|
-
|
|
110
|
-
renderComponent({ ...defaultProps, isOpen: true, browserRef });
|
|
111
|
-
|
|
112
|
-
const seoAlerts = screen.getByText("SEO Alerts");
|
|
113
|
-
expect(seoAlerts).toBeTruthy();
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
it("should not render the errors banner when there are no SEO errors", () => {
|
|
117
|
-
(headingsUtils.parseHeadingsTree as jest.Mock).mockReturnValueOnce(mockHeadings);
|
|
118
|
-
|
|
119
|
-
renderComponent({ ...defaultProps, isOpen: true, browserRef });
|
|
120
|
-
|
|
121
|
-
const seoAlerts = screen.queryByText("SEO Alerts");
|
|
122
|
-
expect(seoAlerts).toBeNull();
|
|
123
|
-
});
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
describe("HeadingsPreviewModal component events", () => {
|
|
127
|
-
it("should call toggleModal when close button is clicked", () => {
|
|
128
|
-
const toggleModalMock = jest.fn();
|
|
129
|
-
|
|
130
|
-
renderComponent({ ...defaultProps, isOpen: true, toggleModal: toggleModalMock });
|
|
131
|
-
|
|
132
|
-
const closeButton = screen.getByTestId("icon-action-component");
|
|
133
|
-
fireEvent.click(closeButton);
|
|
134
|
-
expect(toggleModalMock).toBeCalled();
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
it("should call setHeadingsFilter when a filter button is clicked", () => {
|
|
138
|
-
const setHeadingsFilterMock = jest.fn();
|
|
139
|
-
(headingsUtils.parseHeadingsTree as jest.Mock).mockReturnValueOnce(mockHeadings);
|
|
140
|
-
|
|
141
|
-
renderComponent({ ...defaultProps, isOpen: true, browserRef, setHeadingsFilter: setHeadingsFilterMock });
|
|
142
|
-
|
|
143
|
-
const h1Texts = screen.getAllByText("h1");
|
|
144
|
-
const h1FilterButton = h1Texts.find((el) => el.tagName === "BUTTON") as HTMLElement;
|
|
145
|
-
expect(h1FilterButton).toBeTruthy();
|
|
146
|
-
fireEvent.click(h1FilterButton);
|
|
147
|
-
expect(setHeadingsFilterMock).toBeCalledWith("h1");
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
it("should select a heading item when clicked", () => {
|
|
151
|
-
(headingsUtils.parseHeadingsTree as jest.Mock).mockReturnValueOnce(mockHeadings);
|
|
152
|
-
|
|
153
|
-
renderComponent({ ...defaultProps, isOpen: true, browserRef });
|
|
154
|
-
|
|
155
|
-
const allButtons = screen.getAllByRole("button");
|
|
156
|
-
const headingItems = allButtons.filter((el) => el.hasAttribute("data-heading-id"));
|
|
157
|
-
expect(headingItems.length).toBeGreaterThan(0);
|
|
158
|
-
|
|
159
|
-
fireEvent.click(headingItems[0]);
|
|
160
|
-
|
|
161
|
-
expect(headingItems[0]).toBeTruthy();
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
it("should deselect a heading item when clicked again", () => {
|
|
165
|
-
(headingsUtils.parseHeadingsTree as jest.Mock).mockReturnValueOnce(mockHeadings);
|
|
166
|
-
|
|
167
|
-
renderComponent({ ...defaultProps, isOpen: true, browserRef });
|
|
168
|
-
|
|
169
|
-
const allButtons = screen.getAllByRole("button");
|
|
170
|
-
const headingItems = allButtons.filter((el) => el.hasAttribute("data-heading-id"));
|
|
171
|
-
expect(headingItems.length).toBeGreaterThan(0);
|
|
172
|
-
|
|
173
|
-
fireEvent.click(headingItems[0]);
|
|
174
|
-
fireEvent.click(headingItems[0]);
|
|
175
|
-
|
|
176
|
-
expect(headingItems[0]).toBeTruthy();
|
|
177
|
-
});
|
|
178
|
-
});
|
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
import type { HeadingNode } from "@ax/types";
|
|
2
|
-
import {
|
|
3
|
-
analyzeHeadings,
|
|
4
|
-
extractUniqueHeadingTypes,
|
|
5
|
-
filterHeadings,
|
|
6
|
-
getHeadColor,
|
|
7
|
-
} from "@ax/components/HeadingsPreviewModal/utils";
|
|
8
|
-
|
|
9
|
-
const flatNode = (
|
|
10
|
-
tag: HeadingNode["tag"],
|
|
11
|
-
text: string,
|
|
12
|
-
level: HeadingNode["level"],
|
|
13
|
-
children: HeadingNode[] = [],
|
|
14
|
-
): HeadingNode => ({ tag, text, level, isHidden: false, children });
|
|
15
|
-
|
|
16
|
-
describe("analyzeHeadings", () => {
|
|
17
|
-
it("should return no errors for empty headings", () => {
|
|
18
|
-
expect(analyzeHeadings([])).toEqual([]);
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it("should return no errors when H1 is present and structure is correct", () => {
|
|
22
|
-
const headings = [flatNode("h1", "Title", 1, [flatNode("h2", "Subtitle", 2)])];
|
|
23
|
-
expect(analyzeHeadings(headings)).toHaveLength(0);
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it("should report missing H1 when not filtering", () => {
|
|
27
|
-
const headings = [flatNode("h2", "Subtitle", 2)];
|
|
28
|
-
const errors = analyzeHeadings(headings, false);
|
|
29
|
-
expect(errors.some((e) => e.message === "No H1 in this page")).toBe(true);
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
it("should not report missing H1 when filtering is active", () => {
|
|
33
|
-
const headings = [flatNode("h2", "Subtitle", 2)];
|
|
34
|
-
const errors = analyzeHeadings(headings, true);
|
|
35
|
-
expect(errors.some((e) => e.message === "No H1 in this page")).toBe(false);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it("should report nesting error when heading levels are skipped", () => {
|
|
39
|
-
const headings = [flatNode("h1", "Title", 1), flatNode("h3", "Sub-subtitle", 3)];
|
|
40
|
-
const errors = analyzeHeadings(headings);
|
|
41
|
-
expect(errors.some((e) => e.message === "Check heading structure for SEO compliance")).toBe(true);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it("should include the ID of the incorrectly nested heading", () => {
|
|
45
|
-
const headings = [flatNode("h1", "Title", 1), flatNode("h3", "Sub-subtitle", 3)];
|
|
46
|
-
const errors = analyzeHeadings(headings);
|
|
47
|
-
const nestingError = errors.find((e) => e.message === "Check heading structure for SEO compliance");
|
|
48
|
-
expect(nestingError?.headingIds.length).toBeGreaterThan(0);
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it("should not report nesting error for sequential heading levels", () => {
|
|
52
|
-
const headings = [flatNode("h1", "Title", 1), flatNode("h2", "Subtitle", 2)];
|
|
53
|
-
const errors = analyzeHeadings(headings);
|
|
54
|
-
expect(errors.some((e) => e.message === "Check heading structure for SEO compliance")).toBe(false);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it("should report duplicate heading texts", () => {
|
|
58
|
-
const headings = [flatNode("h1", "Title", 1), flatNode("h2", "Same text", 2), flatNode("h2", "Same text", 2)];
|
|
59
|
-
const errors = analyzeHeadings(headings);
|
|
60
|
-
expect(errors.some((e) => e.message === "Avoid header repetitions for SEO")).toBe(true);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it("should not report duplicates when all headings are unique", () => {
|
|
64
|
-
const headings = [flatNode("h1", "Title", 1), flatNode("h2", "Subtitle A", 2), flatNode("h2", "Subtitle B", 2)];
|
|
65
|
-
const errors = analyzeHeadings(headings);
|
|
66
|
-
expect(errors.some((e) => e.message === "Avoid header repetitions for SEO")).toBe(false);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it("should report excessive heading length when text exceeds 70 characters", () => {
|
|
70
|
-
const longText = "A".repeat(71);
|
|
71
|
-
const headings = [flatNode("h1", longText, 1)];
|
|
72
|
-
const errors = analyzeHeadings(headings);
|
|
73
|
-
expect(errors.some((e) => e.message === "Some headings are too long")).toBe(true);
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
it("should not report excessive length for headings within 70 characters", () => {
|
|
77
|
-
const headings = [flatNode("h1", "A".repeat(70), 1)];
|
|
78
|
-
const errors = analyzeHeadings(headings);
|
|
79
|
-
expect(errors.some((e) => e.message === "Some headings are too long")).toBe(false);
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
it("should detect errors in nested children", () => {
|
|
83
|
-
const headings = [flatNode("h1", "Title", 1, [flatNode("h2", "Same", 2), flatNode("h2", "Same", 2)])];
|
|
84
|
-
const errors = analyzeHeadings(headings);
|
|
85
|
-
expect(errors.some((e) => e.message === "Avoid header repetitions for SEO")).toBe(true);
|
|
86
|
-
});
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
describe("filterHeadings", () => {
|
|
90
|
-
const headings = [flatNode("h1", "Title", 1, [flatNode("h2", "Subtitle A", 2), flatNode("h2", "Subtitle B", 2)])];
|
|
91
|
-
|
|
92
|
-
it("should return all headings when filter is 'all'", () => {
|
|
93
|
-
const result = filterHeadings(headings, "all");
|
|
94
|
-
expect(result).toEqual(headings);
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
it("should return only h1 headings when filter is 'h1'", () => {
|
|
98
|
-
const result = filterHeadings(headings, "h1");
|
|
99
|
-
expect(result).toHaveLength(1);
|
|
100
|
-
expect(result[0].tag).toBe("h1");
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
it("should flatten and return only h2 headings when filter is 'h2'", () => {
|
|
104
|
-
const result = filterHeadings(headings, "h2");
|
|
105
|
-
expect(result).toHaveLength(2);
|
|
106
|
-
expect(result.every((h) => h.tag === "h2")).toBe(true);
|
|
107
|
-
expect(result.every((h) => h.children.length === 0)).toBe(true);
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
it("should return empty array when no headings match the filter", () => {
|
|
111
|
-
const result = filterHeadings(headings, "h3");
|
|
112
|
-
expect(result).toHaveLength(0);
|
|
113
|
-
});
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
describe("extractUniqueHeadingTypes", () => {
|
|
117
|
-
it("should return empty array for empty headings", () => {
|
|
118
|
-
expect(extractUniqueHeadingTypes([])).toEqual([]);
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
it("should return unique types sorted alphabetically", () => {
|
|
122
|
-
const headings = [flatNode("h3", "Sub-subtitle", 3), flatNode("h1", "Title", 1)];
|
|
123
|
-
expect(extractUniqueHeadingTypes(headings)).toEqual(["h1", "h3"]);
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
it("should include types from nested children", () => {
|
|
127
|
-
const headings = [flatNode("h1", "Title", 1, [flatNode("h2", "Subtitle", 2, [flatNode("h4", "Deep", 4)])])];
|
|
128
|
-
expect(extractUniqueHeadingTypes(headings)).toEqual(["h1", "h2", "h4"]);
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
it("should deduplicate repeated types", () => {
|
|
132
|
-
const headings = [flatNode("h2", "A", 2), flatNode("h2", "B", 2)];
|
|
133
|
-
expect(extractUniqueHeadingTypes(headings)).toEqual(["h2"]);
|
|
134
|
-
});
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
describe("getHeadColor", () => {
|
|
138
|
-
it("should return an rgb color string for a known heading tag", () => {
|
|
139
|
-
expect(getHeadColor("h1")).toBe("rgb(255, 240, 109)");
|
|
140
|
-
expect(getHeadColor("h2")).toBe("rgb(255, 184, 248)");
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
it("should return an rgba color string when opacity is provided", () => {
|
|
144
|
-
expect(getHeadColor("h1", 0.5)).toBe("rgba(255, 240, 109, 0.5)");
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
it("should return white for an unknown tag", () => {
|
|
148
|
-
expect(getHeadColor("unknown")).toBe("rgb(255, 255, 255)");
|
|
149
|
-
});
|
|
150
|
-
});
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
|
|
3
|
-
import { ThemeProvider } from "styled-components";
|
|
4
|
-
import { render, cleanup, screen, fireEvent } from "@testing-library/react";
|
|
5
|
-
import { parseTheme } from "@ax/helpers";
|
|
6
|
-
import "@testing-library/jest-dom";
|
|
7
|
-
|
|
8
|
-
import KeywordItem from "@ax/components/KeywordsPreviewModal/KeywordItem";
|
|
9
|
-
import globalTheme from "@ax/themes/theme.json";
|
|
10
|
-
|
|
11
|
-
afterEach(cleanup);
|
|
12
|
-
|
|
13
|
-
const defaultProps = {
|
|
14
|
-
keyword: "seo",
|
|
15
|
-
count: 3,
|
|
16
|
-
isSelected: false,
|
|
17
|
-
onClick: jest.fn(),
|
|
18
|
-
deleteKeyword: jest.fn(),
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
const renderComponent = (props = defaultProps) =>
|
|
22
|
-
render(
|
|
23
|
-
<ThemeProvider theme={parseTheme(globalTheme)}>
|
|
24
|
-
<KeywordItem {...props} />
|
|
25
|
-
</ThemeProvider>,
|
|
26
|
-
);
|
|
27
|
-
|
|
28
|
-
describe("KeywordItem component rendering", () => {
|
|
29
|
-
it("should render the keyword name", () => {
|
|
30
|
-
renderComponent();
|
|
31
|
-
expect(screen.getByText("seo")).toBeTruthy();
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it("should render the keyword usage count", () => {
|
|
35
|
-
renderComponent();
|
|
36
|
-
expect(screen.getByText("3 used")).toBeTruthy();
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it("should render zero usage count", () => {
|
|
40
|
-
renderComponent({ ...defaultProps, count: 0 });
|
|
41
|
-
expect(screen.getByText("0 used")).toBeTruthy();
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it("should render the delete icon action", () => {
|
|
45
|
-
renderComponent();
|
|
46
|
-
expect(screen.getByTestId("icon-action-component")).toBeTruthy();
|
|
47
|
-
});
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
describe("KeywordItem component events", () => {
|
|
51
|
-
it("should call onClick when the keyword item is clicked", () => {
|
|
52
|
-
const onClickMock = jest.fn();
|
|
53
|
-
renderComponent({ ...defaultProps, onClick: onClickMock });
|
|
54
|
-
|
|
55
|
-
// The keyword item is a div with role="button"; the icon-action is a <button> inside it
|
|
56
|
-
const buttons = screen.getAllByRole("button");
|
|
57
|
-
const keywordItem = buttons.find((el) => el.tagName === "DIV");
|
|
58
|
-
expect(keywordItem).toBeTruthy();
|
|
59
|
-
fireEvent.click(keywordItem!);
|
|
60
|
-
expect(onClickMock).toBeCalled();
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it("should call deleteKeyword directly when count is zero and delete is clicked", () => {
|
|
64
|
-
const deleteKeywordMock = jest.fn();
|
|
65
|
-
renderComponent({ ...defaultProps, count: 0, deleteKeyword: deleteKeywordMock });
|
|
66
|
-
|
|
67
|
-
const deleteButton = screen.getByTestId("icon-action-component");
|
|
68
|
-
fireEvent.click(deleteButton);
|
|
69
|
-
expect(deleteKeywordMock).toBeCalledWith("seo");
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it("should open the delete confirmation modal when count is greater than zero and delete is clicked", () => {
|
|
73
|
-
renderComponent({ ...defaultProps, count: 3 });
|
|
74
|
-
|
|
75
|
-
expect(screen.queryByTestId("modal-wrapper")).toBeNull();
|
|
76
|
-
|
|
77
|
-
const deleteButton = screen.getByTestId("icon-action-component");
|
|
78
|
-
fireEvent.click(deleteButton);
|
|
79
|
-
|
|
80
|
-
expect(screen.getByTestId("modal-wrapper")).toBeTruthy();
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it("should not call onClick when delete button is clicked (stopPropagation)", () => {
|
|
84
|
-
const onClickMock = jest.fn();
|
|
85
|
-
renderComponent({ ...defaultProps, count: 0, onClick: onClickMock });
|
|
86
|
-
|
|
87
|
-
const deleteButton = screen.getByTestId("icon-action-component");
|
|
88
|
-
fireEvent.click(deleteButton);
|
|
89
|
-
expect(onClickMock).not.toBeCalled();
|
|
90
|
-
});
|
|
91
|
-
});
|
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
|
|
3
|
-
import { ThemeProvider } from "styled-components";
|
|
4
|
-
import { render, cleanup, screen, fireEvent } from "@testing-library/react";
|
|
5
|
-
import { parseTheme } from "@ax/helpers";
|
|
6
|
-
import "@testing-library/jest-dom";
|
|
7
|
-
|
|
8
|
-
import KeywordsPreviewModal from "@ax/components/KeywordsPreviewModal";
|
|
9
|
-
import * as keywordsUtils from "@ax/components/KeywordsPreviewModal/utils";
|
|
10
|
-
import globalTheme from "@ax/themes/theme.json";
|
|
11
|
-
|
|
12
|
-
jest.mock("@ax/components/KeywordsPreviewModal/utils", () => ({
|
|
13
|
-
...jest.requireActual("@ax/components/KeywordsPreviewModal/utils"),
|
|
14
|
-
countKeywords: jest.fn().mockReturnValue({}),
|
|
15
|
-
}));
|
|
16
|
-
|
|
17
|
-
afterEach(cleanup);
|
|
18
|
-
|
|
19
|
-
const browserRef = { current: document.createElement("div") } as React.RefObject<HTMLDivElement>;
|
|
20
|
-
|
|
21
|
-
const defaultProps = {
|
|
22
|
-
isOpen: false,
|
|
23
|
-
browserRef: { current: null } as React.RefObject<HTMLDivElement>,
|
|
24
|
-
toggleModal: jest.fn(),
|
|
25
|
-
keywords: ["seo", "react"],
|
|
26
|
-
keywordsFilter: [] as string[],
|
|
27
|
-
setKeywordsFilter: jest.fn(),
|
|
28
|
-
addKeywords: jest.fn(),
|
|
29
|
-
deleteKeyword: jest.fn(),
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
const renderComponent = (props = defaultProps) =>
|
|
33
|
-
render(
|
|
34
|
-
<ThemeProvider theme={parseTheme(globalTheme)}>
|
|
35
|
-
<KeywordsPreviewModal {...props} />
|
|
36
|
-
</ThemeProvider>,
|
|
37
|
-
);
|
|
38
|
-
|
|
39
|
-
describe("KeywordsPreviewModal component rendering", () => {
|
|
40
|
-
it("should not render the keywords content when closed", () => {
|
|
41
|
-
renderComponent();
|
|
42
|
-
|
|
43
|
-
expect(screen.queryByText("Add new keyword")).toBeNull();
|
|
44
|
-
expect(screen.queryByText("Show keyword:")).toBeNull();
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it("should render the Add new keyword button when open", () => {
|
|
48
|
-
renderComponent({ ...defaultProps, isOpen: true });
|
|
49
|
-
|
|
50
|
-
expect(screen.getByText("Add new keyword")).toBeTruthy();
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it("should render keyword items when open with keywords", () => {
|
|
54
|
-
(keywordsUtils.countKeywords as jest.Mock).mockReturnValueOnce({ seo: 3, react: 1 });
|
|
55
|
-
|
|
56
|
-
renderComponent({ ...defaultProps, isOpen: true, browserRef });
|
|
57
|
-
|
|
58
|
-
expect(screen.getByText("seo")).toBeTruthy();
|
|
59
|
-
expect(screen.getByText("react")).toBeTruthy();
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it("should render keyword count for each keyword item", () => {
|
|
63
|
-
(keywordsUtils.countKeywords as jest.Mock).mockReturnValueOnce({ seo: 3 });
|
|
64
|
-
|
|
65
|
-
renderComponent({ ...defaultProps, isOpen: true, browserRef });
|
|
66
|
-
|
|
67
|
-
expect(screen.getByText("3 used")).toBeTruthy();
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it("should render the filter section when keywordsFilter is set", () => {
|
|
71
|
-
renderComponent({ ...defaultProps, isOpen: true, keywordsFilter: ["seo"] });
|
|
72
|
-
|
|
73
|
-
expect(screen.getByText("Show keyword:")).toBeTruthy();
|
|
74
|
-
expect(screen.getByText("seo")).toBeTruthy();
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
it("should not render the filter section when keywordsFilter is empty", () => {
|
|
78
|
-
renderComponent({ ...defaultProps, isOpen: true });
|
|
79
|
-
|
|
80
|
-
expect(screen.queryByText("Show keyword:")).toBeNull();
|
|
81
|
-
});
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
describe("KeywordsPreviewModal component events", () => {
|
|
85
|
-
it("should call toggleModal when close button is clicked", () => {
|
|
86
|
-
const toggleModalMock = jest.fn();
|
|
87
|
-
|
|
88
|
-
renderComponent({ ...defaultProps, isOpen: true, toggleModal: toggleModalMock });
|
|
89
|
-
|
|
90
|
-
const closeButton = screen.getByTestId("icon-action-component");
|
|
91
|
-
fireEvent.click(closeButton);
|
|
92
|
-
expect(toggleModalMock).toBeCalled();
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it("should call setKeywordsFilter with empty array when filter tag is deleted", () => {
|
|
96
|
-
const setKeywordsFilterMock = jest.fn();
|
|
97
|
-
|
|
98
|
-
renderComponent({
|
|
99
|
-
...defaultProps,
|
|
100
|
-
isOpen: true,
|
|
101
|
-
keywordsFilter: ["seo"],
|
|
102
|
-
setKeywordsFilter: setKeywordsFilterMock,
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
const deleteIconWrapper = screen.getByTestId("delete-icon-wrapper");
|
|
106
|
-
fireEvent.click(deleteIconWrapper);
|
|
107
|
-
expect(setKeywordsFilterMock).toBeCalledWith([]);
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
it("should call setKeywordsFilter with the clicked keyword when a keyword item is clicked", () => {
|
|
111
|
-
const setKeywordsFilterMock = jest.fn();
|
|
112
|
-
(keywordsUtils.countKeywords as jest.Mock).mockReturnValueOnce({ seo: 3 });
|
|
113
|
-
|
|
114
|
-
renderComponent({ ...defaultProps, isOpen: true, browserRef, setKeywordsFilter: setKeywordsFilterMock });
|
|
115
|
-
|
|
116
|
-
const keywordItems = screen.getAllByRole("button");
|
|
117
|
-
const seoItem = keywordItems.find((el) => el.textContent?.includes("seo") && el.getAttribute("tabindex") === "0");
|
|
118
|
-
expect(seoItem).toBeTruthy();
|
|
119
|
-
fireEvent.click(seoItem!);
|
|
120
|
-
expect(setKeywordsFilterMock).toBeCalledWith(["seo"]);
|
|
121
|
-
});
|
|
122
|
-
});
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { countKeywords } from "@ax/components/KeywordsPreviewModal/utils";
|
|
2
|
-
|
|
3
|
-
describe("countKeywords", () => {
|
|
4
|
-
it("should return empty object when div has no .frame-content iframe", () => {
|
|
5
|
-
const div = document.createElement("div");
|
|
6
|
-
const result = countKeywords(div, ["seo", "react"]);
|
|
7
|
-
expect(result).toEqual({});
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
it("should return empty object for empty keywords array", () => {
|
|
11
|
-
const div = document.createElement("div");
|
|
12
|
-
const result = countKeywords(div, []);
|
|
13
|
-
expect(result).toEqual({});
|
|
14
|
-
});
|
|
15
|
-
});
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
|
|
3
|
-
import { ThemeProvider } from "styled-components";
|
|
4
|
-
import { render, cleanup, screen, fireEvent } from "@testing-library/react";
|
|
5
|
-
import { parseTheme } from "@ax/helpers";
|
|
6
|
-
import "@testing-library/jest-dom";
|
|
7
|
-
|
|
8
|
-
import { AddKeywordsModal, DeleteKeywordsModal } from "@ax/components/KeywordsPreviewModal/atoms";
|
|
9
|
-
import globalTheme from "@ax/themes/theme.json";
|
|
10
|
-
|
|
11
|
-
afterEach(cleanup);
|
|
12
|
-
|
|
13
|
-
const renderWithTheme = (ui: React.ReactElement) =>
|
|
14
|
-
render(<ThemeProvider theme={parseTheme(globalTheme)}>{ui}</ThemeProvider>);
|
|
15
|
-
|
|
16
|
-
describe("AddKeywordsModal component rendering", () => {
|
|
17
|
-
it("should render the modal when isOpen is true", () => {
|
|
18
|
-
renderWithTheme(<AddKeywordsModal isOpen={true} toggleModal={jest.fn()} addNewKeyword={jest.fn()} />);
|
|
19
|
-
|
|
20
|
-
expect(screen.getByTestId("modal-wrapper")).toBeTruthy();
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it("should not render the modal when isOpen is false", () => {
|
|
24
|
-
renderWithTheme(<AddKeywordsModal isOpen={false} toggleModal={jest.fn()} addNewKeyword={jest.fn()} />);
|
|
25
|
-
|
|
26
|
-
expect(screen.queryByTestId("modal-wrapper")).toBeNull();
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it("should render the New keyword title", () => {
|
|
30
|
-
renderWithTheme(<AddKeywordsModal isOpen={true} toggleModal={jest.fn()} addNewKeyword={jest.fn()} />);
|
|
31
|
-
|
|
32
|
-
expect(screen.getByText("New keyword")).toBeTruthy();
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it("should render the Create keyword button disabled when no keywords are entered", () => {
|
|
36
|
-
renderWithTheme(<AddKeywordsModal isOpen={true} toggleModal={jest.fn()} addNewKeyword={jest.fn()} />);
|
|
37
|
-
|
|
38
|
-
const createButton = screen.getByText("Create keyword").closest("button");
|
|
39
|
-
expect(createButton).toBeDisabled();
|
|
40
|
-
});
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
describe("AddKeywordsModal component events", () => {
|
|
44
|
-
it("should call toggleModal when Cancel button is clicked", () => {
|
|
45
|
-
const toggleModalMock = jest.fn();
|
|
46
|
-
|
|
47
|
-
renderWithTheme(<AddKeywordsModal isOpen={true} toggleModal={toggleModalMock} addNewKeyword={jest.fn()} />);
|
|
48
|
-
|
|
49
|
-
const cancelButton = screen.getByText("Cancel").closest("button");
|
|
50
|
-
expect(cancelButton).toBeTruthy();
|
|
51
|
-
cancelButton && fireEvent.click(cancelButton);
|
|
52
|
-
expect(toggleModalMock).toBeCalled();
|
|
53
|
-
});
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
describe("DeleteKeywordsModal component rendering", () => {
|
|
57
|
-
it("should render the modal when isOpen is true", () => {
|
|
58
|
-
renderWithTheme(<DeleteKeywordsModal isOpen={true} toggleModal={jest.fn()} deleteKeyword={jest.fn()} />);
|
|
59
|
-
|
|
60
|
-
expect(screen.getByTestId("modal-wrapper")).toBeTruthy();
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it("should not render the modal when isOpen is false", () => {
|
|
64
|
-
renderWithTheme(<DeleteKeywordsModal isOpen={false} toggleModal={jest.fn()} deleteKeyword={jest.fn()} />);
|
|
65
|
-
|
|
66
|
-
expect(screen.queryByTestId("modal-wrapper")).toBeNull();
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it("should render the Delete keyword title", () => {
|
|
70
|
-
renderWithTheme(<DeleteKeywordsModal isOpen={true} toggleModal={jest.fn()} deleteKeyword={jest.fn()} />);
|
|
71
|
-
|
|
72
|
-
expect(screen.getAllByText("Delete keyword").length).toBeGreaterThan(0);
|
|
73
|
-
});
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
describe("DeleteKeywordsModal component events", () => {
|
|
77
|
-
it("should call deleteKeyword when Delete keyword button is clicked", () => {
|
|
78
|
-
const deleteKeywordMock = jest.fn();
|
|
79
|
-
|
|
80
|
-
renderWithTheme(<DeleteKeywordsModal isOpen={true} toggleModal={jest.fn()} deleteKeyword={deleteKeywordMock} />);
|
|
81
|
-
|
|
82
|
-
const deleteButton = screen
|
|
83
|
-
.getAllByText("Delete keyword")
|
|
84
|
-
.map((el) => el.closest("button"))
|
|
85
|
-
.find(Boolean);
|
|
86
|
-
expect(deleteButton).toBeTruthy();
|
|
87
|
-
deleteButton && fireEvent.click(deleteButton);
|
|
88
|
-
expect(deleteKeywordMock).toBeCalled();
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
it("should call toggleModal when Cancel button is clicked", () => {
|
|
92
|
-
const toggleModalMock = jest.fn();
|
|
93
|
-
|
|
94
|
-
renderWithTheme(<DeleteKeywordsModal isOpen={true} toggleModal={toggleModalMock} deleteKeyword={jest.fn()} />);
|
|
95
|
-
|
|
96
|
-
const cancelButton = screen.getByText("Cancel").closest("button");
|
|
97
|
-
expect(cancelButton).toBeTruthy();
|
|
98
|
-
cancelButton && fireEvent.click(cancelButton);
|
|
99
|
-
expect(toggleModalMock).toBeCalled();
|
|
100
|
-
});
|
|
101
|
-
});
|