From 99285deb677fe0132a0007b9d435be35abb52eed Mon Sep 17 00:00:00 2001 From: jsonq Date: Sat, 30 Aug 2025 15:09:09 +0800 Subject: [PATCH 01/16] test: add private util test case --- .../sqi-web/src/_util/__tests__/dom.test.ts | 110 ++++++++++ .../sqi-web/src/_util/__tests__/ref.test.tsx | 203 ++++++++++++++++++ .../src/_util/__tests__/reposeive.test.ts | 187 ++++++++++++++++ .../src/_util/__tests__/toArray.test.ts | 138 ++++++++++++ 4 files changed, 638 insertions(+) create mode 100644 packages/sqi-web/src/_util/__tests__/dom.test.ts create mode 100644 packages/sqi-web/src/_util/__tests__/ref.test.tsx create mode 100644 packages/sqi-web/src/_util/__tests__/reposeive.test.ts create mode 100644 packages/sqi-web/src/_util/__tests__/toArray.test.ts diff --git a/packages/sqi-web/src/_util/__tests__/dom.test.ts b/packages/sqi-web/src/_util/__tests__/dom.test.ts new file mode 100644 index 0000000..b6239b5 --- /dev/null +++ b/packages/sqi-web/src/_util/__tests__/dom.test.ts @@ -0,0 +1,110 @@ +import { describe, it, expect, vi } from 'vitest'; +import { isDOM, getDOM, getRefDom, getReactNodeRef } from '../dom'; +import React from 'react'; + +describe('dom utilities', () => { + describe('isDOM', () => { + it('should return true for HTMLElement', () => { + const div = document.createElement('div'); + expect(isDOM(div)).toBe(true); + }); + + it('should return true for SVGElement', () => { + const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); + expect(isDOM(svg)).toBe(true); + }); + + it('should return false for non-DOM objects', () => { + expect(isDOM(null)).toBe(false); + expect(isDOM(undefined)).toBe(false); + expect(isDOM({})).toBe(false); + expect(isDOM('string')).toBe(false); + expect(isDOM(123)).toBe(false); + }); + }); + + describe('getDOM', () => { + it('should return currentElement for ref objects', () => { + const div = document.createElement('div'); + const refObject = { currentElement: div }; + expect(getDOM(refObject)).toBe(div); + }); + + it('should return the node itself if it is a DOM element', () => { + const div = document.createElement('div'); + expect(getDOM(div)).toBe(div); + }); + + it('should return null for non-DOM objects', () => { + expect(getDOM(null)).toBe(null); + expect(getDOM(undefined)).toBe(null); + expect(getDOM({})).toBe(null); + expect(getDOM('string')).toBe(null); + }); + + it('should return null for objects without currentElement', () => { + const obj = { foo: 'bar' }; + expect(getDOM(obj)).toBe(null); + }); + }); + + describe('getRefDom', () => { + it('should return undefined for falsy input', () => { + expect(getRefDom(null as any)).toBeUndefined(); + expect(getRefDom(undefined as any)).toBeUndefined(); + }); + + it('should return currentElement.currentElement for special refs', () => { + const div = document.createElement('div'); + const ref = { current: { currentElement: div } }; + expect(getRefDom(ref)).toBe(div); + }); + + it('should return current property for regular refs', () => { + const div = document.createElement('div'); + const ref = { current: div }; + expect(getRefDom(ref)).toBe(div); + }); + + it('should return undefined when current is falsy', () => { + const ref = { current: null }; + expect(getRefDom(ref)).toBeNull(); + }); + }); + + describe('getReactNodeRef', () => { + it('should return null for non-elements', () => { + expect(getReactNodeRef(null)).toBe(null); + expect(getReactNodeRef(undefined)).toBe(null); + expect(getReactNodeRef('string')).toBe(null); + expect(getReactNodeRef(123)).toBe(null); + }); + + it('should return ref based on React version', () => { + // Mock React version + const versionSpy = vi.spyOn(React, 'version', 'get'); + + // Test React 19+ behavior (ref in props) + versionSpy.mockReturnValue('19.0.0'); + const divWithRefProp = React.createElement('div', { ref: 'test-ref' }); + expect(getReactNodeRef(divWithRefProp)).toBe('test-ref'); + + // Test pre-React 19 behavior (ref as property) + versionSpy.mockReturnValue('18.2.0'); + const divWithRefProperty = React.createElement('div', {}); + expect(getReactNodeRef(divWithRefProperty)).toBeNull(); + + versionSpy.mockRestore(); + }); + + it('should return null when element has no ref', () => { + const versionSpy = vi.spyOn(React, 'version', 'get'); + versionSpy.mockReturnValue('19.0.0'); + + const divWithoutRef = React.createElement('div', {}); + expect(getReactNodeRef(divWithoutRef)).toBe(null); + + versionSpy.mockRestore(); + }); + }); +}); diff --git a/packages/sqi-web/src/_util/__tests__/ref.test.tsx b/packages/sqi-web/src/_util/__tests__/ref.test.tsx new file mode 100644 index 0000000..4ff4925 --- /dev/null +++ b/packages/sqi-web/src/_util/__tests__/ref.test.tsx @@ -0,0 +1,203 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import * as React from 'react'; +import { composeRef, fillRef, useComposeRef, supportRef, supportNodeRef } from '../ref'; +import { render, renderHook } from '@testing-library/react'; + +// 创建一个变量来保存模拟的React版本 +let mockReactVersion = '18.0.0'; + +// 模拟react模块以允许控制version导出 +vi.mock('react', async () => { + const actualReact = await vi.importActual('react'); + return { + ...actualReact, + get version() { + return mockReactVersion; + }, + }; +}); + +describe('ref utilities', () => { + beforeEach(() => { + mockReactVersion = '18.0.0'; + }); + + describe('fillRef', () => { + it('should call function ref with node', () => { + const ref = vi.fn(); + const node = document.createElement('div'); + fillRef(ref, node); + expect(ref).toHaveBeenCalledWith(node); + }); + + it('should set current property of object ref', () => { + const ref = { current: null }; + const node = document.createElement('div'); + fillRef(ref, node); + expect(ref.current).toBe(node); + }); + + it('should not throw error for null ref', () => { + expect(() => fillRef(null, document.createElement('div'))).not.toThrow(); + }); + }); + + describe('composeRef', () => { + it('should return undefined when no refs provided', () => { + expect(composeRef()).toBeUndefined(); + }); + + it('should return the only ref when one ref provided', () => { + const ref = vi.fn(); + expect(composeRef(ref)).toBe(ref); + }); + + it('should compose multiple function refs', () => { + const ref1 = vi.fn(); + const ref2 = vi.fn(); + const node = document.createElement('div'); + + const composedRef = composeRef(ref1, ref2); + (composedRef as React.RefCallback)(node); + + expect(ref1).toHaveBeenCalledWith(node); + expect(ref2).toHaveBeenCalledWith(node); + }); + + it('should compose multiple object refs', () => { + const ref1 = React.createRef(); + const ref2 = React.createRef(); + const node = document.createElement('div'); + + const composedRef = composeRef(ref1, ref2); + (composedRef as React.RefCallback)(node); + + expect(ref1.current).toBe(node); + expect(ref2.current).toBe(node); + }); + + it('should compose mixed refs', () => { + const ref1 = vi.fn(); + const ref2 = React.createRef(); + const node = document.createElement('div'); + + const composedRef = composeRef(ref1, ref2); + (composedRef as React.RefCallback)(node); + + expect(ref1).toHaveBeenCalledWith(node); + expect(ref2.current).toBe(node); + }); + }); + + describe('useComposeRef', () => { + it('should compose refs with hook', () => { + const ref1 = vi.fn(); + const ref2 = React.createRef(); + + const TestComponent = () => { + const composedRef = useComposeRef(ref1, ref2); + return ( +
+ Test +
+ ); + }; + + const { getByTestId } = render(); + + expect(ref1).toHaveBeenCalledWith(getByTestId('test')); + expect(ref2.current).toBe(getByTestId('test')); + }); + + it('should return same ref when refs are not changed', () => { + const ref1 = vi.fn(); + const { result, rerender } = renderHook(({ ref1 }) => useComposeRef(ref1), { initialProps: { ref1 } }); + const firstRef = result.current; + + rerender({ ref1 }); + expect(result.current).toBe(firstRef); + }); + + it('should return new ref when refs changed', () => { + const ref1 = vi.fn(); + const ref2 = vi.fn(); + const { result, rerender } = renderHook(({ refs }) => useComposeRef(...refs), { initialProps: { refs: [ref1] } }); + const firstRef = result.current; + + rerender({ refs: [ref1, ref2] }); + expect(result.current).not.toBe(firstRef); + }); + }); + + describe('supportRef', () => { + it('should return false for falsy values', () => { + expect(supportRef(null)).toBe(false); + expect(supportRef(undefined)).toBe(false); + }); + + it('should return true for React 19+ elements', () => { + mockReactVersion = '19.0.0'; + + const element = React.createElement('div'); + expect(supportRef(element)).toBe(true); + }); + + it('should return false for React 19+ function components without forwardRef', () => { + mockReactVersion = '19.0.0'; + + const FunctionComponent = () =>
; + expect(supportRef(FunctionComponent)).toBe(false); + }); + + it('should return true for forwardRef components', () => { + const ForwardRefComponent = React.forwardRef(() =>
); + expect(supportRef(ForwardRefComponent)).toBe(true); + }); + + it('should return true for class components', () => { + class ClassComponent extends React.Component { + render() { + return
; + } + } + expect(supportRef(ClassComponent)).toBe(true); + }); + + it('should return false for function components without forwardRef in React 18', () => { + mockReactVersion = '18.0.0'; + + const FunctionComponent = () =>
; + expect(supportRef(FunctionComponent)).toBe(false); + }); + + it('should handle memo components', () => { + const FunctionComponent = () =>
; + const MemoComponent = React.memo(FunctionComponent); + expect(supportRef(MemoComponent)).toBe(false); + }); + }); + + describe('supportNodeRef', () => { + it('should return false for non-elements', () => { + expect(supportNodeRef(null)).toBe(false); + expect(supportNodeRef('string')).toBe(false); + expect(supportNodeRef(123)).toBe(false); + }); + + it('should return true for elements that support ref in React 19+', () => { + mockReactVersion = '19.0.0'; + + const element = React.createElement('div'); + expect(supportNodeRef(element)).toBe(true); + }); + + it('should return false for elements that do not support ref', () => { + mockReactVersion = '18.0.0'; + + const FunctionComponent = () =>
; + const element = React.createElement(FunctionComponent); + + expect(supportNodeRef(element)).toBe(false); + }); + }); +}); diff --git a/packages/sqi-web/src/_util/__tests__/reposeive.test.ts b/packages/sqi-web/src/_util/__tests__/reposeive.test.ts new file mode 100644 index 0000000..7000d6c --- /dev/null +++ b/packages/sqi-web/src/_util/__tests__/reposeive.test.ts @@ -0,0 +1,187 @@ +import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; +import responsiveObserve, { responsiveArray, responsiveMap } from '../responsiveObserve'; + +type Fn = (...args: any[]) => any; + +describe('responsiveObserve', () => { + // 保存原始的matchMedia + const originalMatchMedia = window.matchMedia; + + beforeEach(() => { + responsiveObserve.handlers = {}; + + window.matchMedia = vi.fn().mockImplementation((query) => ({ + matches: false, + media: query, + onchange: null, + addListener: vi.fn(), + removeListener: vi.fn(), + addEventListener: vi.fn(), + removeEventListener: vi.fn(), + dispatchEvent: vi.fn(), + })); + }); + + afterEach(() => { + window.matchMedia = originalMatchMedia; + }); + + describe('responsiveArray', () => { + it('should be defined', () => { + expect(responsiveArray).toEqual(['xxl', 'xl', 'lg', 'md', 'sm', 'xs']); + }); + }); + + describe('responsiveMap', () => { + it('should be defined with correct breakpoints', () => { + expect(responsiveMap).toEqual({ + xs: '(max-width: 575px)', + sm: '(min-width: 576px)', + md: '(min-width: 768px)', + lg: '(min-width: 992px)', + xl: '(min-width: 1200px)', + xxl: '(min-width: 1600px)', + }); + }); + }); + + describe('subscribe and unsubscribe', () => { + it('should subscribe and unsubscribe correctly', () => { + const callback = vi.fn(); + + // 订阅 + const token = responsiveObserve.subscribe(callback); + expect(typeof token).toBe('number'); + expect(callback).toHaveBeenCalled(); + + // 取消订阅 + responsiveObserve.unsubscribe(token); + // 验证回调函数被调用次数 + expect(callback).toHaveBeenCalledTimes(1); + }); + + it('should call all subscribers when dispatch is called', () => { + const callback1 = vi.fn(); + const callback2 = vi.fn(); + + const token1 = responsiveObserve.subscribe(callback1); + const token2 = responsiveObserve.subscribe(callback2); + + // 模拟屏幕变化 + const screens = { xs: true, sm: false }; + responsiveObserve.dispatch(screens); + + expect(callback1).toHaveBeenCalledWith(screens); + expect(callback2).toHaveBeenCalledWith(screens); + + // 清理 + responsiveObserve.unsubscribe(token1); + responsiveObserve.unsubscribe(token2); + }); + }); + + describe('register and unregister', () => { + it('should register media queries on first subscription', () => { + const mockMql = { + matches: false, + addListener: vi.fn(), + removeListener: vi.fn(), + }; + + // 模拟matchMedia返回自定义对象 + window.matchMedia = vi.fn().mockReturnValue(mockMql); + + const callback = vi.fn(); + responsiveObserve.subscribe(callback); + + // 验证为每个断点都注册了媒体查询监听器 + expect(window.matchMedia).toHaveBeenCalledTimes(Object.keys(responsiveMap).length); + expect(mockMql.addListener).toHaveBeenCalledTimes(Object.keys(responsiveMap).length); + }); + + it('should unregister media queries when no subscribers left', () => { + const mockMqls: any[] = []; + + window.matchMedia = vi.fn().mockImplementation((query) => { + const mockMql = { + matches: false, + media: query, + addListener: vi.fn(), + removeListener: vi.fn(), + }; + mockMqls.push(mockMql); + return mockMql; + }); + + const callback = vi.fn(); + const token = responsiveObserve.subscribe(callback); + + // 确保监听器已添加 + mockMqls.forEach((mql) => { + expect(mql.addListener).toHaveBeenCalled(); + }); + + // 取消订阅,应该触发unregister + responsiveObserve.unsubscribe(token); + + // 验证监听器已被移除 + mockMqls.forEach((mql) => { + expect(mql.removeListener).toHaveBeenCalled(); + }); + }); + }); + + describe('media query handling', () => { + it('should update screens when media query matches change', () => { + const listeners: Fn[] = []; + const mqlMocks: any[] = []; + + // 创建更真实的matchMedia模拟 + window.matchMedia = vi.fn().mockImplementation((query) => { + const mqlMock = { + matches: false, + media: query, + addListener: vi.fn((listener: Fn) => { + listeners.push(listener); + }), + removeListener: vi.fn(), + }; + mqlMocks.push(mqlMock); + return mqlMock; + }); + + const callback = vi.fn(); + responsiveObserve.subscribe(callback); + + // 模拟媒体查询变化 + if (listeners[0]) { + listeners[0]({ matches: true }); + } + + // 验证回调被调用 + expect(callback).toHaveBeenCalled(); + }); + }); + + describe('dispatch', () => { + it('should update screens and notify subscribers', () => { + const callback = vi.fn(); + responsiveObserve.subscribe(callback); + + const screens = { xs: true, sm: false, md: false, lg: false, xl: false, xxl: false }; + const result = responsiveObserve.dispatch(screens); + + expect(result).toBe(true); // 因为有订阅者 + expect(callback).toHaveBeenCalledWith(screens); + }); + + it('should return false when no subscribers', () => { + responsiveObserve.unregister(); + + const screens = { xs: true }; + const result = responsiveObserve.dispatch(screens); + + expect(result).toBe(false); + }); + }); +}); diff --git a/packages/sqi-web/src/_util/__tests__/toArray.test.ts b/packages/sqi-web/src/_util/__tests__/toArray.test.ts new file mode 100644 index 0000000..45e4f5e --- /dev/null +++ b/packages/sqi-web/src/_util/__tests__/toArray.test.ts @@ -0,0 +1,138 @@ +import { describe, it, expect } from 'vitest'; +import { toArray } from '../toArray'; +import * as React from 'react'; + +describe('toArray', () => { + it('should convert null or undefined to empty array', () => { + expect(toArray(null)).toEqual([]); + expect(toArray(undefined)).toEqual([]); + }); + + it('should convert string to array with single string element', () => { + expect(toArray('hello')).toEqual(['hello']); + }); + + it('should convert number to array with single number element', () => { + expect(toArray(42)).toEqual([42]); + }); + + it('should convert single React element to array with that element', () => { + const element = React.createElement('div', null, 'Hello'); + const result = toArray(element); + expect(result).toHaveLength(1); + expect((result[0] as React.ReactElement).type).toBe('div'); + expect((result[0] as any).props.children).toBe('Hello'); + }); + + it('should convert single React element to array with that element', () => { + const element = React.createElement('div', null, 'Hello'); + const result = toArray(element); + expect(result).toHaveLength(1); + expect((result[0] as React.ReactElement).type).toBe('div'); + expect((result[0] as any).props.children).toBe('Hello'); + }); + + it('should flatten arrays', () => { + const element1 = React.createElement('div', null, 'First'); + const element2 = React.createElement('span', null, 'Second'); + const result = toArray([element1, element2]); + expect(result).toHaveLength(2); + expect((result[0] as React.ReactElement).type).toBe('div'); + expect((result[1] as React.ReactElement).type).toBe('span'); + expect((result[0] as any).props.children).toBe('First'); + expect((result[1] as any).props.children).toBe('Second'); + }); + + it('should filter out null and undefined values from arrays', () => { + const element = React.createElement('div', null, 'Hello'); + const result = toArray([element, null, undefined, 'text']); + expect(result).toHaveLength(2); + expect((result[0] as React.ReactElement).type).toBe('div'); + expect((result[0] as any).props.children).toBe('Hello'); + expect(result[1]).toBe('text'); + }); + + it('should recursively flatten nested arrays', () => { + const element1 = React.createElement('div', null, 'First'); + const element2 = React.createElement('span', null, 'Second'); + const element3 = React.createElement('p', null, 'Third'); + const nestedArray = [element1, [element2, [element3]]]; + const result = toArray(nestedArray); + expect(result).toHaveLength(3); + expect((result[0] as React.ReactElement).type).toBe('div'); + expect((result[1] as React.ReactElement).type).toBe('span'); + expect((result[2] as React.ReactElement).type).toBe('p'); + expect((result[0] as any).props.children).toBe('First'); + expect((result[1] as any).props.children).toBe('Second'); + expect((result[2] as any).props.children).toBe('Third'); + }); + + it('should unwrap Fragment children', () => { + const fragment = React.createElement( + React.Fragment, + null, + React.createElement('div', null, 'Child 1'), + React.createElement('span', null, 'Child 2'), + ); + + const result = toArray(fragment); + expect(result).toHaveLength(2); + expect((result[0] as React.ReactElement).type).toBe('div'); + expect((result[1] as React.ReactElement).type).toBe('span'); + }); + + it('should unwrap nested Fragment children', () => { + const nestedFragment = React.createElement( + React.Fragment, + null, + React.createElement(React.Fragment, null, React.createElement('div', null, 'Deep child')), + ); + + const result = toArray(nestedFragment); + expect(result).toHaveLength(1); + expect((result[0] as React.ReactElement).type).toBe('div'); + }); + + it('should handle Fragment with no children', () => { + const emptyFragment = React.createElement(React.Fragment, null); + const result = toArray(emptyFragment); + expect(result).toEqual([]); + }); + + it('should handle Fragment with null/undefined children', () => { + const fragmentWithNulls = React.createElement(React.Fragment, null, null, undefined, 'text'); + + const result = toArray(fragmentWithNulls); + expect(result).toEqual(['text']); + }); + + it('should handle mixed content with Fragments', () => { + const element = React.createElement('div', null, 'Regular element'); + const fragment = React.createElement(React.Fragment, null, React.createElement('span', null, 'Fragment child')); + + const result = toArray([element, fragment, 'text']); + expect(result).toHaveLength(3); + expect((result[0] as React.ReactElement).type).toBe('div'); + expect((result[1] as React.ReactElement).type).toBe('span'); + expect(result[2]).toBe('text'); + }); + + it('should handle complex nested structure', () => { + const complexStructure = [ + 'text', + null, + React.createElement('div', null, 'Element'), + [ + React.createElement(React.Fragment, null, React.createElement('span', null, 'Fragment child'), null), + 'nested text', + ], + ]; + + const result = toArray(complexStructure); + expect(result).toHaveLength(4); + expect(result[0]).toBe('text'); + expect((result[1] as React.ReactElement).type).toBe('div'); + expect((result[2] as React.ReactElement).type).toBe('span'); + expect(result[3]).toBe('nested text'); + }); +}); From 1cabb1e262ea62213658f73a69e4a678753bc044 Mon Sep 17 00:00:00 2001 From: jsonq Date: Sun, 31 Aug 2025 19:49:42 +0800 Subject: [PATCH 02/16] test: add alert test case --- .../__snapshots__/alert.test.tsx.snap | 187 ++++++++++++++++++ .../src/alert/__tests__/alert.test.tsx | 107 ++++++++++ 2 files changed, 294 insertions(+) create mode 100644 packages/sqi-web/src/alert/__tests__/__snapshots__/alert.test.tsx.snap create mode 100644 packages/sqi-web/src/alert/__tests__/alert.test.tsx diff --git a/packages/sqi-web/src/alert/__tests__/__snapshots__/alert.test.tsx.snap b/packages/sqi-web/src/alert/__tests__/__snapshots__/alert.test.tsx.snap new file mode 100644 index 0000000..e52735d --- /dev/null +++ b/packages/sqi-web/src/alert/__tests__/__snapshots__/alert.test.tsx.snap @@ -0,0 +1,187 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`Alert Component > should display icons correctly 1`] = ` + +`; + +exports[`Alert Component > should display icons correctly 2`] = ` + +`; + +exports[`Alert Component > should render basic Alert correctly 1`] = ` + +`; + +exports[`Alert Component > should render different types of Alert correctly 1`] = ` + +`; + +exports[`Alert Component > should render title and description correctly 1`] = ` + +`; diff --git a/packages/sqi-web/src/alert/__tests__/alert.test.tsx b/packages/sqi-web/src/alert/__tests__/alert.test.tsx new file mode 100644 index 0000000..4f343ec --- /dev/null +++ b/packages/sqi-web/src/alert/__tests__/alert.test.tsx @@ -0,0 +1,107 @@ +import React from 'react'; +import { describe, it, expect, vi } from 'vitest'; +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; +import Alert from '../Alert'; + +describe('Alert Component', () => { + it('should render basic Alert correctly', () => { + const { container, getByText } = render(); + expect(container.firstChild).toBeInTheDocument(); + expect(container.firstChild).toHaveClass('sqi-alert'); + expect(getByText('This is an alert message')).toBeInTheDocument(); + expect(container.firstChild).toMatchSnapshot(); + }); + + it('should render different types of Alert correctly', () => { + const { container: successAlert } = render(); + expect(successAlert.firstChild).toHaveClass('sqi-alert-success'); + + const { container: infoAlert } = render(); + expect(infoAlert.firstChild).toHaveClass('sqi-alert-info'); + + const { container: warningAlert } = render(); + expect(warningAlert.firstChild).toHaveClass('sqi-alert-warning'); + + const { container: errorAlert } = render(); + expect(errorAlert.firstChild).toHaveClass('sqi-alert-error'); + + expect(successAlert.firstChild).toMatchSnapshot(); + expect(infoAlert.firstChild).toMatchSnapshot; + expect(warningAlert.firstChild).toMatchSnapshot; + expect(errorAlert.firstChild).toMatchSnapshot; + }); + + it('should render title and description correctly', () => { + const { container, getByText } = render(); + expect(getByText('Alert Title')).toBeInTheDocument(); + expect(getByText('Alert Description')).toBeInTheDocument(); + expect(getByText('Alert Title')).toHaveClass('sqi-alert-title'); + expect(getByText('Alert Description')).toHaveClass('sqi-alert-description'); + + expect(container.firstChild).toMatchSnapshot(); + }); + + it('should display icons correctly', () => { + const { container: hasIconAlert } = render(); + + // Check default icon display + expect(hasIconAlert.firstChild).toHaveClass('sqi-alert'); + expect(hasIconAlert.firstChild?.firstChild).toHaveClass('sqi-alert-icon'); + + // Check when icon is hidden + const { container: noIconAlert } = render(); + expect(noIconAlert.firstChild?.firstChild).not.toHaveClass('sqi-alert-icon'); + + expect(hasIconAlert.firstChild).toMatchSnapshot(); + expect(noIconAlert.firstChild).toMatchSnapshot(); + }); + + it('should render custom icons correctly', () => { + const { container, getByTestId } = render( + Custom Icon} description="Custom icon alert" />, + ); + expect(getByTestId('custom-icon')).toBeInTheDocument(); + + expect(container.firstChild?.firstChild).toHaveClass('sqi-alert-icon'); + }); + + it('should render action area correctly', () => { + const action = ( + + ); + const { container } = render(); + expect(screen.getByTestId('alert-action')).toBeInTheDocument(); + expect((container.firstChild as any)?.children[2]).toHaveClass('sqi-alert-action'); + }); + + it('should render close button correctly', () => { + const { container } = render(); + expect(screen.getByRole('button')).toBeInTheDocument(); + expect(container.firstChild?.lastChild).toHaveClass('sqi-alert-close'); + }); + + it('should hide after clicking close button', async () => { + const { container } = render(); + + expect(container.firstChild).toBeInTheDocument(); + + fireEvent.click(screen.getByRole('button')); + + await waitFor( + () => { + expect(container.firstChild).not.toBeInTheDocument(); + }, + { timeout: 500 }, + ); + }); + + it('should trigger onClose callback when closing', () => { + const onClose = vi.fn(); + render(); + + fireEvent.click(screen.getByRole('button')); + expect(onClose).toHaveBeenCalledTimes(1); + }); +}); From d4be6ca9f3943cfafca58c22639ed0969266aa8f Mon Sep 17 00:00:00 2001 From: jsonq Date: Sun, 31 Aug 2025 20:13:39 +0800 Subject: [PATCH 03/16] test: add checkbox test case --- .../__snapshots__/alert.test.tsx.snap | 10 +- .../src/alert/__tests__/alert.test.tsx | 2 +- .../checkbox-group.test.tsx.snap | 103 +++++++++++ .../__snapshots__/checkbox.test.tsx.snap | 24 +++ .../__tests__/checkbox-group.test.tsx | 162 ++++++++++++++++++ .../src/checkbox/__tests__/checkbox.test.tsx | 105 ++++++++++++ packages/sqi-web/src/checkbox/type.ts | 2 +- 7 files changed, 401 insertions(+), 7 deletions(-) create mode 100644 packages/sqi-web/src/checkbox/__tests__/__snapshots__/checkbox-group.test.tsx.snap create mode 100644 packages/sqi-web/src/checkbox/__tests__/__snapshots__/checkbox.test.tsx.snap create mode 100644 packages/sqi-web/src/checkbox/__tests__/checkbox-group.test.tsx create mode 100644 packages/sqi-web/src/checkbox/__tests__/checkbox.test.tsx diff --git a/packages/sqi-web/src/alert/__tests__/__snapshots__/alert.test.tsx.snap b/packages/sqi-web/src/alert/__tests__/__snapshots__/alert.test.tsx.snap index e52735d..877793c 100644 --- a/packages/sqi-web/src/alert/__tests__/__snapshots__/alert.test.tsx.snap +++ b/packages/sqi-web/src/alert/__tests__/__snapshots__/alert.test.tsx.snap @@ -1,6 +1,6 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`Alert Component > should display icons correctly 1`] = ` +exports[`Alert > should display icons correctly 1`] = ` `; -exports[`Alert Component > should display icons correctly 2`] = ` +exports[`Alert > should display icons correctly 2`] = ` `; -exports[`Alert Component > should render basic Alert correctly 1`] = ` +exports[`Alert > should render basic Alert correctly 1`] = ` `; -exports[`Alert Component > should render different types of Alert correctly 1`] = ` +exports[`Alert > should render different types of Alert correctly 1`] = ` `; -exports[`Alert Component > should render title and description correctly 1`] = ` +exports[`Alert > should render title and description correctly 1`] = ` `; +exports[`Alert > should render different types of Alert correctly 2`] = ` + +`; + +exports[`Alert > should render different types of Alert correctly 3`] = ` + +`; + +exports[`Alert > should render different types of Alert correctly 4`] = ` + +`; + exports[`Alert > should render title and description correctly 1`] = `
{ expect(errorAlert.firstChild).toHaveClass('sqi-alert-error'); expect(successAlert.firstChild).toMatchSnapshot(); - expect(infoAlert.firstChild).toMatchSnapshot; - expect(warningAlert.firstChild).toMatchSnapshot; - expect(errorAlert.firstChild).toMatchSnapshot; + expect(infoAlert.firstChild).toMatchSnapshot(); + expect(warningAlert.firstChild).toMatchSnapshot(); + expect(errorAlert.firstChild).toMatchSnapshot(); }); it('should render title and description correctly', () => { diff --git a/packages/sqi-web/src/checkbox/__tests__/checkbox-group.test.tsx b/packages/sqi-web/src/checkbox/__tests__/checkbox-group.test.tsx index b9ef4f1..abca26d 100644 --- a/packages/sqi-web/src/checkbox/__tests__/checkbox-group.test.tsx +++ b/packages/sqi-web/src/checkbox/__tests__/checkbox-group.test.tsx @@ -111,7 +111,7 @@ describe('CheckboxGroup Component', () => { expect(container.firstChild).toHaveClass('custom-group'); // expect(container.firstChild).toHaveStyle('background-color: red'); - expect((container.firstChild as HTMLInputElement)?.getAttribute('style')).toContain('color: red'); + expect((container.firstChild as HTMLInputElement)?.getAttribute('style')).toContain('background-color: red'); }); it('should work with renderOption', () => { diff --git a/packages/sqi-web/src/tooltip/__tests__/__snapshots__/tooltip.test.tsx.snap b/packages/sqi-web/src/tooltip/__tests__/__snapshots__/tooltip.test.tsx.snap index a711a7e..d550361 100644 --- a/packages/sqi-web/src/tooltip/__tests__/__snapshots__/tooltip.test.tsx.snap +++ b/packages/sqi-web/src/tooltip/__tests__/__snapshots__/tooltip.test.tsx.snap @@ -41,3 +41,108 @@ exports[`Tooltip > renders with different themes 1`] = `
`; + +exports[`Tooltip > renders with different themes 2`] = ` +