Closed7
[Tauri] Tauri の React アプリを Vitest でテストする
ピン留めされたアイテム
- Tauri App の作成
yarn create tauri-app
? What is your app name? tauri-app
? What should the window title be? Tauri App
? What UI recipe would you like to add? create-vite
? Add "@tauri-apps/api" npm package? Yes
? Which vite template would you like to use? react-ts
インストール
yarn add -D vitest jsdom @vitest/coverage-c8
yarn add -D @testing-library/{react,jest-dom,user-event,dom}
設定
vite.config.ts
+ /// <reference types="vitest" />
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
+ test: {
+ globals: true,
+ environment: 'jsdom',
+ coverage: {
+ enabled: true,
+ reporter: ['text'],
+ },
+ },
});
tsconfig.json
{
"compilerOptions": {
+ "types": ["vitest/globals"]
}
}
package.json
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"tauri": "tauri",
+ "test": "vitest"
}
.gitignore
+ coverage/tmp
# Logs
logs
*.log
React アプリ
-
count
ステートが更新されたらウィンドウのタイトルバーへ反映 - Rust へ投げる処理を
tauri.invoke()
で呼び出す
src/App.tsx
import { useEffect, useState } from 'react';
import reactLogo from './assets/react.svg';
import './App.css';
import { invoke } from '@tauri-apps/api/tauri';
import { getCurrent } from '@tauri-apps/api/window';
function App() {
const [count, setCount] = useState(0);
useEffect(() => {
getCurrent().setTitle(`Tauri App: ${count}`);
}, [count]);
return (
<div className="App">
<div>
<a href="https://vitejs.dev" target="_blank">
<img src="/vite.svg" className="logo" alt="Vite logo" />
</a>
<a href="https://reactjs.org" target="_blank">
<img src={reactLogo} className="logo react" alt="React logo" />
</a>
</div>
<h1>Vite + React</h1>
<div className="card">
<button
data-testid="count"
onClick={() => setCount((count) => count + 1)}
>
count is {count}
</button>
<button
data-testid="add"
onClick={() => {
invoke('add', { a: 1, b: 2 }).then((result) =>
console.log(`result: ${result}`)
);
}}
>
Add
</button>
</div>
<p className="read-the-docs">
Click on the Vite and React logos to learn more
</p>
</div>
);
}
export default App;
Rust サイド
invoke_handler
へ add 関数を登録。
src-tauri/src/main.rs
#[tauri::command]
async fn add(a: i32, b: i32) -> i32 {
a + b
}
fn main() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![add])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
Tauri ウィンドウにラベルを付けておく。
src-tauri/tauri.config.json
{
"tauri": {
"allowlist": {
"all": true
},
"windows": [
{
"title": "Tauri App",
+ "label": "main"
}
]
}
}
テストを書く
src/App.test.tsx
import '@testing-library/jest-dom';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { randomFillSync } from 'node:crypto';
import { mockIPC, mockWindows } from '@tauri-apps/api/mocks';
import App from './App';
/** window.crypto の拡張が必要らしい */
beforeAll(() => {
window.crypto = {
// @ts-ignore
getRandomValues: (buffer: NodeJS.ArrayBufferView) => {
return randomFillSync(buffer);
},
};
});
test('render App component', async () => {
// 'tauri.invoke()' をモック
mockIPC((cmd, args) => {
if (cmd === 'add') {
return (args.a as number) + (args.b as number);
}
});
// '@tauri-apps/api/window' をモック
mockWindows('main');
const { getCurrent } = await import('@tauri-apps/api/window');
expect(getCurrent()).toHaveProperty('label', 'main');
// <App /> をレンダー
render(<App />);
const countButton = screen.getByTestId('count');
await userEvent.click(countButton);
expect(countButton).toHaveTextContent('count is 1');
// 発生するIPC通信をスパイ
const spy = vi.spyOn(window, '__TAURI_IPC__');
await userEvent.click(screen.getByTestId('add'));
expect(spy).toHaveBeenCalled();
});
このスクラップは2022/06/26にクローズされました