Adjust testing with localization
This commit is contained in:
@@ -13,32 +13,48 @@ const FeatureGridContainer = memo<FeatureGridProps>(
|
|||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
backgroundColor: "bg-[var(--color-surface-default-brand-royal)]",
|
backgroundColor: "bg-[var(--color-surface-default-brand-royal)]",
|
||||||
labelLine1: t("pages.home.featureGrid.features.decisionMaking.labelLine1"),
|
labelLine1: t(
|
||||||
labelLine2: t("pages.home.featureGrid.features.decisionMaking.labelLine2"),
|
"pages.home.featureGrid.features.decisionMaking.labelLine1",
|
||||||
|
),
|
||||||
|
labelLine2: t(
|
||||||
|
"pages.home.featureGrid.features.decisionMaking.labelLine2",
|
||||||
|
),
|
||||||
panelContent: "/assets/Feature_Support.png",
|
panelContent: "/assets/Feature_Support.png",
|
||||||
ariaLabel: t("featureGrid.features.decisionMaking.ariaLabel"),
|
ariaLabel: t("featureGrid.features.decisionMaking.ariaLabel"),
|
||||||
href: "#decision-making",
|
href: "#decision-making",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
backgroundColor: "bg-[#D1FFE2]",
|
backgroundColor: "bg-[#D1FFE2]",
|
||||||
labelLine1: t("pages.home.featureGrid.features.valuesAlignment.labelLine1"),
|
labelLine1: t(
|
||||||
labelLine2: t("pages.home.featureGrid.features.valuesAlignment.labelLine2"),
|
"pages.home.featureGrid.features.valuesAlignment.labelLine1",
|
||||||
|
),
|
||||||
|
labelLine2: t(
|
||||||
|
"pages.home.featureGrid.features.valuesAlignment.labelLine2",
|
||||||
|
),
|
||||||
panelContent: "/assets/Feature_Exercises.png",
|
panelContent: "/assets/Feature_Exercises.png",
|
||||||
ariaLabel: t("featureGrid.features.valuesAlignment.ariaLabel"),
|
ariaLabel: t("featureGrid.features.valuesAlignment.ariaLabel"),
|
||||||
href: "#values-alignment",
|
href: "#values-alignment",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
backgroundColor: "bg-[#F4CAFF]",
|
backgroundColor: "bg-[#F4CAFF]",
|
||||||
labelLine1: t("pages.home.featureGrid.features.membershipGuidance.labelLine1"),
|
labelLine1: t(
|
||||||
labelLine2: t("pages.home.featureGrid.features.membershipGuidance.labelLine2"),
|
"pages.home.featureGrid.features.membershipGuidance.labelLine1",
|
||||||
|
),
|
||||||
|
labelLine2: t(
|
||||||
|
"pages.home.featureGrid.features.membershipGuidance.labelLine2",
|
||||||
|
),
|
||||||
panelContent: "/assets/Feature_Guidance.png",
|
panelContent: "/assets/Feature_Guidance.png",
|
||||||
ariaLabel: t("featureGrid.features.membershipGuidance.ariaLabel"),
|
ariaLabel: t("featureGrid.features.membershipGuidance.ariaLabel"),
|
||||||
href: "#membership-guidance",
|
href: "#membership-guidance",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
backgroundColor: "bg-[#CBDDFF]",
|
backgroundColor: "bg-[#CBDDFF]",
|
||||||
labelLine1: t("pages.home.featureGrid.features.conflictResolution.labelLine1"),
|
labelLine1: t(
|
||||||
labelLine2: t("pages.home.featureGrid.features.conflictResolution.labelLine2"),
|
"pages.home.featureGrid.features.conflictResolution.labelLine1",
|
||||||
|
),
|
||||||
|
labelLine2: t(
|
||||||
|
"pages.home.featureGrid.features.conflictResolution.labelLine2",
|
||||||
|
),
|
||||||
panelContent: "/assets/Feature_Tools.png",
|
panelContent: "/assets/Feature_Tools.png",
|
||||||
ariaLabel: t("featureGrid.features.conflictResolution.ariaLabel"),
|
ariaLabel: t("featureGrid.features.conflictResolution.ariaLabel"),
|
||||||
href: "#conflict-resolution",
|
href: "#conflict-resolution",
|
||||||
|
|||||||
@@ -17,10 +17,7 @@ const Footer = memo(() => {
|
|||||||
name: t("organization.name"),
|
name: t("organization.name"),
|
||||||
email: t("organization.email"),
|
email: t("organization.email"),
|
||||||
url: t("organization.url"),
|
url: t("organization.url"),
|
||||||
sameAs: [
|
sameAs: [t("social.bluesky.url"), t("social.gitlab.url")],
|
||||||
t("social.bluesky.url"),
|
|
||||||
t("social.gitlab.url"),
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -96,7 +96,11 @@ const HeaderContainer = memo<HeaderProps>(() => {
|
|||||||
|
|
||||||
const renderLoginButton = (size: NavSize) => {
|
const renderLoginButton = (size: NavSize) => {
|
||||||
return (
|
return (
|
||||||
<MenuBarItem href="#" size={size} ariaLabel={t("ariaLabels.logInToAccount")}>
|
<MenuBarItem
|
||||||
|
href="#"
|
||||||
|
size={size}
|
||||||
|
ariaLabel={t("ariaLabels.logInToAccount")}
|
||||||
|
>
|
||||||
{t("buttons.logIn")}
|
{t("buttons.logIn")}
|
||||||
</MenuBarItem>
|
</MenuBarItem>
|
||||||
);
|
);
|
||||||
@@ -108,10 +112,7 @@ const HeaderContainer = memo<HeaderProps>(() => {
|
|||||||
avatarSize: "small" | "medium" | "large" | "xlarge",
|
avatarSize: "small" | "medium" | "large" | "xlarge",
|
||||||
) => {
|
) => {
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button size={buttonSize} ariaLabel={t("ariaLabels.createNewRule")}>
|
||||||
size={buttonSize}
|
|
||||||
ariaLabel={t("ariaLabels.createNewRule")}
|
|
||||||
>
|
|
||||||
{renderAvatarGroup(containerSize, avatarSize)}
|
{renderAvatarGroup(containerSize, avatarSize)}
|
||||||
<span>{t("buttons.createRule")}</span>
|
<span>{t("buttons.createRule")}</span>
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
export { default } from "./Header.container";
|
export { default } from "./Header.container";
|
||||||
export type { HeaderProps } from "./Header.types";
|
export type { HeaderProps } from "./Header.types";
|
||||||
export { navigationItems, avatarImages, logoConfig } from "./Header.container";
|
export { avatarImages, logoConfig } from "./Header.container";
|
||||||
|
|||||||
@@ -12,7 +12,10 @@ interface MessagesProviderProps {
|
|||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function MessagesProvider({ messages, children }: MessagesProviderProps) {
|
export function MessagesProvider({
|
||||||
|
messages,
|
||||||
|
children,
|
||||||
|
}: MessagesProviderProps) {
|
||||||
return (
|
return (
|
||||||
<MessagesContext.Provider value={messages}>
|
<MessagesContext.Provider value={messages}>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
+1
-5
@@ -87,11 +87,7 @@ export const metadata: Metadata = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function RootLayout({
|
export default function RootLayout({ children }: { children: ReactNode }) {
|
||||||
children,
|
|
||||||
}: {
|
|
||||||
children: ReactNode;
|
|
||||||
}) {
|
|
||||||
// Load messages for the default locale (single locale setup)
|
// Load messages for the default locale (single locale setup)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ This guide explains how to work with translations in the CommunityRule applicati
|
|||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
All UI text is stored in JSON files under `messages/en/`. The structure follows best practices:
|
All UI text is stored in JSON files under `messages/en/`. The structure follows best practices:
|
||||||
|
|
||||||
- **Page-specific content** lives in `pages/` (varies by page context)
|
- **Page-specific content** lives in `pages/` (varies by page context)
|
||||||
- **Component defaults** live in `components/` (shared across pages)
|
- **Component defaults** live in `components/` (shared across pages)
|
||||||
- **Common strings** live in `common.json` (shared UI elements)
|
- **Common strings** live in `common.json` (shared UI elements)
|
||||||
@@ -35,6 +36,7 @@ messages/
|
|||||||
## When to Use `pages/` vs `components/`
|
## When to Use `pages/` vs `components/`
|
||||||
|
|
||||||
### Use `pages/` for:
|
### Use `pages/` for:
|
||||||
|
|
||||||
- **Page-specific content**: Titles, subtitles, descriptions that vary by page
|
- **Page-specific content**: Titles, subtitles, descriptions that vary by page
|
||||||
- **Context-aware text**: Content that changes based on where the component is used
|
- **Context-aware text**: Content that changes based on where the component is used
|
||||||
- **User-facing content**: All text that users see on a specific page
|
- **User-facing content**: All text that users see on a specific page
|
||||||
@@ -42,6 +44,7 @@ messages/
|
|||||||
**Example:** The home page hero banner title "Collaborate" goes in `pages/home.json`, not `components/heroBanner.json`
|
**Example:** The home page hero banner title "Collaborate" goes in `pages/home.json`, not `components/heroBanner.json`
|
||||||
|
|
||||||
### Use `components/` for:
|
### Use `components/` for:
|
||||||
|
|
||||||
- **Component defaults**: Aria-labels, alt text patterns, shared behavior text
|
- **Component defaults**: Aria-labels, alt text patterns, shared behavior text
|
||||||
- **Shared across pages**: Text that doesn't vary by page context
|
- **Shared across pages**: Text that doesn't vary by page context
|
||||||
- **Accessibility text**: Aria-labels and alt texts that are component-level
|
- **Accessibility text**: Aria-labels and alt texts that are component-level
|
||||||
@@ -49,6 +52,7 @@ messages/
|
|||||||
**Example:** The hero banner image alt text "Hero illustration" stays in `components/heroBanner.json` because it's the same across all pages
|
**Example:** The hero banner image alt text "Hero illustration" stays in `components/heroBanner.json` because it's the same across all pages
|
||||||
|
|
||||||
### Use `common.json` for:
|
### Use `common.json` for:
|
||||||
|
|
||||||
- **Shared UI strings**: Buttons, links, labels used across multiple components
|
- **Shared UI strings**: Buttons, links, labels used across multiple components
|
||||||
- **Global strings**: Text that appears in many places
|
- **Global strings**: Text that appears in many places
|
||||||
|
|
||||||
@@ -57,23 +61,25 @@ messages/
|
|||||||
For page-specific content, use the `pages.*` namespace pattern:
|
For page-specific content, use the `pages.*` namespace pattern:
|
||||||
|
|
||||||
**Server Components:**
|
**Server Components:**
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import messages from "../../messages/en/index";
|
import messages from "../../messages/en/index";
|
||||||
import { getTranslation } from "../../lib/i18n/getTranslation";
|
import { getTranslation } from "../../lib/i18n/getTranslation";
|
||||||
|
|
||||||
export default function LearnPage() {
|
export default function LearnPage() {
|
||||||
const t = (key: string) => getTranslation(messages, key);
|
const t = (key: string) => getTranslation(messages, key);
|
||||||
|
|
||||||
const contentLockupData = {
|
const contentLockupData = {
|
||||||
title: t("pages.learn.contentLockup.title"),
|
title: t("pages.learn.contentLockup.title"),
|
||||||
subtitle: t("pages.learn.contentLockup.subtitle"),
|
subtitle: t("pages.learn.contentLockup.subtitle"),
|
||||||
};
|
};
|
||||||
|
|
||||||
return <ContentLockup {...contentLockupData} />;
|
return <ContentLockup {...contentLockupData} />;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**Client Components:**
|
**Client Components:**
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
"use client";
|
"use client";
|
||||||
import { useTranslation } from "../../contexts/MessagesContext";
|
import { useTranslation } from "../../contexts/MessagesContext";
|
||||||
@@ -95,6 +101,7 @@ Determine which component needs the translation. If it's a shared string (like a
|
|||||||
Open the appropriate JSON file and add your translation key. Use descriptive, semantic keys:
|
Open the appropriate JSON file and add your translation key. Use descriptive, semantic keys:
|
||||||
|
|
||||||
**Good:**
|
**Good:**
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"heroBanner": {
|
"heroBanner": {
|
||||||
@@ -105,6 +112,7 @@ Open the appropriate JSON file and add your translation key. Use descriptive, se
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Bad:**
|
**Bad:**
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"text1": "Collaborate",
|
"text1": "Collaborate",
|
||||||
@@ -131,24 +139,26 @@ Group related translations together:
|
|||||||
### 4. Update the Component or Page
|
### 4. Update the Component or Page
|
||||||
|
|
||||||
**For Page Components (Server Components):**
|
**For Page Components (Server Components):**
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import messages from "../../messages/en/index";
|
import messages from "../../messages/en/index";
|
||||||
import { getTranslation } from "../../lib/i18n/getTranslation";
|
import { getTranslation } from "../../lib/i18n/getTranslation";
|
||||||
|
|
||||||
export default function MyPage() {
|
export default function MyPage() {
|
||||||
const t = (key: string) => getTranslation(messages, key);
|
const t = (key: string) => getTranslation(messages, key);
|
||||||
|
|
||||||
// Use page-specific keys
|
// Use page-specific keys
|
||||||
const data = {
|
const data = {
|
||||||
title: t("pages.home.heroBanner.title"),
|
title: t("pages.home.heroBanner.title"),
|
||||||
subtitle: t("pages.home.heroBanner.subtitle"),
|
subtitle: t("pages.home.heroBanner.subtitle"),
|
||||||
};
|
};
|
||||||
|
|
||||||
return <HeroBanner {...data} />;
|
return <HeroBanner {...data} />;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**For Client Components:**
|
**For Client Components:**
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
"use client";
|
"use client";
|
||||||
import { useTranslation } from "../../contexts/MessagesContext";
|
import { useTranslation } from "../../contexts/MessagesContext";
|
||||||
@@ -157,7 +167,7 @@ export default function MyComponent() {
|
|||||||
// For page-specific content
|
// For page-specific content
|
||||||
const t = useTranslation("pages.home.heroBanner");
|
const t = useTranslation("pages.home.heroBanner");
|
||||||
return <h1>{t("title")}</h1>;
|
return <h1>{t("title")}</h1>;
|
||||||
|
|
||||||
// For component defaults
|
// For component defaults
|
||||||
const tDefault = useTranslation("heroBanner");
|
const tDefault = useTranslation("heroBanner");
|
||||||
return <img alt={tDefault("imageAlt")} />;
|
return <img alt={tDefault("imageAlt")} />;
|
||||||
@@ -173,6 +183,7 @@ export default function MyComponent() {
|
|||||||
5. **Include context in comments**: Use `_comment` fields for clarity
|
5. **Include context in comments**: Use `_comment` fields for clarity
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"_comment": "HeroBanner component translations",
|
"_comment": "HeroBanner component translations",
|
||||||
@@ -194,6 +205,7 @@ When migrating a component to use translations:
|
|||||||
### Example Migration
|
### Example Migration
|
||||||
|
|
||||||
**Before:**
|
**Before:**
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
export default function HeroBanner() {
|
export default function HeroBanner() {
|
||||||
return (
|
return (
|
||||||
@@ -206,6 +218,7 @@ export default function HeroBanner() {
|
|||||||
```
|
```
|
||||||
|
|
||||||
**After:**
|
**After:**
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
"use client";
|
"use client";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
@@ -231,6 +244,7 @@ When creating a new page that needs translations:
|
|||||||
4. **Use in page component**: Use `t("pages.about.*")` pattern in your page
|
4. **Use in page component**: Use `t("pages.about.*")` pattern in your page
|
||||||
|
|
||||||
**Example:**
|
**Example:**
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// messages/en/pages/about.json
|
// messages/en/pages/about.json
|
||||||
{
|
{
|
||||||
@@ -280,6 +294,7 @@ When adding support for a new language:
|
|||||||
## Common Patterns
|
## Common Patterns
|
||||||
|
|
||||||
### Buttons and CTAs
|
### Buttons and CTAs
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"buttons": {
|
"buttons": {
|
||||||
@@ -290,6 +305,7 @@ When adding support for a new language:
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Aria Labels
|
### Aria Labels
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"ariaLabels": {
|
"ariaLabels": {
|
||||||
@@ -300,7 +316,9 @@ When adding support for a new language:
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Dynamic Content
|
### Dynamic Content
|
||||||
|
|
||||||
For content that varies (like card text), use arrays or numbered keys:
|
For content that varies (like card text), use arrays or numbered keys:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"cards": {
|
"cards": {
|
||||||
@@ -316,6 +334,7 @@ For content that varies (like card text), use arrays or numbered keys:
|
|||||||
### Translation Key Not Found
|
### Translation Key Not Found
|
||||||
|
|
||||||
If you see a key path like `heroBanner.title` instead of the text:
|
If you see a key path like `heroBanner.title` instead of the text:
|
||||||
|
|
||||||
1. Check the JSON file exists and has the key
|
1. Check the JSON file exists and has the key
|
||||||
2. Verify the key path matches exactly (case-sensitive)
|
2. Verify the key path matches exactly (case-sensitive)
|
||||||
3. Restart the dev server if you just added the key
|
3. Restart the dev server if you just added the key
|
||||||
@@ -323,6 +342,7 @@ If you see a key path like `heroBanner.title` instead of the text:
|
|||||||
### TypeScript Errors
|
### TypeScript Errors
|
||||||
|
|
||||||
If TypeScript complains about translation keys:
|
If TypeScript complains about translation keys:
|
||||||
|
|
||||||
1. Ensure the key exists in the JSON file
|
1. Ensure the key exists in the JSON file
|
||||||
2. Check for typos in the key path
|
2. Check for typos in the key path
|
||||||
3. Verify the namespace is correct if using `useTranslations("namespace")`
|
3. Verify the namespace is correct if using `useTranslations("namespace")`
|
||||||
@@ -330,6 +350,7 @@ If TypeScript complains about translation keys:
|
|||||||
### Missing Translations
|
### Missing Translations
|
||||||
|
|
||||||
If text doesn't appear:
|
If text doesn't appear:
|
||||||
|
|
||||||
1. Check the browser console for errors
|
1. Check the browser console for errors
|
||||||
2. Verify the component is wrapped in `MessagesProvider` (for client components)
|
2. Verify the component is wrapped in `MessagesProvider` (for client components)
|
||||||
3. Ensure `getTranslation()` is called correctly in server components
|
3. Ensure `getTranslation()` is called correctly in server components
|
||||||
@@ -338,10 +359,12 @@ If text doesn't appear:
|
|||||||
## Architecture: Hybrid Approach
|
## Architecture: Hybrid Approach
|
||||||
|
|
||||||
This implementation follows the recognized best practice of combining:
|
This implementation follows the recognized best practice of combining:
|
||||||
|
|
||||||
- **Globalized, shared UI elements**: Component defaults in `components/` (aria-labels, alt texts)
|
- **Globalized, shared UI elements**: Component defaults in `components/` (aria-labels, alt texts)
|
||||||
- **Context-aware, localized content pages**: Page-specific content in `pages/` (titles, descriptions)
|
- **Context-aware, localized content pages**: Page-specific content in `pages/` (titles, descriptions)
|
||||||
|
|
||||||
This allows:
|
This allows:
|
||||||
|
|
||||||
- Components to remain flexible and reusable
|
- Components to remain flexible and reusable
|
||||||
- Page content to be easily edited without code changes
|
- Page content to be easily edited without code changes
|
||||||
- Clear separation between shared defaults and page-specific content
|
- Clear separation between shared defaults and page-specific content
|
||||||
|
|||||||
@@ -11,10 +11,7 @@ type Messages = typeof messages;
|
|||||||
* @param key - Dot-separated key path (e.g., "heroBanner.title")
|
* @param key - Dot-separated key path (e.g., "heroBanner.title")
|
||||||
* @returns The translation string or the key if not found
|
* @returns The translation string or the key if not found
|
||||||
*/
|
*/
|
||||||
export function getTranslation(
|
export function getTranslation(messages: Messages, key: string): string {
|
||||||
messages: Messages,
|
|
||||||
key: string,
|
|
||||||
): string {
|
|
||||||
const keys = key.split(".");
|
const keys = key.split(".");
|
||||||
let value: any = messages;
|
let value: any = messages;
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* Type definitions for translation keys
|
* Type definitions for translation keys
|
||||||
*
|
*
|
||||||
* These types provide type safety when accessing translation keys.
|
* These types provide type safety when accessing translation keys.
|
||||||
* The actual types are inferred from the JSON files in messages/en/
|
* The actual types are inferred from the JSON files in messages/en/
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -3,6 +3,11 @@
|
|||||||
"home": {
|
"home": {
|
||||||
"title": "CommunityRule - Build operating manuals for successful communities",
|
"title": "CommunityRule - Build operating manuals for successful communities",
|
||||||
"description": "Help your community make important decisions in a way that reflects its unique values.",
|
"description": "Help your community make important decisions in a way that reflects its unique values.",
|
||||||
"keywords": ["community", "governance", "decision-making", "operating manual"]
|
"keywords": [
|
||||||
|
"community",
|
||||||
|
"governance",
|
||||||
|
"decision-making",
|
||||||
|
"operating manual"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { render, screen } from "@testing-library/react";
|
import { renderWithProviders as render, screen } from "../utils/test-utils";
|
||||||
import { describe, it, expect } from "vitest";
|
import { describe, it, expect } from "vitest";
|
||||||
import AskOrganizer from "../../app/components/AskOrganizer";
|
import AskOrganizer from "../../app/components/AskOrganizer";
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { render, screen } from "@testing-library/react";
|
import { renderWithProviders as render, screen } from "../utils/test-utils";
|
||||||
import { describe, it, expect } from "vitest";
|
import { describe, it, expect } from "vitest";
|
||||||
import FeatureGrid from "../../app/components/FeatureGrid";
|
import FeatureGrid from "../../app/components/FeatureGrid";
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { render, screen } from "@testing-library/react";
|
import { renderWithProviders as render, screen } from "../utils/test-utils";
|
||||||
import { describe, it, expect } from "vitest";
|
import { describe, it, expect } from "vitest";
|
||||||
import Footer from "../../app/components/Footer";
|
import Footer from "../../app/components/Footer";
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { render, screen } from "@testing-library/react";
|
import { renderWithProviders as render, screen } from "../utils/test-utils";
|
||||||
import { describe, it, expect } from "vitest";
|
import { describe, it, expect } from "vitest";
|
||||||
import HeroBanner from "../../app/components/HeroBanner";
|
import HeroBanner from "../../app/components/HeroBanner";
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
import { describe, test, expect } from "vitest";
|
import { describe, test, expect } from "vitest";
|
||||||
import { render, screen, waitFor } from "@testing-library/react";
|
import {
|
||||||
|
renderWithProviders as render,
|
||||||
|
screen,
|
||||||
|
waitFor,
|
||||||
|
} from "../utils/test-utils";
|
||||||
import Page from "../../app/page";
|
import Page from "../../app/page";
|
||||||
|
|
||||||
describe("Page", () => {
|
describe("Page", () => {
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
import { render, screen, cleanup, waitFor } from "@testing-library/react";
|
import {
|
||||||
|
renderWithProviders as render,
|
||||||
|
screen,
|
||||||
|
cleanup,
|
||||||
|
waitFor,
|
||||||
|
} from "../utils/test-utils";
|
||||||
import userEvent from "@testing-library/user-event";
|
import userEvent from "@testing-library/user-event";
|
||||||
import { vi, describe, test, expect, afterEach } from "vitest";
|
import { vi, describe, test, expect, afterEach } from "vitest";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
import { render, screen, cleanup, waitFor } from "@testing-library/react";
|
import {
|
||||||
|
renderWithProviders as render,
|
||||||
|
screen,
|
||||||
|
cleanup,
|
||||||
|
waitFor,
|
||||||
|
} from "../utils/test-utils";
|
||||||
import userEvent from "@testing-library/user-event";
|
import userEvent from "@testing-library/user-event";
|
||||||
import { vi, describe, test, expect, afterEach } from "vitest";
|
import { vi, describe, test, expect, afterEach } from "vitest";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
import { render, screen, cleanup } from "@testing-library/react";
|
import {
|
||||||
|
renderWithProviders as render,
|
||||||
|
screen,
|
||||||
|
cleanup,
|
||||||
|
} from "../utils/test-utils";
|
||||||
import { describe, test, expect, afterEach } from "vitest";
|
import { describe, test, expect, afterEach } from "vitest";
|
||||||
import NumberedCards from "../../app/components/NumberedCards";
|
import NumberedCards from "../../app/components/NumberedCards";
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
import { render, screen, cleanup } from "@testing-library/react";
|
import {
|
||||||
|
renderWithProviders as render,
|
||||||
|
screen,
|
||||||
|
cleanup,
|
||||||
|
} from "../utils/test-utils";
|
||||||
import { vi, describe, test, expect, afterEach } from "vitest";
|
import { vi, describe, test, expect, afterEach } from "vitest";
|
||||||
import QuoteBlock from "../../app/components/QuoteBlock";
|
import QuoteBlock from "../../app/components/QuoteBlock";
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
import { render, screen, fireEvent } from "@testing-library/react";
|
import {
|
||||||
|
renderWithProviders as render,
|
||||||
|
screen,
|
||||||
|
fireEvent,
|
||||||
|
} from "../utils/test-utils";
|
||||||
import { describe, it, expect, vi } from "vitest";
|
import { describe, it, expect, vi } from "vitest";
|
||||||
import RuleCard from "../../app/components/RuleCard";
|
import RuleCard from "../../app/components/RuleCard";
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
import { render, screen, cleanup } from "@testing-library/react";
|
import {
|
||||||
|
renderWithProviders as render,
|
||||||
|
screen,
|
||||||
|
cleanup,
|
||||||
|
} from "../utils/test-utils";
|
||||||
import userEvent from "@testing-library/user-event";
|
import userEvent from "@testing-library/user-event";
|
||||||
import { vi, describe, test, expect, afterEach } from "vitest";
|
import { vi, describe, test, expect, afterEach } from "vitest";
|
||||||
import { logger } from "../../lib/logger";
|
import { logger } from "../../lib/logger";
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { describe, it, expect } from "vitest";
|
import { describe, it, expect } from "vitest";
|
||||||
import { render, screen } from "@testing-library/react";
|
import { screen } from "@testing-library/react";
|
||||||
import userEvent from "@testing-library/user-event";
|
import userEvent from "@testing-library/user-event";
|
||||||
import { axe } from "jest-axe";
|
import { axe } from "jest-axe";
|
||||||
|
import { renderWithProviders as render } from "./test-utils";
|
||||||
|
|
||||||
type TestCases = {
|
type TestCases = {
|
||||||
renders?: boolean;
|
renders?: boolean;
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import React, { type ReactElement } from "react";
|
||||||
|
import { render, type RenderOptions } from "@testing-library/react";
|
||||||
|
import { MessagesProvider } from "../../app/contexts/MessagesContext";
|
||||||
|
import messages from "../../messages/en/index";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom render function that wraps components with MessagesProvider
|
||||||
|
* Use this instead of the default render from @testing-library/react
|
||||||
|
* for components that use useTranslation hook
|
||||||
|
*/
|
||||||
|
export function renderWithProviders(
|
||||||
|
ui: ReactElement,
|
||||||
|
options?: Omit<RenderOptions, "wrapper">,
|
||||||
|
) {
|
||||||
|
function Wrapper({ children }: { children: React.ReactNode }) {
|
||||||
|
return <MessagesProvider messages={messages}>{children}</MessagesProvider>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return render(ui, { wrapper: Wrapper, ...options });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-export everything from @testing-library/react for convenience
|
||||||
|
export * from "@testing-library/react";
|
||||||
Reference in New Issue
Block a user