Case de engenharia de software
{caseStudy.title}: {caseStudy.subtitle}
- {caseStudy.summary} -
-- {caseStudy.impact} + {caseStudy.detail?.answer.text ?? caseStudy.summary}
+ {!caseStudy.detail ? ( ++ {caseStudy.impact} +
+ ) : null} +-
+ {caseStudy.stack.map((item) => (
+
- + {item} + + ))} +
+ Desafio verificável +
++ {caseStudy.detail.challenge.text} +
++ Decisões de implementação +
+-
+ {caseStudy.detail.implementation.map((statement) => (
+
- {statement.text} + ))} +
+ Limites desta evidência +
+-
+ {caseStudy.detail.boundaries.map((boundary) => (
+
- {boundary.text} + ))} +
+ Fontes +
+-
+ {caseStudy.detail.sources.map((source) => {
+ const sha = getSourceSha(source.url);
+ return (
+
-
+
+ Fonte: {source.label}
+
+ {sha ? (
+
+ Commit:
{sha}+ + ) : null} +
+ );
+ })}
+
Serviços relacionados
+- Tecnologias e contexto técnico -
-- Este case demonstra como aplico engenharia de software a produtos - com requisitos de performance, integração e operação, para - empresas do Norte de Santa Catarina, Curitiba e atuação remota. -
--
- {caseStudy.stack.map((item) => (
-
- - {item} - - ))} -
Próximos caminhos
+Selected work
Cases em software, Next.js, integrações e produtos conectados.
- Esta página organiza projetos com leitura mais indexável e - comercial, conectando stack, contexto técnico e tipo de problema - resolvido. + {pageDescription}
- Preencha o Formulário -
- -- Entre em Contato -
-{data.contact.email}
-{whatsAppDisplay}
-- {data.contact.linkedin.replace("https://www.", "")} -
-GitHub
-- {data.contact.github.replace("https://github.com/", "")} -
-- Localização -
-📍 {data.contact.location}
-- Disponível para projetos remotos e trabalho presencial na região. -
-- Timeline de Resposta -
-- Em breve, uma timeline detalhada do processo de contato e resposta. -
-{title}
- {children} -{title}
- {children} -{title}
- {children} -{title}
+ {children} +{exp.title}
++ {exp.title} +
{exp.company} • {exp.type}
@@ -87,7 +89,9 @@ export default function Experience({{exp.description}
++ {exp.description} +
-
{exp.achievements?.map((achievement) => (
diff --git a/app/components/Footer.ariaLabels.pbt.test.tsx b/app/components/Footer.ariaLabels.pbt.test.tsx
deleted file mode 100644
index 546e535..0000000
--- a/app/components/Footer.ariaLabels.pbt.test.tsx
+++ /dev/null
@@ -1,300 +0,0 @@
-import { render } from "@testing-library/react";
-import fc from "fast-check";
-import Footer from "./Footer";
-
-describe("Property 9: ARIA Labels for Links", () => {
- it("should have descriptive aria-label attributes for all social media links", () => {
- /**
- * **Validates: Requirements 6.1**
- *
- * Property: For any social media link in the Footer component, it should have
- * a descriptive aria-label attribute that identifies the platform and action.
- * This ensures accessibility for users with assistive technologies.
- */
- fc.assert(
- fc.property(
- fc.constant(null), // No random input needed, testing static component
- () => {
- const { container } = render();
-
- // Find all social media links (they have SVG icons and are in the Connect section)
- const socialLinks = container.querySelectorAll(
- "footer a[aria-label]",
- );
-
- // Filter to only social links (those with SVG children)
- const socialLinksWithSvg = Array.from(socialLinks).filter((link) => {
- return link.querySelector("svg") !== null;
- });
-
- // Verify we found social links
- if (socialLinksWithSvg.length === 0) {
- return false;
- }
-
- // Check each social link has a non-empty, descriptive aria-label
- return socialLinksWithSvg.every((link) => {
- const ariaLabel = link.getAttribute("aria-label");
-
- // aria-label must exist and be non-empty
- if (!ariaLabel || ariaLabel.trim().length === 0) {
- return false;
- }
-
- // aria-label should be descriptive (at least 5 characters)
- // and should describe an action (contain words like "Visit", "View", "Go to", etc.)
- return ariaLabel.length >= 5;
- });
- },
- ),
- { numRuns: 100 },
- );
- });
-
- it("should have unique aria-label for each social platform", () => {
- /**
- * **Validates: Requirements 6.1**
- *
- * Property: For any set of social media links, each should have a unique
- * aria-label that identifies the specific platform. This ensures users can
- * distinguish between different social links when using screen readers.
- */
- fc.assert(
- fc.property(fc.constant(null), () => {
- const { container } = render();
-
- // Find all social media links with aria-labels
- const socialLinks = container.querySelectorAll("footer a[aria-label]");
-
- // Filter to only social links (those with SVG children)
- const socialLinksWithSvg = Array.from(socialLinks).filter((link) => {
- return link.querySelector("svg") !== null;
- });
-
- // Collect all aria-labels
- const ariaLabels = socialLinksWithSvg.map((link) =>
- link.getAttribute("aria-label"),
- );
-
- // Verify we have aria-labels
- if (ariaLabels.length === 0) {
- return false;
- }
-
- // Check that all aria-labels are unique
- const uniqueLabels = new Set(ariaLabels);
- return uniqueLabels.size === ariaLabels.length;
- }),
- { numRuns: 100 },
- );
- });
-
- it("should have aria-label that identifies both platform and action", () => {
- /**
- * **Validates: Requirements 6.1**
- *
- * Property: For any social media link, the aria-label should contain both
- * the platform name (GitHub, LinkedIn, etc.) and an action verb (Visit, View, etc.).
- * This provides complete context for assistive technology users.
- */
- fc.assert(
- fc.property(fc.constant(null), () => {
- const { container } = render();
-
- // Find all social media links
- const socialLinks = container.querySelectorAll("footer a[aria-label]");
-
- // Filter to only social links (those with SVG children)
- const socialLinksWithSvg = Array.from(socialLinks).filter((link) => {
- return link.querySelector("svg") !== null;
- });
-
- // Verify we found social links
- if (socialLinksWithSvg.length === 0) {
- return false;
- }
-
- // Common action verbs for social links
- const actionVerbs = ["visit", "view", "go to", "open", "see", "check"];
-
- // Check each link has an action verb in its aria-label
- return socialLinksWithSvg.every((link) => {
- const ariaLabel = link.getAttribute("aria-label");
-
- if (!ariaLabel) {
- return false;
- }
-
- const lowerLabel = ariaLabel.toLowerCase();
-
- // Check if aria-label contains at least one action verb
- return actionVerbs.some((verb) => lowerLabel.includes(verb));
- });
- }),
- { numRuns: 100 },
- );
- });
-
- it("should maintain aria-labels across multiple renders", () => {
- /**
- * **Validates: Requirements 6.1**
- *
- * Property: For any number of component renders, social links should consistently
- * maintain their aria-label attributes. This verifies that accessibility attributes
- * are not accidentally removed during re-renders.
- */
- fc.assert(
- fc.property(
- fc.integer({ min: 1, max: 5 }), // Test 1-5 renders
- (numRenders) => {
- let allRendersValid = true;
-
- for (let i = 0; i < numRenders; i++) {
- const { container, unmount } = render();
-
- // Find social links
- const socialLinks = container.querySelectorAll(
- "footer a[aria-label]",
- );
- const socialLinksWithSvg = Array.from(socialLinks).filter(
- (link) => {
- return link.querySelector("svg") !== null;
- },
- );
-
- // Check each link has a valid aria-label
- socialLinksWithSvg.forEach((link) => {
- const ariaLabel = link.getAttribute("aria-label");
-
- if (!ariaLabel || ariaLabel.trim().length < 5) {
- allRendersValid = false;
- }
- });
-
- unmount();
- }
-
- return allRendersValid;
- },
- ),
- { numRuns: 100 },
- );
- });
-
- it('should have aria-label for every social link with target="_blank"', () => {
- /**
- * **Validates: Requirements 6.1**
- *
- * Property: For any social link that opens in a new tab (target="_blank"),
- * it must have an aria-label. This ensures users with assistive technologies
- * are informed about where the link leads before activating it.
- */
- fc.assert(
- fc.property(fc.constant(null), () => {
- const { container } = render();
-
- // Find all links that open in new tab
- const newTabLinks = container.querySelectorAll(
- 'footer a[target="_blank"]',
- );
-
- // Verify we found links
- if (newTabLinks.length === 0) {
- return false;
- }
-
- // Each new tab link should have an aria-label
- return Array.from(newTabLinks).every((link) => {
- const ariaLabel = link.getAttribute("aria-label");
- return ariaLabel !== null && ariaLabel.trim().length > 0;
- });
- }),
- { numRuns: 100 },
- );
- });
-
- it("should not have empty or whitespace-only aria-labels", () => {
- /**
- * **Validates: Requirements 6.1**
- *
- * Property: For any social media link with an aria-label attribute,
- * the value should not be empty or contain only whitespace. Empty
- * aria-labels provide no accessibility benefit and can confuse screen readers.
- */
- fc.assert(
- fc.property(fc.constant(null), () => {
- const { container } = render();
-
- // Find all social media links
- const socialLinks = container.querySelectorAll("footer a[aria-label]");
-
- // Filter to only social links (those with SVG children)
- const socialLinksWithSvg = Array.from(socialLinks).filter((link) => {
- return link.querySelector("svg") !== null;
- });
-
- // Verify we found social links
- if (socialLinksWithSvg.length === 0) {
- return false;
- }
-
- // Check that no aria-label is empty or whitespace-only
- return socialLinksWithSvg.every((link) => {
- const ariaLabel = link.getAttribute("aria-label");
- return ariaLabel !== null && ariaLabel.trim().length > 0;
- });
- }),
- { numRuns: 100 },
- );
- });
-
- it("should have aria-label that matches or relates to the link title", () => {
- /**
- * **Validates: Requirements 6.1**
- *
- * Property: For any social media link with both aria-label and title attributes,
- * the aria-label should provide context that relates to the title (platform name).
- * This ensures consistency between visual tooltips and screen reader announcements.
- */
- fc.assert(
- fc.property(fc.constant(null), () => {
- const { container } = render();
-
- // Find all social media links with both aria-label and title
- const socialLinks = container.querySelectorAll(
- "footer a[aria-label][title]",
- );
-
- // Filter to only social links (those with SVG children)
- const socialLinksWithSvg = Array.from(socialLinks).filter((link) => {
- return link.querySelector("svg") !== null;
- });
-
- // If no links have both attributes, that's okay (not all implementations require title)
- if (socialLinksWithSvg.length === 0) {
- return true;
- }
-
- // Check that aria-label relates to title
- return socialLinksWithSvg.every((link) => {
- const ariaLabel = link.getAttribute("aria-label");
- const title = link.getAttribute("title");
-
- if (!ariaLabel || !title) {
- return false;
- }
-
- // aria-label should contain the platform name from title
- // or title should be contained in aria-label
- const lowerLabel = ariaLabel.toLowerCase();
- const lowerTitle = title.toLowerCase();
-
- return (
- lowerLabel.includes(lowerTitle) || lowerTitle.includes(lowerLabel)
- );
- });
- }),
- { numRuns: 100 },
- );
- });
-});
diff --git a/app/components/Footer.responsiveLayout.pbt.test.tsx b/app/components/Footer.responsiveLayout.pbt.test.tsx
deleted file mode 100644
index 8fd6938..0000000
--- a/app/components/Footer.responsiveLayout.pbt.test.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import { render } from "@testing-library/react";
-import fc from "fast-check";
-import Footer from "./Footer";
-
-describe("Footer responsive structure", () => {
- it("keeps the responsive design-system structure across renders", () => {
- fc.assert(
- fc.property(fc.integer({ min: 1, max: 5 }), (renderCount) => {
- for (let index = 0; index < renderCount; index += 1) {
- const { container, unmount } = render();
- const footer = container.querySelector("footer");
- const grid = footer?.querySelector(".ds-grid-2");
-
- expect(footer).toHaveClass("ds-section");
- expect(
- footer?.querySelector(".ds-section-shell"),
- ).toBeInTheDocument();
- expect(grid).toBeInTheDocument();
- expect(grid?.querySelectorAll(":scope > .ds-card")).toHaveLength(2);
-
- unmount();
- }
- }),
- { numRuns: 25 },
- );
- });
-
- it("uses the mobile-first grid primitive instead of the legacy grid contract", () => {
- const { container } = render();
- const grid = container.querySelector(".ds-grid-2");
-
- expect(grid).toBeInTheDocument();
- expect(grid).not.toHaveClass("grid-cols-1");
- expect(grid).not.toHaveClass("lg:grid-cols-3");
- });
-});
diff --git a/app/components/Footer.socialLinks.pbt.test.tsx b/app/components/Footer.socialLinks.pbt.test.tsx
deleted file mode 100644
index 270e055..0000000
--- a/app/components/Footer.socialLinks.pbt.test.tsx
+++ /dev/null
@@ -1,246 +0,0 @@
-import { render } from "@testing-library/react";
-import fc from "fast-check";
-import Footer from "./Footer";
-
-describe("Property 2: Social Links Open in New Tab", () => {
- it('should have target="_blank" and rel="noopener noreferrer" for all social links', () => {
- /**
- * **Validates: Requirements 2.7**
- *
- * Property: For any social media link in the Footer component, it should have
- * target="_blank" and rel="noopener noreferrer" attributes to open in a new tab
- * securely. This ensures consistent behavior across all social links and prevents
- * security vulnerabilities (tabnabbing).
- */
- fc.assert(
- fc.property(
- fc.constant(null), // No random input needed, testing static component
- () => {
- const { container } = render();
-
- // Find all social media links (they are within the "Connect" section)
- const socialLinks = container.querySelectorAll(
- 'a[aria-label*="Visit"]',
- );
-
- // Verify we found social links
- if (socialLinks.length === 0) {
- return false;
- }
-
- // Check each social link
- let allLinksValid = true;
-
- socialLinks.forEach((link) => {
- const target = link.getAttribute("target");
- const rel = link.getAttribute("rel");
-
- // Both attributes must be present and correct
- if (target !== "_blank" || rel !== "noopener noreferrer") {
- allLinksValid = false;
- }
- });
-
- return allLinksValid;
- },
- ),
- { numRuns: 100 },
- );
- });
-
- it("should consistently apply security attributes across all social link variations", () => {
- /**
- * **Validates: Requirements 2.7**
- *
- * Property: For any subset of social links rendered, each link should maintain
- * the security attributes (target="_blank" and rel="noopener noreferrer").
- * This property verifies that the security pattern is consistently applied
- * regardless of which social platforms are included.
- */
- fc.assert(
- fc.property(
- fc.integer({ min: 0, max: 10 }), // Random seed for variation
- () => {
- const { container } = render();
-
- // Get all anchor tags in the footer
- const allLinks = container.querySelectorAll("footer a");
-
- // Filter to only social links (those with SVG icons and aria-labels about visiting profiles)
- const socialLinks = Array.from(allLinks).filter((link) => {
- const ariaLabel = link.getAttribute("aria-label");
- const hasSvg = link.querySelector("svg") !== null;
- return ariaLabel && ariaLabel.includes("Visit") && hasSvg;
- });
-
- // Verify we found social links
- if (socialLinks.length === 0) {
- return false;
- }
-
- // Check that ALL social links have the required attributes
- return socialLinks.every((link) => {
- const target = link.getAttribute("target");
- const rel = link.getAttribute("rel");
- return target === "_blank" && rel === "noopener noreferrer";
- });
- },
- ),
- { numRuns: 100 },
- );
- });
-
- it("should not apply new tab attributes to non-social links", () => {
- /**
- * **Validates: Requirements 2.7**
- *
- * Property: Only social media links should have target="_blank". Other links
- * in the footer (like email) should not force new tab behavior. This ensures
- * the new tab behavior is intentionally applied only where appropriate.
- */
- fc.assert(
- fc.property(fc.constant(null), () => {
- const { container } = render();
-
- // Get all anchor tags in the footer
- const allLinks = container.querySelectorAll("footer a");
-
- // Separate social links from other links
- const socialLinks: Element[] = [];
- const otherLinks: Element[] = [];
-
- allLinks.forEach((link) => {
- const ariaLabel = link.getAttribute("aria-label");
- const hasSvg = link.querySelector("svg") !== null;
-
- if (ariaLabel && ariaLabel.includes("Visit") && hasSvg) {
- socialLinks.push(link);
- } else {
- otherLinks.push(link);
- }
- });
-
- // All social links should have target="_blank"
- const allSocialLinksHaveTarget = socialLinks.every((link) => {
- return link.getAttribute("target") === "_blank";
- });
-
- // Non-social links (like email) should NOT have target="_blank"
- const noOtherLinksHaveTarget = otherLinks.every((link) => {
- return link.getAttribute("target") !== "_blank";
- });
-
- return allSocialLinksHaveTarget && noOtherLinksHaveTarget;
- }),
- { numRuns: 100 },
- );
- });
-
- it("should maintain security attributes after multiple renders", () => {
- /**
- * **Validates: Requirements 2.7**
- *
- * Property: For any number of component renders, social links should consistently
- * maintain their security attributes. This verifies that the attributes are not
- * accidentally removed or modified during re-renders.
- */
- fc.assert(
- fc.property(
- fc.integer({ min: 1, max: 5 }), // Test 1-5 renders
- (numRenders) => {
- let allRendersValid = true;
-
- for (let i = 0; i < numRenders; i++) {
- const { container, unmount } = render();
-
- // Find social links
- const socialLinks = container.querySelectorAll(
- 'a[aria-label*="Visit"]',
- );
-
- // Check each link
- socialLinks.forEach((link) => {
- const target = link.getAttribute("target");
- const rel = link.getAttribute("rel");
-
- if (target !== "_blank" || rel !== "noopener noreferrer") {
- allRendersValid = false;
- }
- });
-
- unmount();
- }
-
- return allRendersValid;
- },
- ),
- { numRuns: 100 },
- );
- });
-
- it("should have both target and rel attributes present together", () => {
- /**
- * **Validates: Requirements 2.7**
- *
- * Property: For any social link, if target="_blank" is present, then
- * rel="noopener noreferrer" must also be present. These attributes work
- * together to provide secure new tab behavior, and one without the other
- * creates a security vulnerability.
- */
- fc.assert(
- fc.property(fc.constant(null), () => {
- const { container } = render();
-
- // Find all links with target="_blank"
- const linksWithTarget = container.querySelectorAll(
- 'footer a[target="_blank"]',
- );
-
- // Verify we found links
- if (linksWithTarget.length === 0) {
- return false;
- }
-
- // Each link with target="_blank" must have rel="noopener noreferrer"
- return Array.from(linksWithTarget).every((link) => {
- const rel = link.getAttribute("rel");
- return rel === "noopener noreferrer";
- });
- }),
- { numRuns: 100 },
- );
- });
-
- it("should have exactly the expected number of social links with security attributes", () => {
- /**
- * **Validates: Requirements 2.7**
- *
- * Property: The Footer should render a specific number of social links (based on
- * the socialLinks array), and all of them should have the security attributes.
- * This ensures no links are accidentally missing the attributes.
- */
- fc.assert(
- fc.property(fc.constant(null), () => {
- const { container } = render();
-
- // Find all social links with security attributes
- const secureLinks = container.querySelectorAll(
- 'footer a[target="_blank"][rel="noopener noreferrer"]',
- );
-
- // Find all social links (with aria-label containing "Visit")
- const allSocialLinks = container.querySelectorAll(
- 'footer a[aria-label*="Visit"]',
- );
-
- // The number of secure links should equal the number of social links
- // This ensures ALL social links have the security attributes
- return (
- secureLinks.length === allSocialLinks.length &&
- allSocialLinks.length > 0
- );
- }),
- { numRuns: 100 },
- );
- });
-});
diff --git a/app/components/Footer.test.tsx b/app/components/Footer.test.tsx
index a9a751f..632daeb 100644
--- a/app/components/Footer.test.tsx
+++ b/app/components/Footer.test.tsx
@@ -72,18 +72,7 @@ describe("Footer social links", () => {
});
});
-describe("Footer structure", () => {
- it("uses the current design-system shell and two-card grid", () => {
- const { container } = render();
- const footer = container.querySelector("footer");
-
- expect(footer).toHaveClass("ds-section");
- expect(footer?.querySelector(".ds-section-shell")).toBeInTheDocument();
- expect(footer?.querySelector(".ds-grid-2")).toBeInTheDocument();
- expect(footer?.querySelectorAll(":scope .ds-card")).toHaveLength(2);
- expect(footer?.querySelectorAll("h3")).toHaveLength(2);
- });
-
+describe("Footer copyright", () => {
it("renders the current year without hardcoded copyright text", () => {
render();
diff --git a/app/components/GoogleAnalytics.test.tsx b/app/components/GoogleAnalytics.test.tsx
new file mode 100644
index 0000000..9567d1e
--- /dev/null
+++ b/app/components/GoogleAnalytics.test.tsx
@@ -0,0 +1,220 @@
+import { render, screen, waitFor } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import type { ComponentProps } from "react";
+import GoogleAnalytics from "./GoogleAnalytics";
+
+jest.mock("next/script", () => ({
+ __esModule: true,
+ default: ({
+ strategy: _strategy,
+ ...props
+ }: ComponentProps<"script"> & { strategy?: string }) => ,
+}));
+
+const CONSENT_KEY = "evandro.analytics-consent.v1";
+const GA_DISABLE_KEY = "ga-disable-G-QTT9QT0T3S";
+const GA_SRC = "https://www.googletagmanager.com/gtag/js?id=G-QTT9QT0T3S";
+
+type AnalyticsWindow = Window & {
+ dataLayer?: IArguments[];
+ gtag?: (...args: unknown[]) => void;
+ [GA_DISABLE_KEY]?: boolean;
+};
+
+const analyticsWindow = window as AnalyticsWindow;
+
+function consentUpdates(gtag: jest.Mock) {
+ return gtag.mock.calls.filter(
+ ([command, action]) => command === "consent" && action === "update",
+ );
+}
+
+function setCookie(value: string) {
+ // biome-ignore lint/suspicious/noDocumentCookie: JSDOM cookie setup is the behavior boundary exercised by revocation.
+ document.cookie = value;
+}
+
+describe("GoogleAnalytics consent", () => {
+ beforeEach(() => {
+ jest.replaceProperty(process.env, "NODE_ENV", "production");
+ document.getElementById("google-analytics-script")?.remove();
+ localStorage.clear();
+ setCookie("_ga=; Max-Age=0; Path=/");
+ setCookie("_ga_PORTFOLIO=; Max-Age=0; Path=/");
+ setCookie("portfolio_session=; Max-Age=0; Path=/");
+ analyticsWindow.dataLayer = undefined;
+ analyticsWindow.gtag = undefined;
+ analyticsWindow[GA_DISABLE_KEY] = undefined;
+ });
+
+ afterEach(() => {
+ jest.restoreAllMocks();
+ });
+
+ it("waits for a decision before loading analytics", async () => {
+ render(
+ {"Privacidade // Analytics"} +
++ Você decide se permite métricas anônimas para melhorar este site. + Publicidade e personalização continuam desativadas. +
++ {"Preferências // Analytics"} +
++ Analytics está {consent === "granted" ? "ativo" : "desativado"}. + Dados de publicidade permanecem negados. +
+