Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import storybook from 'eslint-plugin-storybook';
import importPlugin from 'eslint-plugin-import';
import simpleImportSort from 'eslint-plugin-simple-import-sort';
import vitestPlugin from '@vitest/eslint-plugin';

import js from '@eslint/js';
import globals from 'globals';
Expand Down Expand Up @@ -38,6 +39,18 @@ export default tseslint.config(
'simple-import-sort/imports': 'error',
'@typescript-eslint/consistent-type-imports': 'error'
}
},
{
files: ['**/*.test.{ts,tsx}', '**/*.spec.{ts,tsx}'],
plugins: { vitest: vitestPlugin },
...vitestPlugin.configs.recommended,
languageOptions: {
globals: {
...vitestPlugin.environments.env.globals
}
},
settings: { vitest: { typecheck: true } },
rules: { 'vitest/expect-expect': 'error' }
}
],
storybook.configs['flat/recommended']
Expand Down
16 changes: 8 additions & 8 deletions lib/components/DualScrollSync/DualScrollSync.test.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
import { render } from '@testing-library/react';

import { DualScrollSync } from './DualScrollSync';
import { DualScrollSyncBase } from './DualScrollSync';

describe('DualScrollSync', () => {
it('should render children correctly', () => {
const { getByTestId } = render(
<DualScrollSync>
<DualScrollSyncBase>
<div>Test Content</div>
</DualScrollSync>
</DualScrollSyncBase>
);

expect(getByTestId('dual-scroll-sync')).toBeInTheDocument();
});

it('should render with provided id', () => {
const { getByTestId } = render(
<DualScrollSync id="custom-id">
<DualScrollSyncBase id="custom-id">
<div>Test Content</div>
</DualScrollSync>
</DualScrollSyncBase>
);

expect(getByTestId('custom-id')).toBeInTheDocument();
Expand All @@ -29,7 +29,7 @@ describe('DualScrollSync', () => {
{ label: 'Item 2', sectionKey: 's2' }
];

const { getByTestId } = render(<DualScrollSync items={items} id="test" />);
const { getByTestId } = render(<DualScrollSyncBase items={items} id="test" />);

expect(getByTestId('test-nav-id')).toBeInTheDocument();
expect(getByTestId('test-nav-id-item-s1')).toBeInTheDocument();
Expand All @@ -41,9 +41,9 @@ describe('DualScrollSync', () => {

it('should render children when items prop is not provided', () => {
const { getByTestId, getByText } = render(
<DualScrollSync id="test">
<DualScrollSyncBase id="test">
<h1 data-testid="child-heading">Child Heading</h1>
</DualScrollSync>
</DualScrollSyncBase>
);

expect(getByTestId('test')).toBeInTheDocument();
Expand Down
19 changes: 16 additions & 3 deletions lib/components/DualScrollSync/DualScrollSync.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,19 @@ import { DualScrollSyncContext } from '@/contexts';
import { useScrollSyncObserver, useValidateChildren } from '@/hooks';

import styles from './DualScrollSync.module.scss';
import type { DualScrollSyncProps } from './DualScrollSync.types';
import type { DualScrollSyncProps, DualScrollSyncType } from './DualScrollSync.types';
import { DualScrollSyncContent } from './DualScrollSyncContent';
import { DualScrollSyncContentSection } from './DualScrollSyncContentSection/DualScrollSyncContentSection';
import { DualScrollSyncLabel } from './DualScrollSyncLabel/DualScrollSyncLabel';
import { DualScrollSyncNav } from './DualScrollSyncNav';
import { DualScrollSyncNavItem } from './DualScrollSyncNavItem';

export const DualScrollSync: FC<DualScrollSyncProps> = ({ children, id, items, onItemClick }) => {
export const DualScrollSyncBase: FC<DualScrollSyncProps> = ({
children,
id,
items,
onItemClick
}) => {
const baseId = id ?? 'dual-scroll-sync';
const navId = `${baseId}-nav`;
const contentId = `${baseId}-content`;
Expand Down Expand Up @@ -80,4 +85,12 @@ export const DualScrollSync: FC<DualScrollSyncProps> = ({ children, id, items, o
);
};

DualScrollSync.displayName = 'DualScrollSync';
DualScrollSyncBase.displayName = 'DualScrollSync';

export const DualScrollSync: DualScrollSyncType = Object.assign(DualScrollSyncBase, {
Nav: DualScrollSyncNav,
NavItem: DualScrollSyncNavItem,
Content: DualScrollSyncContent,
ContentSection: DualScrollSyncContentSection,
Label: DualScrollSyncLabel
});
17 changes: 1 addition & 16 deletions lib/components/DualScrollSync/index.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,6 @@
import { DualScrollSync as DualScrollSyncBase } from './DualScrollSync';
import type { DualScrollSyncType } from './DualScrollSync.types';
import { DualScrollSyncContent } from './DualScrollSyncContent';
import { DualScrollSyncContentSection } from './DualScrollSyncContentSection/DualScrollSyncContentSection';
import { DualScrollSyncLabel } from './DualScrollSyncLabel';
import { DualScrollSyncNav } from './DualScrollSyncNav';
import { DualScrollSyncNavItem } from './DualScrollSyncNavItem';

export { DualScrollSync } from './DualScrollSync';
export type { DualScrollSyncItem, DualScrollSyncProps } from './DualScrollSync.types';
export type * from './DualScrollSyncContent';
export type * from './DualScrollSyncContentSection';
export type * from './DualScrollSyncNav';
export type * from './DualScrollSyncNavItem';

export const DualScrollSync: DualScrollSyncType = Object.assign(DualScrollSyncBase, {
Nav: DualScrollSyncNav,
NavItem: DualScrollSyncNavItem,
Content: DualScrollSyncContent,
ContentSection: DualScrollSyncContentSection,
Label: DualScrollSyncLabel
});
35 changes: 22 additions & 13 deletions lib/hooks/useValidateChildren.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { renderHook, waitFor } from '@testing-library/react';
import type { FC } from 'react';
import { act, type FC } from 'react';
import { vi } from 'vitest';

import { DualScrollSyncContent } from '@/components/DualScrollSync/DualScrollSyncContent';
Expand All @@ -22,17 +22,20 @@ describe('useValidateChildren', () => {
warnSpy.mockRestore();
});

it('should not log warnings when items prop is provided', () => {
it('should not log warnings when items prop is provided', async () => {
const items = [
{ sectionKey: 'section1', label: 'Section 1', children: <div>Content 1</div> },
{ sectionKey: 'section2', label: 'Section 2', children: <div>Content 2</div> }
];

renderHook(() => useValidateChildren({ items, children: null }));
expect(warnSpy).not.toHaveBeenCalled();
act(() => renderHook(() => useValidateChildren({ items, children: null })));

await waitFor(() => {
expect(warnSpy).toHaveBeenCalledTimes(0);
});
});

it('should not log warnings when NavItems and ContentSections match', () => {
it('should not log warnings when NavItems and ContentSections match', async () => {
const children = (
<>
<DualScrollSyncNav>
Expand All @@ -46,8 +49,11 @@ describe('useValidateChildren', () => {
</>
);

renderHook(() => useValidateChildren({ children, items: undefined }));
expect(warnSpy).not.toHaveBeenCalled();
act(() => renderHook(() => useValidateChildren({ children, items: undefined })));

await waitFor(() => {
expect(warnSpy).toHaveBeenCalledTimes(0);
});
});

it('should log warnings for missing ContentSections', async () => {
Expand All @@ -63,7 +69,7 @@ describe('useValidateChildren', () => {
</>
);

renderHook(() => useValidateChildren({ children, items: undefined }));
act(() => renderHook(() => useValidateChildren({ children, items: undefined })));

await waitFor(() => {
expect(warnSpy).toHaveBeenCalledWith(
Expand All @@ -85,7 +91,7 @@ describe('useValidateChildren', () => {
</>
);

renderHook(() => useValidateChildren({ children, items: undefined }));
act(() => renderHook(() => useValidateChildren({ children, items: undefined })));

await waitFor(() => {
expect(warnSpy).toHaveBeenCalledWith('[DualScrollSync] Missing NavItem for "section2"');
Expand All @@ -107,7 +113,7 @@ describe('useValidateChildren', () => {
</div>
);

renderHook(() => useValidateChildren({ children, items: undefined }));
act(() => renderHook(() => useValidateChildren({ children, items: undefined })));

await waitFor(() => {
expect(warnSpy).toHaveBeenCalledWith(
Expand All @@ -116,10 +122,13 @@ describe('useValidateChildren', () => {
});
});

it('should not log warnings for string children', () => {
it('should not log warnings for string children', async () => {
const children = <span>Hello World</span>;

renderHook(() => useValidateChildren({ children, items: undefined }));
expect(warnSpy).not.toHaveBeenCalled();
act(() => renderHook(() => useValidateChildren({ children, items: undefined })));

await waitFor(() => {
expect(warnSpy).toHaveBeenCalledTimes(0);
});
});
});
4 changes: 2 additions & 2 deletions lib/utils/scrollToSectionView.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,12 @@ describe('scrollToSectionView', () => {

it("should' not scroll if container is null", () => {
scrollToSectionView(null, target);
expect(container.scrollTo).not.toHaveBeenCalled();
expect(container.scrollTo).toHaveBeenCalledTimes(0);
});

it("should' not scroll if target is null", () => {
scrollToSectionView(container, null);
expect(container.scrollTo).not.toHaveBeenCalled();
expect(container.scrollTo).toHaveBeenCalledTimes(0);
});

it('should calculate scrollTop correctly and call scrollTo', () => {
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
"@vitejs/plugin-react-swc": "^4.0.0",
"@vitest/browser": "^3.2.4",
"@vitest/coverage-v8": "^3.2.4",
"@vitest/eslint-plugin": "^1.3.8",
"chromatic": "^13.1.3",
"conventional-changelog-cli": "^5.0.0",
"conventional-changelog-conventionalcommits": "^9.1.0",
Expand Down
Loading