Add ESLint back into CI pipeline
This commit is contained in:
+13
-14
@@ -491,20 +491,19 @@ jobs:
|
|||||||
# - run: npm run test:sb
|
# - run: npm run test:sb
|
||||||
# env: { CI: true }
|
# env: { CI: true }
|
||||||
|
|
||||||
# Temporarily disabled - 523 pre-existing ESLint issues will be addressed in separate ticket
|
lint:
|
||||||
# lint:
|
runs-on: [self-hosted, macos-latest]
|
||||||
# runs-on: [self-hosted, macos-latest]
|
steps:
|
||||||
# steps:
|
- uses: actions/checkout@v4
|
||||||
# - uses: actions/checkout@v4
|
- uses: actions/setup-node@v4
|
||||||
# - uses: actions/setup-node@v4
|
if: ${{ github.server_url == 'https://github.com' }}
|
||||||
# if: ${{ github.server_url == 'https://github.com' }}
|
with: { node-version: 20, cache: npm }
|
||||||
# with: { node-version: 20, cache: npm }
|
- uses: actions/setup-node@v4
|
||||||
# - uses: actions/setup-node@v4
|
if: ${{ github.server_url != 'https://github.com' || !github.server_url }}
|
||||||
# if: ${{ github.server_url != 'https://github.com' || !github.server_url }}
|
with: { node-version: 20 }
|
||||||
# with: { node-version: 20 }
|
- run: npm ci
|
||||||
# - run: npm ci
|
- run: npm run lint
|
||||||
# - run: npm run lint
|
- run: npm exec prettier -- --check "**/*.{js,jsx,ts,tsx,json,css,md}"
|
||||||
# - run: npm exec prettier -- --check "**/*.{js,jsx,ts,tsx,json,css,md}"
|
|
||||||
|
|
||||||
build:
|
build:
|
||||||
runs-on: [self-hosted, macos-latest]
|
runs-on: [self-hosted, macos-latest]
|
||||||
|
|||||||
@@ -151,13 +151,12 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
|
|||||||
`.trim();
|
`.trim();
|
||||||
|
|
||||||
// Form field handlers with disabled state handling
|
// Form field handlers with disabled state handling
|
||||||
const { handleChange, handleFocus, handleBlur } = useFormField<
|
const { handleChange, handleFocus, handleBlur } =
|
||||||
HTMLInputElement
|
useFormField<HTMLInputElement>(disabled, {
|
||||||
>(disabled, {
|
onChange,
|
||||||
onChange,
|
onFocus,
|
||||||
onFocus,
|
onBlur,
|
||||||
onBlur,
|
});
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={containerClasses}>
|
<div className={containerClasses}>
|
||||||
|
|||||||
@@ -10,25 +10,23 @@ interface NumberedCardProps {
|
|||||||
iconColor?: string;
|
iconColor?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const NumberedCard = memo<NumberedCardProps>(
|
const NumberedCard = memo<NumberedCardProps>(({ number, text }) => {
|
||||||
({ number, text }) => {
|
return (
|
||||||
return (
|
<div className="bg-[var(--color-surface-inverse-primary)] rounded-[12px] p-5 shadow-lg flex flex-col gap-4 sm:p-8 sm:gap-8 sm:flex-row sm:items-center lg:p-8 lg:gap-0 lg:flex-row lg:items-stretch lg:relative lg:h-[238px]">
|
||||||
<div className="bg-[var(--color-surface-inverse-primary)] rounded-[12px] p-5 shadow-lg flex flex-col gap-4 sm:p-8 sm:gap-8 sm:flex-row sm:items-center lg:p-8 lg:gap-0 lg:flex-row lg:items-stretch lg:relative lg:h-[238px]">
|
{/* Section Number - Top right (lg breakpoint) */}
|
||||||
{/* Section Number - Top right (lg breakpoint) */}
|
<div className="flex justify-end sm:justify-start sm:flex-shrink-0 lg:absolute lg:top-8 lg:right-8">
|
||||||
<div className="flex justify-end sm:justify-start sm:flex-shrink-0 lg:absolute lg:top-8 lg:right-8">
|
<SectionNumber number={number} />
|
||||||
<SectionNumber number={number} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Card Content - Bottom left (lg breakpoint) */}
|
|
||||||
<div className="sm:flex-1 lg:absolute lg:bottom-8 lg:left-8 lg:right-16">
|
|
||||||
<p className="font-bricolage-grotesque font-medium text-[24px] leading-[32px] sm:font-normal sm:leading-[24px] sm:text-[24px] lg:text-[24px] lg:leading-[24px] xl:text-[32px] xl:leading-[32px] text-[#141414]">
|
|
||||||
{text}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
|
||||||
},
|
{/* Card Content - Bottom left (lg breakpoint) */}
|
||||||
);
|
<div className="sm:flex-1 lg:absolute lg:bottom-8 lg:left-8 lg:right-16">
|
||||||
|
<p className="font-bricolage-grotesque font-medium text-[24px] leading-[32px] sm:font-normal sm:leading-[24px] sm:text-[24px] lg:text-[24px] lg:leading-[24px] xl:text-[32px] xl:leading-[32px] text-[#141414]">
|
||||||
|
{text}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
NumberedCard.displayName = "NumberedCard";
|
NumberedCard.displayName = "NumberedCard";
|
||||||
|
|
||||||
|
|||||||
@@ -155,13 +155,12 @@ const TextArea = forwardRef<HTMLTextAreaElement, TextAreaProps>(
|
|||||||
`.trim();
|
`.trim();
|
||||||
|
|
||||||
// Form field handlers with disabled state handling
|
// Form field handlers with disabled state handling
|
||||||
const { handleChange, handleFocus, handleBlur } = useFormField<
|
const { handleChange, handleFocus, handleBlur } =
|
||||||
HTMLTextAreaElement
|
useFormField<HTMLTextAreaElement>(disabled, {
|
||||||
>(disabled, {
|
onChange,
|
||||||
onChange,
|
onFocus,
|
||||||
onFocus,
|
onBlur,
|
||||||
onBlur,
|
});
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={containerClasses}>
|
<div className={containerClasses}>
|
||||||
|
|||||||
+6
-1
@@ -12,7 +12,12 @@ export { useComponentId } from "./useComponentId";
|
|||||||
export { useFormField } from "./useFormField";
|
export { useFormField } from "./useFormField";
|
||||||
export { useComponentStyles } from "./useComponentStyles";
|
export { useComponentStyles } from "./useComponentStyles";
|
||||||
export { useSchemaData } from "./useSchemaData";
|
export { useSchemaData } from "./useSchemaData";
|
||||||
export { useMediaQuery, useIsMobile, useIsDesktop, BREAKPOINTS } from "./useMediaQuery";
|
export {
|
||||||
|
useMediaQuery,
|
||||||
|
useIsMobile,
|
||||||
|
useIsDesktop,
|
||||||
|
BREAKPOINTS,
|
||||||
|
} from "./useMediaQuery";
|
||||||
export { useFormValidation, validationRules } from "./useFormValidation";
|
export { useFormValidation, validationRules } from "./useFormValidation";
|
||||||
export type {
|
export type {
|
||||||
SizeStyleConfig,
|
SizeStyleConfig,
|
||||||
|
|||||||
@@ -61,9 +61,7 @@ export interface UseComponentStylesOptions {
|
|||||||
* });
|
* });
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
export function useComponentStyles(
|
export function useComponentStyles(options: UseComponentStylesOptions): {
|
||||||
options: UseComponentStylesOptions,
|
|
||||||
): {
|
|
||||||
sizeClasses: Record<string, string>;
|
sizeClasses: Record<string, string>;
|
||||||
stateClasses: Record<string, string>;
|
stateClasses: Record<string, string>;
|
||||||
} {
|
} {
|
||||||
|
|||||||
@@ -22,24 +22,30 @@ export const validationRules = {
|
|||||||
return emailRegex.test(value) ? null : "Please enter a valid email address";
|
return emailRegex.test(value) ? null : "Please enter a valid email address";
|
||||||
},
|
},
|
||||||
|
|
||||||
minLength: (min: number) => (value: string): string | null => {
|
minLength:
|
||||||
if (!value) return null;
|
(min: number) =>
|
||||||
return value.length >= min
|
(value: string): string | null => {
|
||||||
? null
|
if (!value) return null;
|
||||||
: `Must be at least ${min} characters long`;
|
return value.length >= min
|
||||||
},
|
? null
|
||||||
|
: `Must be at least ${min} characters long`;
|
||||||
|
},
|
||||||
|
|
||||||
maxLength: (max: number) => (value: string): string | null => {
|
maxLength:
|
||||||
if (!value) return null;
|
(max: number) =>
|
||||||
return value.length <= max
|
(value: string): string | null => {
|
||||||
? null
|
if (!value) return null;
|
||||||
: `Must be no more than ${max} characters long`;
|
return value.length <= max
|
||||||
},
|
? null
|
||||||
|
: `Must be no more than ${max} characters long`;
|
||||||
|
},
|
||||||
|
|
||||||
pattern: (regex: RegExp, message: string) => (value: string): string | null => {
|
pattern:
|
||||||
if (!value) return null;
|
(regex: RegExp, message: string) =>
|
||||||
return regex.test(value) ? null : message;
|
(value: string): string | null => {
|
||||||
},
|
if (!value) return null;
|
||||||
|
return regex.test(value) ? null : message;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -182,13 +188,16 @@ export function useFormValidation(options: UseFormValidationOptions) {
|
|||||||
}, [initialValues]);
|
}, [initialValues]);
|
||||||
|
|
||||||
// Set field value programmatically
|
// Set field value programmatically
|
||||||
const setValue = useCallback((name: string, value: string) => {
|
const setValue = useCallback(
|
||||||
setValues((prev) => ({ ...prev, [name]: value }));
|
(name: string, value: string) => {
|
||||||
if (validateOnChange) {
|
setValues((prev) => ({ ...prev, [name]: value }));
|
||||||
const error = validateField(name, value);
|
if (validateOnChange) {
|
||||||
setErrors((prev) => ({ ...prev, [name]: error }));
|
const error = validateField(name, value);
|
||||||
}
|
setErrors((prev) => ({ ...prev, [name]: error }));
|
||||||
}, [validateOnChange, validateField]);
|
}
|
||||||
|
},
|
||||||
|
[validateOnChange, validateField],
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
values,
|
values,
|
||||||
|
|||||||
@@ -144,7 +144,12 @@ export function useSchemaData(
|
|||||||
type: "BreadcrumbList";
|
type: "BreadcrumbList";
|
||||||
items: Array<{ name: string; url: string }>;
|
items: Array<{ name: string; url: string }>;
|
||||||
},
|
},
|
||||||
): SchemaOrganization | SchemaWebSite | SchemaHowTo | SchemaArticle | SchemaBreadcrumbList {
|
):
|
||||||
|
| SchemaOrganization
|
||||||
|
| SchemaWebSite
|
||||||
|
| SchemaHowTo
|
||||||
|
| SchemaArticle
|
||||||
|
| SchemaBreadcrumbList {
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
switch (config.type) {
|
switch (config.type) {
|
||||||
case "Organization":
|
case "Organization":
|
||||||
@@ -216,7 +221,9 @@ export function useSchemaData(
|
|||||||
"@id": config.mainEntityOfPage,
|
"@id": config.mainEntityOfPage,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
...(config.articleSection && { articleSection: config.articleSection }),
|
...(config.articleSection && {
|
||||||
|
articleSection: config.articleSection,
|
||||||
|
}),
|
||||||
...(config.keywords && { keywords: config.keywords }),
|
...(config.keywords && { keywords: config.keywords }),
|
||||||
} as SchemaArticle;
|
} as SchemaArticle;
|
||||||
|
|
||||||
|
|||||||
+26
-12
@@ -15,6 +15,7 @@ Detects clicks outside of specified elements. Useful for closing dropdowns, moda
|
|||||||
**Location:** `app/hooks/useClickOutside.ts`
|
**Location:** `app/hooks/useClickOutside.ts`
|
||||||
|
|
||||||
**Usage:**
|
**Usage:**
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
import { useClickOutside } from "../hooks";
|
import { useClickOutside } from "../hooks";
|
||||||
|
|
||||||
@@ -26,6 +27,7 @@ useClickOutside([menuRef, buttonRef], () => setIsOpen(false), isOpen);
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Parameters:**
|
**Parameters:**
|
||||||
|
|
||||||
- `refs`: Array of refs to elements that should not trigger the callback
|
- `refs`: Array of refs to elements that should not trigger the callback
|
||||||
- `handler`: Callback function to execute when clicking outside
|
- `handler`: Callback function to execute when clicking outside
|
||||||
- `enabled`: Whether the hook is enabled (default: true)
|
- `enabled`: Whether the hook is enabled (default: true)
|
||||||
@@ -41,6 +43,7 @@ Centralized analytics tracking for component interactions. Supports both Google
|
|||||||
**Location:** `app/hooks/useAnalytics.ts`
|
**Location:** `app/hooks/useAnalytics.ts`
|
||||||
|
|
||||||
**Usage:**
|
**Usage:**
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
import { useAnalytics } from "../hooks";
|
import { useAnalytics } from "../hooks";
|
||||||
|
|
||||||
@@ -66,6 +69,7 @@ trackCustomEvent(
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Returns:**
|
**Returns:**
|
||||||
|
|
||||||
- `trackEvent`: Function to track standard analytics events
|
- `trackEvent`: Function to track standard analytics events
|
||||||
- `trackCustomEvent`: Function to track custom events with optional callback
|
- `trackCustomEvent`: Function to track custom events with optional callback
|
||||||
|
|
||||||
@@ -80,6 +84,7 @@ Generates unique component IDs for accessibility. Provides consistent ID generat
|
|||||||
**Location:** `app/hooks/useComponentId.ts`
|
**Location:** `app/hooks/useComponentId.ts`
|
||||||
|
|
||||||
**Usage:**
|
**Usage:**
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
import { useComponentId } from "../hooks";
|
import { useComponentId } from "../hooks";
|
||||||
|
|
||||||
@@ -89,10 +94,12 @@ const { id, labelId } = useComponentId("input", props.id);
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Parameters:**
|
**Parameters:**
|
||||||
|
|
||||||
- `prefix`: Prefix for the generated ID (e.g., "input", "select")
|
- `prefix`: Prefix for the generated ID (e.g., "input", "select")
|
||||||
- `providedId`: Optional ID provided via props (takes precedence)
|
- `providedId`: Optional ID provided via props (takes precedence)
|
||||||
|
|
||||||
**Returns:**
|
**Returns:**
|
||||||
|
|
||||||
- `id`: Component ID
|
- `id`: Component ID
|
||||||
- `labelId`: Associated label ID for accessibility
|
- `labelId`: Associated label ID for accessibility
|
||||||
|
|
||||||
@@ -107,6 +114,7 @@ Manages form field event handlers with disabled state handling. Ensures handlers
|
|||||||
**Location:** `app/hooks/useFormField.ts`
|
**Location:** `app/hooks/useFormField.ts`
|
||||||
|
|
||||||
**Usage:**
|
**Usage:**
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
import { useFormField } from "../hooks";
|
import { useFormField } from "../hooks";
|
||||||
|
|
||||||
@@ -117,18 +125,16 @@ const { handleChange, handleFocus, handleBlur } = useFormField(disabled, {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Use in component
|
// Use in component
|
||||||
<input
|
<input onChange={handleChange} onFocus={handleFocus} onBlur={handleBlur} />;
|
||||||
onChange={handleChange}
|
|
||||||
onFocus={handleFocus}
|
|
||||||
onBlur={handleBlur}
|
|
||||||
/>
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**Parameters:**
|
**Parameters:**
|
||||||
|
|
||||||
- `disabled`: Whether the field is disabled
|
- `disabled`: Whether the field is disabled
|
||||||
- `handlers`: Object containing onChange, onFocus, onBlur handlers
|
- `handlers`: Object containing onChange, onFocus, onBlur handlers
|
||||||
|
|
||||||
**Returns:**
|
**Returns:**
|
||||||
|
|
||||||
- `handleChange`: Wrapped onChange handler
|
- `handleChange`: Wrapped onChange handler
|
||||||
- `handleFocus`: Wrapped onFocus handler
|
- `handleFocus`: Wrapped onFocus handler
|
||||||
- `handleBlur`: Wrapped onBlur handler
|
- `handleBlur`: Wrapped onBlur handler
|
||||||
@@ -144,6 +150,7 @@ Manages component size and state styles. Provides a consistent pattern for styli
|
|||||||
**Location:** `app/hooks/useComponentStyles.ts`
|
**Location:** `app/hooks/useComponentStyles.ts`
|
||||||
|
|
||||||
**Usage:**
|
**Usage:**
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
import { useComponentStyles } from "../hooks";
|
import { useComponentStyles } from "../hooks";
|
||||||
|
|
||||||
@@ -178,6 +185,7 @@ Generates Schema.org structured data (JSON-LD) for SEO and search engines.
|
|||||||
**Location:** `app/hooks/useSchemaData.ts`
|
**Location:** `app/hooks/useSchemaData.ts`
|
||||||
|
|
||||||
**Usage:**
|
**Usage:**
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
import { useSchemaData } from "../hooks";
|
import { useSchemaData } from "../hooks";
|
||||||
|
|
||||||
@@ -205,10 +213,11 @@ const orgSchema = useSchemaData({
|
|||||||
<script
|
<script
|
||||||
type="application/ld+json"
|
type="application/ld+json"
|
||||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaData) }}
|
dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaData) }}
|
||||||
/>
|
/>;
|
||||||
```
|
```
|
||||||
|
|
||||||
**Supported Types:**
|
**Supported Types:**
|
||||||
|
|
||||||
- `Organization` - Organization information
|
- `Organization` - Organization information
|
||||||
- `WebSite` - Website navigation and search
|
- `WebSite` - Website navigation and search
|
||||||
- `HowTo` - Step-by-step instructions
|
- `HowTo` - Step-by-step instructions
|
||||||
@@ -226,6 +235,7 @@ Responsive breakpoint detection using window.matchMedia.
|
|||||||
**Location:** `app/hooks/useMediaQuery.ts`
|
**Location:** `app/hooks/useMediaQuery.ts`
|
||||||
|
|
||||||
**Usage:**
|
**Usage:**
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
import { useMediaQuery, useIsMobile, useIsDesktop } from "../hooks";
|
import { useMediaQuery, useIsMobile, useIsDesktop } from "../hooks";
|
||||||
|
|
||||||
@@ -242,6 +252,7 @@ const isDesktop = useIsDesktop(); // lg breakpoint and above
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Available Breakpoints:**
|
**Available Breakpoints:**
|
||||||
|
|
||||||
- `sm`: 640px
|
- `sm`: 640px
|
||||||
- `md`: 768px
|
- `md`: 768px
|
||||||
- `lg`: 1024px
|
- `lg`: 1024px
|
||||||
@@ -259,6 +270,7 @@ Form validation with field-level error handling.
|
|||||||
**Location:** `app/hooks/useFormValidation.ts`
|
**Location:** `app/hooks/useFormValidation.ts`
|
||||||
|
|
||||||
**Usage:**
|
**Usage:**
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
import { useFormValidation, validationRules } from "../hooks";
|
import { useFormValidation, validationRules } from "../hooks";
|
||||||
|
|
||||||
@@ -275,10 +287,7 @@ const {
|
|||||||
initialValues: { email: "", password: "" },
|
initialValues: { email: "", password: "" },
|
||||||
validationRules: {
|
validationRules: {
|
||||||
email: [validationRules.required, validationRules.email],
|
email: [validationRules.required, validationRules.email],
|
||||||
password: [
|
password: [validationRules.required, validationRules.minLength(8)],
|
||||||
validationRules.required,
|
|
||||||
validationRules.minLength(8),
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
validateOnChange: true,
|
validateOnChange: true,
|
||||||
validateOnBlur: true,
|
validateOnBlur: true,
|
||||||
@@ -290,11 +299,14 @@ const {
|
|||||||
value={values.email}
|
value={values.email}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
onBlur={handleBlur}
|
onBlur={handleBlur}
|
||||||
/>
|
/>;
|
||||||
{errors.email && touched.email && <span>{errors.email}</span>}
|
{
|
||||||
|
errors.email && touched.email && <span>{errors.email}</span>;
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**Available Validation Rules:**
|
**Available Validation Rules:**
|
||||||
|
|
||||||
- `validationRules.required` - Field is required
|
- `validationRules.required` - Field is required
|
||||||
- `validationRules.email` - Valid email format
|
- `validationRules.email` - Valid email format
|
||||||
- `validationRules.minLength(n)` - Minimum length
|
- `validationRules.minLength(n)` - Minimum length
|
||||||
@@ -302,6 +314,7 @@ const {
|
|||||||
- `validationRules.pattern(regex, message)` - Custom regex pattern
|
- `validationRules.pattern(regex, message)` - Custom regex pattern
|
||||||
|
|
||||||
**Returns:**
|
**Returns:**
|
||||||
|
|
||||||
- `values` - Current form values
|
- `values` - Current form values
|
||||||
- `errors` - Field error messages
|
- `errors` - Field error messages
|
||||||
- `touched` - Fields that have been interacted with
|
- `touched` - Fields that have been interacted with
|
||||||
@@ -317,6 +330,7 @@ const {
|
|||||||
## Best Practices
|
## Best Practices
|
||||||
|
|
||||||
1. **Import from index:** Always import hooks from `app/hooks` index file:
|
1. **Import from index:** Always import hooks from `app/hooks` index file:
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
import { useAnalytics, useComponentId } from "../hooks";
|
import { useAnalytics, useComponentId } from "../hooks";
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -9,29 +9,35 @@ This directory contains all project documentation organized by topic.
|
|||||||
Comprehensive guides for different aspects of the project:
|
Comprehensive guides for different aspects of the project:
|
||||||
|
|
||||||
#### Testing
|
#### Testing
|
||||||
|
|
||||||
- **[testing.md](./guides/testing.md)** - Complete testing strategy and philosophy
|
- **[testing.md](./guides/testing.md)** - Complete testing strategy and philosophy
|
||||||
- **[testing-framework.md](./guides/testing-framework.md)** - Detailed testing framework documentation
|
- **[testing-framework.md](./guides/testing-framework.md)** - Detailed testing framework documentation
|
||||||
- **[testing-quick-reference.md](./guides/testing-quick-reference.md)** - Quick reference for daily development
|
- **[testing-quick-reference.md](./guides/testing-quick-reference.md)** - Quick reference for daily development
|
||||||
- **[visual-regression.md](./guides/visual-regression.md)** - Visual regression testing guide
|
- **[visual-regression.md](./guides/visual-regression.md)** - Visual regression testing guide
|
||||||
|
|
||||||
#### Performance
|
#### Performance
|
||||||
|
|
||||||
- **[performance.md](./guides/performance.md)** - Performance optimization and monitoring guide
|
- **[performance.md](./guides/performance.md)** - Performance optimization and monitoring guide
|
||||||
|
|
||||||
#### Content
|
#### Content
|
||||||
|
|
||||||
- **[content-creation.md](./guides/content-creation.md)** - Content creation guidelines
|
- **[content-creation.md](./guides/content-creation.md)** - Content creation guidelines
|
||||||
|
|
||||||
## 🎯 Quick Navigation
|
## 🎯 Quick Navigation
|
||||||
|
|
||||||
### For New Team Members
|
### For New Team Members
|
||||||
|
|
||||||
1. Start with **[testing.md](./guides/testing.md)** to understand the testing strategy
|
1. Start with **[testing.md](./guides/testing.md)** to understand the testing strategy
|
||||||
2. Use **[testing-quick-reference.md](./guides/testing-quick-reference.md)** for daily development
|
2. Use **[testing-quick-reference.md](./guides/testing-quick-reference.md)** for daily development
|
||||||
3. Reference **[performance.md](./guides/performance.md)** for performance optimization
|
3. Reference **[performance.md](./guides/performance.md)** for performance optimization
|
||||||
|
|
||||||
### For Daily Development
|
### For Daily Development
|
||||||
|
|
||||||
- **[testing-quick-reference.md](./guides/testing-quick-reference.md)** - Essential commands and troubleshooting
|
- **[testing-quick-reference.md](./guides/testing-quick-reference.md)** - Essential commands and troubleshooting
|
||||||
- **[testing-framework.md](./guides/testing-framework.md)** - Detailed testing explanations
|
- **[testing-framework.md](./guides/testing-framework.md)** - Detailed testing explanations
|
||||||
|
|
||||||
### For Specific Topics
|
### For Specific Topics
|
||||||
|
|
||||||
- **Visual Testing**: [visual-regression.md](./guides/visual-regression.md)
|
- **Visual Testing**: [visual-regression.md](./guides/visual-regression.md)
|
||||||
- **Performance**: [performance.md](./guides/performance.md)
|
- **Performance**: [performance.md](./guides/performance.md)
|
||||||
- **Content**: [content-creation.md](./guides/content-creation.md)
|
- **Content**: [content-creation.md](./guides/content-creation.md)
|
||||||
|
|||||||
@@ -24,4 +24,3 @@ export const logger = {
|
|||||||
console.error(...args);
|
console.error(...args);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -332,11 +332,19 @@ class PlaywrightPerformanceMonitor extends PerformanceMonitor {
|
|||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Handle interstitial/blocking errors
|
// Handle interstitial/blocking errors
|
||||||
if (error.message.includes("interstitial") || error.message.includes("prevented")) {
|
if (
|
||||||
console.warn("Page load was blocked, attempting to continue:", error.message);
|
error.message.includes("interstitial") ||
|
||||||
|
error.message.includes("prevented")
|
||||||
|
) {
|
||||||
|
console.warn(
|
||||||
|
"Page load was blocked, attempting to continue:",
|
||||||
|
error.message,
|
||||||
|
);
|
||||||
// Try to wait for the page to be in a usable state
|
// Try to wait for the page to be in a usable state
|
||||||
try {
|
try {
|
||||||
await this.page.waitForLoadState("domcontentloaded", { timeout: 10000 });
|
await this.page.waitForLoadState("domcontentloaded", {
|
||||||
|
timeout: 10000,
|
||||||
|
});
|
||||||
} catch {
|
} catch {
|
||||||
throw new Error(`Page failed to load: ${error.message}`);
|
throw new Error(`Page failed to load: ${error.message}`);
|
||||||
}
|
}
|
||||||
@@ -349,9 +357,11 @@ class PlaywrightPerformanceMonitor extends PerformanceMonitor {
|
|||||||
// This ensures code-split components have loaded
|
// This ensures code-split components have loaded
|
||||||
try {
|
try {
|
||||||
// Wait for main content sections that use dynamic imports
|
// Wait for main content sections that use dynamic imports
|
||||||
await this.page.waitForSelector("section", { timeout: 10000 }).catch(() => {
|
await this.page
|
||||||
// Ignore if sections don't appear - page might still be valid
|
.waitForSelector("section", { timeout: 10000 })
|
||||||
});
|
.catch(() => {
|
||||||
|
// Ignore if sections don't appear - page might still be valid
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Continue even if some components haven't loaded - we still want to measure performance
|
// Continue even if some components haven't loaded - we still want to measure performance
|
||||||
console.warn("Some components may not have loaded:", error.message);
|
console.warn("Some components may not have loaded:", error.message);
|
||||||
|
|||||||
@@ -100,7 +100,8 @@ describe("Page", () => {
|
|||||||
// Wait for dynamically imported FeatureGrid component to load
|
// Wait for dynamically imported FeatureGrid component to load
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(
|
expect(
|
||||||
screen.getAllByText("We've got your back, every step of the way").length,
|
screen.getAllByText("We've got your back, every step of the way")
|
||||||
|
.length,
|
||||||
).toBeGreaterThan(0);
|
).toBeGreaterThan(0);
|
||||||
});
|
});
|
||||||
expect(
|
expect(
|
||||||
@@ -143,7 +144,8 @@ describe("Page", () => {
|
|||||||
// FeatureGrid
|
// FeatureGrid
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(
|
expect(
|
||||||
screen.getAllByText("We've got your back, every step of the way").length,
|
screen.getAllByText("We've got your back, every step of the way")
|
||||||
|
.length,
|
||||||
).toBeGreaterThan(0);
|
).toBeGreaterThan(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -221,7 +223,8 @@ describe("Page", () => {
|
|||||||
});
|
});
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(
|
expect(
|
||||||
screen.getAllByText("We've got your back, every step of the way").length,
|
screen.getAllByText("We've got your back, every step of the way")
|
||||||
|
.length,
|
||||||
).toBeGreaterThan(0);
|
).toBeGreaterThan(0);
|
||||||
});
|
});
|
||||||
expect(screen.getAllByText("Still have questions?").length).toBeGreaterThan(
|
expect(screen.getAllByText("Still have questions?").length).toBeGreaterThan(
|
||||||
@@ -236,7 +239,8 @@ describe("Page", () => {
|
|||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
// Check all three numbered card items (using getAllByText since there are multiple instances)
|
// Check all three numbered card items (using getAllByText since there are multiple instances)
|
||||||
expect(
|
expect(
|
||||||
screen.getAllByText("Document how your community makes decisions").length,
|
screen.getAllByText("Document how your community makes decisions")
|
||||||
|
.length,
|
||||||
).toBeGreaterThan(0);
|
).toBeGreaterThan(0);
|
||||||
});
|
});
|
||||||
expect(
|
expect(
|
||||||
|
|||||||
@@ -26,7 +26,9 @@ describe("useClickOutside", () => {
|
|||||||
result.current.current = div;
|
result.current.current = div;
|
||||||
|
|
||||||
act(() => {
|
act(() => {
|
||||||
document.body.dispatchEvent(new MouseEvent("mousedown", { bubbles: true }));
|
document.body.dispatchEvent(
|
||||||
|
new MouseEvent("mousedown", { bubbles: true }),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(handler).toHaveBeenCalledTimes(1);
|
expect(handler).toHaveBeenCalledTimes(1);
|
||||||
@@ -62,7 +64,9 @@ describe("useClickOutside", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
act(() => {
|
act(() => {
|
||||||
document.body.dispatchEvent(new MouseEvent("mousedown", { bubbles: true }));
|
document.body.dispatchEvent(
|
||||||
|
new MouseEvent("mousedown", { bubbles: true }),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(handler).not.toHaveBeenCalled();
|
expect(handler).not.toHaveBeenCalled();
|
||||||
@@ -84,7 +88,9 @@ describe("useClickOutside", () => {
|
|||||||
result.current.ref2.current = div2;
|
result.current.ref2.current = div2;
|
||||||
|
|
||||||
act(() => {
|
act(() => {
|
||||||
document.body.dispatchEvent(new MouseEvent("mousedown", { bubbles: true }));
|
document.body.dispatchEvent(
|
||||||
|
new MouseEvent("mousedown", { bubbles: true }),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(handler).toHaveBeenCalledTimes(1);
|
expect(handler).toHaveBeenCalledTimes(1);
|
||||||
|
|||||||
+2
-1
@@ -52,7 +52,8 @@ Object.defineProperty(window, "matchMedia", {
|
|||||||
|
|
||||||
// Use window.innerWidth if set by tests, otherwise default to desktop (1200px)
|
// Use window.innerWidth if set by tests, otherwise default to desktop (1200px)
|
||||||
// This allows tests to override viewport width by setting window.innerWidth
|
// This allows tests to override viewport width by setting window.innerWidth
|
||||||
const viewportWidth = (typeof window !== "undefined" && window.innerWidth) || 1200;
|
const viewportWidth =
|
||||||
|
(typeof window !== "undefined" && window.innerWidth) || 1200;
|
||||||
let matches = true;
|
let matches = true;
|
||||||
|
|
||||||
if (minWidthMatch) {
|
if (minWidthMatch) {
|
||||||
|
|||||||
Reference in New Issue
Block a user