@eox/pages-theme-eox 0.11.4 → 1.0.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.
Files changed (38) hide show
  1. package/.gitlab-ci.yml +30 -0
  2. package/.release-it.json +15 -0
  3. package/CHANGELOG.md +8 -0
  4. package/README.md +235 -0
  5. package/cypress/support/commands.js +25 -0
  6. package/cypress/support/component-index.html +11 -0
  7. package/cypress/support/component.js +22 -0
  8. package/cypress/support/mocks/features.data.js +9 -0
  9. package/cypress/support/mocks/helpers.js +48 -0
  10. package/cypress/support/mocks/vitepress.js +83 -0
  11. package/cypress.config.js +46 -0
  12. package/package.json +12 -2
  13. package/src/components/CTASection.cy.js +72 -0
  14. package/src/components/CookieBanner.cy.js +49 -0
  15. package/src/components/CookieSettings.cy.js +73 -0
  16. package/src/components/DataTable.cy.js +58 -0
  17. package/src/components/FeatureCard.cy.js +83 -0
  18. package/src/components/FeatureCard.vue +13 -6
  19. package/src/components/FeatureSection.cy.js +63 -0
  20. package/src/components/FeaturesGallery.cy.js +45 -0
  21. package/src/components/FeaturesGallery.vue +6 -3
  22. package/src/components/Footer.cy.js +52 -0
  23. package/src/components/Footer.vue +26 -7
  24. package/src/components/HeroSection.cy.js +66 -0
  25. package/src/components/LogoSection.cy.js +53 -0
  26. package/src/components/MobileNavDropdown.cy.js +67 -0
  27. package/src/components/MobileNavDropdown.vue +47 -0
  28. package/src/components/NavBar.cy.js +106 -0
  29. package/src/components/NavBar.vue +55 -3
  30. package/src/components/NavDropdown.cy.js +71 -0
  31. package/src/components/NavDropdown.vue +61 -0
  32. package/src/components/NewsBanner.cy.js +23 -0
  33. package/src/components/NewsBanner.vue +0 -1
  34. package/src/components/NotFound.cy.js +16 -0
  35. package/src/components/PricingTable.cy.js +91 -0
  36. package/src/helpers.js +53 -0
  37. package/src/index.js +1 -0
  38. package/src/style.css +121 -4
@@ -0,0 +1,49 @@
1
+ import CookieBanner from "./CookieBanner.vue";
2
+ import { __setMockData } from "../../cypress/support/mocks/vitepress";
3
+ import * as helpers from "../../cypress/support/mocks/helpers";
4
+
5
+ describe("<CookieBanner />", () => {
6
+ beforeEach(() => {
7
+ // Spy on helper functions to verify interactions
8
+ // Requires mock that delegates to window.__helpersMock
9
+ cy.spy(window.__helpersMock, "acceptCookies").as("acceptCookies");
10
+ cy.spy(window.__helpersMock, "declineCookies").as("declineCookies");
11
+ });
12
+
13
+ it("renders with privacy link", () => {
14
+ const policyLink = "/privacy-policy";
15
+ __setMockData({
16
+ theme: {
17
+ theme: {
18
+ brandConfig: {
19
+ legal: {
20
+ privacyPolicy: policyLink,
21
+ },
22
+ },
23
+ },
24
+ },
25
+ });
26
+
27
+ cy.mount(CookieBanner);
28
+
29
+ cy.contains("We use optional cookies").should("be.visible");
30
+ cy.contains("a", "privacy policy").should("have.attr", "href", policyLink);
31
+ cy.contains("a", "manage cookies").should(
32
+ "have.attr",
33
+ "href",
34
+ "/cookie-settings",
35
+ );
36
+ });
37
+
38
+ it("accepts cookies on click", () => {
39
+ cy.mount(CookieBanner);
40
+ cy.contains("button", "Accept all").click();
41
+ cy.get("@acceptCookies").should("have.been.called");
42
+ });
43
+
44
+ it("declines cookies on click", () => {
45
+ cy.mount(CookieBanner);
46
+ cy.contains("button", "Reject all").click();
47
+ cy.get("@declineCookies").should("have.been.called");
48
+ });
49
+ });
@@ -0,0 +1,73 @@
1
+ import CookieSettings from "./CookieSettings.vue";
2
+ import {
3
+ __setMockData,
4
+ __setRouteMock,
5
+ } from "../../cypress/support/mocks/vitepress";
6
+ import * as helpers from "../../cypress/support/mocks/helpers";
7
+
8
+ describe("<CookieSettings />", () => {
9
+ beforeEach(() => {
10
+ // Reset mocks
11
+ cy.spy(window.__helpersMock, "enableTracking").as("enableTracking");
12
+ cy.spy(window.__helpersMock, "showBanner").as("showBanner");
13
+
14
+ // Mock global _paq if strictly necessary, but helpers are mocked so logic inside component that calls _paq directly needs attention.
15
+ // The component calls _paq directly in the watcher.
16
+ // We can mock window._paq
17
+ cy.window().then((win) => {
18
+ win._paq = [];
19
+ cy.spy(win._paq, "push").as("paqPush");
20
+ });
21
+
22
+ __setMockData({
23
+ theme: {
24
+ theme: {
25
+ brandConfig: {
26
+ legal: {
27
+ privacyPolicy: "/privacy",
28
+ },
29
+ },
30
+ },
31
+ },
32
+ });
33
+
34
+ // Mock route data structure expected by component
35
+ __setRouteMock({
36
+ data: { title: "" },
37
+ onBeforeRouteChange: () => {},
38
+ });
39
+ });
40
+
41
+ it("renders cookie categories", () => {
42
+ cy.mount(CookieSettings);
43
+
44
+ cy.contains("Cookie Settings").should("be.visible");
45
+ cy.contains("h6", "Essential").should("be.visible");
46
+ cy.contains("h6", "Analytics").should("be.visible");
47
+ });
48
+
49
+ it("renders privacy policy link", () => {
50
+ cy.mount(CookieSettings);
51
+ cy.contains("a", "Privacy Policy").should("have.attr", "href", "/privacy");
52
+ });
53
+
54
+ it("toggles analytics cookies", () => {
55
+ cy.mount(CookieSettings);
56
+
57
+ // Analytics checkbox should be unchecked by default (if no cookie present)
58
+ cy.get('#Analytics input[type="checkbox"]').should("not.be.checked");
59
+
60
+ // Check it
61
+ cy.get('#Analytics input[type="checkbox"]').check({ force: true }); // force because of custom styling potentially hiding input
62
+
63
+ // verify _paq interaction
64
+ cy.get("@paqPush").should("have.been.calledWith", ["forgetUserOptOut"]);
65
+ cy.get("@enableTracking").should("have.been.calledWith", true); // second arg router is object
66
+ });
67
+
68
+ it("disables essential cookies checkbox", () => {
69
+ cy.mount(CookieSettings);
70
+ cy.get('#Essential input[type="checkbox"]').should("be.checked");
71
+ cy.get('#Essential input[type="checkbox"]').should("be.disabled");
72
+ });
73
+ });
@@ -0,0 +1,58 @@
1
+ import DataTable from "./DataTable.vue";
2
+
3
+ describe("<DataTable />", () => {
4
+ const headers = ["Name", "Description"];
5
+ const data = [
6
+ {
7
+ summary: { Name: "Item 1", Description: "Desc 1" },
8
+ content: "Detailed content for Item 1",
9
+ },
10
+ {
11
+ summary: { Name: "Item 2", Description: "Desc 2" },
12
+ content: "Detailed content for Item 2",
13
+ },
14
+ ];
15
+
16
+ it("renders headers correctly", () => {
17
+ cy.mount(DataTable, {
18
+ props: { headers, data },
19
+ });
20
+ headers.forEach((h) => cy.contains("th", h).should("be.visible"));
21
+ });
22
+
23
+ it("renders summary data in rows", () => {
24
+ cy.mount(DataTable, {
25
+ props: { headers, data },
26
+ });
27
+ cy.contains("Item 1").should("be.visible");
28
+ cy.contains("Desc 1").should("be.visible");
29
+ cy.contains("Item 2").should("be.visible");
30
+ });
31
+
32
+ it("expands row to show detail content on click", () => {
33
+ cy.mount(DataTable, {
34
+ props: { headers, data },
35
+ });
36
+
37
+ // Content should not be visible initially
38
+ cy.contains("Detailed content for Item 1").should("not.exist");
39
+
40
+ // Click the first row (the summary part)
41
+ cy.contains("Item 1").parents("tr").click();
42
+
43
+ // Content should now be visible
44
+ cy.contains("Detailed content for Item 1").should("be.visible");
45
+ });
46
+
47
+ it("collapses row on second click", () => {
48
+ cy.mount(DataTable, {
49
+ props: { headers, data },
50
+ });
51
+
52
+ cy.contains("Item 1").parents("tr").click();
53
+ cy.contains("Detailed content for Item 1").should("be.visible");
54
+
55
+ cy.contains("Item 1").parents("tr").click();
56
+ cy.contains("Detailed content for Item 1").should("not.exist");
57
+ });
58
+ });
@@ -0,0 +1,83 @@
1
+ import FeatureCard from "./FeatureCard.vue";
2
+
3
+ describe("<FeatureCard />", () => {
4
+ it("renders title and content using props", () => {
5
+ const title = "Test Feature";
6
+ const content = "This is a test feature content.";
7
+
8
+ cy.mount(FeatureCard, {
9
+ props: {
10
+ title,
11
+ content,
12
+ },
13
+ });
14
+
15
+ cy.get("h5").should("contain", title);
16
+ // When using content prop without slots, it renders a div with v-html
17
+ cy.contains(content).should("exist");
18
+ });
19
+
20
+ it("renders content in a paragraph when using slots", () => {
21
+ const title = "Slot Feature";
22
+ const slotContent = "This is content from a slot.";
23
+
24
+ cy.mount(FeatureCard, {
25
+ props: {
26
+ title,
27
+ },
28
+ slots: {
29
+ default: slotContent,
30
+ },
31
+ });
32
+
33
+ cy.get("h5").should("contain", title);
34
+ // When using slots, it renders a p tag
35
+ cy.get("p").should("contain", slotContent);
36
+ });
37
+
38
+ it("renders link when provided", () => {
39
+ const link = {
40
+ text: "Learn More",
41
+ href: "https://example.com",
42
+ target: "_blank",
43
+ };
44
+
45
+ cy.mount(FeatureCard, {
46
+ props: {
47
+ title: "With Link",
48
+ content: "Content",
49
+ link,
50
+ },
51
+ });
52
+
53
+ cy.get("a").should("have.attr", "href", "https://example.com");
54
+ cy.get("a").should("contain", "Learn More");
55
+ cy.get("a").should("have.attr", "target", "_blank");
56
+ });
57
+
58
+ it("renders icon from class string", () => {
59
+ cy.mount(FeatureCard, {
60
+ props: {
61
+ title: "With Icon",
62
+ content: "Content",
63
+ icon: "mdi-home",
64
+ },
65
+ });
66
+
67
+ cy.get("i.mdi.mdi-home").should("exist");
68
+ });
69
+
70
+ it("renders html icon", () => {
71
+ const iconHtml = '<svg><circle cx="50" cy="50" r="40" /></svg>';
72
+ cy.mount(FeatureCard, {
73
+ props: {
74
+ title: "With SVG Icon",
75
+ content: "Content",
76
+ icon: { html: iconHtml },
77
+ },
78
+ });
79
+
80
+ cy.get(".icon").should("exist");
81
+ cy.get(".icon").find("svg").should("exist");
82
+ });
83
+ });
@@ -21,16 +21,23 @@ const iconStyle = {
21
21
  <template>
22
22
  <article class="vertical large-padding">
23
23
  <div>
24
- <i v-if="typeof icon === 'string' && icon.startsWith('mdi-')" :class="`mdi ${icon}`"></i>
25
- <div v-else-if="icon" :style="iconStyle" v-html="icon.html" class="icon"></div>
24
+ <i
25
+ v-if="typeof icon === 'string' && icon.startsWith('mdi-')"
26
+ :class="`mdi ${icon}`"
27
+ ></i>
28
+ <div
29
+ v-else-if="icon"
30
+ :style="iconStyle"
31
+ v-html="icon.html"
32
+ class="icon"
33
+ ></div>
26
34
  <h5 class="small">{{ title }}</h5>
27
- <p>
35
+ <p v-if="$slots.default">
28
36
  <slot>{{ content }}</slot>
29
37
  </p>
38
+ <div v-else v-html="content"></div>
30
39
  </div>
31
- <nav
32
- v-if="link"
33
- >
40
+ <nav v-if="link">
34
41
  <a
35
42
  :href="link.href"
36
43
  :target="link.target ?? '_blank'"
@@ -0,0 +1,63 @@
1
+ import FeatureSection from "./FeatureSection.vue";
2
+
3
+ describe("<FeatureSection />", () => {
4
+ const defaultProps = {
5
+ title: "Feature Title",
6
+ tagline: "Feature Tagline",
7
+ icon: "mdi-star",
8
+ };
9
+
10
+ it("renders title, tagline and icon", () => {
11
+ cy.mount(FeatureSection, {
12
+ props: defaultProps,
13
+ });
14
+
15
+ cy.contains(defaultProps.title).should("exist");
16
+ cy.contains(defaultProps.tagline).should("exist");
17
+ cy.get(".mdi-star").should("exist");
18
+ });
19
+
20
+ it("renders slot content", () => {
21
+ cy.mount(FeatureSection, {
22
+ props: defaultProps,
23
+ slots: {
24
+ default: "This is the feature content",
25
+ },
26
+ });
27
+
28
+ cy.contains("This is the feature content").should("exist");
29
+ });
30
+
31
+ it("renders buttons when links are provided", () => {
32
+ cy.mount(FeatureSection, {
33
+ props: {
34
+ ...defaultProps,
35
+ primaryLink: "/primary",
36
+ primaryButton: "Primary Btn",
37
+ secondaryLink: "/secondary",
38
+ secondaryButton: "Secondary Btn",
39
+ altLink: "/alt",
40
+ altButton: "Alt Btn",
41
+ },
42
+ });
43
+
44
+ cy.contains("a", "Primary Btn").should("have.attr", "href", "/primary");
45
+ cy.contains("a", "Secondary Btn").should("have.attr", "href", "/secondary");
46
+ cy.contains("a", "Alt Btn").should("have.attr", "href", "/alt");
47
+ });
48
+
49
+ it("applies styling classes based on props", () => {
50
+ cy.mount(FeatureSection, {
51
+ props: {
52
+ ...defaultProps,
53
+ landing: true,
54
+ dark: true,
55
+ reverse: true,
56
+ },
57
+ });
58
+
59
+ cy.get(".feature-section").should("have.class", "landing");
60
+ cy.get(".feature-section").should("have.class", "dark");
61
+ cy.get(".feature-section").should("have.class", "reverse");
62
+ });
63
+ });
@@ -0,0 +1,45 @@
1
+ import FeaturesGallery from "./FeaturesGallery.vue";
2
+ import { __setMockData } from "../../cypress/support/mocks/vitepress";
3
+
4
+ describe("<FeaturesGallery />", () => {
5
+ const cards = [
6
+ { id: 1, title: "Card 1", content: "Content 1", icon: "mdi-home" },
7
+ { id: 2, title: "Card 2", content: "Content 2" },
8
+ ];
9
+
10
+ beforeEach(() => {
11
+ __setMockData({});
12
+ });
13
+
14
+ it("renders section title", () => {
15
+ cy.mount(FeaturesGallery, {
16
+ props: {
17
+ sectionTitle: "My Gallery",
18
+ cards,
19
+ },
20
+ });
21
+
22
+ cy.contains("h5", "My Gallery").should("be.visible");
23
+ });
24
+
25
+ it("renders feature cards", () => {
26
+ cy.mount(FeaturesGallery, {
27
+ props: { cards },
28
+ });
29
+
30
+ cy.contains("Card 1").should("exist");
31
+ cy.contains("Content 1").should("exist");
32
+ cy.contains("Card 2").should("exist");
33
+ });
34
+
35
+ it("applies background classes", () => {
36
+ cy.mount(FeaturesGallery, {
37
+ props: {
38
+ cards,
39
+ background: "custom-bg-class",
40
+ },
41
+ });
42
+
43
+ cy.get("section").should("have.class", "custom-bg-class");
44
+ });
45
+ });
@@ -69,7 +69,10 @@ const featuresExcerpts =
69
69
  );
70
70
  })
71
71
  .filter((f) => f);
72
- const siteTitle = sectionTitle !== false ? (sectionTitle || `More ${site.value.title} features:`) : false;
72
+ const siteTitle =
73
+ sectionTitle !== false
74
+ ? sectionTitle || `More ${site.value.title} features:`
75
+ : false;
73
76
  </script>
74
77
 
75
78
  <template>
@@ -126,11 +129,11 @@ section {
126
129
  .link-card {
127
130
  display: block;
128
131
  text-decoration: none;
129
- border: .0625rem solid transparent;
132
+ border: 0.0625rem solid transparent;
130
133
  }
131
134
  .link-card:hover article {
132
135
  box-shadow: none;
133
- border: .0625rem solid var(--outline-variant);
136
+ border: 0.0625rem solid var(--outline-variant);
134
137
  }
135
138
 
136
139
  .card-wrapper {
@@ -0,0 +1,52 @@
1
+ import Footer from "./Footer.vue";
2
+ import { __setMockData } from "../../cypress/support/mocks/vitepress";
3
+
4
+ describe("<Footer />", () => {
5
+ beforeEach(() => {
6
+ __setMockData({
7
+ site: { title: "EOX Site" },
8
+ theme: {
9
+ logo: { light: "/logo.png" },
10
+ nav: [
11
+ { text: "Link 1", link: "/link1" },
12
+ { text: "Contact", link: "/contact" },
13
+ ],
14
+ footer: {
15
+ copyright: "© 2026 EOX",
16
+ },
17
+ theme: {
18
+ brandConfig: {
19
+ legal: {
20
+ about: "/about",
21
+ termsAndConditions: "/terms",
22
+ privacyPolicy: "/privacy",
23
+ },
24
+ },
25
+ },
26
+ },
27
+ });
28
+ });
29
+
30
+ it("renders logo and copyright", () => {
31
+ cy.mount(Footer);
32
+
33
+ cy.get("img.logo").should("have.attr", "src", "/logo.png");
34
+ cy.contains("© 2026 EOX").should("exist");
35
+ });
36
+
37
+ it("renders contact button", () => {
38
+ cy.mount(Footer);
39
+ cy.contains("a", "Contact").should("have.attr", "href", "/contact");
40
+ });
41
+
42
+ it("renders legal links", () => {
43
+ cy.mount(Footer);
44
+ cy.contains("a", "About").should("have.attr", "href", "/about");
45
+ cy.contains("a", "Terms & Conditions").should(
46
+ "have.attr",
47
+ "href",
48
+ "/terms",
49
+ );
50
+ cy.contains("a", "Privacy").should("have.attr", "href", "/privacy");
51
+ });
52
+ });
@@ -11,12 +11,16 @@
11
11
  />
12
12
  <div class="small-space"></div>
13
13
  <a
14
- v-if="theme.nav.find((i) => i.link.includes('contact'))"
15
- :href="theme.nav.find((i) => i.link.includes('contact')).link"
14
+ v-if="theme.nav.find((i) => i.link && i.link.includes('contact'))"
15
+ :href="
16
+ theme.nav.find((i) => i.link && i.link.includes('contact')).link
17
+ "
16
18
  class="button small border no-margin"
17
19
  style="color: var(--on-surface)"
18
20
  @click="trackEvent(['CTA', 'Click', 'Footer', action.text])"
19
- >{{ theme.nav.find((i) => i.link.includes("contact")).text }}</a
21
+ >{{
22
+ theme.nav.find((i) => i.link && i.link.includes("contact")).text
23
+ }}</a
20
24
  >
21
25
  <p v-html="theme.footer.copyright"></p>
22
26
  <p class="middle-align">
@@ -33,9 +37,12 @@
33
37
  </div>
34
38
  <div class="s12 l6">
35
39
  <div class="grid large-line">
36
- <div class="s6">
40
+ <div
41
+ class="s6 l4"
42
+ v-if="theme.nav.filter((i) => !i.action && i.link).length"
43
+ >
37
44
  <p class="bold">About</p>
38
- <p v-for="item in theme.nav.filter((i) => !i.action)">
45
+ <p v-for="item in theme.nav.filter((i) => !i.action && i.link)">
39
46
  <a
40
47
  :href="withBase(item.link)"
41
48
  :target="item.target"
@@ -45,7 +52,19 @@
45
52
  >
46
53
  </p>
47
54
  </div>
48
- <div class="s6">
55
+ <div class="s6 l4" v-for="item in theme.nav.filter((i) => i.items)">
56
+ <p class="bold">{{ item.text }}</p>
57
+ <p v-for="subItem in getFlatList(item.items)">
58
+ <a
59
+ :href="withBase(subItem.link)"
60
+ :target="subItem.target"
61
+ :rel="subItem.rel"
62
+ class="link"
63
+ >{{ subItem.text }}</a
64
+ >
65
+ </p>
66
+ </div>
67
+ <div class="s6 l4">
49
68
  <p class="bold">Legal</p>
50
69
  <p>
51
70
  <a
@@ -94,7 +113,7 @@
94
113
 
95
114
  <script setup>
96
115
  import { useData, withBase } from "vitepress";
97
- import { trackEvent } from "../helpers";
116
+ import { trackEvent, getFlatList } from "../helpers";
98
117
  const { site, theme } = useData();
99
118
  </script>
100
119
 
@@ -0,0 +1,66 @@
1
+ import HeroSection from "./HeroSection.vue";
2
+ import { __setMockData } from "../../cypress/support/mocks/vitepress";
3
+
4
+ describe("<HeroSection />", () => {
5
+ beforeEach(() => {
6
+ // Reset mock data before each test
7
+ __setMockData({
8
+ frontmatter: {
9
+ hero: {
10
+ text: "Hero Text",
11
+ tagline: "Hero Tagline",
12
+ actions: [
13
+ { text: "Start", link: "/start", theme: "brand" },
14
+ { text: "More", link: "/more", theme: "secondary" },
15
+ ],
16
+ image: { src: "/image.png", alt: "Hero Image" },
17
+ },
18
+ },
19
+ });
20
+ });
21
+
22
+ it("renders correctly with default data", () => {
23
+ cy.mount(HeroSection);
24
+
25
+ cy.get("h1").should("contain", "Hero Text");
26
+ cy.get("p").should("contain", "Hero Tagline");
27
+
28
+ // Check actions
29
+ cy.contains("a", "Start").should("have.attr", "href", "/start");
30
+ cy.contains("a", "Start").should("have.class", "primary-text"); // brand theme
31
+
32
+ cy.contains("a", "More").should("have.attr", "href", "/more");
33
+ cy.contains("a", "More").should("have.class", "secondary"); // secondary theme
34
+
35
+ // Check image
36
+ cy.get("img.hero-image").should("have.attr", "src", "/image.png");
37
+ });
38
+
39
+ it("renders video when provided", () => {
40
+ __setMockData({
41
+ frontmatter: {
42
+ hero: {
43
+ text: "Video Hero",
44
+ video: { src: "/video.mp4" },
45
+ },
46
+ },
47
+ });
48
+
49
+ cy.mount(HeroSection);
50
+ cy.get("video.hero-image").should("have.attr", "src", "/video.mp4");
51
+ });
52
+
53
+ it("renders background image", () => {
54
+ __setMockData({
55
+ frontmatter: {
56
+ hero: {
57
+ text: "BG Hero",
58
+ background: { src: "/bg.jpg" },
59
+ },
60
+ },
61
+ });
62
+
63
+ cy.mount(HeroSection);
64
+ cy.get("img.background-image").should("have.attr", "src", "/bg.jpg");
65
+ });
66
+ });
@@ -0,0 +1,53 @@
1
+ import LogoSection from "./LogoSection.vue";
2
+
3
+ describe("<LogoSection />", () => {
4
+ const logos = [
5
+ { image: "/logo1.png", alt: "Logo 1", link: "https://example.com" },
6
+ { image: "/logo2.png", alt: "Logo 2" }, // No link
7
+ ];
8
+
9
+ it("renders list of logos", () => {
10
+ cy.mount(LogoSection, {
11
+ props: { logos },
12
+ });
13
+
14
+ cy.get(".logo-row").find("img").should("have.length", 2);
15
+ cy.get('img[alt="Logo 1"]').should("exist");
16
+ cy.get('img[alt="Logo 2"]').should("exist");
17
+ });
18
+
19
+ it("wraps logos with link when provided", () => {
20
+ cy.mount(LogoSection, {
21
+ props: { logos },
22
+ });
23
+
24
+ cy.get('a[href="https://example.com"]')
25
+ .find('img[alt="Logo 1"]')
26
+ .should("exist");
27
+ });
28
+
29
+ it("renders div instead of link when no link provided", () => {
30
+ cy.mount(LogoSection, {
31
+ props: { logos },
32
+ });
33
+
34
+ cy.get("div.logo").find('img[alt="Logo 2"]').should("exist");
35
+ });
36
+
37
+ it("sets css variables on mount", () => {
38
+ cy.mount(LogoSection, {
39
+ props: {
40
+ logos: [],
41
+ baseHeight: 5,
42
+ strength: 0.8,
43
+ },
44
+ });
45
+
46
+ cy.get(".logo-row")
47
+ .should("have.attr", "style")
48
+ .and("include", "--base-height: 5rem");
49
+ cy.get(".logo-row")
50
+ .should("have.attr", "style")
51
+ .and("include", "--strength: 0.8");
52
+ });
53
+ });