Skip to content

Commit 0027d59

Browse files
committed
add test case
1 parent 90657e5 commit 0027d59

File tree

7 files changed

+185
-52
lines changed

7 files changed

+185
-52
lines changed

.github/workflows/deploy.yml

Lines changed: 0 additions & 42 deletions
This file was deleted.

.github/workflows/test-on-release.yml

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,62 @@
11
name: Test on Release
22

33
on:
4-
release:
5-
types: [published]
64
push:
5+
branches:
6+
- '**'
77
tags:
8-
- 'v*'
8+
- 'v[0-9]+.[0-9]+.[0-9]+'
99

1010
jobs:
1111
test:
12+
if: github.ref_type == 'branch'
1213
runs-on: ubuntu-latest
13-
14+
1415
steps:
1516
- uses: actions/checkout@v3
16-
17+
1718
- name: Setup Node.js
1819
uses: actions/setup-node@v3
1920
with:
20-
node-version: '18'
21+
node-version: '20'
2122
cache: 'yarn'
22-
23+
2324
- name: Install dependencies
2425
run: yarn install --frozen-lockfile
25-
26+
2627
- name: Run tests
27-
run: yarn test
28+
run: yarn test
29+
30+
deploy:
31+
if: startsWith(github.ref, 'refs/tags/v')
32+
runs-on: ubuntu-latest
33+
34+
defaults:
35+
run:
36+
working-directory: ./example
37+
38+
steps:
39+
- name: Checkout repository
40+
uses: actions/checkout@v3
41+
42+
- name: Wait for npm publish
43+
run: sleep 30 # 延遲30秒,可根據實際情況調整
44+
45+
- name: Set up Node.js
46+
uses: actions/setup-node@v3
47+
with:
48+
node-version: '20'
49+
50+
- name: Replace local links with actual versions
51+
run: |
52+
sed -i 's#"@acrool/react-toaster": "link:.."#"@acrool/react-toaster": "latest"#' package.json
53+
sed -i 's#"react": "link:../node_modules/react"#"react": "^19.1.0"#' package.json
54+
sed -i 's#"react-dom": "link:../node_modules/react-dom"#"react-dom": "^19.1.0"#' package.json
55+
56+
- name: Install dependencies
57+
run: yarn install
58+
59+
- name: Build Storybook & Deploy to Cloudflare Pages
60+
run: yarn pages:deploy
61+
env:
62+
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}

jest.config.mjs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ export default {
22
coverageDirectory: 'coverage',
33
preset: 'ts-jest',
44
testEnvironment: 'jsdom',
5-
testMatch: ['<rootDir>/**/*.spec.ts?(x)'],
5+
testMatch: ['<rootDir>/**/*.test.ts?(x)'],
66
transform: {
77
'^.+\\.(t|j)sx?$': [
88
'@swc/jest',
@@ -20,7 +20,12 @@ export default {
2020
moduleNameMapper: {
2121
'^@/(.*)$': '<rootDir>/src/$1',
2222
'\\.(css|scss)$': 'identity-obj-proxy',
23+
"\\.svg\\?(react|url)$": "<rootDir>/src/__mocks__/svgMock.tsx"
24+
2325
},
26+
transformIgnorePatterns: [
27+
'/node_modules/(?!@acrool/react-portal)'
28+
],
2429
setupFilesAfterEnv: ['@testing-library/jest-dom'],
2530
};
2631

src/Toaster.test.tsx

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import '@testing-library/jest-dom';
2+
3+
import {act,render, screen, waitFor} from '@testing-library/react';
4+
import React from 'react';
5+
6+
import Toaster, {toast} from './Toaster';
7+
8+
jest.useFakeTimers();
9+
10+
describe('Toaster', () => {
11+
beforeEach(() => {
12+
// 渲染 Toaster 元件到 document.body
13+
render(<Toaster />);
14+
});
15+
16+
afterEach(() => {
17+
jest.clearAllTimers();
18+
});
19+
20+
it('應該能顯示訊息', () => {
21+
act(() => {
22+
toast.success('成功訊息');
23+
});
24+
expect(screen.getByText('成功訊息')).toBeInTheDocument();
25+
});
26+
27+
it('應該能根據狀態顯示多種訊息', () => {
28+
act(() => {
29+
toast.success('成功訊息');
30+
toast.error('錯誤訊息');
31+
toast.info('資訊訊息');
32+
toast.warning('警告訊息');
33+
});
34+
expect(screen.getByText('成功訊息')).toBeInTheDocument();
35+
expect(screen.getByText('錯誤訊息')).toBeInTheDocument();
36+
expect(screen.getByText('資訊訊息')).toBeInTheDocument();
37+
expect(screen.getByText('警告訊息')).toBeInTheDocument();
38+
});
39+
40+
it('應該能自動隱藏訊息', async () => {
41+
act(() => {
42+
toast.success('自動消失訊息', {timeout: 1000});
43+
});
44+
expect(screen.getByText('自動消失訊息')).toBeInTheDocument();
45+
act(() => {
46+
jest.advanceTimersByTime(1000);
47+
});
48+
await waitFor(() => {
49+
expect(screen.queryByText('自動消失訊息')).not.toBeInTheDocument();
50+
});
51+
});
52+
53+
it('超過限制時,最舊的訊息會被移除', () => {
54+
act(() => {
55+
for(let i=1; i<=6; i++){
56+
toast.success(`訊息${i}`);
57+
}
58+
});
59+
// 預設 limit=5,訊息1 應該被移除
60+
expect(screen.queryByText('訊息1')).not.toBeInTheDocument();
61+
for(let i=2; i<=6; i++){
62+
expect(screen.getByText(`訊息${i}`)).toBeInTheDocument();
63+
}
64+
});
65+
66+
it('點擊訊息可手動關閉', async () => {
67+
act(() => {
68+
toast.success('可點擊關閉訊息');
69+
});
70+
const msg = screen.getByText('可點擊關閉訊息');
71+
act(() => {
72+
msg.click();
73+
});
74+
await waitFor(() => {
75+
expect(screen.queryByText('可點擊關閉訊息')).not.toBeInTheDocument();
76+
});
77+
});
78+
});

src/__mocks__/svgMock.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
const SvgMock = (props: any) => {
2+
return (
3+
<svg {...props}>
4+
<title>Mock SVG</title>
5+
</svg>
6+
);
7+
};
8+
9+
export default SvgMock;

src/utils.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import {removeByIndex} from './utils';
2+
3+
describe('removeByIndex', () => {
4+
it('應正確移除指定索引的元素', () => {
5+
expect(removeByIndex([1, 2, 3], 1)).toEqual([1, 3]);
6+
expect(removeByIndex(['a', 'b', 'c'], 0)).toEqual(['b', 'c']);
7+
expect(removeByIndex([true, false], 1)).toEqual([true]);
8+
});
9+
10+
it('索引為 -1 時應回傳原陣列', () => {
11+
const arr = [1, 2, 3];
12+
expect(removeByIndex(arr, -1)).toBe(arr);
13+
});
14+
15+
it('索引超出範圍時應回傳原陣列', () => {
16+
const arr = [1, 2, 3];
17+
expect(removeByIndex(arr, 3)).toBe(arr);
18+
expect(removeByIndex(arr, 100)).toBe(arr);
19+
});
20+
21+
it('空陣列時應回傳空陣列', () => {
22+
expect(removeByIndex([], 0)).toEqual([]);
23+
expect(removeByIndex([], 1)).toEqual([]);
24+
});
25+
});

src/vite-env.d.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,26 @@
11
/// <reference types="vite/client" />
22
/// <reference types="vite-plugin-svgr/client" />
33

4+
/**
5+
* Default CSS definition for typescript,
6+
* will be overridden with file-specific definitions by rollup
7+
*/
8+
declare module '*.css' {
9+
const content: { [className: string]: string };
10+
export default content;
11+
}
12+
13+
interface SvgrComponent extends React.StatelessComponent<React.SVGAttributes<SVGElement>> {}
14+
15+
16+
declare module '*.svg' {
17+
const svgUrl: string;
18+
const svgComponent: SvgrComponent;
19+
export default svgUrl;
20+
export {svgComponent as ReactComponent};
21+
}
22+
23+
24+
declare module '*.png';
25+
26+
export {};

0 commit comments

Comments
 (0)