Files
log/oauth/src/components/AvatarTestPanel.jsx
2025-06-19 13:09:37 +09:00

246 lines
7.5 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useState } from 'react'
import AvatarImage from './AvatarImage.jsx'
import { getValidAvatar, clearAvatarCache, getAvatarCacheStats } from '../utils/avatarFetcher.js'
export default function AvatarTestPanel() {
const [testHandle, setTestHandle] = useState('ai.syui.ai')
const [testResult, setTestResult] = useState(null)
const [loading, setLoading] = useState(false)
const [cacheStats, setCacheStats] = useState(null)
// ダミーレコードを作成(実際の投稿したレコード形式)
const createTestRecord = (handle, brokenAvatar = false) => ({
value: {
author: {
did: null, // DIDはnullにして、handleから取得させる
handle: handle,
displayName: "Test User",
avatar: brokenAvatar ? "https://broken.example.com/avatar.jpg" : null
},
text: "テストコメント",
createdAt: new Date().toISOString()
}
})
const testAvatarFetch = async (useBrokenAvatar = false) => {
setLoading(true)
setTestResult(null)
try {
const testRecord = createTestRecord(testHandle, useBrokenAvatar)
const avatarUrl = await getValidAvatar(testRecord)
setTestResult({
success: true,
avatarUrl,
handle: testHandle,
brokenTest: useBrokenAvatar,
timestamp: new Date().toISOString()
})
} catch (error) {
setTestResult({
success: false,
error: error.message,
handle: testHandle,
brokenTest: useBrokenAvatar
})
} finally {
setLoading(false)
}
}
const handleClearCache = () => {
clearAvatarCache()
setCacheStats(null)
alert('Avatar cache cleared!')
}
const handleShowCacheStats = () => {
const stats = getAvatarCacheStats()
setCacheStats(stats)
}
return (
<div className="test-ui">
<h2>🖼 Avatar Test Panel</h2>
<p className="description">
Avatar取得システムのテスト投稿済みのdummy recordを使用してavatar取得処理を確認できます
</p>
<div className="form-group">
<label htmlFor="test-handle">Test Handle:</label>
<input
id="test-handle"
type="text"
value={testHandle}
onChange={(e) => setTestHandle(e.target.value)}
placeholder="ai.syui.ai"
disabled={loading}
/>
</div>
<div className="form-actions">
<button
onClick={() => testAvatarFetch(false)}
disabled={loading || !testHandle.trim()}
className="btn btn-primary"
>
{loading ? '⏳ Testing...' : '🔄 Test Avatar Fetch'}
</button>
<button
onClick={() => testAvatarFetch(true)}
disabled={loading || !testHandle.trim()}
className="btn btn-outline"
>
{loading ? '⏳ Testing...' : '💥 Test Broken Avatar'}
</button>
<button
onClick={handleClearCache}
disabled={loading}
className="btn btn-danger btn-sm"
>
🗑 Clear Cache
</button>
<button
onClick={handleShowCacheStats}
disabled={loading}
className="btn btn-outline btn-sm"
>
📊 Cache Stats
</button>
</div>
{testResult && (
<div className="test-result">
<h3>Test Result:</h3>
{testResult.success ? (
<div className="success-message">
Avatar fetched successfully!
<div className="result-details">
<p><strong>Handle:</strong> {testResult.handle}</p>
<p><strong>Broken Test:</strong> {testResult.brokenTest ? 'Yes' : 'No'}</p>
<p><strong>Avatar URL:</strong> {testResult.avatarUrl || 'None'}</p>
<p><strong>Timestamp:</strong> {testResult.timestamp}</p>
{testResult.avatarUrl && (
<div className="avatar-preview">
<p><strong>Preview:</strong></p>
<img
src={testResult.avatarUrl}
alt="Avatar preview"
style={{
width: 60,
height: 60,
borderRadius: '50%',
objectFit: 'cover',
border: '2px solid #ddd'
}}
/>
</div>
)}
</div>
</div>
) : (
<div className="error-message">
Test failed: {testResult.error}
</div>
)}
</div>
)}
{cacheStats && (
<div className="cache-stats">
<h3>Cache Statistics:</h3>
<p><strong>Entries:</strong> {cacheStats.size}</p>
{cacheStats.entries.length > 0 && (
<div className="cache-entries">
<h4>Cached Avatars:</h4>
{cacheStats.entries.map((entry, i) => (
<div key={i} className="cache-entry">
<p><strong>Key:</strong> {entry.key}</p>
<p><strong>Age:</strong> {Math.floor(entry.age / 1000)}s</p>
<p><strong>Profile:</strong> {entry.profile?.displayName} (@{entry.profile?.handle})</p>
{entry.avatar && (
<img
src={entry.avatar}
alt="Cached avatar"
style={{ width: 30, height: 30, borderRadius: '50%' }}
/>
)}
</div>
))}
</div>
)}
</div>
)}
<div className="live-demo">
<h3>Live Avatar Component Demo:</h3>
<p>実際のAvatarImageコンポーネントの動作確認:</p>
<div style={{ display: 'flex', gap: '16px', alignItems: 'center', marginTop: '12px' }}>
<AvatarImage record={createTestRecord(testHandle, false)} size={40} />
<span>Normal avatar test</span>
</div>
<div style={{ display: 'flex', gap: '16px', alignItems: 'center', marginTop: '12px' }}>
<AvatarImage record={createTestRecord(testHandle, true)} size={40} />
<span>Broken avatar test (should fetch fresh)</span>
</div>
</div>
<style jsx>{`
.test-result {
margin-top: 20px;
padding: 16px;
border: 1px solid #ddd;
border-radius: 8px;
background: #f9f9f9;
}
.result-details {
margin-top: 12px;
font-size: 14px;
}
.result-details p {
margin: 4px 0;
}
.avatar-preview {
margin-top: 12px;
padding: 12px;
border: 1px solid #eee;
border-radius: 4px;
background: white;
}
.cache-stats {
margin-top: 20px;
padding: 16px;
border: 1px solid #ddd;
border-radius: 8px;
background: #f0f8ff;
}
.cache-entries {
margin-top: 12px;
}
.cache-entry {
padding: 8px;
margin: 8px 0;
border: 1px solid #ddd;
border-radius: 4px;
background: white;
font-size: 12px;
}
.cache-entry p {
margin: 2px 0;
}
.live-demo {
margin-top: 20px;
padding: 16px;
border: 1px solid #ddd;
border-radius: 8px;
background: #f8f9fa;
}
`}</style>
</div>
)
}